speechflow 1.4.5 → 1.5.1
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/CHANGELOG.md +35 -0
- package/README.md +242 -7
- package/etc/claude.md +70 -0
- package/etc/speechflow.yaml +13 -11
- package/etc/stx.conf +7 -0
- package/package.json +7 -6
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js +155 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +287 -0
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js +208 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.d.ts +15 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js +312 -0
- package/speechflow-cli/dst/speechflow-node-a2a-dynamics.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js +161 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +208 -0
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-ffmpeg.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-filler.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js +233 -0
- package/speechflow-cli/dst/speechflow-node-a2a-filler.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.d.ts +12 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js +125 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gain.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-gender.d.ts +0 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +28 -12
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +12 -8
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js +2 -1
- package/speechflow-cli/dst/speechflow-node-a2a-mute.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js +55 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js +184 -0
- package/speechflow-cli/dst/speechflow-node-a2a-rnnoise.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.d.ts +14 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js +156 -0
- package/speechflow-cli/dst/speechflow-node-a2a-speex.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js +3 -3
- package/speechflow-cli/dst/speechflow-node-a2a-vad.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js +22 -17
- package/speechflow-cli/dst/speechflow-node-a2a-wav.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.d.ts +18 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js +312 -0
- package/speechflow-cli/dst/speechflow-node-a2t-awstranscribe.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js +16 -14
- package/speechflow-cli/dst/speechflow-node-a2t-deepgram.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.d.ts +19 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js +351 -0
- package/speechflow-cli/dst/speechflow-node-a2t-openaitranscribe.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.d.ts +16 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js +204 -0
- package/speechflow-cli/dst/speechflow-node-t2a-awspolly.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js +19 -14
- package/speechflow-cli/dst/speechflow-node-t2a-elevenlabs.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js +47 -8
- package/speechflow-cli/dst/speechflow-node-t2a-kokoro.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js +175 -0
- package/speechflow-cli/dst/speechflow-node-t2t-awstranslate.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js +14 -15
- package/speechflow-cli/dst/speechflow-node-t2t-deepl.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-format.js +10 -15
- package/speechflow-cli/dst/speechflow-node-t2t-format.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-google.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-node-t2t-google.js +153 -0
- package/speechflow-cli/dst/speechflow-node-t2t-google.js.map +1 -0
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js +80 -33
- package/speechflow-cli/dst/speechflow-node-t2t-ollama.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js +78 -45
- package/speechflow-cli/dst/speechflow-node-t2t-openai.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js +8 -8
- package/speechflow-cli/dst/speechflow-node-t2t-sentence.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js +13 -14
- package/speechflow-cli/dst/speechflow-node-t2t-subtitle.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js +23 -27
- package/speechflow-cli/dst/speechflow-node-t2t-transformers.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-filter.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js +50 -15
- package/speechflow-cli/dst/speechflow-node-x2x-filter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +17 -18
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-device.js +13 -21
- package/speechflow-cli/dst/speechflow-node-xio-device.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js +22 -16
- package/speechflow-cli/dst/speechflow-node-xio-mqtt.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js +19 -19
- package/speechflow-cli/dst/speechflow-node-xio-websocket.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node.d.ts +6 -3
- package/speechflow-cli/dst/speechflow-node.js +13 -2
- package/speechflow-cli/dst/speechflow-node.js.map +1 -1
- package/speechflow-cli/dst/speechflow-utils-audio-wt.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js +124 -0
- package/speechflow-cli/dst/speechflow-utils-audio-wt.js.map +1 -0
- package/speechflow-cli/dst/speechflow-utils-audio.d.ts +13 -0
- package/speechflow-cli/dst/speechflow-utils-audio.js +137 -0
- package/speechflow-cli/dst/speechflow-utils-audio.js.map +1 -0
- package/speechflow-cli/dst/speechflow-utils.d.ts +34 -0
- package/speechflow-cli/dst/speechflow-utils.js +256 -35
- package/speechflow-cli/dst/speechflow-utils.js.map +1 -1
- package/speechflow-cli/dst/speechflow.js +75 -26
- package/speechflow-cli/dst/speechflow.js.map +1 -1
- package/speechflow-cli/etc/biome.jsonc +2 -1
- package/speechflow-cli/etc/oxlint.jsonc +113 -11
- package/speechflow-cli/etc/stx.conf +2 -2
- package/speechflow-cli/etc/tsconfig.json +1 -1
- package/speechflow-cli/package.d/@shiguredo+rnnoise-wasm+2025.1.5.patch +25 -0
- package/speechflow-cli/package.json +103 -94
- package/speechflow-cli/src/lib.d.ts +24 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor-wt.ts +151 -0
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +303 -0
- package/speechflow-cli/src/speechflow-node-a2a-expander-wt.ts +158 -0
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +212 -0
- package/speechflow-cli/src/speechflow-node-a2a-ffmpeg.ts +3 -3
- package/speechflow-cli/src/speechflow-node-a2a-filler.ts +223 -0
- package/speechflow-cli/src/speechflow-node-a2a-gain.ts +98 -0
- package/speechflow-cli/src/speechflow-node-a2a-gender.ts +31 -17
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +13 -9
- package/speechflow-cli/src/speechflow-node-a2a-mute.ts +3 -2
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise-wt.ts +62 -0
- package/speechflow-cli/src/speechflow-node-a2a-rnnoise.ts +164 -0
- package/speechflow-cli/src/speechflow-node-a2a-speex.ts +137 -0
- package/speechflow-cli/src/speechflow-node-a2a-vad.ts +3 -3
- package/speechflow-cli/src/speechflow-node-a2a-wav.ts +20 -13
- package/speechflow-cli/src/speechflow-node-a2t-awstranscribe.ts +306 -0
- package/speechflow-cli/src/speechflow-node-a2t-deepgram.ts +17 -15
- package/speechflow-cli/src/speechflow-node-a2t-openaitranscribe.ts +337 -0
- package/speechflow-cli/src/speechflow-node-t2a-awspolly.ts +187 -0
- package/speechflow-cli/src/speechflow-node-t2a-elevenlabs.ts +19 -14
- package/speechflow-cli/src/speechflow-node-t2a-kokoro.ts +15 -9
- package/speechflow-cli/src/speechflow-node-t2t-awstranslate.ts +153 -0
- package/speechflow-cli/src/speechflow-node-t2t-deepl.ts +14 -15
- package/speechflow-cli/src/speechflow-node-t2t-format.ts +10 -15
- package/speechflow-cli/src/speechflow-node-t2t-google.ts +133 -0
- package/speechflow-cli/src/speechflow-node-t2t-ollama.ts +58 -44
- package/speechflow-cli/src/speechflow-node-t2t-openai.ts +59 -58
- package/speechflow-cli/src/speechflow-node-t2t-sentence.ts +10 -10
- package/speechflow-cli/src/speechflow-node-t2t-subtitle.ts +18 -18
- package/speechflow-cli/src/speechflow-node-t2t-transformers.ts +28 -32
- package/speechflow-cli/src/speechflow-node-x2x-filter.ts +20 -16
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +20 -19
- package/speechflow-cli/src/speechflow-node-xio-device.ts +15 -23
- package/speechflow-cli/src/speechflow-node-xio-mqtt.ts +23 -16
- package/speechflow-cli/src/speechflow-node-xio-websocket.ts +19 -19
- package/speechflow-cli/src/speechflow-node.ts +21 -8
- package/speechflow-cli/src/speechflow-utils-audio-wt.ts +172 -0
- package/speechflow-cli/src/speechflow-utils-audio.ts +147 -0
- package/speechflow-cli/src/speechflow-utils.ts +314 -32
- package/speechflow-cli/src/speechflow.ts +84 -33
- package/speechflow-ui-db/dst/app-font-fa-brands-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-regular-400.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-solid-900.woff2 +0 -0
- package/speechflow-ui-db/dst/app-font-fa-v4compatibility.woff2 +0 -0
- package/speechflow-ui-db/dst/index.css +2 -2
- package/speechflow-ui-db/dst/index.js +37 -38
- package/speechflow-ui-db/etc/eslint.mjs +0 -1
- package/speechflow-ui-db/etc/tsc-client.json +3 -3
- package/speechflow-ui-db/package.json +12 -11
- package/speechflow-ui-db/src/app.vue +20 -6
- package/speechflow-ui-st/dst/index.js +26 -26
- package/speechflow-ui-st/etc/eslint.mjs +0 -1
- package/speechflow-ui-st/etc/tsc-client.json +3 -3
- package/speechflow-ui-st/package.json +12 -11
- package/speechflow-ui-st/src/app.vue +5 -12
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
** SpeechFlow - Speech Processing Flow Graph
|
|
4
|
+
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
|
+
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
41
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
42
|
+
};
|
|
43
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
|
+
/* standard dependencies */
|
|
45
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
46
|
+
/* external dependencies */
|
|
47
|
+
const translate_1 = require("@google-cloud/translate");
|
|
48
|
+
const arktype = __importStar(require("arktype"));
|
|
49
|
+
/* internal dependencies */
|
|
50
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
51
|
+
const utils = __importStar(require("./speechflow-utils"));
|
|
52
|
+
/* SpeechFlow node for Google Translate text-to-text translations */
|
|
53
|
+
class SpeechFlowNodeGoogle extends speechflow_node_1.default {
|
|
54
|
+
/* declare official node name */
|
|
55
|
+
static name = "google";
|
|
56
|
+
/* internal state */
|
|
57
|
+
client = null;
|
|
58
|
+
/* construct node */
|
|
59
|
+
constructor(id, cfg, opts, args) {
|
|
60
|
+
super(id, cfg, opts, args);
|
|
61
|
+
/* declare node configuration parameters */
|
|
62
|
+
this.configure({
|
|
63
|
+
key: { type: "string", val: process.env.SPEECHFLOW_GOOGLE_KEY ?? "" },
|
|
64
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en|fr|it)$/ },
|
|
65
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en|fr|it)$/ }
|
|
66
|
+
});
|
|
67
|
+
/* validate API key and project */
|
|
68
|
+
if (this.params.key === "")
|
|
69
|
+
throw new Error("Google Cloud API credentials JSON key is required");
|
|
70
|
+
/* sanity check situation */
|
|
71
|
+
if (this.params.src === this.params.dst)
|
|
72
|
+
throw new Error("source and destination languages cannot be the same");
|
|
73
|
+
/* declare node input/output format */
|
|
74
|
+
this.input = "text";
|
|
75
|
+
this.output = "text";
|
|
76
|
+
}
|
|
77
|
+
/* one-time status of node */
|
|
78
|
+
async status() {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
/* open node */
|
|
82
|
+
async open() {
|
|
83
|
+
/* instantiate Google Translate client */
|
|
84
|
+
const data = utils.run("Google Cloud API credentials key", () => JSON.parse(this.params.key));
|
|
85
|
+
const credentials = utils.importObject("Google Cloud API credentials key", data, arktype.type({
|
|
86
|
+
project_id: "string",
|
|
87
|
+
private_key: "string",
|
|
88
|
+
client_email: "string",
|
|
89
|
+
}));
|
|
90
|
+
this.client = new translate_1.TranslationServiceClient({
|
|
91
|
+
credentials: {
|
|
92
|
+
private_key: credentials.private_key,
|
|
93
|
+
client_email: credentials.client_email
|
|
94
|
+
},
|
|
95
|
+
projectId: credentials.project_id
|
|
96
|
+
});
|
|
97
|
+
/* provide text-to-text translation */
|
|
98
|
+
const translate = utils.runner("Google Translate API", async (text) => {
|
|
99
|
+
const [response] = await this.client.translateText({
|
|
100
|
+
parent: `projects/${credentials.project_id}/locations/global`,
|
|
101
|
+
contents: [text],
|
|
102
|
+
mimeType: "text/plain",
|
|
103
|
+
sourceLanguageCode: this.params.src,
|
|
104
|
+
targetLanguageCode: this.params.dst
|
|
105
|
+
});
|
|
106
|
+
return response.translations?.[0]?.translatedText ?? text;
|
|
107
|
+
});
|
|
108
|
+
/* establish a duplex stream and connect it to Google Translate */
|
|
109
|
+
this.stream = new node_stream_1.default.Transform({
|
|
110
|
+
readableObjectMode: true,
|
|
111
|
+
writableObjectMode: true,
|
|
112
|
+
decodeStrings: false,
|
|
113
|
+
highWaterMark: 1,
|
|
114
|
+
transform(chunk, encoding, callback) {
|
|
115
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
116
|
+
callback(new Error("invalid chunk payload type"));
|
|
117
|
+
else if (chunk.payload === "") {
|
|
118
|
+
this.push(chunk);
|
|
119
|
+
callback();
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
translate(chunk.payload).then((payload) => {
|
|
123
|
+
const chunkNew = chunk.clone();
|
|
124
|
+
chunkNew.payload = payload;
|
|
125
|
+
this.push(chunkNew);
|
|
126
|
+
callback();
|
|
127
|
+
}).catch((error) => {
|
|
128
|
+
callback(utils.ensureError(error));
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
final(callback) {
|
|
133
|
+
this.push(null);
|
|
134
|
+
callback();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/* close node */
|
|
139
|
+
async close() {
|
|
140
|
+
/* close stream */
|
|
141
|
+
if (this.stream !== null) {
|
|
142
|
+
this.stream.destroy();
|
|
143
|
+
this.stream = null;
|
|
144
|
+
}
|
|
145
|
+
/* shutdown Google Translate client */
|
|
146
|
+
if (this.client !== null) {
|
|
147
|
+
this.client.close();
|
|
148
|
+
this.client = null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
exports.default = SpeechFlowNodeGoogle;
|
|
153
|
+
//# sourceMappingURL=speechflow-node-t2t-google.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"speechflow-node-t2t-google.js","sourceRoot":"","sources":["../src/speechflow-node-t2t-google.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
5
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
6
|
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
7
40
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
41
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
42
|
};
|
|
@@ -14,6 +47,7 @@ const node_stream_1 = __importDefault(require("node:stream"));
|
|
|
14
47
|
const ollama_1 = require("ollama");
|
|
15
48
|
/* internal dependencies */
|
|
16
49
|
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
50
|
+
const utils = __importStar(require("./speechflow-utils"));
|
|
17
51
|
/* SpeechFlow node for Ollama text-to-text translation */
|
|
18
52
|
class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
19
53
|
/* declare official node name */
|
|
@@ -44,11 +78,11 @@ class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
|
44
78
|
"The text you have to correct is:\n",
|
|
45
79
|
chat: [
|
|
46
80
|
{ role: "user", content: "I luve my wyfe" },
|
|
47
|
-
{ role: "
|
|
81
|
+
{ role: "assistant", content: "I love my wife." },
|
|
48
82
|
{ role: "user", content: "The weether is wunderfull!" },
|
|
49
|
-
{ role: "
|
|
50
|
-
{ role: "user", content: "The
|
|
51
|
-
{ role: "
|
|
83
|
+
{ role: "assistant", content: "The weather is wonderful!" },
|
|
84
|
+
{ role: "user", content: "The life awesome but I'm hungry." },
|
|
85
|
+
{ role: "assistant", content: "The life is awesome, but I'm hungry." }
|
|
52
86
|
]
|
|
53
87
|
},
|
|
54
88
|
/* German (DE) spellchecking only */
|
|
@@ -74,11 +108,11 @@ class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
|
74
108
|
"Der von dir zu korrigierende Text ist:\n",
|
|
75
109
|
chat: [
|
|
76
110
|
{ role: "user", content: "Ich ljebe meine Frao" },
|
|
77
|
-
{ role: "
|
|
111
|
+
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
78
112
|
{ role: "user", content: "Die Wedter ist wunderschoen." },
|
|
79
|
-
{ role: "
|
|
113
|
+
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
80
114
|
{ role: "user", content: "Das Leben einfach großartig aber ich bin hungrig." },
|
|
81
|
-
{ role: "
|
|
115
|
+
{ role: "assistant", content: "Das Leben ist einfach großartig, aber ich bin hungrig." }
|
|
82
116
|
]
|
|
83
117
|
},
|
|
84
118
|
/* English (EN) to German (DE) translation */
|
|
@@ -97,11 +131,11 @@ class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
|
97
131
|
"Directly translate text from English (EN) to fluent and natural German (DE) language.\n",
|
|
98
132
|
chat: [
|
|
99
133
|
{ role: "user", content: "I love my wife." },
|
|
100
|
-
{ role: "
|
|
134
|
+
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
101
135
|
{ role: "user", content: "The weather is wonderful." },
|
|
102
|
-
{ role: "
|
|
103
|
-
{ role: "user", content: "The
|
|
104
|
-
{ role: "
|
|
136
|
+
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
137
|
+
{ role: "user", content: "The life is awesome." },
|
|
138
|
+
{ role: "assistant", content: "Das Leben ist einfach großartig." }
|
|
105
139
|
]
|
|
106
140
|
},
|
|
107
141
|
/* German (DE) to English (EN) translation */
|
|
@@ -112,19 +146,19 @@ class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
|
112
146
|
"Do not chat.\n" +
|
|
113
147
|
"Do not show any explanations.\n" +
|
|
114
148
|
"Do not show any introduction.\n" +
|
|
115
|
-
"Do not show any preamble
|
|
116
|
-
"Do not show any prolog
|
|
117
|
-
"Do not show any epilog
|
|
149
|
+
"Do not show any preamble.\n" +
|
|
150
|
+
"Do not show any prolog.\n" +
|
|
151
|
+
"Do not show any epilog.\n" +
|
|
118
152
|
"Get to the point.\n" +
|
|
119
153
|
"Preserve the original meaning, tone, and nuance.\n" +
|
|
120
154
|
"Directly translate text from German (DE) to fluent and natural English (EN) language.\n",
|
|
121
155
|
chat: [
|
|
122
156
|
{ role: "user", content: "Ich liebe meine Frau." },
|
|
123
|
-
{ role: "
|
|
157
|
+
{ role: "assistant", content: "I love my wife." },
|
|
124
158
|
{ role: "user", content: "Das Wetter ist wunderschön." },
|
|
125
|
-
{ role: "
|
|
159
|
+
{ role: "assistant", content: "The weather is wonderful." },
|
|
126
160
|
{ role: "user", content: "Das Leben ist einfach großartig." },
|
|
127
|
-
{ role: "
|
|
161
|
+
{ role: "assistant", content: "The life is awesome." }
|
|
128
162
|
]
|
|
129
163
|
}
|
|
130
164
|
};
|
|
@@ -153,34 +187,47 @@ class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
|
153
187
|
/* instantiate Ollama API */
|
|
154
188
|
this.ollama = new ollama_1.Ollama({ host: this.params.api });
|
|
155
189
|
/* ensure the model is available */
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
190
|
+
let models;
|
|
191
|
+
try {
|
|
192
|
+
models = await this.ollama.list();
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
throw new Error(`failed to connect to Ollama API at ${this.params.api}: ${err}`);
|
|
196
|
+
}
|
|
197
|
+
const exists = models.models.some((m) => m.name === this.params.model);
|
|
159
198
|
if (!exists) {
|
|
160
|
-
this.log("info", `Ollama: model "${model}" still not present in Ollama -- ` +
|
|
199
|
+
this.log("info", `Ollama: model "${this.params.model}" still not present in Ollama -- ` +
|
|
161
200
|
"automatically downloading model");
|
|
162
201
|
let artifact = "";
|
|
163
202
|
let percent = 0;
|
|
203
|
+
let lastLoggedPercent = -1;
|
|
164
204
|
const interval = setInterval(() => {
|
|
165
|
-
|
|
205
|
+
if (percent !== lastLoggedPercent) {
|
|
206
|
+
this.log("info", `downloaded ${percent.toFixed(2)}% of artifact "${artifact}"`);
|
|
207
|
+
lastLoggedPercent = percent;
|
|
208
|
+
}
|
|
166
209
|
}, 1000);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
210
|
+
try {
|
|
211
|
+
const progress = await this.ollama.pull({ model: this.params.model, stream: true });
|
|
212
|
+
for await (const event of progress) {
|
|
213
|
+
if (event.digest)
|
|
214
|
+
artifact = event.digest;
|
|
215
|
+
if (event.completed && event.total)
|
|
216
|
+
percent = (event.completed / event.total) * 100;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
clearInterval(interval);
|
|
173
221
|
}
|
|
174
|
-
clearInterval(interval);
|
|
175
222
|
}
|
|
176
223
|
else
|
|
177
|
-
this.log("info", `Ollama: model "${model}" already present in Ollama`);
|
|
224
|
+
this.log("info", `Ollama: model "${this.params.model}" already present in Ollama`);
|
|
178
225
|
/* provide text-to-text translation */
|
|
179
226
|
const translate = async (text) => {
|
|
180
227
|
const key = `${this.params.src}-${this.params.dst}`;
|
|
181
228
|
const cfg = this.setup[key];
|
|
182
229
|
const response = await this.ollama.chat({
|
|
183
|
-
model,
|
|
230
|
+
model: this.params.model,
|
|
184
231
|
messages: [
|
|
185
232
|
{ role: "system", content: cfg.systemPrompt },
|
|
186
233
|
...cfg.chat,
|
|
@@ -217,8 +264,8 @@ class SpeechFlowNodeOllama extends speechflow_node_1.default {
|
|
|
217
264
|
chunkNew.payload = payload;
|
|
218
265
|
this.push(chunkNew);
|
|
219
266
|
callback();
|
|
220
|
-
}).catch((
|
|
221
|
-
callback(
|
|
267
|
+
}).catch((error) => {
|
|
268
|
+
callback(utils.ensureError(error));
|
|
222
269
|
});
|
|
223
270
|
}
|
|
224
271
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"speechflow-node-t2t-ollama.js","sourceRoot":"","sources":["../src/speechflow-node-t2t-ollama.ts"],"names":[],"mappings":";AAAA;;;;EAIE
|
|
1
|
+
{"version":3,"file":"speechflow-node-t2t-ollama.js","sourceRoot":"","sources":["../src/speechflow-node-t2t-ollama.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,6 +4,39 @@
|
|
|
4
4
|
** Copyright (c) 2024-2025 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
5
5
|
** Licensed under GPL 3.0 <https://spdx.org/licenses/GPL-3.0-only>
|
|
6
6
|
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
7
40
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
41
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
42
|
};
|
|
@@ -14,6 +47,7 @@ const node_stream_1 = __importDefault(require("node:stream"));
|
|
|
14
47
|
const openai_1 = __importDefault(require("openai"));
|
|
15
48
|
/* internal dependencies */
|
|
16
49
|
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
50
|
+
const utils = __importStar(require("./speechflow-utils"));
|
|
17
51
|
/* SpeechFlow node for OpenAI/GPT text-to-text translation */
|
|
18
52
|
class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
19
53
|
/* declare official node name */
|
|
@@ -44,11 +78,11 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
44
78
|
"The text you have to correct is:\n",
|
|
45
79
|
chat: [
|
|
46
80
|
{ role: "user", content: "I luve my wyfe" },
|
|
47
|
-
{ role: "
|
|
81
|
+
{ role: "assistant", content: "I love my wife." },
|
|
48
82
|
{ role: "user", content: "The weether is wunderfull!" },
|
|
49
|
-
{ role: "
|
|
50
|
-
{ role: "user", content: "The
|
|
51
|
-
{ role: "
|
|
83
|
+
{ role: "assistant", content: "The weather is wonderful!" },
|
|
84
|
+
{ role: "user", content: "The life awesome but I'm hungry." },
|
|
85
|
+
{ role: "assistant", content: "The life is awesome, but I'm hungry." }
|
|
52
86
|
]
|
|
53
87
|
},
|
|
54
88
|
/* German (DE) spellchecking only */
|
|
@@ -74,11 +108,11 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
74
108
|
"Der von dir zu korrigierende Text ist:\n",
|
|
75
109
|
chat: [
|
|
76
110
|
{ role: "user", content: "Ich ljebe meine Frao" },
|
|
77
|
-
{ role: "
|
|
111
|
+
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
78
112
|
{ role: "user", content: "Die Wedter ist wunderschoen." },
|
|
79
|
-
{ role: "
|
|
113
|
+
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
80
114
|
{ role: "user", content: "Das Leben einfach großartig aber ich bin hungrig." },
|
|
81
|
-
{ role: "
|
|
115
|
+
{ role: "assistant", content: "Das Leben ist einfach großartig, aber ich bin hungrig." }
|
|
82
116
|
]
|
|
83
117
|
},
|
|
84
118
|
/* English (EN) to German (DE) translation */
|
|
@@ -97,11 +131,11 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
97
131
|
"Directly translate text from English (EN) to fluent and natural German (DE) language.\n",
|
|
98
132
|
chat: [
|
|
99
133
|
{ role: "user", content: "I love my wife." },
|
|
100
|
-
{ role: "
|
|
134
|
+
{ role: "assistant", content: "Ich liebe meine Frau." },
|
|
101
135
|
{ role: "user", content: "The weather is wonderful." },
|
|
102
|
-
{ role: "
|
|
103
|
-
{ role: "user", content: "The
|
|
104
|
-
{ role: "
|
|
136
|
+
{ role: "assistant", content: "Das Wetter ist wunderschön." },
|
|
137
|
+
{ role: "user", content: "The life is awesome." },
|
|
138
|
+
{ role: "assistant", content: "Das Leben ist einfach großartig." }
|
|
105
139
|
]
|
|
106
140
|
},
|
|
107
141
|
/* German (DE) to English (EN) translation */
|
|
@@ -110,21 +144,21 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
110
144
|
"Output only the requested text.\n" +
|
|
111
145
|
"Do not use markdown.\n" +
|
|
112
146
|
"Do not chat.\n" +
|
|
113
|
-
"Do not show any explanations
|
|
147
|
+
"Do not show any explanations.\n" +
|
|
114
148
|
"Do not show any introduction.\n" +
|
|
115
|
-
"Do not show any preamble
|
|
116
|
-
"Do not show any prolog
|
|
117
|
-
"Do not show any epilog
|
|
149
|
+
"Do not show any preamble.\n" +
|
|
150
|
+
"Do not show any prolog.\n" +
|
|
151
|
+
"Do not show any epilog.\n" +
|
|
118
152
|
"Get to the point.\n" +
|
|
119
153
|
"Preserve the original meaning, tone, and nuance.\n" +
|
|
120
154
|
"Directly translate text from German (DE) to fluent and natural English (EN) language.\n",
|
|
121
155
|
chat: [
|
|
122
156
|
{ role: "user", content: "Ich liebe meine Frau." },
|
|
123
|
-
{ role: "
|
|
157
|
+
{ role: "assistant", content: "I love my wife." },
|
|
124
158
|
{ role: "user", content: "Das Wetter ist wunderschön." },
|
|
125
|
-
{ role: "
|
|
159
|
+
{ role: "assistant", content: "The weather is wonderful." },
|
|
126
160
|
{ role: "user", content: "Das Leben ist einfach großartig." },
|
|
127
|
-
{ role: "
|
|
161
|
+
{ role: "assistant", content: "The life is awesome." }
|
|
128
162
|
]
|
|
129
163
|
}
|
|
130
164
|
};
|
|
@@ -135,9 +169,9 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
135
169
|
this.configure({
|
|
136
170
|
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
137
171
|
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ },
|
|
138
|
-
key: { type: "string", val: process.env.SPEECHFLOW_OPENAI_KEY },
|
|
139
|
-
api: { type: "string", val: "https://api.openai.com/v1", match: /^https
|
|
140
|
-
model: { type: "string", val: "gpt-
|
|
172
|
+
key: { type: "string", val: process.env.SPEECHFLOW_OPENAI_KEY, match: /^.+$/ },
|
|
173
|
+
api: { type: "string", val: "https://api.openai.com/v1", match: /^https?:\/\/.+/ },
|
|
174
|
+
model: { type: "string", val: "gpt-5-mini", match: /^.+$/ }
|
|
141
175
|
});
|
|
142
176
|
/* tell effective mode */
|
|
143
177
|
if (this.params.src === this.params.dst)
|
|
@@ -151,33 +185,34 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
151
185
|
}
|
|
152
186
|
/* open node */
|
|
153
187
|
async open() {
|
|
188
|
+
/* validate API key */
|
|
189
|
+
if (!this.params.key)
|
|
190
|
+
throw new Error("OpenAI API key is required");
|
|
154
191
|
/* instantiate OpenAI API */
|
|
155
192
|
this.openai = new openai_1.default({
|
|
156
193
|
baseURL: this.params.api,
|
|
157
194
|
apiKey: this.params.key,
|
|
158
|
-
|
|
195
|
+
timeout: 30000
|
|
159
196
|
});
|
|
160
197
|
/* provide text-to-text translation */
|
|
161
198
|
const translate = async (text) => {
|
|
162
199
|
const key = `${this.params.src}-${this.params.dst}`;
|
|
163
200
|
const cfg = this.setup[key];
|
|
164
|
-
|
|
165
|
-
|
|
201
|
+
if (!this.openai)
|
|
202
|
+
throw new Error("OpenAI client not available");
|
|
203
|
+
const completion = await this.openai.chat.completions.create({
|
|
166
204
|
model: this.params.model,
|
|
167
|
-
|
|
168
|
-
temperature: 0.7,
|
|
169
|
-
n: 1,
|
|
205
|
+
temperature: this.params.model.endsWith("-mini") ? 1.0 : 0.7,
|
|
170
206
|
messages: [
|
|
171
207
|
{ role: "system", content: cfg.systemPrompt },
|
|
172
208
|
...cfg.chat,
|
|
173
209
|
{ role: "user", content: text }
|
|
174
210
|
]
|
|
175
211
|
});
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return translation;
|
|
212
|
+
const content = completion?.choices?.[0]?.message?.content;
|
|
213
|
+
if (!content)
|
|
214
|
+
throw new Error("OpenAI API returned empty content");
|
|
215
|
+
return content;
|
|
181
216
|
};
|
|
182
217
|
/* establish a duplex stream and connect it to OpenAI */
|
|
183
218
|
this.stream = new node_stream_1.default.Transform({
|
|
@@ -188,21 +223,19 @@ class SpeechFlowNodeOpenAI extends speechflow_node_1.default {
|
|
|
188
223
|
transform(chunk, encoding, callback) {
|
|
189
224
|
if (Buffer.isBuffer(chunk.payload))
|
|
190
225
|
callback(new Error("invalid chunk payload type"));
|
|
226
|
+
else if (chunk.payload === "") {
|
|
227
|
+
this.push(chunk);
|
|
228
|
+
callback();
|
|
229
|
+
}
|
|
191
230
|
else {
|
|
192
|
-
|
|
193
|
-
|
|
231
|
+
translate(chunk.payload).then((payload) => {
|
|
232
|
+
const chunkNew = chunk.clone();
|
|
233
|
+
chunkNew.payload = payload;
|
|
234
|
+
this.push(chunkNew);
|
|
194
235
|
callback();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const chunkNew = chunk.clone();
|
|
199
|
-
chunkNew.payload = payload;
|
|
200
|
-
this.push(chunkNew);
|
|
201
|
-
callback();
|
|
202
|
-
}).catch((err) => {
|
|
203
|
-
callback(err);
|
|
204
|
-
});
|
|
205
|
-
}
|
|
236
|
+
}).catch((error) => {
|
|
237
|
+
callback(utils.ensureError(error));
|
|
238
|
+
});
|
|
206
239
|
}
|
|
207
240
|
},
|
|
208
241
|
final(callback) {
|