speechflow 1.4.4 → 1.5.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.
Files changed (168) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +273 -7
  3. package/etc/claude.md +70 -0
  4. package/etc/speechflow.png +0 -0
  5. package/etc/speechflow.yaml +29 -11
  6. package/etc/stx.conf +7 -0
  7. package/package.json +7 -6
  8. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.d.ts +1 -0
  9. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +155 -0
  10. package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -0
  11. package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +15 -0
  12. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +287 -0
  13. package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -0
  14. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.d.ts +1 -0
  15. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +208 -0
  16. package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +1 -0
  17. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.d.ts +15 -0
  18. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +312 -0
  19. package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +1 -0
  20. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.d.ts +1 -0
  21. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +161 -0
  22. package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -0
  23. package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +13 -0
  24. package/speechflow-cli/dst/speechflow-node-a2a-expander.js +208 -0
  25. package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -0
  26. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +13 -3
  27. package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
  28. package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +14 -0
  29. package/speechflow-cli/dst/speechflow-node-a2a-filler.js +233 -0
  30. package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -0
  31. package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +12 -0
  32. package/speechflow-cli/dst/speechflow-node-a2a-gain.js +125 -0
  33. package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -0
  34. package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +0 -1
  35. package/speechflow-cli/dst/speechflow-node-a2a-gender.js +28 -12
  36. package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
  37. package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -1
  38. package/speechflow-cli/dst/speechflow-node-a2a-meter.js +35 -53
  39. package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
  40. package/speechflow-cli/dst/speechflow-node-a2a-mute.js +2 -1
  41. package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
  42. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.d.ts +1 -0
  43. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +55 -0
  44. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -0
  45. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +14 -0
  46. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +184 -0
  47. package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -0
  48. package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +14 -0
  49. package/speechflow-cli/dst/speechflow-node-a2a-speex.js +156 -0
  50. package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -0
  51. package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
  52. package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
  53. package/speechflow-cli/dst/speechflow-node-a2a-wav.js +22 -17
  54. package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
  55. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +18 -0
  56. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +317 -0
  57. package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -0
  58. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -33
  59. package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
  60. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +19 -0
  61. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +351 -0
  62. package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -0
  63. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +16 -0
  64. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +171 -0
  65. package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -0
  66. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +19 -14
  67. package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
  68. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +11 -6
  69. package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
  70. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +13 -0
  71. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +141 -0
  72. package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -0
  73. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +13 -15
  74. package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
  75. package/speechflow-cli/dst/speechflow-node-t2t-format.js +10 -15
  76. package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
  77. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +44 -31
  78. package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
  79. package/speechflow-cli/dst/speechflow-node-t2t-openai.js +44 -45
  80. package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
  81. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +8 -8
  82. package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
  83. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +10 -12
  84. package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
  85. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +22 -27
  86. package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
  87. package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -0
  88. package/speechflow-cli/dst/speechflow-node-x2x-filter.js +50 -15
  89. package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
  90. package/speechflow-cli/dst/speechflow-node-x2x-trace.js +17 -18
  91. package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
  92. package/speechflow-cli/dst/speechflow-node-xio-device.js +13 -21
  93. package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
  94. package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -0
  95. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +22 -16
  96. package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
  97. package/speechflow-cli/dst/speechflow-node-xio-websocket.js +19 -19
  98. package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
  99. package/speechflow-cli/dst/speechflow-node.d.ts +6 -3
  100. package/speechflow-cli/dst/speechflow-node.js +13 -2
  101. package/speechflow-cli/dst/speechflow-node.js.map +1 -1
  102. package/speechflow-cli/dst/speechflow-utils-audio-wt.d.ts +1 -0
  103. package/speechflow-cli/dst/speechflow-utils-audio-wt.js +124 -0
  104. package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +1 -0
  105. package/speechflow-cli/dst/speechflow-utils-audio.d.ts +13 -0
  106. package/speechflow-cli/dst/speechflow-utils-audio.js +137 -0
  107. package/speechflow-cli/dst/speechflow-utils-audio.js.map +1 -0
  108. package/speechflow-cli/dst/speechflow-utils.d.ts +18 -0
  109. package/speechflow-cli/dst/speechflow-utils.js +123 -35
  110. package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
  111. package/speechflow-cli/dst/speechflow.js +114 -27
  112. package/speechflow-cli/dst/speechflow.js.map +1 -1
  113. package/speechflow-cli/etc/oxlint.jsonc +112 -11
  114. package/speechflow-cli/etc/stx.conf +2 -2
  115. package/speechflow-cli/etc/tsconfig.json +1 -1
  116. package/speechflow-cli/package.d/@shiguredo+rnnoise-wasm+2025.1.5.patch +25 -0
  117. package/speechflow-cli/package.json +102 -94
  118. package/speechflow-cli/src/lib.d.ts +24 -0
  119. package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +151 -0
  120. package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +303 -0
  121. package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +158 -0
  122. package/speechflow-cli/src/speechflow-node-a2a-expander.ts +212 -0
  123. package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +13 -3
  124. package/speechflow-cli/src/speechflow-node-a2a-filler.ts +223 -0
  125. package/speechflow-cli/src/speechflow-node-a2a-gain.ts +98 -0
  126. package/speechflow-cli/src/speechflow-node-a2a-gender.ts +31 -17
  127. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +37 -56
  128. package/speechflow-cli/src/speechflow-node-a2a-mute.ts +3 -2
  129. package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +62 -0
  130. package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +164 -0
  131. package/speechflow-cli/src/speechflow-node-a2a-speex.ts +137 -0
  132. package/speechflow-cli/src/speechflow-node-a2a-vad.ts +3 -3
  133. package/speechflow-cli/src/speechflow-node-a2a-wav.ts +20 -13
  134. package/speechflow-cli/src/speechflow-node-a2t-awstranscribe.ts +308 -0
  135. package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +16 -33
  136. package/speechflow-cli/src/speechflow-node-a2t-openaitranscribe.ts +337 -0
  137. package/speechflow-cli/src/speechflow-node-t2a-awspolly.ts +187 -0
  138. package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +19 -14
  139. package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +12 -7
  140. package/speechflow-cli/src/speechflow-node-t2t-awstranslate.ts +152 -0
  141. package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +13 -15
  142. package/speechflow-cli/src/speechflow-node-t2t-format.ts +10 -15
  143. package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +55 -42
  144. package/speechflow-cli/src/speechflow-node-t2t-openai.ts +58 -58
  145. package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +10 -10
  146. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +15 -16
  147. package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +27 -32
  148. package/speechflow-cli/src/speechflow-node-x2x-filter.ts +20 -16
  149. package/speechflow-cli/src/speechflow-node-x2x-trace.ts +20 -19
  150. package/speechflow-cli/src/speechflow-node-xio-device.ts +15 -23
  151. package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +23 -16
  152. package/speechflow-cli/src/speechflow-node-xio-websocket.ts +19 -19
  153. package/speechflow-cli/src/speechflow-node.ts +21 -8
  154. package/speechflow-cli/src/speechflow-utils-audio-wt.ts +172 -0
  155. package/speechflow-cli/src/speechflow-utils-audio.ts +147 -0
  156. package/speechflow-cli/src/speechflow-utils.ts +125 -32
  157. package/speechflow-cli/src/speechflow.ts +118 -30
  158. package/speechflow-ui-db/dst/index.css +1 -1
  159. package/speechflow-ui-db/dst/index.js +31 -31
  160. package/speechflow-ui-db/etc/eslint.mjs +0 -1
  161. package/speechflow-ui-db/etc/tsc-client.json +3 -3
  162. package/speechflow-ui-db/package.json +11 -10
  163. package/speechflow-ui-db/src/app.vue +96 -78
  164. package/speechflow-ui-st/dst/index.js +26 -26
  165. package/speechflow-ui-st/etc/eslint.mjs +0 -1
  166. package/speechflow-ui-st/etc/tsc-client.json +3 -3
  167. package/speechflow-ui-st/package.json +11 -10
  168. package/speechflow-ui-st/src/app.vue +5 -12
@@ -15,6 +15,7 @@ import Inert from "@hapi/inert"
15
15
  import WebSocket from "ws"
16
16
  import HAPIWebSocket from "hapi-plugin-websocket"
17
17
  import HAPIHeader from "hapi-plugin-header"
18
+ import OSC from "osc-js"
18
19
 
19
20
  /* external dependencies */
20
21
  import { DateTime } from "luxon"
@@ -48,6 +49,7 @@ type wsPeerInfo = {
48
49
  }
49
50
 
50
51
  /* establish asynchronous environment */
52
+ let debug = false
51
53
  ;(async () => {
52
54
  /* determine system paths */
53
55
  const { dataDir } = syspath({
@@ -69,6 +71,7 @@ type wsPeerInfo = {
69
71
  "[-p|--port <tcp-port>] " +
70
72
  "[-C|--cache <directory>] " +
71
73
  "[-d|--dashboard <type>:<id>:<name>[,...]] " +
74
+ "[-o|--osc <ip-address>:<udp-port> " +
72
75
  "[-e|--expression <expression>] " +
73
76
  "[-f|--file <file>] " +
74
77
  "[-c|--config <id>@<yaml-config-file>] " +
@@ -136,6 +139,15 @@ type wsPeerInfo = {
136
139
  default: "",
137
140
  describe: "list of dashboard block types and names"
138
141
  })
142
+ .option("o", {
143
+ alias: "osc",
144
+ type: "string",
145
+ array: false,
146
+ coerce,
147
+ nargs: 1,
148
+ default: "",
149
+ describe: "OSC/UDP endpoint to send dashboard information"
150
+ })
139
151
  .option("e", {
140
152
  alias: "expression",
141
153
  type: "string",
@@ -186,17 +198,26 @@ type wsPeerInfo = {
186
198
  logTime: true,
187
199
  logPrefix: pkg.name
188
200
  })
201
+ if (args.v.match(/^(?:info|debug)$/))
202
+ debug = true
189
203
 
190
204
  /* catch uncaught exceptions */
191
205
  process.on("uncaughtException", (err) => {
192
- cli!.log("error", `uncaught exception: ${err.message}\n${err.stack}`)
206
+ if (debug)
207
+ cli!.log("error", `uncaught exception: ${err.message}\n${err.stack}`)
208
+ else
209
+ cli!.log("error", `uncaught exception: ${err.message}`)
193
210
  process.exit(1)
194
211
  })
195
212
 
196
213
  /* catch unhandled promise rejections */
197
214
  process.on("unhandledRejection", (reason) => {
198
- if (reason instanceof Error)
199
- cli!.log("error", `unhandled rejection: ${reason.message}\n${reason.stack}`)
215
+ if (reason instanceof Error) {
216
+ if (debug)
217
+ cli!.log("error", `unhandled rejection: ${reason.message}\n${reason.stack}`)
218
+ else
219
+ cli!.log("error", `unhandled rejection: ${reason.message}`)
220
+ }
200
221
  else
201
222
  cli!.log("error", `unhandled rejection: ${reason}`)
202
223
  process.exit(1)
@@ -206,7 +227,11 @@ type wsPeerInfo = {
206
227
  cli.log("info", `starting SpeechFlow ${pkg["x-stdver"]} (${pkg["x-release"]})`)
207
228
 
208
229
  /* load .env files */
209
- const result = dotenvx.config({ encoding: "utf8", quiet: true })
230
+ const result = dotenvx.config({
231
+ encoding: "utf8",
232
+ ignore: [ "MISSING_ENV_FILE" ],
233
+ quiet: true
234
+ })
210
235
  if (result?.parsed !== undefined)
211
236
  for (const key of Object.keys(result.parsed))
212
237
  cli.log("info", `loaded environment variable "${key}" from ".env" files`)
@@ -216,8 +241,10 @@ type wsPeerInfo = {
216
241
  if (typeof args.e === "string" && args.e !== "") n++
217
242
  if (typeof args.f === "string" && args.f !== "") n++
218
243
  if (typeof args.c === "string" && args.c !== "") n++
219
- if (n !== 1)
220
- throw new Error("cannot use more than one FlowLink specification source (either option -e, -f or -c)")
244
+ if (n === 0)
245
+ throw new Error("need at least one FlowLink specification source (use one of the options -e, -f or -c)")
246
+ else if (n !== 1)
247
+ throw new Error("cannot use more than one FlowLink specification source (use only one of the options -e, -f or -c)")
221
248
 
222
249
  /* read configuration */
223
250
  let config = ""
@@ -251,15 +278,25 @@ type wsPeerInfo = {
251
278
 
252
279
  /* load internal SpeechFlow nodes */
253
280
  const pkgsI = [
281
+ "./speechflow-node-a2a-compressor.js",
282
+ "./speechflow-node-a2a-expander.js",
254
283
  "./speechflow-node-a2a-ffmpeg.js",
284
+ "./speechflow-node-a2a-filler.js",
285
+ "./speechflow-node-a2a-gain.js",
255
286
  "./speechflow-node-a2a-gender.js",
256
287
  "./speechflow-node-a2a-meter.js",
257
288
  "./speechflow-node-a2a-mute.js",
289
+ "./speechflow-node-a2a-rnnoise.js",
290
+ "./speechflow-node-a2a-speex.js",
258
291
  "./speechflow-node-a2a-vad.js",
259
292
  "./speechflow-node-a2a-wav.js",
293
+ "./speechflow-node-a2t-awstranscribe.js",
260
294
  "./speechflow-node-a2t-deepgram.js",
295
+ "./speechflow-node-a2t-openaitranscribe.js",
296
+ "./speechflow-node-t2a-awspolly.js",
261
297
  "./speechflow-node-t2a-elevenlabs.js",
262
298
  "./speechflow-node-t2a-kokoro.js",
299
+ "./speechflow-node-t2t-awstranslate.js",
263
300
  "./speechflow-node-t2t-deepl.js",
264
301
  "./speechflow-node-t2t-format.js",
265
302
  "./speechflow-node-t2t-ollama.js",
@@ -313,6 +350,19 @@ type wsPeerInfo = {
313
350
  cacheDir: args.C
314
351
  }
315
352
 
353
+ /* provide access to internal communication busses */
354
+ const busses = new Map<string, EventEmitter>()
355
+ const accessBus = (name: string): EventEmitter => {
356
+ let bus: EventEmitter
357
+ if (busses.has(name))
358
+ bus = busses.get(name)!
359
+ else {
360
+ bus = new EventEmitter()
361
+ busses.set(name, bus)
362
+ }
363
+ return bus
364
+ }
365
+
316
366
  /* handle one-time status query of nodes */
317
367
  if (args.S) {
318
368
  const table = new Table({
@@ -328,6 +378,7 @@ type wsPeerInfo = {
328
378
  for (const name of Object.keys(nodes)) {
329
379
  cli!.log("info", `gathering status of node <${name}>`)
330
380
  const node = new nodes[name](name, cfg, {}, [])
381
+ node._accessBus = accessBus
331
382
  const status = await Promise.race<{ [ key: string ]: string | number }>([
332
383
  node.status(),
333
384
  new Promise<never>((resolve, reject) => setTimeout(() =>
@@ -387,6 +438,7 @@ type wsPeerInfo = {
387
438
  nodeNums.set(NodeClass, ++num)
388
439
  const name = num === 1 ? id : `${id}:${num}`
389
440
  node = new NodeClass(name, cfg, opts, args)
441
+ node._accessBus = accessBus
390
442
  }
391
443
  catch (err) {
392
444
  /* fatal error */
@@ -402,7 +454,7 @@ type wsPeerInfo = {
402
454
  graphNodes.add(node)
403
455
  return node
404
456
  },
405
- connectNode (node1: SpeechFlowNode, node2: SpeechFlowNode) {
457
+ connectNodes (node1: SpeechFlowNode, node2: SpeechFlowNode) {
406
458
  cli!.log("info", `connect node <${node1.id}> to node <${node2.id}>`)
407
459
  node1.connect(node2)
408
460
  }
@@ -465,7 +517,7 @@ type wsPeerInfo = {
465
517
  new Promise<never>((resolve, reject) => setTimeout(() =>
466
518
  reject(new Error("timeout")), 10 * 1000))
467
519
  ]).catch((err: Error) => {
468
- cli!.log("error", `[${node.id}]: failed to open node <${node.id}>: ${err.message}`)
520
+ cli!.log("error", `<${node.id}>: failed to open node <${node.id}>: ${err.message}`)
469
521
  throw new Error(`failed to open node <${node.id}>: ${err.message}`)
470
522
  })
471
523
  }
@@ -615,8 +667,7 @@ type wsPeerInfo = {
615
667
  hapi.route({
616
668
  method: "GET",
617
669
  path: "/api/{req}/{node}/{params*}",
618
- options: {
619
- },
670
+ options: {},
620
671
  handler: (request: HAPI.Request, h: HAPI.ResponseToolkit) => {
621
672
  const peer = request.info.remoteAddress
622
673
  const params = request.params.params as string ?? ""
@@ -628,11 +679,9 @@ type wsPeerInfo = {
628
679
  args: params.split("/").filter((seg) => seg !== "")
629
680
  }
630
681
  cli!.log("info", `HAPI: peer ${peer}: GET: ${JSON.stringify(req)}`)
631
- return consumeExternalRequest(req).then(() => {
632
- return h.response({ response: "OK" }).code(200)
633
- }).catch((err) => {
634
- return h.response({ response: "ERROR", data: err.message }).code(417)
635
- })
682
+ return consumeExternalRequest(req)
683
+ .then(() => h.response({ response: "OK" }).code(200))
684
+ .catch((err) => h.response({ response: "ERROR", data: err.message }).code(417))
636
685
  }
637
686
  })
638
687
  hapi.route({
@@ -675,11 +724,9 @@ type wsPeerInfo = {
675
724
  if (req instanceof arktype.type.errors)
676
725
  return h.response({ response: "ERROR", data: `invalid request: ${req.summary}` }).code(417)
677
726
  cli!.log("info", `HAPI: peer ${peer}: POST: ${JSON.stringify(req)}`)
678
- return consumeExternalRequest(req).then(() => {
679
- return h.response({ response: "OK" }).code(200)
680
- }).catch((err: Error) => {
681
- return h.response({ response: "ERROR", data: err.message }).code(417)
682
- })
727
+ return consumeExternalRequest(req)
728
+ .then(() => h.response({ response: "OK" }).code(200))
729
+ .catch((err: Error) => h.response({ response: "ERROR", data: err.message }).code(417))
683
730
  }
684
731
  })
685
732
  await hapi.start()
@@ -697,10 +744,25 @@ type wsPeerInfo = {
697
744
  })
698
745
  }
699
746
 
700
- /* hook for dashboardInfo method of nodes */
747
+ /* establish OSC event emission */
748
+ let sendOSC: (url: string, ...args: any[]) => void
749
+ if (args.o !== "") {
750
+ const osc = new OSC({ plugin: new OSC.DatagramPlugin({ type: "udp4" }) })
751
+ const m = args.o.match(/^(.+?):(\d+)$/)
752
+ if (m === null)
753
+ throw new Error("invalid OSC/UDP endpoint (expected <ip-adress>:<udp-port>)")
754
+ const host = m[1]
755
+ const port = m[2]
756
+ sendOSC = (url: string, ...args: any[]) => {
757
+ const msg = new OSC.Message(url, ...args)
758
+ osc.send(msg, { host, port })
759
+ }
760
+ }
761
+
762
+ /* hook for send-dashboard method of nodes */
701
763
  for (const node of graphNodes) {
702
- node.on("dashboard-info", (info: {
703
- type: string,
764
+ node.on("send-dashboard", (info: {
765
+ type: "audio" | "text",
704
766
  id: string,
705
767
  kind: "final" | "intermediate",
706
768
  value: string | number
@@ -714,6 +776,17 @@ type wsPeerInfo = {
714
776
  cli!.log("debug", `HAPI: dashboard peer ${peer}: send ${data}`)
715
777
  info.ws.send(data)
716
778
  }
779
+ for (const node of graphNodes) {
780
+ Promise.race<void>([
781
+ node.receiveDashboard(info.type, info.id, info.kind, info.value),
782
+ new Promise<never>((resolve, reject) => setTimeout(() =>
783
+ reject(new Error("timeout")), 10 * 1000))
784
+ ]).catch((err: Error) => {
785
+ cli!.log("warning", `sending dashboard info to node <${node.id}> failed: ${err.message}`)
786
+ })
787
+ }
788
+ if (args.o !== "")
789
+ sendOSC("/speechflow/dashboard", info.type, info.id, info.kind, info.value)
717
790
  })
718
791
  }
719
792
 
@@ -849,25 +922,40 @@ type wsPeerInfo = {
849
922
  /* re-hook into uncaught exception handler */
850
923
  process.removeAllListeners("uncaughtException")
851
924
  process.on("uncaughtException", (err) => {
852
- cli!.log("error", `uncaught exception: ${err.message}\n${err.stack}`)
925
+ if (debug)
926
+ cli!.log("error", `uncaught exception: ${err.message}\n${err.stack}`)
927
+ else
928
+ cli!.log("error", `uncaught exception: ${err.message}`)
853
929
  shutdown("exception")
854
930
  })
855
931
 
856
932
  /* re-hook into unhandled promise rejection handler */
857
933
  process.removeAllListeners("unhandledRejection")
858
934
  process.on("unhandledRejection", (reason) => {
859
- if (reason instanceof Error)
860
- cli!.log("error", `unhandled rejection: ${reason.message}\n${reason.stack}`)
935
+ if (reason instanceof Error) {
936
+ if (debug)
937
+ cli!.log("error", `unhandled rejection: ${reason.message}\n${reason.stack}`)
938
+ else
939
+ cli!.log("error", `unhandled rejection: ${reason.message}`)
940
+ }
861
941
  else
862
942
  cli!.log("error", `unhandled rejection: ${reason}`)
863
943
  shutdown("exception")
864
944
  })
865
945
  })().catch((err: Error) => {
866
946
  /* top-level exception handling */
867
- if (cli !== null)
868
- cli.log("error", `${err.message}:\n${err.stack}`)
869
- else
870
- process.stderr.write(`${pkg.name}: ${chalk.red("ERROR")}: ${err.message}\n${err.stack}\n`)
947
+ if (cli !== null) {
948
+ if (debug)
949
+ cli.log("error", `${err.message}\n${err.stack}`)
950
+ else
951
+ cli.log("error", `${err.message}`)
952
+ }
953
+ else {
954
+ if (debug)
955
+ process.stderr.write(`${pkg.name}: ${chalk.red("ERROR")}: ${err.message}\n${err.stack}\n`)
956
+ else
957
+ process.stderr.write(`${pkg.name}: ${chalk.red("ERROR")}: ${err.message}`)
958
+ }
871
959
  process.exit(1)
872
960
  })
873
961