speechflow 1.3.2 → 1.4.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 (154) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/etc/stx.conf +54 -58
  3. package/package.json +25 -106
  4. package/speechflow-cli/etc/stx.conf +77 -0
  5. package/speechflow-cli/package.json +116 -0
  6. package/speechflow-cli/src/speechflow-node-a2a-meter.ts +217 -0
  7. package/{src → speechflow-cli/src}/speechflow-node-a2a-vad.ts +14 -21
  8. package/{src → speechflow-cli/src}/speechflow-node-a2t-deepgram.ts +21 -38
  9. package/{src → speechflow-cli/src}/speechflow-node-t2a-elevenlabs.ts +10 -16
  10. package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +276 -0
  11. package/{src → speechflow-cli/src}/speechflow-node-x2x-filter.ts +5 -1
  12. package/{src → speechflow-cli/src}/speechflow-node-x2x-trace.ts +15 -7
  13. package/{src → speechflow-cli/src}/speechflow-node.ts +7 -0
  14. package/{src → speechflow-cli/src}/speechflow.ts +81 -25
  15. package/speechflow-ui-db/etc/eslint.mjs +106 -0
  16. package/speechflow-ui-db/etc/htmllint.json +55 -0
  17. package/speechflow-ui-db/etc/stx.conf +79 -0
  18. package/speechflow-ui-db/etc/stylelint.js +46 -0
  19. package/speechflow-ui-db/etc/stylelint.yaml +33 -0
  20. package/speechflow-ui-db/etc/tsc-client.json +30 -0
  21. package/speechflow-ui-db/etc/tsc.node.json +9 -0
  22. package/speechflow-ui-db/etc/vite-client.mts +63 -0
  23. package/speechflow-ui-db/package.d/htmllint-cli+0.0.7.patch +20 -0
  24. package/speechflow-ui-db/package.json +75 -0
  25. package/speechflow-ui-db/src/app-icon.ai +1989 -4
  26. package/speechflow-ui-db/src/app-icon.svg +26 -0
  27. package/speechflow-ui-db/src/app.styl +64 -0
  28. package/speechflow-ui-db/src/app.vue +221 -0
  29. package/speechflow-ui-db/src/index.html +23 -0
  30. package/speechflow-ui-db/src/index.ts +26 -0
  31. package/{dst/speechflow.d.ts → speechflow-ui-db/src/lib.d.ts} +5 -3
  32. package/speechflow-ui-db/src/tsconfig.json +3 -0
  33. package/speechflow-ui-st/etc/eslint.mjs +106 -0
  34. package/speechflow-ui-st/etc/htmllint.json +55 -0
  35. package/speechflow-ui-st/etc/stx.conf +79 -0
  36. package/speechflow-ui-st/etc/stylelint.js +46 -0
  37. package/speechflow-ui-st/etc/stylelint.yaml +33 -0
  38. package/speechflow-ui-st/etc/tsc-client.json +30 -0
  39. package/speechflow-ui-st/etc/tsc.node.json +9 -0
  40. package/speechflow-ui-st/etc/vite-client.mts +63 -0
  41. package/speechflow-ui-st/package.d/htmllint-cli+0.0.7.patch +20 -0
  42. package/speechflow-ui-st/package.json +79 -0
  43. package/speechflow-ui-st/src/app-icon.ai +1989 -4
  44. package/speechflow-ui-st/src/app-icon.svg +26 -0
  45. package/speechflow-ui-st/src/app.styl +64 -0
  46. package/speechflow-ui-st/src/app.vue +142 -0
  47. package/speechflow-ui-st/src/index.html +23 -0
  48. package/speechflow-ui-st/src/index.ts +26 -0
  49. package/speechflow-ui-st/src/lib.d.ts +9 -0
  50. package/speechflow-ui-st/src/tsconfig.json +3 -0
  51. package/dst/speechflow-node-a2a-ffmpeg.d.ts +0 -13
  52. package/dst/speechflow-node-a2a-ffmpeg.js +0 -153
  53. package/dst/speechflow-node-a2a-ffmpeg.js.map +0 -1
  54. package/dst/speechflow-node-a2a-gender.d.ts +0 -20
  55. package/dst/speechflow-node-a2a-gender.js +0 -349
  56. package/dst/speechflow-node-a2a-gender.js.map +0 -1
  57. package/dst/speechflow-node-a2a-meter.d.ts +0 -14
  58. package/dst/speechflow-node-a2a-meter.js +0 -196
  59. package/dst/speechflow-node-a2a-meter.js.map +0 -1
  60. package/dst/speechflow-node-a2a-mute.d.ts +0 -17
  61. package/dst/speechflow-node-a2a-mute.js +0 -117
  62. package/dst/speechflow-node-a2a-mute.js.map +0 -1
  63. package/dst/speechflow-node-a2a-vad.d.ts +0 -19
  64. package/dst/speechflow-node-a2a-vad.js +0 -383
  65. package/dst/speechflow-node-a2a-vad.js.map +0 -1
  66. package/dst/speechflow-node-a2a-wav.d.ts +0 -11
  67. package/dst/speechflow-node-a2a-wav.js +0 -211
  68. package/dst/speechflow-node-a2a-wav.js.map +0 -1
  69. package/dst/speechflow-node-a2t-deepgram.d.ts +0 -19
  70. package/dst/speechflow-node-a2t-deepgram.js +0 -345
  71. package/dst/speechflow-node-a2t-deepgram.js.map +0 -1
  72. package/dst/speechflow-node-t2a-elevenlabs.d.ts +0 -18
  73. package/dst/speechflow-node-t2a-elevenlabs.js +0 -244
  74. package/dst/speechflow-node-t2a-elevenlabs.js.map +0 -1
  75. package/dst/speechflow-node-t2a-kokoro.d.ts +0 -14
  76. package/dst/speechflow-node-t2a-kokoro.js +0 -155
  77. package/dst/speechflow-node-t2a-kokoro.js.map +0 -1
  78. package/dst/speechflow-node-t2t-deepl.d.ts +0 -15
  79. package/dst/speechflow-node-t2t-deepl.js +0 -146
  80. package/dst/speechflow-node-t2t-deepl.js.map +0 -1
  81. package/dst/speechflow-node-t2t-format.d.ts +0 -11
  82. package/dst/speechflow-node-t2t-format.js +0 -82
  83. package/dst/speechflow-node-t2t-format.js.map +0 -1
  84. package/dst/speechflow-node-t2t-ollama.d.ts +0 -13
  85. package/dst/speechflow-node-t2t-ollama.js +0 -247
  86. package/dst/speechflow-node-t2t-ollama.js.map +0 -1
  87. package/dst/speechflow-node-t2t-openai.d.ts +0 -13
  88. package/dst/speechflow-node-t2t-openai.js +0 -227
  89. package/dst/speechflow-node-t2t-openai.js.map +0 -1
  90. package/dst/speechflow-node-t2t-sentence.d.ts +0 -17
  91. package/dst/speechflow-node-t2t-sentence.js +0 -250
  92. package/dst/speechflow-node-t2t-sentence.js.map +0 -1
  93. package/dst/speechflow-node-t2t-subtitle.d.ts +0 -12
  94. package/dst/speechflow-node-t2t-subtitle.js +0 -166
  95. package/dst/speechflow-node-t2t-subtitle.js.map +0 -1
  96. package/dst/speechflow-node-t2t-transformers.d.ts +0 -14
  97. package/dst/speechflow-node-t2t-transformers.js +0 -265
  98. package/dst/speechflow-node-t2t-transformers.js.map +0 -1
  99. package/dst/speechflow-node-x2x-filter.d.ts +0 -11
  100. package/dst/speechflow-node-x2x-filter.js +0 -117
  101. package/dst/speechflow-node-x2x-filter.js.map +0 -1
  102. package/dst/speechflow-node-x2x-trace.d.ts +0 -11
  103. package/dst/speechflow-node-x2x-trace.js +0 -104
  104. package/dst/speechflow-node-x2x-trace.js.map +0 -1
  105. package/dst/speechflow-node-xio-device.d.ts +0 -13
  106. package/dst/speechflow-node-xio-device.js +0 -230
  107. package/dst/speechflow-node-xio-device.js.map +0 -1
  108. package/dst/speechflow-node-xio-file.d.ts +0 -11
  109. package/dst/speechflow-node-xio-file.js +0 -216
  110. package/dst/speechflow-node-xio-file.js.map +0 -1
  111. package/dst/speechflow-node-xio-mqtt.d.ts +0 -13
  112. package/dst/speechflow-node-xio-mqtt.js +0 -188
  113. package/dst/speechflow-node-xio-mqtt.js.map +0 -1
  114. package/dst/speechflow-node-xio-websocket.d.ts +0 -13
  115. package/dst/speechflow-node-xio-websocket.js +0 -278
  116. package/dst/speechflow-node-xio-websocket.js.map +0 -1
  117. package/dst/speechflow-node.d.ts +0 -63
  118. package/dst/speechflow-node.js +0 -177
  119. package/dst/speechflow-node.js.map +0 -1
  120. package/dst/speechflow-utils.d.ts +0 -74
  121. package/dst/speechflow-utils.js +0 -519
  122. package/dst/speechflow-utils.js.map +0 -1
  123. package/dst/speechflow.js +0 -787
  124. package/dst/speechflow.js.map +0 -1
  125. package/src/speechflow-node-a2a-meter.ts +0 -177
  126. package/src/speechflow-node-t2t-subtitle.ts +0 -149
  127. /package/{etc → speechflow-cli/etc}/biome.jsonc +0 -0
  128. /package/{etc → speechflow-cli/etc}/eslint.mjs +0 -0
  129. /package/{etc → speechflow-cli/etc}/oxlint.jsonc +0 -0
  130. /package/{etc → speechflow-cli/etc}/speechflow.bat +0 -0
  131. /package/{etc → speechflow-cli/etc}/speechflow.sh +0 -0
  132. /package/{etc → speechflow-cli/etc}/speechflow.yaml +0 -0
  133. /package/{etc → speechflow-cli/etc}/tsconfig.json +0 -0
  134. /package/{package.d → speechflow-cli/package.d}/@ericedouard+vad-node-realtime+0.2.0.patch +0 -0
  135. /package/{src → speechflow-cli/src}/lib.d.ts +0 -0
  136. /package/{src → speechflow-cli/src}/speechflow-logo.ai +0 -0
  137. /package/{src → speechflow-cli/src}/speechflow-logo.svg +0 -0
  138. /package/{src → speechflow-cli/src}/speechflow-node-a2a-ffmpeg.ts +0 -0
  139. /package/{src → speechflow-cli/src}/speechflow-node-a2a-gender.ts +0 -0
  140. /package/{src → speechflow-cli/src}/speechflow-node-a2a-mute.ts +0 -0
  141. /package/{src → speechflow-cli/src}/speechflow-node-a2a-wav.ts +0 -0
  142. /package/{src → speechflow-cli/src}/speechflow-node-t2a-kokoro.ts +0 -0
  143. /package/{src → speechflow-cli/src}/speechflow-node-t2t-deepl.ts +0 -0
  144. /package/{src → speechflow-cli/src}/speechflow-node-t2t-format.ts +0 -0
  145. /package/{src → speechflow-cli/src}/speechflow-node-t2t-ollama.ts +0 -0
  146. /package/{src → speechflow-cli/src}/speechflow-node-t2t-openai.ts +0 -0
  147. /package/{src → speechflow-cli/src}/speechflow-node-t2t-sentence.ts +0 -0
  148. /package/{src → speechflow-cli/src}/speechflow-node-t2t-transformers.ts +0 -0
  149. /package/{src → speechflow-cli/src}/speechflow-node-xio-device.ts +0 -0
  150. /package/{src → speechflow-cli/src}/speechflow-node-xio-file.ts +0 -0
  151. /package/{src → speechflow-cli/src}/speechflow-node-xio-mqtt.ts +0 -0
  152. /package/{src → speechflow-cli/src}/speechflow-node-xio-websocket.ts +0 -0
  153. /package/{src → speechflow-cli/src}/speechflow-utils.ts +0 -0
  154. /package/{tsconfig.json → speechflow-cli/tsconfig.json} +0 -0
package/dst/speechflow.js DELETED
@@ -1,787 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
- /*!
4
- ** SpeechFlow - Speech Processing Flow Graph
5
- ** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
6
- ** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
7
- */
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
- var __importDefault = (this && this.__importDefault) || function (mod) {
42
- return (mod && mod.__esModule) ? mod : { "default": mod };
43
- };
44
- Object.defineProperty(exports, "__esModule", { value: true });
45
- /* standard dependencies */
46
- const node_path_1 = __importDefault(require("node:path"));
47
- const node_stream_1 = __importDefault(require("node:stream"));
48
- const node_events_1 = require("node:events");
49
- const HAPI = __importStar(require("@hapi/hapi"));
50
- const ws_1 = __importDefault(require("ws"));
51
- const hapi_plugin_websocket_1 = __importDefault(require("hapi-plugin-websocket"));
52
- const hapi_plugin_header_1 = __importDefault(require("hapi-plugin-header"));
53
- /* external dependencies */
54
- const luxon_1 = require("luxon");
55
- const cli_io_1 = __importDefault(require("cli-io"));
56
- const yargs_1 = __importDefault(require("yargs"));
57
- const helpers_1 = require("yargs/helpers");
58
- const js_yaml_1 = __importDefault(require("js-yaml"));
59
- const flowlink_1 = __importDefault(require("flowlink"));
60
- const object_path_1 = __importDefault(require("object-path"));
61
- const installed_packages_1 = __importDefault(require("installed-packages"));
62
- const dotenvx_1 = __importDefault(require("@dotenvx/dotenvx"));
63
- const syspath_1 = __importDefault(require("syspath"));
64
- const arktype = __importStar(require("arktype"));
65
- const cli_table3_1 = __importDefault(require("cli-table3"));
66
- const chalk_1 = __importDefault(require("chalk"));
67
- const package_json_1 = __importDefault(require("../package.json"));
68
- /* central CLI context */
69
- let cli = null;
70
- (async () => {
71
- /* determine system paths */
72
- const { dataDir } = (0, syspath_1.default)({
73
- appName: "speechflow",
74
- dataDirAutoCreate: true
75
- });
76
- /* parse command-line arguments */
77
- const coerce = (arg) => Array.isArray(arg) ? arg[arg.length - 1] : arg;
78
- const args = await (0, yargs_1.default)()
79
- /* eslint @stylistic/indent: off */
80
- .usage("Usage: $0 " +
81
- "[-h|--help] " +
82
- "[-V|--version] " +
83
- "[-S|--status] " +
84
- "[-v|--verbose <level>] " +
85
- "[-a|--address <ip-address>] " +
86
- "[-p|--port <tcp-port>] " +
87
- "[-C|--cache <directory>] " +
88
- "[-e|--expression <expression>] " +
89
- "[-f|--file <file>] " +
90
- "[-c|--config <id>@<yaml-config-file>] " +
91
- "[<argument> [...]]")
92
- .version(false)
93
- .option("V", {
94
- alias: "version",
95
- type: "boolean",
96
- array: false,
97
- coerce,
98
- default: false,
99
- describe: "show program version information"
100
- })
101
- .option("S", {
102
- alias: "status",
103
- type: "boolean",
104
- array: false,
105
- coerce,
106
- default: false,
107
- describe: "show one-time status of nodes"
108
- })
109
- .option("v", {
110
- alias: "log-level",
111
- type: "string",
112
- array: false,
113
- coerce,
114
- nargs: 1,
115
- default: "warning",
116
- describe: "level for verbose logging ('none', 'error', 'warning', 'info', 'debug')"
117
- })
118
- .option("a", {
119
- alias: "address",
120
- type: "string",
121
- array: false,
122
- coerce,
123
- nargs: 1,
124
- default: "0.0.0.0",
125
- describe: "IP address for REST/WebSocket API"
126
- })
127
- .option("p", {
128
- alias: "port",
129
- type: "number",
130
- array: false,
131
- coerce,
132
- nargs: 1,
133
- default: 8484,
134
- describe: "TCP port for REST/WebSocket API"
135
- })
136
- .option("C", {
137
- alias: "cache",
138
- type: "string",
139
- array: false,
140
- coerce,
141
- nargs: 1,
142
- default: node_path_1.default.join(dataDir, "cache"),
143
- describe: "directory for cached files (primarily AI model files)"
144
- })
145
- .option("e", {
146
- alias: "expression",
147
- type: "string",
148
- array: false,
149
- coerce,
150
- nargs: 1,
151
- default: "",
152
- describe: "FlowLink expression string"
153
- })
154
- .option("f", {
155
- alias: "file",
156
- type: "string",
157
- array: false,
158
- coerce,
159
- nargs: 1,
160
- default: "",
161
- describe: "FlowLink expression file"
162
- })
163
- .option("c", {
164
- alias: "config",
165
- type: "string",
166
- array: false,
167
- coerce,
168
- nargs: 1,
169
- default: "",
170
- describe: "FlowLink expression reference into YAML file (in format <id>@<file>)"
171
- })
172
- .help("h", "show usage help")
173
- .alias("h", "help")
174
- .showHelpOnFail(true)
175
- .strict()
176
- .demand(0)
177
- .parse((0, helpers_1.hideBin)(process.argv));
178
- /* short-circuit version request */
179
- if (args.V) {
180
- process.stderr.write(`SpeechFlow ${package_json_1.default["x-stdver"]} (${package_json_1.default["x-release"]}) <${package_json_1.default.homepage}>\n`);
181
- process.stderr.write(`${package_json_1.default.description}\n`);
182
- process.stderr.write(`Copyright (c) 2024-2025 ${package_json_1.default.author.name} <${package_json_1.default.author.url}>\n`);
183
- process.stderr.write(`Licensed under ${package_json_1.default.license} <http://spdx.org/licenses/${package_json_1.default.license}.html>\n`);
184
- process.exit(0);
185
- }
186
- /* establish CLI environment */
187
- cli = new cli_io_1.default({
188
- encoding: "utf8",
189
- logLevel: args.v,
190
- logTime: true,
191
- logPrefix: package_json_1.default.name
192
- });
193
- /* catch uncaught exceptions */
194
- process.on("uncaughtException", (err) => {
195
- cli.log("error", `uncaught exception: ${err.message}`);
196
- process.exit(1);
197
- });
198
- /* catch unhandled promise rejections */
199
- process.on("unhandledRejection", (reason) => {
200
- if (reason instanceof Error)
201
- cli.log("error", `unhandled rejection: ${reason.message}`);
202
- else
203
- cli.log("error", `unhandled rejection: ${reason}`);
204
- process.exit(1);
205
- });
206
- /* provide startup information */
207
- cli.log("info", `starting SpeechFlow ${package_json_1.default["x-stdver"]} (${package_json_1.default["x-release"]})`);
208
- /* load .env files */
209
- const result = dotenvx_1.default.config({ encoding: "utf8", quiet: true });
210
- if (result?.parsed !== undefined)
211
- for (const key of Object.keys(result.parsed))
212
- cli.log("info", `loaded environment variable "${key}" from ".env" files`);
213
- /* sanity check usage */
214
- let n = 0;
215
- if (typeof args.e === "string" && args.e !== "")
216
- n++;
217
- if (typeof args.f === "string" && args.f !== "")
218
- n++;
219
- if (typeof args.c === "string" && args.c !== "")
220
- n++;
221
- if (n !== 1)
222
- throw new Error("cannot use more than one FlowLink specification source (either option -e, -f or -c)");
223
- /* read configuration */
224
- let config = "";
225
- if (typeof args.e === "string" && args.e !== "")
226
- config = args.e;
227
- else if (typeof args.f === "string" && args.f !== "")
228
- config = await cli.input(args.f, { encoding: "utf8" });
229
- else if (typeof args.c === "string" && args.c !== "") {
230
- const m = args.c.match(/^(.+?)@(.+)$/);
231
- if (m === null)
232
- throw new Error("invalid configuration file specification (expected \"<id>@<yaml-config-file>\")");
233
- const [, id, file] = m;
234
- const yaml = await cli.input(file, { encoding: "utf8" });
235
- let obj;
236
- try {
237
- obj = js_yaml_1.default.load(yaml);
238
- }
239
- catch (err) {
240
- if (err instanceof Error)
241
- throw new Error(`failed to parse YAML configuration: ${err.message}`);
242
- else
243
- throw new Error(`failed to parse YAML configuration: ${err}`);
244
- }
245
- if (obj[id] === undefined)
246
- throw new Error(`no such id "${id}" found in configuration file`);
247
- config = obj[id];
248
- }
249
- /* track the available SpeechFlow nodes */
250
- const nodes = {};
251
- /* load internal SpeechFlow nodes */
252
- const pkgsI = [
253
- "./speechflow-node-a2a-ffmpeg.js",
254
- "./speechflow-node-a2a-gender.js",
255
- "./speechflow-node-a2a-meter.js",
256
- "./speechflow-node-a2a-mute.js",
257
- "./speechflow-node-a2a-vad.js",
258
- "./speechflow-node-a2a-wav.js",
259
- "./speechflow-node-a2t-deepgram.js",
260
- "./speechflow-node-t2a-elevenlabs.js",
261
- "./speechflow-node-t2a-kokoro.js",
262
- "./speechflow-node-t2t-deepl.js",
263
- "./speechflow-node-t2t-format.js",
264
- "./speechflow-node-t2t-ollama.js",
265
- "./speechflow-node-t2t-openai.js",
266
- "./speechflow-node-t2t-sentence.js",
267
- "./speechflow-node-t2t-subtitle.js",
268
- "./speechflow-node-t2t-transformers.js",
269
- "./speechflow-node-x2x-filter.js",
270
- "./speechflow-node-x2x-trace.js",
271
- "./speechflow-node-xio-device.js",
272
- "./speechflow-node-xio-file.js",
273
- "./speechflow-node-xio-mqtt.js",
274
- "./speechflow-node-xio-websocket.js"
275
- ];
276
- for (const pkg of pkgsI) {
277
- let node = await import(pkg);
278
- while (node.default !== undefined)
279
- node = node.default;
280
- if (typeof node === "function" && typeof node.name === "string") {
281
- cli.log("info", `loading SpeechFlow node <${node.name}> from internal module`);
282
- nodes[node.name] = node;
283
- }
284
- }
285
- /* load external SpeechFlow nodes */
286
- const pkgsE = await (0, installed_packages_1.default)();
287
- for (const pkg of pkgsE) {
288
- if (pkg.match(/^(?:@[^/]+\/)?speechflow-node-.+$/)) {
289
- let node = await import(pkg);
290
- while (node.default !== undefined)
291
- node = node.default;
292
- if (typeof node === "function" && typeof node.name === "string") {
293
- if (nodes[node.name] !== undefined) {
294
- cli.log("warning", `failed loading SpeechFlow node <${node.name}> ` +
295
- `from external module "${pkg}" -- node already exists`);
296
- continue;
297
- }
298
- cli.log("info", `loading SpeechFlow node <${node.name}> from external module "${pkg}"`);
299
- nodes[node.name] = node;
300
- }
301
- }
302
- }
303
- /* static configuration */
304
- const cfg = {
305
- audioChannels: 1,
306
- audioBitDepth: 16,
307
- audioLittleEndian: true,
308
- audioSampleRate: 48000,
309
- textEncoding: "utf8",
310
- cacheDir: args.C
311
- };
312
- /* handle one-time status query of nodes */
313
- if (args.S) {
314
- const table = new cli_table3_1.default({
315
- head: [
316
- chalk_1.default.reset.bold("NODE"),
317
- chalk_1.default.reset.bold("PROPERTY"),
318
- chalk_1.default.reset.bold("VALUE")
319
- ],
320
- colWidths: [15, 15, 50 - (2 * 2 + 2 * 3)],
321
- style: { "padding-left": 1, "padding-right": 1, border: ["grey"], compact: true },
322
- chars: { "left-mid": "", mid: "", "mid-mid": "", "right-mid": "" }
323
- });
324
- for (const name of Object.keys(nodes)) {
325
- cli.log("info", `gathering status of node <${name}>`);
326
- const node = new nodes[name](name, cfg, {}, []);
327
- const status = await Promise.race([
328
- node.status(),
329
- new Promise((resolve, reject) => setTimeout(() => reject(new Error("timeout")), 10 * 1000))
330
- ]).catch((err) => {
331
- cli.log("warning", `[${node.id}]: failed to gather status of node <${node.id}>: ${err.message}`);
332
- return {};
333
- });
334
- if (Object.keys(status).length > 0) {
335
- let first = true;
336
- for (const key of Object.keys(status)) {
337
- table.push([first ? chalk_1.default.bold(name) : "", key, chalk_1.default.blue(status[key])]);
338
- first = false;
339
- }
340
- }
341
- }
342
- const output = table.toString();
343
- process.stdout.write(output + "\n");
344
- process.exit(0);
345
- }
346
- /* graph processing: PASS 1: parse DSL and create and connect nodes */
347
- const flowlink = new flowlink_1.default({
348
- trace: (msg) => {
349
- cli.log("debug", msg);
350
- }
351
- });
352
- const variables = { argv: args._, env: process.env };
353
- const graphNodes = new Set();
354
- const nodeNums = new Map();
355
- let ast;
356
- try {
357
- ast = flowlink.compile(config);
358
- }
359
- catch (err) {
360
- if (err instanceof Error && err.name === "FlowLinkError")
361
- cli.log("error", `failed to parse SpeechFlow configuration: ${err.toString()}`);
362
- else if (err instanceof Error)
363
- cli.log("error", `failed to parse SpeechFlow configuration: ${err.message}`);
364
- else
365
- cli.log("error", "failed to parse SpeechFlow configuration: internal error");
366
- process.exit(1);
367
- }
368
- try {
369
- flowlink.execute(ast, {
370
- resolveVariable(id) {
371
- if (!object_path_1.default.has(variables, id))
372
- throw new Error(`failed to resolve variable "${id}"`);
373
- const value = object_path_1.default.get(variables, id);
374
- cli.log("info", `resolve variable: "${id}" -> "${value}"`);
375
- return value;
376
- },
377
- createNode(id, opts, args) {
378
- if (nodes[id] === undefined)
379
- throw new Error(`unknown node <${id}>`);
380
- let node;
381
- try {
382
- let num = nodeNums.get(nodes[id]) ?? 0;
383
- nodeNums.set(nodes[id], ++num);
384
- const name = num === 1 ? id : `${id}:${num}`;
385
- node = new nodes[id](name, cfg, opts, args);
386
- }
387
- catch (err) {
388
- /* fatal error */
389
- if (err instanceof Error)
390
- cli.log("error", `creation of node <${id}> failed: ${err.message}`);
391
- else
392
- cli.log("error", `creation of node <${id}> failed: ${err}`);
393
- process.exit(1);
394
- }
395
- const params = Object.keys(node.params)
396
- .map((key) => `${key}: ${JSON.stringify(node.params[key])}`).join(", ");
397
- cli.log("info", `create node <${node.id}> (${params})`);
398
- graphNodes.add(node);
399
- return node;
400
- },
401
- connectNode(node1, node2) {
402
- cli.log("info", `connect node <${node1.id}> to node <${node2.id}>`);
403
- node1.connect(node2);
404
- }
405
- });
406
- }
407
- catch (err) {
408
- if (err instanceof Error && err.name === "FlowLinkError")
409
- cli.log("error", `failed to materialize SpeechFlow configuration: ${err.toString()}`);
410
- else if (err instanceof Error)
411
- cli.log("error", `failed to materialize SpeechFlow configuration: ${err.message}`);
412
- else
413
- cli.log("error", "failed to materialize SpeechFlow configuration: internal error");
414
- process.exit(1);
415
- }
416
- /* graph processing: PASS 2: prune connections of nodes */
417
- for (const node of graphNodes) {
418
- /* determine connections */
419
- let connectionsIn = Array.from(node.connectionsIn);
420
- let connectionsOut = Array.from(node.connectionsOut);
421
- /* ensure necessary incoming links */
422
- if (node.input !== "none" && connectionsIn.length === 0)
423
- throw new Error(`node <${node.id}> requires input but has no input nodes connected`);
424
- /* prune unnecessary incoming links */
425
- if (node.input === "none" && connectionsIn.length > 0)
426
- connectionsIn.forEach((other) => { other.disconnect(node); });
427
- /* ensure necessary outgoing links */
428
- if (node.output !== "none" && connectionsOut.length === 0)
429
- throw new Error(`node <${node.id}> requires output but has no output nodes connected`);
430
- /* prune unnecessary outgoing links */
431
- if (node.output === "none" && connectionsOut.length > 0)
432
- connectionsOut.forEach((other) => { node.disconnect(other); });
433
- /* check for payload compatibility */
434
- connectionsIn = Array.from(node.connectionsIn);
435
- connectionsOut = Array.from(node.connectionsOut);
436
- for (const other of connectionsOut)
437
- if (other.input !== node.output)
438
- throw new Error(`${node.output} output node <${node.id}> cannot be ` +
439
- `connected to ${other.input} input node <${other.id}> (payload is incompatible)`);
440
- }
441
- /* graph processing: PASS 3: open nodes */
442
- const timeZero = luxon_1.DateTime.now();
443
- for (const node of graphNodes) {
444
- /* connect node events */
445
- node.on("log", (level, msg, data) => {
446
- let str = `<${node.id}>: ${msg}`;
447
- if (data !== undefined)
448
- str += ` (${JSON.stringify(data)})`;
449
- cli.log(level, str);
450
- });
451
- /* open node */
452
- cli.log("info", `open node <${node.id}>`);
453
- node.setTimeZero(timeZero);
454
- await Promise.race([
455
- node.open(),
456
- new Promise((resolve, reject) => setTimeout(() => reject(new Error("timeout")), 10 * 1000))
457
- ]).catch((err) => {
458
- cli.log("error", `[${node.id}]: failed to open node <${node.id}>: ${err.message}`);
459
- throw new Error(`failed to open node <${node.id}>: ${err.message}`);
460
- });
461
- }
462
- /* graph processing: PASS 4: connect node streams */
463
- for (const node of graphNodes) {
464
- if (node.stream === null)
465
- throw new Error(`stream of node <${node.id}> still not initialized`);
466
- for (const other of Array.from(node.connectionsOut)) {
467
- if (other.stream === null)
468
- throw new Error(`stream of incoming node <${other.id}> still not initialized`);
469
- cli.log("info", `connect stream of node <${node.id}> to stream of node <${other.id}>`);
470
- if (!(node.stream instanceof node_stream_1.default.Readable
471
- || node.stream instanceof node_stream_1.default.Duplex))
472
- throw new Error(`stream of output node <${node.id}> is neither of Readable nor Duplex type`);
473
- if (!(other.stream instanceof node_stream_1.default.Writable
474
- || other.stream instanceof node_stream_1.default.Duplex))
475
- throw new Error(`stream of input node <${other.id}> is neither of Writable nor Duplex type`);
476
- node.stream.pipe(other.stream);
477
- }
478
- }
479
- /* graph processing: PASS 5: track stream finishing */
480
- const activeNodes = new Set();
481
- const finishEvents = new node_events_1.EventEmitter();
482
- finishEvents.setMaxListeners(graphNodes.size + 10);
483
- for (const node of graphNodes) {
484
- if (node.stream === null)
485
- throw new Error(`stream of node <${node.id}> still not initialized`);
486
- cli.log("info", `observe stream of node <${node.id}> for finish event`);
487
- activeNodes.add(node);
488
- const deactivateNode = (node, msg) => {
489
- if (activeNodes.has(node))
490
- activeNodes.delete(node);
491
- cli.log("info", `${msg} (${activeNodes.size} active nodes remaining)`);
492
- if (activeNodes.size === 0) {
493
- const timeFinished = luxon_1.DateTime.now();
494
- const duration = timeFinished.diff(timeZero);
495
- cli.log("info", "**** everything finished -- stream processing in SpeechFlow graph stops " +
496
- `(total duration: ${duration.toFormat("hh:mm:ss.SSS")}) ****`);
497
- finishEvents.emit("finished");
498
- }
499
- };
500
- node.stream.on("end", () => {
501
- deactivateNode(node, `readable stream side of node <${node.id}> raised "end" event`);
502
- });
503
- node.stream.on("finish", () => {
504
- deactivateNode(node, `writable stream side of node <${node.id}> raised "finish" event`);
505
- });
506
- }
507
- /* define external request/response structure */
508
- const requestValidator = arktype.type({
509
- request: "string",
510
- node: "string",
511
- args: "unknown[]"
512
- });
513
- /* forward external request to target node in graph */
514
- const consumeExternalRequest = async (_req) => {
515
- const req = requestValidator(_req);
516
- if (req instanceof arktype.type.errors)
517
- throw new Error(`invalid request: ${req.summary}`);
518
- if (req.request !== "COMMAND")
519
- throw new Error("invalid external request (command expected)");
520
- const name = req.node;
521
- const args = req.args;
522
- const foundNode = Array.from(graphNodes).find((node) => node.id === name);
523
- if (foundNode === undefined) {
524
- cli.log("warning", `external request failed: no such node <${name}>`);
525
- throw new Error(`external request failed: no such node <${name}>`);
526
- }
527
- else {
528
- await Promise.race([
529
- foundNode.receiveRequest(args),
530
- new Promise((resolve, reject) => setTimeout(() => reject(new Error("timeout")), 10 * 1000))
531
- ]).catch((err) => {
532
- cli.log("warning", `external request to node <${name}> failed: ${err.message}`);
533
- });
534
- }
535
- };
536
- /* establish REST/WebSocket API */
537
- const wsPeers = new Map();
538
- const hapi = new HAPI.Server({
539
- address: args.a,
540
- port: args.p
541
- });
542
- await hapi.register({ plugin: hapi_plugin_header_1.default, options: { Server: `${package_json_1.default.name}/${package_json_1.default.version}` } });
543
- await hapi.register({ plugin: hapi_plugin_websocket_1.default });
544
- hapi.events.on("response", (request) => {
545
- let protocol = `HTTP/${request.raw.req.httpVersion}`;
546
- const ws = request.websocket();
547
- if (ws.mode === "websocket") {
548
- const wsVersion = ws.ws.protocolVersion ??
549
- request.headers["sec-websocket-version"] ?? "13?";
550
- protocol = `WebSocket/${wsVersion}+${protocol}`;
551
- }
552
- const msg = "remote=" + request.info.remoteAddress + ", " +
553
- "method=" + request.method.toUpperCase() + ", " +
554
- "url=" + request.url.pathname + ", " +
555
- "protocol=" + protocol + ", " +
556
- "response=" + ("statusCode" in request.response ? request.response.statusCode : "<unknown>");
557
- cli.log("info", `HAPI: request: ${msg}`);
558
- });
559
- hapi.events.on({ name: "request", channels: ["error"] }, (request, event, tags) => {
560
- if (event.error instanceof Error)
561
- cli.log("error", `HAPI: request-error: ${event.error.message}`);
562
- else
563
- cli.log("error", `HAPI: request-error: ${event.error}`);
564
- });
565
- hapi.events.on("log", (event, tags) => {
566
- if (tags.error) {
567
- const err = event.error;
568
- if (err instanceof Error)
569
- cli.log("error", `HAPI: log: ${err.message}`);
570
- else
571
- cli.log("error", `HAPI: log: ${err}`);
572
- }
573
- });
574
- hapi.route({
575
- method: "GET",
576
- path: "/api/{req}/{node}/{params*}",
577
- options: {},
578
- handler: (request, h) => {
579
- const peer = request.info.remoteAddress;
580
- const req = {
581
- request: request.params.req,
582
- node: request.params.node,
583
- args: (request.params.params ?? "").split("/").filter((seg) => seg !== "")
584
- };
585
- cli.log("info", `HAPI: peer ${peer}: GET: ${JSON.stringify(req)}`);
586
- return consumeExternalRequest(req).then(() => {
587
- return h.response({ response: "OK" }).code(200);
588
- }).catch((err) => {
589
- return h.response({ response: "ERROR", data: err.message }).code(417);
590
- });
591
- }
592
- });
593
- hapi.route({
594
- method: "POST",
595
- path: "/api",
596
- options: {
597
- payload: {
598
- output: "data",
599
- parse: true,
600
- allow: "application/json"
601
- },
602
- plugins: {
603
- websocket: {
604
- autoping: 30 * 1000,
605
- connect: (args) => {
606
- const ctx = args.ctx;
607
- const ws = args.ws;
608
- const req = args.req;
609
- const peer = `${req.socket.remoteAddress}:${req.socket.remotePort}`;
610
- ctx.peer = peer;
611
- wsPeers.set(peer, { ctx, ws, req });
612
- cli.log("info", `HAPI: WebSocket: connect: peer ${peer}`);
613
- },
614
- disconnect: (args) => {
615
- const ctx = args.ctx;
616
- const peer = ctx.peer;
617
- wsPeers.delete(peer);
618
- cli.log("info", `HAPI: WebSocket: disconnect: peer ${peer}`);
619
- }
620
- }
621
- }
622
- },
623
- handler: (request, h) => {
624
- /* on WebSocket message transfer */
625
- const peer = request.info.remoteAddress;
626
- const req = requestValidator(request.payload);
627
- if (req instanceof arktype.type.errors)
628
- return h.response({ response: "ERROR", data: `invalid request: ${req.summary}` }).code(417);
629
- cli.log("info", `HAPI: peer ${peer}: POST: ${JSON.stringify(req)}`);
630
- return consumeExternalRequest(req).then(() => {
631
- return h.response({ response: "OK" }).code(200);
632
- }).catch((err) => {
633
- return h.response({ response: "ERROR", data: err.message }).code(417);
634
- });
635
- }
636
- });
637
- await hapi.start();
638
- cli.log("info", `HAPI: started REST/WebSocket network service: http://${args.a}:${args.p}`);
639
- /* hook for sendResponse method of nodes */
640
- for (const node of graphNodes) {
641
- node.on("send-response", (args) => {
642
- const data = JSON.stringify({ response: "NOTIFY", node: node.id, args });
643
- for (const [peer, info] of wsPeers.entries()) {
644
- cli.log("info", `HAPI: peer ${peer}: ${data}`);
645
- if (info.ws.readyState === ws_1.default.OPEN)
646
- info.ws.send(data);
647
- }
648
- });
649
- }
650
- /* start of internal stream processing */
651
- cli.log("info", "**** everything established -- stream processing in SpeechFlow graph starts ****");
652
- /* gracefully shutdown process */
653
- let shuttingDown = false;
654
- const shutdown = async (signal) => {
655
- if (shuttingDown)
656
- return;
657
- shuttingDown = true;
658
- if (signal === "finished")
659
- cli.log("info", "**** streams of all nodes finished -- shutting down service ****");
660
- else if (signal === "exception")
661
- cli.log("warning", "**** exception occurred -- shutting down service ****");
662
- else
663
- cli.log("warning", `**** received signal ${signal} -- shutting down service ****`);
664
- /* shutdown HAPI service */
665
- cli.log("info", `HAPI: stopping REST/WebSocket network service: http://${args.a}:${args.p}`);
666
- await hapi.stop({ timeout: 2000 });
667
- /* clear WebSocket connections */
668
- if (wsPeers.size > 0) {
669
- cli.log("info", "HAPI: closing WebSocket connections");
670
- const closePromises = [];
671
- for (const [peer, info] of wsPeers.entries()) {
672
- closePromises.push(new Promise((resolve, reject) => {
673
- if (info.ws.readyState !== ws_1.default.OPEN)
674
- resolve();
675
- else {
676
- const timeout = setTimeout(() => {
677
- reject(new Error(`timeout for peer ${peer}`));
678
- }, 2 * 1000);
679
- info.ws.once("close", () => {
680
- clearTimeout(timeout);
681
- resolve();
682
- });
683
- info.ws.close();
684
- }
685
- }));
686
- }
687
- await Promise.race([
688
- Promise.all(closePromises),
689
- new Promise((resolve, reject) => setTimeout(() => reject(new Error("timeout for all peers")), 5 * 1000))
690
- ]).catch((err) => {
691
- cli.log("warning", `HAPI: WebSockets failed to close: ${err}`);
692
- });
693
- wsPeers.clear();
694
- }
695
- /* graph processing: PASS 1: disconnect node streams */
696
- for (const node of graphNodes) {
697
- if (node.stream === null) {
698
- cli.log("warning", `stream of node <${node.id}> no longer initialized`);
699
- continue;
700
- }
701
- for (const other of Array.from(node.connectionsOut)) {
702
- if (other.stream === null) {
703
- cli.log("warning", `stream of incoming node <${other.id}> no longer initialized`);
704
- continue;
705
- }
706
- if (!(node.stream instanceof node_stream_1.default.Readable
707
- || node.stream instanceof node_stream_1.default.Duplex)) {
708
- cli.log("warning", `stream of output node <${node.id}> is neither of Readable nor Duplex type`);
709
- continue;
710
- }
711
- if (!(other.stream instanceof node_stream_1.default.Writable
712
- || other.stream instanceof node_stream_1.default.Duplex)) {
713
- cli.log("warning", `stream of input node <${other.id}> is neither of Writable nor Duplex type`);
714
- continue;
715
- }
716
- cli.log("info", `disconnect stream of node <${node.id}> from stream of node <${other.id}>`);
717
- node.stream.unpipe(other.stream);
718
- }
719
- }
720
- /* graph processing: PASS 2: close nodes */
721
- for (const node of graphNodes) {
722
- cli.log("info", `close node <${node.id}>`);
723
- await Promise.race([
724
- node.close(),
725
- new Promise((resolve, reject) => setTimeout(() => reject(new Error("timeout")), 10 * 1000))
726
- ]).catch((err) => {
727
- cli.log("warning", `node <${node.id}> failed to close: ${err.message}`);
728
- });
729
- }
730
- /* graph processing: PASS 3: disconnect nodes */
731
- for (const node of graphNodes) {
732
- cli.log("info", `disconnect node <${node.id}>`);
733
- const connectionsIn = Array.from(node.connectionsIn);
734
- const connectionsOut = Array.from(node.connectionsOut);
735
- connectionsIn.forEach((other) => { other.disconnect(node); });
736
- connectionsOut.forEach((other) => { node.disconnect(other); });
737
- }
738
- /* graph processing: PASS 4: shutdown nodes */
739
- for (const node of graphNodes) {
740
- cli.log("info", `destroy node <${node.id}>`);
741
- graphNodes.delete(node);
742
- }
743
- /* clear event emitters */
744
- finishEvents.removeAllListeners();
745
- /* clear active nodes */
746
- activeNodes.clear();
747
- /* terminate process */
748
- if (signal === "finished") {
749
- cli.log("info", "terminate process (exit code 0)");
750
- process.exit(0);
751
- }
752
- else {
753
- cli.log("info", "terminate process (exit code 1)");
754
- process.exit(1);
755
- }
756
- };
757
- /* hook into regular finish */
758
- finishEvents.on("finished", () => { shutdown("finished"); });
759
- /* hook into process signals */
760
- process.on("SIGINT", () => { shutdown("SIGINT"); });
761
- process.on("SIGUSR1", () => { shutdown("SIGUSR1"); });
762
- process.on("SIGUSR2", () => { shutdown("SIGUSR2"); });
763
- process.on("SIGTERM", () => { shutdown("SIGTERM"); });
764
- /* re-hook into uncaught exception handler */
765
- process.removeAllListeners("uncaughtException");
766
- process.on("uncaughtException", (err) => {
767
- cli.log("error", `uncaught exception: ${err.message}`);
768
- shutdown("exception");
769
- });
770
- /* re-hook into unhandled promise rejection handler */
771
- process.removeAllListeners("unhandledRejection");
772
- process.on("unhandledRejection", (reason) => {
773
- if (reason instanceof Error)
774
- cli.log("error", `unhandled rejection: ${reason.message}`);
775
- else
776
- cli.log("error", `unhandled rejection: ${reason}`);
777
- shutdown("exception");
778
- });
779
- })().catch((err) => {
780
- /* top-level exception handling */
781
- if (cli !== null)
782
- cli.log("error", `${err.message}:\n${err.stack}`);
783
- else
784
- process.stderr.write(`${package_json_1.default.name}: ${chalk_1.default.red("ERROR")}: ${err.message}\n${err.stack}\n`);
785
- process.exit(1);
786
- });
787
- //# sourceMappingURL=speechflow.js.map