@flowingspring/dsh-voco 0.2.9 → 0.3.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 CHANGED
@@ -37,6 +37,7 @@ The plugin uses `XingChenAGI/XingChenASR-V3.2-Ultra` for cloud speech recognitio
37
37
  - Speaks a short contextual acknowledgement before starting delegated Agent work.
38
38
  - Keeps full task reports in the task UI while speaking a concise, purpose-written result.
39
39
  - Supports interruption, browser navigation, reconnects, and restored conversation history.
40
+ - Pure voice sessions get a concise title from the first meaningful voice request, with the transcription as a fallback when the title model is unavailable.
40
41
  - Ships the server and browser surfaces as one public npm package.
41
42
 
42
43
  ## Configuration
package/README.zh.md CHANGED
@@ -37,6 +37,7 @@ SILICONFLOW_API_KEY=你的密钥
37
37
  - 委派任务启动前立即播报一句贴合当前请求的简短确认语。
38
38
  - 完整任务报告保留在任务界面,语音只播报专门生成的简洁结果。
39
39
  - 支持语音打断、页面切换、断线重连以及历史对话恢复。
40
+ - 纯语音会话会根据第一条有效语音请求生成简短标题;标题模型不可用时使用语音转写作为备用。
40
41
  - 服务端和浏览器界面统一通过一个公开 npm 包发行。
41
42
 
42
43
  ## 配置项
package/lib/client.js CHANGED
@@ -18,11 +18,11 @@ window.__ModuleLoader__.load({
18
18
  document.head.appendChild(tag);
19
19
  }
20
20
  var VoiceControl_module_css_default = {
21
- "controls": "TFZsua_controls",
21
+ "interruptButton": "TFZsua_interruptButton",
22
+ "active": "TFZsua_active",
22
23
  "voice-pulse": "TFZsua_voice-pulse",
23
24
  "button": "TFZsua_button",
24
- "active": "TFZsua_active",
25
- "interruptButton": "TFZsua_interruptButton"
25
+ "controls": "TFZsua_controls"
26
26
  };
27
27
  //#endregion
28
28
  //#region ../ui-voice/src/client/VoiceControl.tsx
@@ -141,22 +141,22 @@ window.__ModuleLoader__.load({
141
141
  document.head.appendChild(tag);
142
142
  }
143
143
  var VoiceNodeViews_module_css_default = {
144
- "taskInput": "Kt2smW_taskInput",
145
- "miniWave": "Kt2smW_miniWave",
146
- "taskSummary": "Kt2smW_taskSummary",
147
- "meta": "Kt2smW_meta",
148
- "bubble": "Kt2smW_bubble",
149
- "taskStatus": "Kt2smW_taskStatus",
150
- "taskActions": "Kt2smW_taskActions",
151
144
  "taskLink": "Kt2smW_taskLink",
152
- "taskDetails": "Kt2smW_taskDetails",
153
- "taskDot": "Kt2smW_taskDot",
145
+ "taskCard": "Kt2smW_taskCard",
154
146
  "utterance": "Kt2smW_utterance",
147
+ "taskDot": "Kt2smW_taskDot",
148
+ "bubble": "Kt2smW_bubble",
155
149
  "voiceBadge": "Kt2smW_voiceBadge",
156
- "taskCard": "Kt2smW_taskCard",
157
- "taskCancel": "Kt2smW_taskCancel",
150
+ "taskActions": "Kt2smW_taskActions",
158
151
  "taskChevron": "Kt2smW_taskChevron",
152
+ "taskSummary": "Kt2smW_taskSummary",
153
+ "miniWave": "Kt2smW_miniWave",
154
+ "meta": "Kt2smW_meta",
155
+ "taskDetails": "Kt2smW_taskDetails",
159
156
  "taskUpdate": "Kt2smW_taskUpdate",
157
+ "taskCancel": "Kt2smW_taskCancel",
158
+ "taskInput": "Kt2smW_taskInput",
159
+ "taskStatus": "Kt2smW_taskStatus",
160
160
  "taskTitle": "Kt2smW_taskTitle"
161
161
  };
162
162
  //#endregion
@@ -285,11 +285,11 @@ window.__ModuleLoader__.load({
285
285
  document.head.appendChild(tag);
286
286
  }
287
287
  var VoiceOverlay_module_css_default = {
288
- "wave": "GPPB2G_wave",
289
288
  "root": "GPPB2G_root",
289
+ "status": "GPPB2G_status",
290
+ "wave": "GPPB2G_wave",
290
291
  "voice-wave": "GPPB2G_voice-wave",
291
- "stop": "GPPB2G_stop",
292
- "status": "GPPB2G_status"
292
+ "stop": "GPPB2G_stop"
293
293
  };
294
294
  //#endregion
295
295
  //#region ../ui-voice/src/client/VoiceOverlay.tsx
@@ -159,6 +159,13 @@ function optionalWorkspaceMemory(ctx) {
159
159
  return;
160
160
  }
161
161
  }
162
+ function optionalSessionTitle(ctx) {
163
+ try {
164
+ return ctx.get("sessionTitle");
165
+ } catch {
166
+ return;
167
+ }
168
+ }
162
169
  function workspaceMemoryReference(memory) {
163
170
  const sections = [];
164
171
  if (memory.summary.trim() !== "") sections.push(`稳定摘要:\n${memory.summary.trim()}`);
@@ -193,6 +200,55 @@ const REWRITE_EVENT_INSTRUCTIONS = {
193
200
  warning: "这是警告信息。简洁说明风险、影响以及用户需要知道的事项。",
194
201
  error: "这是失败信息。明确说明没有完成、主要原因以及可行的下一步。"
195
202
  };
203
+ const VOICE_TITLE_SYSTEM_PROMPT = `你是语音会话标题生成器。根据用户第一条有实际内容的语音请求,生成一个简短、准确、便于在会话列表中识别的中文标题。
204
+
205
+ 只输出标题本身,不要解释,不要引号,不要 Markdown,不要句末标点,不要使用“语音会话”“新会话”等泛化名称,不超过 12 个汉字。不要加入文件大小、行数、路径等执行细节。`;
206
+ function voiceTitleCandidate(value) {
207
+ const normalized = value.replace(/```[\s\S]*?```/gu, "").replace(/^\s*(?:标题|title)\s*[::]\s*/iu, "").replace(/["'「」『』]/gu, "").split(/\r?\n/u, 1)[0].replace(/[。!?!?;;,,::]+$/u, "").replace(/\s+/gu, " ").trim();
208
+ return Array.from(normalized).slice(0, 12).join("");
209
+ }
210
+ function isMeaningfulVoiceTitleInput(value) {
211
+ const normalized = value.replace(/\s+/gu, "").trim();
212
+ if (Array.from(normalized).length < 4) return false;
213
+ return !/^(?:你好|您好|嗨|哈喽|喂|在吗|有人吗|测试|谢谢|好的)[,。!?!?、]*$/u.test(normalized);
214
+ }
215
+ async function generateVoiceSessionTitle(ctx, session, input, titleService) {
216
+ let candidate = "";
217
+ try {
218
+ const llm = ctx.get("llm");
219
+ if (llm !== void 0) {
220
+ const selection = ctx.agentDefaultModel.currentSelection();
221
+ const message = createUserMessage({
222
+ content: [{
223
+ type: "text",
224
+ text: `用户第一条有效语音请求:${input}`
225
+ }],
226
+ source: {
227
+ kind: "plugin",
228
+ plugin: "voice-assistant"
229
+ }
230
+ });
231
+ let output = "";
232
+ for await (const chunk of llm.stream({
233
+ provider: selection.provider,
234
+ model: selection.model,
235
+ ...selection.reasoningEffort === void 0 ? {} : { reasoningEffort: selection.reasoningEffort },
236
+ messages: [message],
237
+ system: VOICE_TITLE_SYSTEM_PROMPT
238
+ })) if (chunk.type === "text-delta") output += chunk.text;
239
+ candidate = voiceTitleCandidate(output);
240
+ }
241
+ } catch (error) {
242
+ ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
243
+ }
244
+ if (candidate === "") candidate = voiceTitleCandidate(input);
245
+ if (candidate === "" || titleService.get(session) !== void 0) return;
246
+ try {
247
+ titleService.rename(session, candidate);
248
+ } catch (error) {
249
+ ctx.logger.warn(error instanceof Error ? error : new Error(String(error)));
250
+ }
251
+ }
196
252
  /** Install the driver. @param ctx - composed Agent and voice context. @param config - driver copy and queue bounds. */
197
253
  function apply(ctx, config = {}) {
198
254
  const bindings = /* @__PURE__ */ new Map();
@@ -223,6 +279,7 @@ function apply(ctx, config = {}) {
223
279
  interactionMode: void 0,
224
280
  voiceAttached: false,
225
281
  voiceTurnMarked: false,
282
+ voiceTitleRequested: false,
226
283
  active: void 0,
227
284
  continuousTaskAgent: void 0,
228
285
  lastTerminalTaskId: void 0,
@@ -716,13 +773,22 @@ function apply(ctx, config = {}) {
716
773
  };
717
774
  const endUtterance = (binding, utteranceId, role, state, finalText, responseId) => {
718
775
  const utterance = beginUtterance(binding, utteranceId, role, responseId);
719
- requireSourceSession(binding).append("voice/utterance-end", {
776
+ const sourceSession = requireSourceSession(binding);
777
+ sourceSession.append("voice/utterance-end", {
720
778
  utteranceId,
721
779
  role,
722
780
  text: finalText ?? utterance.text,
723
781
  state,
724
782
  ...responseId === void 0 ? {} : { responseId }
725
783
  });
784
+ const utteranceText = finalText ?? utterance.text;
785
+ if (role === "user" && state === "completed" && isMeaningfulVoiceTitleInput(utteranceText)) {
786
+ const titleService = optionalSessionTitle(ctx);
787
+ if (titleService !== void 0 && !binding.voiceTitleRequested && titleService.get(sourceSession) === void 0) {
788
+ binding.voiceTitleRequested = true;
789
+ generateVoiceSessionTitle(ctx, sourceSession, utteranceText, titleService);
790
+ }
791
+ }
726
792
  binding.utterances.delete(utteranceId);
727
793
  };
728
794
  const interruptAssistantUtterances = (binding, responseId) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flowingspring/dsh-voco",
3
3
  "description": "Persistent voice conversations for DSH with cloud speech recognition, Edge TTS, and background Agent delegation",
4
- "version": "0.2.9",
4
+ "version": "0.3.0",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",