@agentprojectcontext/apx 1.51.1 → 1.52.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/package.json +1 -1
- package/src/interfaces/web/dist/assets/{index-C1tm8nvc.js → index-adeTv5sA.js} +32 -32
- package/src/interfaces/web/dist/assets/{index-C1tm8nvc.js.map → index-adeTv5sA.js.map} +1 -1
- package/src/interfaces/web/dist/index.html +1 -1
- package/src/interfaces/web/src/components/voice/VoiceProviderList.tsx +19 -0
- package/src/interfaces/web/src/i18n/en.ts +1 -0
- package/src/interfaces/web/src/i18n/es.ts +1 -0
- package/src/interfaces/web/src/lib/api/voice.ts +3 -0
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +29 -1
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-adeTv5sA.js"></script>
|
|
22
22
|
<link rel="stylesheet" crossorigin href="/assets/index-L1pXYFUg.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
@@ -16,6 +16,7 @@ interface Props {
|
|
|
16
16
|
engines: TtsEngineInfo[];
|
|
17
17
|
order: string[]; // effective chain order from the daemon
|
|
18
18
|
onToggleEnabled: (id: string, enabled: boolean) => void;
|
|
19
|
+
onToggleEmotions: (id: string, enabled: boolean) => void;
|
|
19
20
|
onReorder: (nextOrder: string[]) => void;
|
|
20
21
|
onConfigure: (id: string) => void;
|
|
21
22
|
onRemove: (id: string) => void;
|
|
@@ -27,6 +28,7 @@ export function VoiceProviderList({
|
|
|
27
28
|
engines,
|
|
28
29
|
order,
|
|
29
30
|
onToggleEnabled,
|
|
31
|
+
onToggleEmotions,
|
|
30
32
|
onReorder,
|
|
31
33
|
onConfigure,
|
|
32
34
|
onRemove,
|
|
@@ -108,6 +110,23 @@ export function VoiceProviderList({
|
|
|
108
110
|
</div>
|
|
109
111
|
|
|
110
112
|
<div className="flex shrink-0 items-center gap-2">
|
|
113
|
+
{e.emotionsApplicable && (
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
onClick={() => onToggleEmotions(id, !e.emotionsOn)}
|
|
117
|
+
disabled={busy}
|
|
118
|
+
title={t("voice_ui.emotions_hint")}
|
|
119
|
+
data-testid={`voice-provider-${id}-emotions`}
|
|
120
|
+
className={cn(
|
|
121
|
+
"rounded-md border px-2 py-1 text-xs font-medium transition-colors disabled:opacity-50",
|
|
122
|
+
e.emotionsOn
|
|
123
|
+
? "border-emerald-500/50 bg-emerald-500/10 text-emerald-300"
|
|
124
|
+
: "border-border text-muted-fg hover:text-fg",
|
|
125
|
+
)}
|
|
126
|
+
>
|
|
127
|
+
{t("voice_ui.emotions_short")}
|
|
128
|
+
</button>
|
|
129
|
+
)}
|
|
111
130
|
<Switch
|
|
112
131
|
checked={e.enabled}
|
|
113
132
|
onChange={(v) => onToggleEnabled(id, v)}
|
|
@@ -1158,6 +1158,7 @@ export const en = {
|
|
|
1158
1158
|
style_ph: "speak in a cheerful, energetic tone",
|
|
1159
1159
|
temperature_label: "Temperature (optional)",
|
|
1160
1160
|
temperature_hint: "Sampling temperature for custom endpoints. Empty = server default.",
|
|
1161
|
+
emotions_short: "Emotions",
|
|
1161
1162
|
emotions_label: "Inline emotion tags",
|
|
1162
1163
|
emotions_hint: "When this engine speaks, let the agent drop [happy]/[whisper]-style tags into voice replies to color the delivery. Only enable it if this engine understands the tags (e.g. a QVox/Qwen3-TTS endpoint) — otherwise they're stripped before synthesis.",
|
|
1163
1164
|
emotions_tags_label: "Allowed tags",
|
|
@@ -1156,6 +1156,7 @@ export const es = {
|
|
|
1156
1156
|
style_ph: "hablá en un tono alegre y enérgico",
|
|
1157
1157
|
temperature_label: "Temperatura (opcional)",
|
|
1158
1158
|
temperature_hint: "Temperatura de sampleo para endpoints custom. Vacío = default del servidor.",
|
|
1159
|
+
emotions_short: "Emociones",
|
|
1159
1160
|
emotions_label: "Tags de emoción inline",
|
|
1160
1161
|
emotions_hint: "Cuando hable este motor, deja que el agente meta tags tipo [happy]/[whisper] en las respuestas de voz para darles color. Activalo solo si este motor entiende los tags (ej. un endpoint QVox/Qwen3-TTS) — si no, se quitan antes de sintetizar.",
|
|
1161
1162
|
emotions_tags_label: "Tags permitidos",
|
|
@@ -25,6 +25,9 @@ export interface TtsEngineInfo {
|
|
|
25
25
|
custom?: boolean; // user-added OpenAI-compatible provider ("custom:<slug>")
|
|
26
26
|
label?: string; // display name for custom providers
|
|
27
27
|
note?: string; // e.g. the custom base_url
|
|
28
|
+
// UI-enriched (computed client-side from config, not reported by the daemon):
|
|
29
|
+
emotionsApplicable?: boolean; // engine can parse inline [tags] (custom/gemini)
|
|
30
|
+
emotionsOn?: boolean; // voice.tts.<...>.emotions.enabled
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export interface TtsProvidersResponse {
|
|
@@ -40,9 +40,20 @@ export function VoiceScreen() {
|
|
|
40
40
|
const transcriptionCfg = (cfgView.transcription || {}) as TranscriptionConfig;
|
|
41
41
|
const configuredProvider = providersData?.configured_provider || voiceCfg.provider || "auto";
|
|
42
42
|
const mode: TtsMode = providersData?.mode || voiceCfg.mode || "chain";
|
|
43
|
-
const engines = providersData?.engines || [];
|
|
44
43
|
const order = providersData?.order || [];
|
|
45
44
|
|
|
45
|
+
// Enrich the daemon's engine list with the emotion-tags state read straight
|
|
46
|
+
// from config (so the in-row toggle is live without a daemon round-trip).
|
|
47
|
+
// Only custom + Gemini parse inline [tags].
|
|
48
|
+
const engines = (providersData?.engines || []).map((e) => {
|
|
49
|
+
const applicable = !!e.custom || e.id === "gemini";
|
|
50
|
+
if (!applicable) return e;
|
|
51
|
+
const block = e.custom
|
|
52
|
+
? voiceCfg.custom?.[e.id.slice(7)]
|
|
53
|
+
: voiceCfg.gemini;
|
|
54
|
+
return { ...e, emotionsApplicable: true, emotionsOn: !!block?.emotions?.enabled };
|
|
55
|
+
});
|
|
56
|
+
|
|
46
57
|
const editingConfig = useMemo<Record<string, unknown>>(() => {
|
|
47
58
|
if (!editing || editing === "__new__") return {};
|
|
48
59
|
if (editing.startsWith("custom:")) {
|
|
@@ -67,6 +78,22 @@ export function VoiceScreen() {
|
|
|
67
78
|
}
|
|
68
79
|
};
|
|
69
80
|
|
|
81
|
+
const toggleEmotions = async (id: string, enabled: boolean) => {
|
|
82
|
+
setBusyDefault(true);
|
|
83
|
+
try {
|
|
84
|
+
const key = id.startsWith("custom:")
|
|
85
|
+
? `voice.tts.custom.${id.slice(7)}.emotions.enabled`
|
|
86
|
+
: `voice.tts.${id}.emotions.enabled`;
|
|
87
|
+
await patch({ [key]: enabled });
|
|
88
|
+
await mutateProviders();
|
|
89
|
+
await mutateCfg();
|
|
90
|
+
} catch (e) {
|
|
91
|
+
toast.error((e as Error).message);
|
|
92
|
+
} finally {
|
|
93
|
+
setBusyDefault(false);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
70
97
|
const reorder = async (nextOrder: string[]) => {
|
|
71
98
|
setBusyDefault(true);
|
|
72
99
|
try {
|
|
@@ -130,6 +157,7 @@ export function VoiceScreen() {
|
|
|
130
157
|
engines={engines}
|
|
131
158
|
order={order}
|
|
132
159
|
onToggleEnabled={toggleEnabled}
|
|
160
|
+
onToggleEmotions={toggleEmotions}
|
|
133
161
|
onReorder={reorder}
|
|
134
162
|
onConfigure={(id) => setEditing(id)}
|
|
135
163
|
onRemove={removeCustom}
|