dsh-agent-voice 0.1.2 → 0.1.4
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/lib/client.js +68 -10
- package/lib/index.js +1 -0
- package/package.json +1 -1
- package/worker/agent_voice_worker.log +1 -0
package/lib/client.js
CHANGED
|
@@ -39,7 +39,6 @@ window.__ModuleLoader__.load({
|
|
|
39
39
|
function AgentVoice(props) {
|
|
40
40
|
const [voices, setVoices] = React.useState(FALLBACK_VOICES);
|
|
41
41
|
const [status, setStatus] = React.useState("");
|
|
42
|
-
const [lastPlayed, setLastPlayed] = React.useState(null);
|
|
43
42
|
|
|
44
43
|
const action = React.useMemo(() => useAction(props.action), [props.action]);
|
|
45
44
|
const cfgSnap = useSnapshot(props.config);
|
|
@@ -51,20 +50,21 @@ window.__ModuleLoader__.load({
|
|
|
51
50
|
const announceAttention = cfg.announceAttention !== false;
|
|
52
51
|
const volume = cfg.volume ?? 1.0;
|
|
53
52
|
|
|
54
|
-
// Live
|
|
53
|
+
// Live status feed: show the last announcement (playback happens in the
|
|
54
|
+
// always-on overlay player, so it works on every tab).
|
|
55
55
|
const statusSnap = useSnapshot(props.status);
|
|
56
56
|
const statusJson = (statusSnap && statusSnap.value && statusSnap.value.json) || null;
|
|
57
|
+
const [lastText, setLastText] = React.useState(null);
|
|
58
|
+
const lastIdRef = React.useRef(null);
|
|
57
59
|
React.useEffect(() => {
|
|
58
60
|
if (!statusJson) return;
|
|
59
61
|
let p = null;
|
|
60
62
|
try { p = JSON.parse(statusJson); } catch { return; }
|
|
61
|
-
if (!p || !p.
|
|
62
|
-
if (p.id ===
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
audio.play().catch(() => {});
|
|
67
|
-
}, [statusJson, lastPlayed]);
|
|
63
|
+
if (!p || !p.id) return;
|
|
64
|
+
if (p.id === lastIdRef.current) return;
|
|
65
|
+
lastIdRef.current = p.id;
|
|
66
|
+
setLastText(p.text || "");
|
|
67
|
+
}, [statusJson]);
|
|
68
68
|
|
|
69
69
|
React.useEffect(() => {
|
|
70
70
|
action({ action: "voices" }).then((r) => {
|
|
@@ -123,7 +123,10 @@ window.__ModuleLoader__.load({
|
|
|
123
123
|
React.createElement("div", { className: "av-row" },
|
|
124
124
|
React.createElement("button", { className: "av-btn av-primary", onClick: test }, "▶ Vyzkoušet hlas"),
|
|
125
125
|
React.createElement("button", { className: "av-btn", onClick: attention }, "🔔 Výzva k pozornosti"),
|
|
126
|
-
status ? React.createElement("span", { className: "av-badge" }, status) : null)
|
|
126
|
+
status ? React.createElement("span", { className: "av-badge" }, status) : null),
|
|
127
|
+
lastText ? React.createElement("div", { className: "av-row" },
|
|
128
|
+
React.createElement("span", { className: "av-tag" }, "Naposledy hlášeno:"),
|
|
129
|
+
React.createElement("span", { className: "av-sub" }, lastText)) : null),
|
|
127
130
|
|
|
128
131
|
React.createElement("p", { className: "av-sub" },
|
|
129
132
|
"Doporučené použití: agent zavolá dsh_agent_voice action='speak' po dokončení úkolu a action='attention', " +
|
|
@@ -139,6 +142,61 @@ window.__ModuleLoader__.load({
|
|
|
139
142
|
const action = ctx.settingsScope.bind({ namespace: "agent-voice-action" });
|
|
140
143
|
const config = ctx.settingsScope.bind({ namespace: "agent-voice" });
|
|
141
144
|
const status = ctx.settingsScope.bind({ namespace: "agent-voice-status" });
|
|
145
|
+
|
|
146
|
+
// Global always-on audio player: plays announcements on EVERY tab, not
|
|
147
|
+
// only when the Agent Voice tab is active. Lives in the root-scoped
|
|
148
|
+
// overlay seat, so it stays mounted across view switches.
|
|
149
|
+
function Player(props) {
|
|
150
|
+
const statusSnap = useSnapshot(props.status);
|
|
151
|
+
const statusJson = (statusSnap && statusSnap.value && statusSnap.value.json) || null;
|
|
152
|
+
const cfgSnap = useSnapshot(props.config);
|
|
153
|
+
const cfg = (cfgSnap && cfgSnap.value) || {};
|
|
154
|
+
const [lastPlayed, setLastPlayed] = React.useState(null);
|
|
155
|
+
const [toast, setToast] = React.useState(null);
|
|
156
|
+
const toastTimer = React.useRef(null);
|
|
157
|
+
|
|
158
|
+
React.useEffect(() => {
|
|
159
|
+
if (!statusJson) return;
|
|
160
|
+
let p = null;
|
|
161
|
+
try { p = JSON.parse(statusJson); } catch { return; }
|
|
162
|
+
if (!p || !p.dataUrl || !p.id) return;
|
|
163
|
+
if (p.id === lastPlayed) return;
|
|
164
|
+
setLastPlayed(p.id);
|
|
165
|
+
const audio = new Audio(p.dataUrl);
|
|
166
|
+
audio.volume = Math.max(0, Math.min(1, Number(p.volume ?? 1)));
|
|
167
|
+
const kindLabel = p.kind === "attention" ? "🔔 Pozor: " : (p.kind === "task" ? "✅ " : "🔊 ");
|
|
168
|
+
setToast(kindLabel + p.text);
|
|
169
|
+
if (toastTimer.current) clearTimeout(toastTimer.current);
|
|
170
|
+
toastTimer.current = setTimeout(() => setToast(null), 6000);
|
|
171
|
+
audio.play().catch(() => {});
|
|
172
|
+
}, [statusJson, lastPlayed]);
|
|
173
|
+
|
|
174
|
+
// Disabled = silent (config read from the same source the host uses).
|
|
175
|
+
if (cfg.enabled === false) return null;
|
|
176
|
+
|
|
177
|
+
return React.createElement("div", {
|
|
178
|
+
style: {
|
|
179
|
+
position: "fixed", bottom: 16, right: 16, zIndex: 9999,
|
|
180
|
+
background: "var(--dsw-alias-bg-overlay)",
|
|
181
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
182
|
+
borderRadius: 12, padding: "10px 14px",
|
|
183
|
+
boxShadow: "0 8px 30px rgba(0,0,0,.35)",
|
|
184
|
+
color: "var(--dsw-alias-label-primary)",
|
|
185
|
+
font: "13px -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif",
|
|
186
|
+
maxWidth: 320, display: toast ? "block" : "none",
|
|
187
|
+
},
|
|
188
|
+
}, toast);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
192
|
+
name: "shell.overlay",
|
|
193
|
+
id: "agent-voice-player",
|
|
194
|
+
order: 100,
|
|
195
|
+
label: () => "🔊 Agent Voice přehrávač",
|
|
196
|
+
inject: () => ({ config, status }),
|
|
197
|
+
children: {},
|
|
198
|
+
}, Player));
|
|
199
|
+
|
|
142
200
|
ctx.slots.inject("conversation.view", () => ctx.slots.register({
|
|
143
201
|
name: "conversation.view",
|
|
144
202
|
id: "studio-agent-voice",
|
package/lib/index.js
CHANGED
|
@@ -111,6 +111,7 @@ function apply(ctx) {
|
|
|
111
111
|
}), { base: {} });
|
|
112
112
|
|
|
113
113
|
async function synthesize(text, voice, language) {
|
|
114
|
+
await ensureWorker();
|
|
114
115
|
const res = await postWorker("/tts", { text, voice, language });
|
|
115
116
|
const bytes = await readFile(res.audio);
|
|
116
117
|
return "data:audio/wav;base64," + Buffer.from(bytes).toString("base64");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agent-voice",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "DeepSeek Harness plugin: gives the agent a voice — it reads completed tasks and attention requests aloud (never reasoning or interim reports) via local Piper / XTTS v2, with a settings panel and a curated set of voices.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
[2026-08-31 22:08:19] agent-voice worker listening on http://127.0.0.1:7863
|
|
2
2
|
[2026-08-31 22:08:23] error: 'PiperVoice' object has no attribute 'synthesize_stream_raw'
|
|
3
3
|
[2026-08-31 22:08:46] agent-voice worker listening on http://127.0.0.1:7863
|
|
4
|
+
[2026-09-01 13:40:42] agent-voice worker listening on http://127.0.0.1:7863
|