@agentprojectcontext/apx 1.36.0 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -3
- package/package.json +1 -1
- package/src/core/mascot.js +80 -80
- package/src/host/daemon/api/agents.js +6 -0
- package/src/host/daemon/api/conversations.js +9 -2
- package/src/host/daemon/api/web.js +20 -1
- package/src/host/daemon/desktop-ws.js +31 -0
- package/src/host/daemon/index.js +12 -2
- package/src/interfaces/cli/commands/agent.js +20 -0
- package/src/interfaces/cli/commands/chat.js +15 -6
- package/src/interfaces/cli/commands/identity.js +20 -1
- package/src/interfaces/cli/commands/update.js +2 -0
- package/src/interfaces/cli/index.js +14 -0
- package/src/interfaces/web/dist/assets/index-CQc_5t8F.js +629 -0
- package/src/interfaces/web/dist/assets/index-CQc_5t8F.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-hwxuTPcK.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/App.tsx +20 -9
- package/src/interfaces/web/src/components/ModelCombobox.tsx +1 -1
- package/src/interfaces/web/src/components/Roby.tsx +96 -0
- package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +11 -11
- package/src/interfaces/web/src/components/TelegramSendDialog.tsx +5 -5
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +2 -2
- package/src/interfaces/web/src/components/chat/ModelPicker.tsx +5 -5
- package/src/interfaces/web/src/components/chat/ToolCall.tsx +23 -19
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +10 -10
- package/src/interfaces/web/src/components/code/CodeContextTab.tsx +7 -7
- package/src/interfaces/web/src/components/code/CodeProjectPicker.tsx +3 -2
- package/src/interfaces/web/src/components/common/TabNav.tsx +3 -2
- package/src/interfaces/web/src/components/config/ConfigTabsEditor.tsx +3 -2
- package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -2
- package/src/interfaces/web/src/components/config/global-config-sections.ts +9 -9
- package/src/interfaces/web/src/components/config/project-config-sections.ts +61 -54
- package/src/interfaces/web/src/components/deck/DaemonCard.tsx +6 -5
- package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +5 -4
- package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +3 -3
- package/src/interfaces/web/src/components/layout/ProjectSidebar.tsx +22 -9
- package/src/interfaces/web/src/components/settings/AdvancedPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +14 -14
- package/src/interfaces/web/src/components/settings/DevicesPanel.tsx +3 -3
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +7 -7
- package/src/interfaces/web/src/components/settings/IdentityPanel.tsx +2 -2
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +37 -37
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +44 -35
- package/src/interfaces/web/src/components/settings/SuperAgentPanel.tsx +5 -5
- package/src/interfaces/web/src/components/settings/TelegramChannelsPanel.tsx +3 -3
- package/src/interfaces/web/src/components/settings/TelegramContactsPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/TelegramGlobalPanel.tsx +3 -3
- package/src/interfaces/web/src/components/settings/TelegramRolesPanel.tsx +1 -1
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +6 -6
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +36 -36
- package/src/interfaces/web/src/components/voice/VoiceProviderList.tsx +15 -14
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +22 -22
- package/src/interfaces/web/src/components/voice/VoiceSttCard.tsx +18 -17
- package/src/interfaces/web/src/components/voice/VoiceTestCard.tsx +19 -18
- package/src/interfaces/web/src/hooks/useChat.ts +6 -5
- package/src/interfaces/web/src/i18n/en.ts +519 -2
- package/src/interfaces/web/src/i18n/es.ts +519 -2
- package/src/interfaces/web/src/i18n/index.ts +1 -1
- package/src/interfaces/web/src/lib/api/voice.ts +5 -5
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +14 -1
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +8 -8
- package/src/interfaces/web/src/screens/base/ComingSoon.tsx +3 -2
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +12 -12
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +15 -15
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +37 -37
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +8 -8
- package/src/interfaces/web/src/screens/project/AgentBrainGraph.tsx +16 -10
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +25 -24
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +2 -2
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +3 -3
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +6 -9
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +66 -52
- package/src/interfaces/web/src/screens/project/TelegramTab.tsx +1 -1
- package/src/interfaces/web/dist/assets/index-Cm0KyPoZ.css +0 -1
- package/src/interfaces/web/dist/assets/index-DJKA763h.js +0 -628
- package/src/interfaces/web/dist/assets/index-DJKA763h.js.map +0 -1
|
@@ -77,7 +77,7 @@ export function VoiceProviderModal({ open, providerId, config, onClose, onSave }
|
|
|
77
77
|
|
|
78
78
|
const hasSecret = providerId !== "piper" && providerId !== "mock";
|
|
79
79
|
const existingKey = hasSecret && isSecretMarker((config as { api_key?: unknown })?.api_key);
|
|
80
|
-
const keyPlaceholder = existingKey ?
|
|
80
|
+
const keyPlaceholder = existingKey ? t("voice_ui.api_key_set", { suffix: secretSuffix((config as { api_key?: unknown })?.api_key) ?? "" }) : t("voice_ui.api_key_label");
|
|
81
81
|
|
|
82
82
|
const submit = async () => {
|
|
83
83
|
setBusy(true);
|
|
@@ -115,7 +115,7 @@ export function VoiceProviderModal({ open, providerId, config, onClose, onSave }
|
|
|
115
115
|
await onSave({ set, unset });
|
|
116
116
|
onClose();
|
|
117
117
|
} catch (e) {
|
|
118
|
-
setError((e as Error).message || "
|
|
118
|
+
setError((e as Error).message || t("voice_ui.err_save"));
|
|
119
119
|
} finally {
|
|
120
120
|
setBusy(false);
|
|
121
121
|
}
|
|
@@ -130,21 +130,21 @@ export function VoiceProviderModal({ open, providerId, config, onClose, onSave }
|
|
|
130
130
|
size="md"
|
|
131
131
|
footer={
|
|
132
132
|
<>
|
|
133
|
-
<Button variant="ghost" onClick={onClose} disabled={busy}>
|
|
134
|
-
<Button variant="primary" onClick={submit} loading={busy} data-testid="voice-provider-save">
|
|
133
|
+
<Button variant="ghost" onClick={onClose} disabled={busy}>{t("common.cancel")}</Button>
|
|
134
|
+
<Button variant="primary" onClick={submit} loading={busy} data-testid="voice-provider-save">{t("common.save")}</Button>
|
|
135
135
|
</>
|
|
136
136
|
}
|
|
137
137
|
>
|
|
138
138
|
<div className="space-y-3">
|
|
139
139
|
{providerId === "piper" && (
|
|
140
140
|
<>
|
|
141
|
-
<Field label="
|
|
141
|
+
<Field label={t("voice_ui.piper_bin_label")} hint={t("voice_ui.piper_bin_hint")}>
|
|
142
142
|
<Input value={f.bin} onChange={(e) => up({ bin: e.target.value })} placeholder="piper" />
|
|
143
143
|
</Field>
|
|
144
|
-
<Field label="
|
|
145
|
-
<Input value={f.model} onChange={(e) => up({ model: e.target.value })} placeholder="/abs/path/
|
|
144
|
+
<Field label={t("voice_ui.piper_model_label")} hint={t("voice_ui.piper_model_hint")}>
|
|
145
|
+
<Input value={f.model} onChange={(e) => up({ model: e.target.value })} placeholder="/abs/path/voice.onnx" />
|
|
146
146
|
</Field>
|
|
147
|
-
<Field label="
|
|
147
|
+
<Field label={t("voice_ui.piper_speaker_label")} hint={t("voice_ui.piper_speaker_hint")}>
|
|
148
148
|
<Input value={f.speaker} onChange={(e) => up({ speaker: e.target.value })} placeholder="0" />
|
|
149
149
|
</Field>
|
|
150
150
|
</>
|
|
@@ -152,16 +152,16 @@ export function VoiceProviderModal({ open, providerId, config, onClose, onSave }
|
|
|
152
152
|
|
|
153
153
|
{providerId === "elevenlabs" && (
|
|
154
154
|
<>
|
|
155
|
-
<Field label="
|
|
155
|
+
<Field label={t("voice_ui.api_key_label")} hint={existingKey ? t("voice_ui.api_key_keep_hint") : t("voice_ui.api_key_secret_hint", { env: "ELEVENLABS_API_KEY" })}>
|
|
156
156
|
<Input type="password" autoComplete="new-password" value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder={keyPlaceholder} />
|
|
157
157
|
</Field>
|
|
158
|
-
<Field label="
|
|
158
|
+
<Field label={t("voice_ui.model_label")}>
|
|
159
159
|
<UiSelect value={f.model || ""} onChange={(v) => up({ model: v })} options={ELEVENLABS_MODELS.map((m) => ({ value: m, label: m }))} placeholder="eleven_multilingual_v2" />
|
|
160
160
|
</Field>
|
|
161
|
-
<Field label="
|
|
161
|
+
<Field label={t("voice_ui.voice_id_label")} hint={t("voice_ui.voice_id_hint")}>
|
|
162
162
|
<Input value={f.voice_id} onChange={(e) => up({ voice_id: e.target.value })} placeholder="EXAVITQu4vr4xnSDxMaL" />
|
|
163
163
|
</Field>
|
|
164
|
-
<Field label="
|
|
164
|
+
<Field label={t("voice_ui.output_format_label")}>
|
|
165
165
|
<Input value={f.output_format} onChange={(e) => up({ output_format: e.target.value })} placeholder="mp3_44100_128" />
|
|
166
166
|
</Field>
|
|
167
167
|
</>
|
|
@@ -169,16 +169,16 @@ export function VoiceProviderModal({ open, providerId, config, onClose, onSave }
|
|
|
169
169
|
|
|
170
170
|
{providerId === "openai" && (
|
|
171
171
|
<>
|
|
172
|
-
<Field label="
|
|
172
|
+
<Field label={t("voice_ui.api_key_label")} hint={existingKey ? t("voice_ui.api_key_keep_hint") : t("voice_ui.api_key_reuse_hint", { engine: "engines.openai.api_key", env: "OPENAI_API_KEY" })}>
|
|
173
173
|
<Input type="password" autoComplete="new-password" value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder={keyPlaceholder} />
|
|
174
174
|
</Field>
|
|
175
|
-
<Field label="
|
|
175
|
+
<Field label={t("voice_ui.model_label")}>
|
|
176
176
|
<UiSelect value={f.model || "tts-1"} onChange={(v) => up({ model: v })} options={OPENAI_TTS_MODELS.map((m) => ({ value: m, label: m }))} />
|
|
177
177
|
</Field>
|
|
178
|
-
<Field label="
|
|
178
|
+
<Field label={t("voice_ui.voice_label")}>
|
|
179
179
|
<UiSelect value={f.voice || "alloy"} onChange={(v) => up({ voice: v })} options={OPENAI_TTS_VOICES.map((m) => ({ value: m, label: m }))} />
|
|
180
180
|
</Field>
|
|
181
|
-
<Field label="
|
|
181
|
+
<Field label={t("voice_ui.format_label")}>
|
|
182
182
|
<UiSelect value={f.format || "mp3"} onChange={(v) => up({ format: v })} options={["mp3", "opus", "aac", "flac", "wav"].map((m) => ({ value: m, label: m }))} />
|
|
183
183
|
</Field>
|
|
184
184
|
</>
|
|
@@ -186,24 +186,24 @@ export function VoiceProviderModal({ open, providerId, config, onClose, onSave }
|
|
|
186
186
|
|
|
187
187
|
{providerId === "gemini" && (
|
|
188
188
|
<>
|
|
189
|
-
<Field label="
|
|
189
|
+
<Field label={t("voice_ui.api_key_label")} hint={existingKey ? t("voice_ui.api_key_keep_hint") : t("voice_ui.api_key_reuse_hint", { engine: "engines.gemini.api_key", env: "GEMINI_API_KEY" })}>
|
|
190
190
|
<Input type="password" autoComplete="new-password" value={apiKey} onChange={(e) => setApiKey(e.target.value)} placeholder={keyPlaceholder} />
|
|
191
191
|
</Field>
|
|
192
|
-
<Field label="
|
|
192
|
+
<Field label={t("voice_ui.model_label")} hint={t("voice_ui.gemini_model_hint")}>
|
|
193
193
|
<Input value={f.model} onChange={(e) => up({ model: e.target.value })} placeholder="gemini-2.5-flash-preview-tts" />
|
|
194
194
|
</Field>
|
|
195
|
-
<Field label="
|
|
195
|
+
<Field label={t("voice_ui.voice_label")}>
|
|
196
196
|
<UiSelect value={f.voice || "Kore"} onChange={(v) => up({ voice: v })} options={GEMINI_TTS_VOICES.map((m) => ({ value: m, label: m }))} />
|
|
197
197
|
</Field>
|
|
198
|
-
<Field label="
|
|
199
|
-
<Textarea rows={2} value={f.style || ""} onChange={(e) => up({ style: e.target.value })} placeholder="
|
|
198
|
+
<Field label={t("voice_ui.style_label")} hint={t("voice_ui.style_hint")}>
|
|
199
|
+
<Textarea rows={2} value={f.style || ""} onChange={(e) => up({ style: e.target.value })} placeholder={t("voice_ui.style_ph")} />
|
|
200
200
|
</Field>
|
|
201
201
|
</>
|
|
202
202
|
)}
|
|
203
203
|
|
|
204
204
|
{providerId === "mock" && (
|
|
205
205
|
<p className="text-sm text-muted-fg">
|
|
206
|
-
|
|
206
|
+
{t("voice_ui.mock_desc")}
|
|
207
207
|
</p>
|
|
208
208
|
)}
|
|
209
209
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Field } from "../ui";
|
|
2
2
|
import { UiSelect } from "../UiSelect";
|
|
3
3
|
import { WHISPER_MODELS, type TranscriptionConfig } from "../../lib/api/voice";
|
|
4
|
+
import { t } from "../../i18n";
|
|
4
5
|
|
|
5
6
|
// STT (speech-to-text) configuration. Persisted under config.transcription.
|
|
6
7
|
// The actual capture happens in the deck overlay / Telegram / CLI; here the
|
|
@@ -12,20 +13,20 @@ interface Props {
|
|
|
12
13
|
busy?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
{ value: "auto", label: "
|
|
17
|
-
{ value: "local", label: "
|
|
18
|
-
{ value: "openai", label: "
|
|
16
|
+
const providerOptions = () => [
|
|
17
|
+
{ value: "auto", label: t("voice_ui.stt_provider_auto") },
|
|
18
|
+
{ value: "local", label: t("voice_ui.stt_provider_local") },
|
|
19
|
+
{ value: "openai", label: t("voice_ui.stt_provider_openai") },
|
|
19
20
|
];
|
|
20
21
|
|
|
21
|
-
const
|
|
22
|
-
{ value: "auto", label: "
|
|
23
|
-
{ value: "es", label: "
|
|
24
|
-
{ value: "en", label: "
|
|
25
|
-
{ value: "pt", label: "
|
|
26
|
-
{ value: "fr", label: "
|
|
27
|
-
{ value: "it", label: "
|
|
28
|
-
{ value: "de", label: "
|
|
22
|
+
const langOptions = () => [
|
|
23
|
+
{ value: "auto", label: t("voice_ui.lang_auto") },
|
|
24
|
+
{ value: "es", label: t("voice_ui.lang_es") },
|
|
25
|
+
{ value: "en", label: t("voice_ui.lang_en") },
|
|
26
|
+
{ value: "pt", label: t("voice_ui.lang_pt") },
|
|
27
|
+
{ value: "fr", label: t("voice_ui.lang_fr") },
|
|
28
|
+
{ value: "it", label: t("voice_ui.lang_it") },
|
|
29
|
+
{ value: "de", label: t("voice_ui.lang_de") },
|
|
29
30
|
];
|
|
30
31
|
|
|
31
32
|
export function VoiceSttCard({ config, onPatch, busy }: Props) {
|
|
@@ -37,11 +38,11 @@ export function VoiceSttCard({ config, onPatch, busy }: Props) {
|
|
|
37
38
|
|
|
38
39
|
return (
|
|
39
40
|
<div className="space-y-3">
|
|
40
|
-
<Field label="
|
|
41
|
+
<Field label={t("voice_ui.stt_engine_label")} hint={t("voice_ui.stt_engine_hint")}>
|
|
41
42
|
<UiSelect
|
|
42
43
|
value={provider}
|
|
43
44
|
onChange={(v) => onPatch({ "transcription.provider": v })}
|
|
44
|
-
options={
|
|
45
|
+
options={providerOptions()}
|
|
45
46
|
disabled={busy}
|
|
46
47
|
className="max-w-md"
|
|
47
48
|
/>
|
|
@@ -49,7 +50,7 @@ export function VoiceSttCard({ config, onPatch, busy }: Props) {
|
|
|
49
50
|
|
|
50
51
|
{usesLocal && (
|
|
51
52
|
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
52
|
-
<Field label="
|
|
53
|
+
<Field label={t("voice_ui.stt_model_label")} hint={t("voice_ui.stt_model_hint")}>
|
|
53
54
|
<UiSelect
|
|
54
55
|
value={model}
|
|
55
56
|
onChange={(v) => onPatch({ "transcription.local.model": v })}
|
|
@@ -57,11 +58,11 @@ export function VoiceSttCard({ config, onPatch, busy }: Props) {
|
|
|
57
58
|
disabled={busy}
|
|
58
59
|
/>
|
|
59
60
|
</Field>
|
|
60
|
-
<Field label="
|
|
61
|
+
<Field label={t("voice_ui.stt_language_label")} hint={t("voice_ui.stt_language_hint")}>
|
|
61
62
|
<UiSelect
|
|
62
63
|
value={language}
|
|
63
64
|
onChange={(v) => onPatch({ "transcription.local.language": v })}
|
|
64
|
-
options={
|
|
65
|
+
options={langOptions()}
|
|
65
66
|
disabled={busy}
|
|
66
67
|
/>
|
|
67
68
|
</Field>
|
|
@@ -5,6 +5,7 @@ import { UiSelect } from "../UiSelect";
|
|
|
5
5
|
import { useToast } from "../Toast";
|
|
6
6
|
import { useTtsPlayer } from "./useTtsPlayer";
|
|
7
7
|
import { Voice, TTS_PROVIDER_META, type TtsEngineInfo, type TtsMode, type TtsSayResult } from "../../lib/api/voice";
|
|
8
|
+
import { t } from "../../i18n";
|
|
8
9
|
|
|
9
10
|
// "Decir esto" tester. Lets you pick which engine to synthesize with (overriding
|
|
10
11
|
// the saved default) and add a free-text speaking-style instruction, then plays
|
|
@@ -21,7 +22,7 @@ interface Props {
|
|
|
21
22
|
export function VoiceTestCard({ engines, defaultProvider, mode }: Props) {
|
|
22
23
|
const toast = useToast();
|
|
23
24
|
const { play, stop, playing, loading: playLoading } = useTtsPlayer();
|
|
24
|
-
const [text, setText] = useState("
|
|
25
|
+
const [text, setText] = useState(t("voice_ui.test_default_text"));
|
|
25
26
|
// "" = use the saved default; otherwise force a specific engine.
|
|
26
27
|
const [engine, setEngine] = useState("");
|
|
27
28
|
const [style, setStyle] = useState("");
|
|
@@ -30,34 +31,34 @@ export function VoiceTestCard({ engines, defaultProvider, mode }: Props) {
|
|
|
30
31
|
|
|
31
32
|
const defaultLabel =
|
|
32
33
|
mode === "single" && defaultProvider && defaultProvider !== "auto"
|
|
33
|
-
?
|
|
34
|
-
: "
|
|
34
|
+
? t("voice_ui.test_default_engine", { name: TTS_PROVIDER_META[defaultProvider]?.name || defaultProvider })
|
|
35
|
+
: t("voice_ui.test_default_chain");
|
|
35
36
|
|
|
36
37
|
const options = [
|
|
37
38
|
{ value: "", label: defaultLabel },
|
|
38
39
|
...engines.map((e) => ({
|
|
39
40
|
value: e.id,
|
|
40
|
-
label: `${TTS_PROVIDER_META[e.id]?.name || e.id}${e.available ? "" : "
|
|
41
|
+
label: `${TTS_PROVIDER_META[e.id]?.name || e.id}${e.available ? "" : t("voice_ui.test_unavailable_suffix")}`,
|
|
41
42
|
})),
|
|
42
43
|
];
|
|
43
44
|
|
|
44
45
|
const say = async () => {
|
|
45
|
-
const
|
|
46
|
-
if (!
|
|
47
|
-
toast.error("
|
|
46
|
+
const txt = text.trim();
|
|
47
|
+
if (!txt) {
|
|
48
|
+
toast.error(t("voice_ui.test_empty_error"));
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
setBusy(true);
|
|
51
52
|
try {
|
|
52
53
|
const res = await Voice.say({
|
|
53
|
-
text:
|
|
54
|
+
text: txt,
|
|
54
55
|
provider: engine || undefined,
|
|
55
56
|
style: style.trim() || undefined,
|
|
56
57
|
});
|
|
57
58
|
setLast(res);
|
|
58
59
|
await play(res.audio_path);
|
|
59
60
|
} catch (e) {
|
|
60
|
-
toast.error((e as Error).message || "
|
|
61
|
+
toast.error((e as Error).message || t("voice_ui.test_synth_error"));
|
|
61
62
|
} finally {
|
|
62
63
|
setBusy(false);
|
|
63
64
|
}
|
|
@@ -66,43 +67,43 @@ export function VoiceTestCard({ engines, defaultProvider, mode }: Props) {
|
|
|
66
67
|
return (
|
|
67
68
|
<div className="space-y-3">
|
|
68
69
|
<div className="grid gap-3 sm:grid-cols-2">
|
|
69
|
-
<Field label="
|
|
70
|
+
<Field label={t("voice_ui.test_engine_label")} hint={t("voice_ui.test_engine_hint")}>
|
|
70
71
|
<UiSelect value={engine} onChange={setEngine} options={options} />
|
|
71
72
|
</Field>
|
|
72
|
-
<Field label="
|
|
73
|
+
<Field label={t("voice_ui.test_style_label")} hint={t("voice_ui.test_style_hint")}>
|
|
73
74
|
<Input
|
|
74
75
|
value={style}
|
|
75
76
|
onChange={(e) => setStyle(e.target.value)}
|
|
76
|
-
placeholder="
|
|
77
|
+
placeholder={t("voice_ui.style_ph")}
|
|
77
78
|
data-testid="voice-test-style"
|
|
78
79
|
/>
|
|
79
80
|
</Field>
|
|
80
81
|
</div>
|
|
81
|
-
<Field label="
|
|
82
|
+
<Field label={t("voice_ui.test_text_label")}>
|
|
82
83
|
<Textarea
|
|
83
84
|
rows={2}
|
|
84
85
|
value={text}
|
|
85
86
|
onChange={(e) => setText(e.target.value)}
|
|
86
|
-
placeholder="
|
|
87
|
+
placeholder={t("voice_ui.test_text_ph")}
|
|
87
88
|
data-testid="voice-test-input"
|
|
88
89
|
/>
|
|
89
90
|
</Field>
|
|
90
91
|
<div className="flex items-center gap-2">
|
|
91
92
|
<Button variant="primary" onClick={say} loading={busy} disabled={playLoading} data-testid="voice-test-say">
|
|
92
|
-
<Volume2 className="size-4" />
|
|
93
|
+
<Volume2 className="size-4" /> {t("voice_ui.say_this")}
|
|
93
94
|
</Button>
|
|
94
95
|
{playing ? (
|
|
95
96
|
<Button variant="secondary" onClick={stop} data-testid="voice-test-stop">
|
|
96
|
-
<Square className="size-4" />
|
|
97
|
+
<Square className="size-4" /> {t("voice_ui.stop")}
|
|
97
98
|
</Button>
|
|
98
99
|
) : last ? (
|
|
99
100
|
<Button variant="secondary" onClick={() => play(last.audio_path)} loading={playLoading} data-testid="voice-test-replay">
|
|
100
|
-
<Play className="size-4" />
|
|
101
|
+
<Play className="size-4" /> {t("voice_ui.replay")}
|
|
101
102
|
</Button>
|
|
102
103
|
) : null}
|
|
103
104
|
{last && (
|
|
104
105
|
<span className="text-xs text-muted-fg">
|
|
105
|
-
|
|
106
|
+
{t("voice_ui.engine_result")}: <strong>{last.provider}</strong>
|
|
106
107
|
{last.duration_s ? ` · ${last.duration_s.toFixed(1)}s` : ""}
|
|
107
108
|
</span>
|
|
108
109
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useRef, useState } from "react";
|
|
2
2
|
import { SuperAgent, Agents, Conversations } from "../lib/api";
|
|
3
3
|
import type { ChatStreamEvent, ChatUsage, ConversationMessage } from "../types/daemon";
|
|
4
|
+
import { t } from "../i18n";
|
|
4
5
|
|
|
5
6
|
export type ToolStatus = "running" | "done" | "error" | "deduped";
|
|
6
7
|
|
|
@@ -259,7 +260,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
|
|
|
259
260
|
const applyEvent = useCallback(
|
|
260
261
|
(ev: ChatStreamEvent) => {
|
|
261
262
|
if (ev.type === "error") {
|
|
262
|
-
onError?.(ev.error || "
|
|
263
|
+
onError?.(ev.error || t("shared_ui.err_stream"));
|
|
263
264
|
return;
|
|
264
265
|
}
|
|
265
266
|
patchLast((m) => applyStreamEvent(m, ev));
|
|
@@ -302,7 +303,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
|
|
|
302
303
|
parts: [{ kind: "text", text: out.text }],
|
|
303
304
|
}));
|
|
304
305
|
} catch (e) {
|
|
305
|
-
onError?.((e as Error)?.message || "
|
|
306
|
+
onError?.((e as Error)?.message || t("shared_ui.err_chat_failed"));
|
|
306
307
|
setMsgs((curr) => curr.filter((_, i) => i !== curr.length - 1));
|
|
307
308
|
} finally {
|
|
308
309
|
setStreaming(false);
|
|
@@ -326,10 +327,10 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
|
|
|
326
327
|
patchLast((m) => ({
|
|
327
328
|
...m,
|
|
328
329
|
pending: false,
|
|
329
|
-
parts: [...m.parts, { kind: "text", text: "
|
|
330
|
+
parts: [...m.parts, { kind: "text", text: t("code_module.stopped") }],
|
|
330
331
|
}));
|
|
331
332
|
} else {
|
|
332
|
-
onError?.((e as Error)?.message || "
|
|
333
|
+
onError?.((e as Error)?.message || t("shared_ui.err_stream_failed"));
|
|
333
334
|
setMsgs((curr) => curr.filter((_, i) => i !== curr.length - 1));
|
|
334
335
|
}
|
|
335
336
|
} finally {
|
|
@@ -364,7 +365,7 @@ export function useChat(pid: string, onError?: (msg: string) => void): UseChatRe
|
|
|
364
365
|
setConversationId(conversationId);
|
|
365
366
|
setMsgs(loaded);
|
|
366
367
|
} catch (e) {
|
|
367
|
-
onError?.((e as Error)?.message || "
|
|
368
|
+
onError?.((e as Error)?.message || t("shared_ui.err_load_conversation"));
|
|
368
369
|
}
|
|
369
370
|
},
|
|
370
371
|
[pid, streaming, onError],
|