speechflow 2.4.0 → 2.4.2

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.
Files changed (76) hide show
  1. package/.ase/service.log +531 -0
  2. package/.ase/service.yaml +1 -0
  3. package/.ase/task/TASK-flush.md +44 -0
  4. package/CHANGELOG.md +33 -2
  5. package/README.md +10 -9
  6. package/etc/speechflow.yaml +1 -1
  7. package/etc/stx.conf +21 -21
  8. package/package.json +9 -5
  9. package/speechflow-cli/dst/speechflow-main-api.js +8 -1
  10. package/speechflow-cli/dst/speechflow-main-api.js.map +1 -1
  11. package/speechflow-cli/dst/speechflow-main-cli.js +2 -2
  12. package/speechflow-cli/dst/speechflow-main-cli.js.map +1 -1
  13. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +2 -0
  14. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  15. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +2 -1
  16. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  17. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +28 -12
  18. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  19. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +5 -1
  20. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  21. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js +9 -15
  22. package/speechflow-cli/dst/speechflow-node-a2a-pitch.js.map +1 -1
  23. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.d.ts +3 -0
  24. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +149 -101
  25. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  26. package/speechflow-cli/dst/speechflow-node-t2t-google.js +3 -0
  27. package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -1
  28. package/speechflow-cli/dst/speechflow-node-t2t-opus.js.map +1 -1
  29. package/speechflow-cli/dst/speechflow-node-t2t-proofread.js +17 -9
  30. package/speechflow-cli/dst/speechflow-node-t2t-proofread.js.map +1 -1
  31. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +20 -3
  32. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  33. package/speechflow-cli/dst/speechflow-node-xio-exec.js +6 -4
  34. package/speechflow-cli/dst/speechflow-node-xio-exec.js.map +1 -1
  35. package/speechflow-cli/dst/speechflow-util-llm.js +9 -2
  36. package/speechflow-cli/dst/speechflow-util-llm.js.map +1 -1
  37. package/speechflow-cli/etc/oxlint.jsonc +5 -0
  38. package/speechflow-cli/etc/stx.conf +6 -6
  39. package/speechflow-cli/package.json +53 -43
  40. package/speechflow-cli/src/speechflow-main-api.ts +8 -1
  41. package/speechflow-cli/src/speechflow-main-cli.ts +1 -1
  42. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -1
  43. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +3 -2
  44. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +30 -13
  45. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +6 -1
  46. package/speechflow-cli/src/speechflow-node-a2a-pitch.ts +12 -20
  47. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +162 -108
  48. package/speechflow-cli/src/speechflow-node-t2t-google.ts +3 -0
  49. package/speechflow-cli/src/speechflow-node-t2t-opus.ts +1 -1
  50. package/speechflow-cli/src/speechflow-node-t2t-proofread.ts +17 -9
  51. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +21 -3
  52. package/speechflow-cli/src/speechflow-node-xio-exec.ts +7 -5
  53. package/speechflow-cli/src/speechflow-util-llm.ts +11 -3
  54. package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
  55. package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
  56. package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
  57. package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
  58. package/speechflow-ui-db/dst/index.css +1 -1
  59. package/speechflow-ui-db/dst/index.js +579 -57
  60. package/speechflow-ui-db/etc/stx.conf +5 -4
  61. package/speechflow-ui-db/etc/vite-client.mts +1 -1
  62. package/speechflow-ui-db/package.json +27 -22
  63. package/speechflow-ui-st/dst/app-font-fa-brands-400.woff2 +0 -0
  64. package/speechflow-ui-st/dst/app-font-fa-regular-400.woff2 +0 -0
  65. package/speechflow-ui-st/dst/app-font-fa-solid-900.woff2 +0 -0
  66. package/speechflow-ui-st/dst/app-font-fa-v4compatibility.woff2 +0 -0
  67. package/speechflow-ui-st/dst/index.css +1 -1
  68. package/speechflow-ui-st/dst/index.js +593 -71
  69. package/speechflow-ui-st/etc/stx.conf +5 -4
  70. package/speechflow-ui-st/etc/vite-client.mts +6 -0
  71. package/speechflow-ui-st/package.json +27 -22
  72. package/speechflow-ui-st/src/app.vue +3 -3
  73. package/speechflow-cli/package.d/sherpa-onnx+1.12.25.patch +0 -12
  74. /package/speechflow-cli/package.d/{@typescript-eslint+typescript-estree+8.59.3.patch → @typescript-eslint+typescript-estree+8.64.0.patch} +0 -0
  75. /package/speechflow-ui-db/package.d/{@typescript-eslint+typescript-estree+8.59.3.patch → @typescript-eslint+typescript-estree+8.64.0.patch} +0 -0
  76. /package/speechflow-ui-st/package.d/{@typescript-eslint+typescript-estree+8.59.3.patch → @typescript-eslint+typescript-estree+8.64.0.patch} +0 -0
@@ -119,12 +119,14 @@ export default class SpeechFlowNodeT2TProofread extends SpeechFlowNode {
119
119
  const cfg = this.setup[this.params.lang]
120
120
  if (!cfg)
121
121
  throw new Error(`unsupported language: ${this.params.lang}`)
122
- this.log("info", `PROMPT "${cfg.systemPrompt}", chat: ${JSON.stringify(cfg.chat)}`)
123
- return llm.complete({
122
+ this.log("info", `input: "${text}"`)
123
+ const output = await llm.complete({
124
124
  system: cfg.systemPrompt,
125
125
  messages: cfg.chat,
126
126
  prompt: text
127
127
  })
128
+ this.log("info", `output: "${output}"`)
129
+ return output
128
130
  }
129
131
 
130
132
  /* establish a transform stream and connect it to LLM */
@@ -141,14 +143,20 @@ export default class SpeechFlowNodeT2TProofread extends SpeechFlowNode {
141
143
  callback()
142
144
  }
143
145
  else {
144
- proofread(chunk.payload).then((payload) => {
145
- const chunkNew = chunk.clone()
146
- chunkNew.payload = payload
147
- this.push(chunkNew)
146
+ if (chunk.kind === "final") {
147
+ proofread(chunk.payload).then((payload) => {
148
+ const chunkNew = chunk.clone()
149
+ chunkNew.payload = payload
150
+ this.push(chunkNew)
151
+ callback()
152
+ }).catch((error: unknown) => {
153
+ callback(util.ensureError(error))
154
+ })
155
+ }
156
+ else {
157
+ this.push(chunk)
148
158
  callback()
149
- }).catch((error: unknown) => {
150
- callback(util.ensureError(error))
151
- })
159
+ }
152
160
  }
153
161
  },
154
162
  final (callback) {
@@ -324,12 +324,30 @@ export default class SpeechFlowNodeT2TSentence extends SpeechFlowNode {
324
324
  return
325
325
  }
326
326
  if (element.chunk.kind === "intermediate") {
327
- self.queueRecv.walk(-1)
328
- self.queueRecv.delete()
327
+ /* the trailing element is a (speculative) intermediate chunk:
328
+ if the newly arrived chunk carries the very same intermediate
329
+ payload (Deepgram re-sends identical intermediates), treat it
330
+ as a no-op to avoid churning the queue (delete+append) and
331
+ re-emitting an unchanged preview */
332
+ if (chunk.kind === "intermediate"
333
+ && (element.chunk.payload as string) === (chunk.payload as string)) {
334
+ self.lastChunkTime = Date.now()
335
+ callback()
336
+ return
337
+ }
338
+
339
+ /* remove the trailing intermediate silently, so the
340
+ transient half-mutated queue (intermediate deleted, but
341
+ replacement not yet appended) does NOT emit a "write"
342
+ event and trigger a premature flush that would emit a
343
+ truncated preview and oscillate the dashboard preview */
344
+ self.queue.silently(() => {
345
+ self.queueRecv.walk(-1)
346
+ self.queueRecv.delete()
347
+ })
329
348
  }
330
349
  }
331
350
  }
332
- previewedPayload = ""
333
351
  self.queueRecv.append({ type: "text-frame", chunk, complete: false })
334
352
  self.lastChunkTime = Date.now()
335
353
  callback()
@@ -93,7 +93,7 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
93
93
  })
94
94
 
95
95
  /* handle subprocess errors */
96
- this.subprocess.on("error", (err) => {
96
+ this.subprocess.nodeChildProcess.on("error", (err: Error) => {
97
97
  this.log("error", `subprocess error: ${err.message}`)
98
98
  /* NOTICE: do not emit("error") on the node itself, since nothing
99
99
  listens for it and it would become an uncaughtException tearing
@@ -104,7 +104,7 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
104
104
  })
105
105
 
106
106
  /* handle subprocess exit */
107
- this.subprocess.on("exit", (code, signal) => {
107
+ this.subprocess.nodeChildProcess.on("exit", (code: number | null, signal: string | null) => {
108
108
  if (code !== 0 && code !== null)
109
109
  this.log("warning", `subprocess exited with code ${code}`)
110
110
  else if (signal)
@@ -126,10 +126,12 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
126
126
  /* dispatch according to mode */
127
127
  if (this.params.mode === "rw") {
128
128
  /* bidirectional mode: both stdin and stdout */
129
+ /* NOTICE: the object form of Stream.Duplex.from() accepts Node streams at
130
+ run-time, but @types/node types it for Web streams only, so we have to cast */
129
131
  this.stream = Stream.Duplex.from({
130
132
  readable: this.subprocess.stdout,
131
133
  writable: this.subprocess.stdin
132
- })
134
+ } as unknown as Parameters<typeof Stream.Duplex.from>[0])
133
135
  const wrapper1 = util.createTransformStreamForWritableSide(this.params.type, highWaterMark)
134
136
  const wrapper2 = util.createTransformStreamForReadableSide(
135
137
  this.params.type, () => this.timeZero, highWaterMark,
@@ -176,8 +178,8 @@ export default class SpeechFlowNodeXIOExec extends SpeechFlowNode {
176
178
  }
177
179
 
178
180
  /* remove event listeners to prevent errors during kill sequence */
179
- this.subprocess.removeAllListeners("error")
180
- this.subprocess.removeAllListeners("exit")
181
+ this.subprocess.nodeChildProcess.removeAllListeners("error")
182
+ this.subprocess.nodeChildProcess.removeAllListeners("exit")
181
183
 
182
184
  /* wait for subprocess to exit gracefully */
183
185
  const ac2 = new AbortController()
@@ -61,7 +61,7 @@ export class LLM extends EventEmitter {
61
61
  api: "",
62
62
  model: "",
63
63
  key: "",
64
- timeout: 30 * 1000,
64
+ timeout: 240 * 1000,
65
65
  temperature: 0.7,
66
66
  maxTokens: 1024,
67
67
  cacheDir: "",
@@ -333,10 +333,18 @@ export class LLM extends EventEmitter {
333
333
  throw new Error(`failed to perform HuggingFace Transformers text generation: ${err}`, { cause: err })
334
334
  })
335
335
  const single = Array.isArray(result) ? result[0] : result
336
- const generatedText = (single as Transformers.TextGenerationSingle).generated_text
337
- const content = typeof generatedText === "string" ?
336
+ const generatedText = (single as Transformers.TextGenerationOutput[number]).generated_text
337
+ const messageContent = typeof generatedText === "string" ?
338
338
  generatedText :
339
339
  generatedText.at(-1)?.content
340
+
341
+ /* flatten the potentially multi-modal message content into plain text */
342
+ const content = typeof messageContent === "string" ?
343
+ messageContent :
344
+ messageContent
345
+ ?.filter((part) => part.type === "text")
346
+ .map((part) => part.text as string)
347
+ .join("")
340
348
  if (!content)
341
349
  throw new Error("HuggingFace Transformers API returned empty content")
342
350
  return content