add speech component

This commit is contained in:
2026-10-27 09:52:28 +07:00
parent 6fdbedac14
commit 9fa4f4f2ff
7 changed files with 448 additions and 19 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ nip29 = { path = "../nip29" }
person = { path = "../person" }
settings = { path = "../settings" }
state = { path = "../state" }
gpui-kit.workspace = true
gpui-kit = { workspace = true, features = ["speech"] }
ui = { path = "../ui" }
gpui.workspace = true
+27 -1
View File
@@ -15,6 +15,7 @@ use gpui_kit::component::dock::{Panel, PanelEvent};
use gpui_kit::component::input::{InputEvent, Textarea, TextareaState};
use gpui_kit::component::notification::Notification;
use gpui_kit::component::scroll::Scrollbar;
use gpui_kit::component::speech::{SpeechButton, SpeechEvent, SpeechState, SpeechWaveform};
use gpui_kit::component::{ActiveTheme, Disableable, Icon, Sizable, WindowExt, h_flex, v_flex};
use nip29::{Group, GroupEvent, GroupKey, GroupsRegistry, Membership, Pin, Row};
use nostr_sdk::prelude::*;
@@ -312,6 +313,8 @@ pub struct GroupPanel {
reply_to: Option<EventId>,
rendered_texts_by_id: BTreeMap<EventId, RenderedText>,
input: Entity<TextareaState>,
/// Dictation session for the composer input
speech: Entity<SpeechState>,
/// Uploaded, non-encrypted attachments waiting to be sent.
attachments: Vec<Url>,
/// Whether an upload is in flight.
@@ -319,7 +322,7 @@ pub struct GroupPanel {
/// Whether the user closed the encryption notice this visit
notice_dismissed: bool,
tasks: SmallVec<[Task<Result<()>>; 4]>,
_subscriptions: SmallVec<[Subscription; 3]>,
_subscriptions: SmallVec<[Subscription; 4]>,
}
impl GroupPanel {
@@ -338,6 +341,8 @@ impl GroupPanel {
.clean_on_escape()
});
let speech = cx.new(SpeechState::new);
let mut subscriptions = smallvec![];
subscriptions.push(
@@ -348,6 +353,22 @@ impl GroupPanel {
}),
);
subscriptions.push(
cx.subscribe_in(&speech, window, |this, _, event, window, cx| {
match event {
SpeechEvent::Final(text) if !text.is_empty() => {
this.input.update(cx, |input, cx| {
input.insert(text.clone(), window, cx);
});
}
SpeechEvent::Error(error) => {
window.push_notification(Notification::error(error.to_string()), cx);
}
_ => {}
};
}),
);
subscriptions.push(cx.subscribe(&group, |this, _group, event, cx| {
if !matches!(event, GroupEvent::Sent(_)) {
this.sync(cx);
@@ -383,6 +404,7 @@ impl GroupPanel {
notice_dismissed: false,
rendered_texts_by_id: BTreeMap::new(),
input,
speech,
attachments: Vec::new(),
uploading: false,
tasks: smallvec![],
@@ -1073,6 +1095,10 @@ impl GroupPanel {
.py_1()
.flex_1(),
)
.when(self.speech.read(cx).status().is_capturing(), |this| {
this.child(SpeechWaveform::new(&self.speech).xsmall().w_12())
})
.child(SpeechButton::new(&self.speech))
.child(
Button::new("send")
.icon(ui::IconName::PaperPlaneFill)