speechflow 0.9.4 → 0.9.7
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 +19 -0
- package/README.md +227 -54
- package/dst/speechflow-node-a2a-ffmpeg.d.ts +13 -0
- package/dst/speechflow-node-a2a-ffmpeg.js +152 -0
- package/dst/speechflow-node-a2a-wav.d.ts +11 -0
- package/dst/speechflow-node-a2a-wav.js +170 -0
- package/dst/speechflow-node-a2t-deepgram.d.ts +12 -0
- package/dst/speechflow-node-a2t-deepgram.js +220 -0
- package/dst/speechflow-node-deepgram.d.ts +3 -1
- package/dst/speechflow-node-deepgram.js +86 -22
- package/dst/speechflow-node-deepl.d.ts +3 -1
- package/dst/speechflow-node-deepl.js +25 -20
- package/dst/speechflow-node-device.d.ts +3 -1
- package/dst/speechflow-node-device.js +53 -2
- package/dst/speechflow-node-elevenlabs.d.ts +4 -1
- package/dst/speechflow-node-elevenlabs.js +88 -49
- package/dst/speechflow-node-ffmpeg.d.ts +3 -1
- package/dst/speechflow-node-ffmpeg.js +42 -4
- package/dst/speechflow-node-file.d.ts +3 -1
- package/dst/speechflow-node-file.js +84 -13
- package/dst/speechflow-node-format.d.ts +11 -0
- package/dst/speechflow-node-format.js +80 -0
- package/dst/speechflow-node-gemma.d.ts +3 -1
- package/dst/speechflow-node-gemma.js +84 -23
- package/dst/speechflow-node-mqtt.d.ts +13 -0
- package/dst/speechflow-node-mqtt.js +181 -0
- package/dst/speechflow-node-opus.d.ts +12 -0
- package/dst/speechflow-node-opus.js +135 -0
- package/dst/speechflow-node-subtitle.d.ts +12 -0
- package/dst/speechflow-node-subtitle.js +96 -0
- package/dst/speechflow-node-t2a-elevenlabs.d.ts +13 -0
- package/dst/speechflow-node-t2a-elevenlabs.js +182 -0
- package/dst/speechflow-node-t2t-deepl.d.ts +12 -0
- package/dst/speechflow-node-t2t-deepl.js +133 -0
- package/dst/speechflow-node-t2t-format.d.ts +11 -0
- package/dst/speechflow-node-t2t-format.js +80 -0
- package/dst/speechflow-node-t2t-gemma.d.ts +13 -0
- package/dst/speechflow-node-t2t-gemma.js +213 -0
- package/dst/speechflow-node-t2t-opus.d.ts +12 -0
- package/dst/speechflow-node-t2t-opus.js +135 -0
- package/dst/speechflow-node-t2t-subtitle.d.ts +12 -0
- package/dst/speechflow-node-t2t-subtitle.js +96 -0
- package/dst/speechflow-node-trace.d.ts +11 -0
- package/dst/speechflow-node-trace.js +88 -0
- package/dst/speechflow-node-wav.d.ts +11 -0
- package/dst/speechflow-node-wav.js +170 -0
- package/dst/speechflow-node-websocket.d.ts +3 -1
- package/dst/speechflow-node-websocket.js +149 -49
- package/dst/speechflow-node-whisper-common.d.ts +34 -0
- package/dst/speechflow-node-whisper-common.js +7 -0
- package/dst/speechflow-node-whisper-ggml.d.ts +1 -0
- package/dst/speechflow-node-whisper-ggml.js +97 -0
- package/dst/speechflow-node-whisper-onnx.d.ts +1 -0
- package/dst/speechflow-node-whisper-onnx.js +131 -0
- package/dst/speechflow-node-whisper-worker-ggml.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker-ggml.js +97 -0
- package/dst/speechflow-node-whisper-worker-onnx.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker-onnx.js +131 -0
- package/dst/speechflow-node-whisper-worker.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker.js +116 -0
- package/dst/speechflow-node-whisper-worker2.d.ts +1 -0
- package/dst/speechflow-node-whisper-worker2.js +82 -0
- package/dst/speechflow-node-whisper.d.ts +19 -0
- package/dst/speechflow-node-whisper.js +604 -0
- package/dst/speechflow-node-x2x-trace.d.ts +11 -0
- package/dst/speechflow-node-x2x-trace.js +88 -0
- package/dst/speechflow-node-xio-device.d.ts +13 -0
- package/dst/speechflow-node-xio-device.js +205 -0
- package/dst/speechflow-node-xio-file.d.ts +11 -0
- package/dst/speechflow-node-xio-file.js +176 -0
- package/dst/speechflow-node-xio-mqtt.d.ts +13 -0
- package/dst/speechflow-node-xio-mqtt.js +181 -0
- package/dst/speechflow-node-xio-websocket.d.ts +13 -0
- package/dst/speechflow-node-xio-websocket.js +275 -0
- package/dst/speechflow-node.d.ts +25 -7
- package/dst/speechflow-node.js +74 -9
- package/dst/speechflow-utils.d.ts +23 -0
- package/dst/speechflow-utils.js +194 -0
- package/dst/speechflow.js +146 -43
- package/etc/biome.jsonc +12 -4
- package/etc/stx.conf +65 -0
- package/package.d/@ericedouard+vad-node-realtime+0.2.0.patch +18 -0
- package/package.json +49 -31
- package/sample.yaml +61 -23
- package/src/lib.d.ts +6 -1
- package/src/{speechflow-node-ffmpeg.ts → speechflow-node-a2a-ffmpeg.ts} +10 -4
- package/src/speechflow-node-a2a-wav.ts +143 -0
- package/src/speechflow-node-a2t-deepgram.ts +199 -0
- package/src/speechflow-node-t2a-elevenlabs.ts +160 -0
- package/src/{speechflow-node-deepl.ts → speechflow-node-t2t-deepl.ts} +36 -25
- package/src/speechflow-node-t2t-format.ts +85 -0
- package/src/{speechflow-node-gemma.ts → speechflow-node-t2t-gemma.ts} +89 -25
- package/src/speechflow-node-t2t-opus.ts +111 -0
- package/src/speechflow-node-t2t-subtitle.ts +101 -0
- package/src/speechflow-node-x2x-trace.ts +92 -0
- package/src/{speechflow-node-device.ts → speechflow-node-xio-device.ts} +25 -3
- package/src/speechflow-node-xio-file.ts +153 -0
- package/src/speechflow-node-xio-mqtt.ts +154 -0
- package/src/speechflow-node-xio-websocket.ts +248 -0
- package/src/speechflow-node.ts +78 -13
- package/src/speechflow-utils.ts +212 -0
- package/src/speechflow.ts +150 -43
- package/etc/nps.yaml +0 -40
- package/src/speechflow-node-deepgram.ts +0 -133
- package/src/speechflow-node-elevenlabs.ts +0 -116
- package/src/speechflow-node-file.ts +0 -108
- package/src/speechflow-node-websocket.ts +0 -179
|
@@ -0,0 +1,182 @@
|
|
|
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 ElevenLabs = __importStar(require("@elevenlabs/elevenlabs-js"));
|
|
48
|
+
const get_stream_1 = require("get-stream");
|
|
49
|
+
const speex_resampler_1 = __importDefault(require("speex-resampler"));
|
|
50
|
+
/* internal dependencies */
|
|
51
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
52
|
+
/* SpeechFlow node for Elevenlabs text-to-speech conversion */
|
|
53
|
+
class SpeechFlowNodeElevenlabs extends speechflow_node_1.default {
|
|
54
|
+
/* declare official node name */
|
|
55
|
+
static name = "elevenlabs";
|
|
56
|
+
/* internal state */
|
|
57
|
+
elevenlabs = null;
|
|
58
|
+
static speexInitialized = false;
|
|
59
|
+
/* construct node */
|
|
60
|
+
constructor(id, cfg, opts, args) {
|
|
61
|
+
super(id, cfg, opts, args);
|
|
62
|
+
/* declare node configuration parameters */
|
|
63
|
+
this.configure({
|
|
64
|
+
key: { type: "string", val: process.env.SPEECHFLOW_KEY_ELEVENLABS },
|
|
65
|
+
voice: { type: "string", val: "Brian", pos: 0, match: /^(?:.+)$/ },
|
|
66
|
+
language: { type: "string", val: "en", pos: 1, match: /^(?:de|en)$/ },
|
|
67
|
+
speed: { type: "number", val: 1.05, pos: 2, match: (n) => n >= 0.7 && n <= 1.2 },
|
|
68
|
+
optimize: { type: "string", val: "latency", pos: 3, match: /^(?:latency|quality)$/ }
|
|
69
|
+
});
|
|
70
|
+
/* declare node input/output format */
|
|
71
|
+
this.input = "text";
|
|
72
|
+
this.output = "audio";
|
|
73
|
+
}
|
|
74
|
+
/* open node */
|
|
75
|
+
async open() {
|
|
76
|
+
/* establish ElevenLabs API connection */
|
|
77
|
+
this.elevenlabs = new ElevenLabs.ElevenLabsClient({
|
|
78
|
+
apiKey: this.params.key
|
|
79
|
+
});
|
|
80
|
+
/* determine maximum sample rate of ElevenLabs tier */
|
|
81
|
+
const maxSampleRates = {
|
|
82
|
+
"free": 16000,
|
|
83
|
+
"starter": 22050,
|
|
84
|
+
"creator": 24000,
|
|
85
|
+
"independent_publisher": 44100,
|
|
86
|
+
"growing_business": 44100,
|
|
87
|
+
"enterprise": 44100
|
|
88
|
+
};
|
|
89
|
+
const sub = await this.elevenlabs.user.subscription.get();
|
|
90
|
+
const tier = (sub.tier ?? "free");
|
|
91
|
+
this.log("info", `determined ElevenLabs tier: "${tier}"`);
|
|
92
|
+
let maxSampleRate = 16000;
|
|
93
|
+
if (maxSampleRates[tier] !== undefined)
|
|
94
|
+
maxSampleRate = maxSampleRates[tier];
|
|
95
|
+
this.log("info", `determined maximum audio sample rate: ${maxSampleRate}`);
|
|
96
|
+
/* determine voice for text-to-speech operation
|
|
97
|
+
(for details see https://elevenlabs.io/text-to-speech) */
|
|
98
|
+
const voices = await this.elevenlabs.voices.getAll();
|
|
99
|
+
let voice = voices.voices.find((voice) => voice.name === this.params.voice);
|
|
100
|
+
if (voice === undefined) {
|
|
101
|
+
voice = voices.voices.find((voice) => voice.name.startsWith(this.params.voice));
|
|
102
|
+
if (voice === undefined)
|
|
103
|
+
throw new Error(`invalid ElevenLabs voice "${this.params.voice}"`);
|
|
104
|
+
}
|
|
105
|
+
const info = Object.keys(voice.labels ?? {}).length > 0 ?
|
|
106
|
+
(", " + Object.entries(voice.labels)
|
|
107
|
+
.map(([key, val]) => `${key}: "${val}"`).join(", ")) : "";
|
|
108
|
+
this.log("info", `selected voice: name: "${voice.name}"${info}`);
|
|
109
|
+
/* perform text-to-speech operation with Elevenlabs API */
|
|
110
|
+
const model = this.params.optimize === "quality" ?
|
|
111
|
+
"eleven_multilingual_v2" :
|
|
112
|
+
"eleven_flash_v2_5";
|
|
113
|
+
const speechStream = (text) => {
|
|
114
|
+
this.log("info", `ElevenLabs: send text "${text}"`);
|
|
115
|
+
return this.elevenlabs.textToSpeech.convert(voice.voiceId, {
|
|
116
|
+
text,
|
|
117
|
+
modelId: model,
|
|
118
|
+
languageCode: this.params.language,
|
|
119
|
+
outputFormat: `pcm_${maxSampleRate}`,
|
|
120
|
+
seed: 815, /* arbitrary, but fixated by us */
|
|
121
|
+
voiceSettings: {
|
|
122
|
+
speed: this.params.speed
|
|
123
|
+
}
|
|
124
|
+
}, {
|
|
125
|
+
timeoutInSeconds: 30,
|
|
126
|
+
maxRetries: 10
|
|
127
|
+
});
|
|
128
|
+
};
|
|
129
|
+
/* establish resampler from ElevenLabs's maximum 24Khz
|
|
130
|
+
output to our standard audio sample rate (48KHz) */
|
|
131
|
+
if (!SpeechFlowNodeElevenlabs.speexInitialized) {
|
|
132
|
+
/* at least once initialize resampler */
|
|
133
|
+
await speex_resampler_1.default.initPromise;
|
|
134
|
+
SpeechFlowNodeElevenlabs.speexInitialized = true;
|
|
135
|
+
}
|
|
136
|
+
const resampler = new speex_resampler_1.default(1, maxSampleRate, this.config.audioSampleRate, 7);
|
|
137
|
+
/* create transform stream and connect it to the ElevenLabs API */
|
|
138
|
+
const log = (level, msg) => { this.log(level, msg); };
|
|
139
|
+
this.stream = new node_stream_1.default.Transform({
|
|
140
|
+
writableObjectMode: true,
|
|
141
|
+
readableObjectMode: true,
|
|
142
|
+
decodeStrings: false,
|
|
143
|
+
transform(chunk, encoding, callback) {
|
|
144
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
145
|
+
callback(new Error("invalid chunk payload type"));
|
|
146
|
+
else {
|
|
147
|
+
speechStream(chunk.payload).then((stream) => {
|
|
148
|
+
(0, get_stream_1.getStreamAsBuffer)(stream).then((buffer) => {
|
|
149
|
+
const bufferResampled = resampler.processChunk(buffer);
|
|
150
|
+
log("info", `ElevenLabs: received audio (buffer length: ${buffer.byteLength})`);
|
|
151
|
+
const chunkNew = chunk.clone();
|
|
152
|
+
chunkNew.type = "audio";
|
|
153
|
+
chunkNew.payload = bufferResampled;
|
|
154
|
+
this.push(chunkNew);
|
|
155
|
+
callback();
|
|
156
|
+
}).catch((error) => {
|
|
157
|
+
callback(error);
|
|
158
|
+
});
|
|
159
|
+
}).catch((error) => {
|
|
160
|
+
callback(error);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
final(callback) {
|
|
165
|
+
this.push(null);
|
|
166
|
+
callback();
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/* close node */
|
|
171
|
+
async close() {
|
|
172
|
+
/* destroy stream */
|
|
173
|
+
if (this.stream !== null) {
|
|
174
|
+
this.stream.destroy();
|
|
175
|
+
this.stream = null;
|
|
176
|
+
}
|
|
177
|
+
/* destroy ElevenLabs API */
|
|
178
|
+
if (this.elevenlabs !== null)
|
|
179
|
+
this.elevenlabs = null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
exports.default = SpeechFlowNodeElevenlabs;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeDeepL extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private deepl;
|
|
5
|
+
constructor(id: string, cfg: {
|
|
6
|
+
[id: string]: any;
|
|
7
|
+
}, opts: {
|
|
8
|
+
[id: string]: any;
|
|
9
|
+
}, args: any[]);
|
|
10
|
+
open(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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 DeepL = __importStar(require("deepl-node"));
|
|
48
|
+
/* internal dependencies */
|
|
49
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
50
|
+
/* SpeechFlow node for DeepL text-to-text translations */
|
|
51
|
+
class SpeechFlowNodeDeepL extends speechflow_node_1.default {
|
|
52
|
+
/* declare official node name */
|
|
53
|
+
static name = "deepl";
|
|
54
|
+
/* internal state */
|
|
55
|
+
deepl = null;
|
|
56
|
+
/* construct node */
|
|
57
|
+
constructor(id, cfg, opts, args) {
|
|
58
|
+
super(id, cfg, opts, args);
|
|
59
|
+
/* declare node configuration parameters */
|
|
60
|
+
this.configure({
|
|
61
|
+
key: { type: "string", val: process.env.SPEECHFLOW_KEY_DEEPL },
|
|
62
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
63
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ },
|
|
64
|
+
optimize: { type: "string", pos: 2, val: "latency", match: /^(?:latency|quality)$/ }
|
|
65
|
+
});
|
|
66
|
+
/* sanity check situation */
|
|
67
|
+
if (this.params.src === this.params.dst)
|
|
68
|
+
throw new Error("source and destination languages cannot be the same");
|
|
69
|
+
/* declare node input/output format */
|
|
70
|
+
this.input = "text";
|
|
71
|
+
this.output = "text";
|
|
72
|
+
}
|
|
73
|
+
/* open node */
|
|
74
|
+
async open() {
|
|
75
|
+
/* instantiate DeepL API SDK */
|
|
76
|
+
this.deepl = new DeepL.Translator(this.params.key);
|
|
77
|
+
/* provide text-to-text translation */
|
|
78
|
+
const translate = async (text) => {
|
|
79
|
+
const src = this.params.src === "en" ? "en-US" : this.params.src;
|
|
80
|
+
const dst = this.params.dst === "en" ? "en-US" : this.params.dst;
|
|
81
|
+
const result = await this.deepl.translateText(text, src, dst, {
|
|
82
|
+
splitSentences: "off",
|
|
83
|
+
modelType: this.params.optimize === "latency" ?
|
|
84
|
+
"latency_optimized" : "prefer_quality_optimized",
|
|
85
|
+
preserveFormatting: true,
|
|
86
|
+
formality: "prefer_more"
|
|
87
|
+
});
|
|
88
|
+
return (result?.text ?? text);
|
|
89
|
+
};
|
|
90
|
+
/* establish a duplex stream and connect it to DeepL translation */
|
|
91
|
+
this.stream = new node_stream_1.default.Transform({
|
|
92
|
+
readableObjectMode: true,
|
|
93
|
+
writableObjectMode: true,
|
|
94
|
+
decodeStrings: false,
|
|
95
|
+
transform(chunk, encoding, callback) {
|
|
96
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
97
|
+
callback(new Error("invalid chunk payload type"));
|
|
98
|
+
else {
|
|
99
|
+
if (chunk.payload === "") {
|
|
100
|
+
this.push(chunk);
|
|
101
|
+
callback();
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
translate(chunk.payload).then((payload) => {
|
|
105
|
+
const chunkNew = chunk.clone();
|
|
106
|
+
chunkNew.payload = payload;
|
|
107
|
+
this.push(chunkNew);
|
|
108
|
+
callback();
|
|
109
|
+
}).catch((err) => {
|
|
110
|
+
callback(err);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
final(callback) {
|
|
116
|
+
this.push(null);
|
|
117
|
+
callback();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/* close node */
|
|
122
|
+
async close() {
|
|
123
|
+
/* close stream */
|
|
124
|
+
if (this.stream !== null) {
|
|
125
|
+
this.stream.destroy();
|
|
126
|
+
this.stream = null;
|
|
127
|
+
}
|
|
128
|
+
/* shutdown DeepL API */
|
|
129
|
+
if (this.deepl !== null)
|
|
130
|
+
this.deepl = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.default = SpeechFlowNodeDeepL;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeFormat extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
constructor(id: string, cfg: {
|
|
5
|
+
[id: string]: any;
|
|
6
|
+
}, opts: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, args: any[]);
|
|
9
|
+
open(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
12
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
+
/* external dependencies */
|
|
14
|
+
const wrap_text_1 = __importDefault(require("wrap-text"));
|
|
15
|
+
/* internal dependencies */
|
|
16
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
17
|
+
/* SpeechFlow node for text-to-text formatting */
|
|
18
|
+
class SpeechFlowNodeFormat extends speechflow_node_1.default {
|
|
19
|
+
/* declare official node name */
|
|
20
|
+
static name = "format";
|
|
21
|
+
/* construct node */
|
|
22
|
+
constructor(id, cfg, opts, args) {
|
|
23
|
+
super(id, cfg, opts, args);
|
|
24
|
+
/* declare node configuration parameters */
|
|
25
|
+
this.configure({
|
|
26
|
+
width: { type: "number", val: 80 }
|
|
27
|
+
});
|
|
28
|
+
/* declare node input/output format */
|
|
29
|
+
this.input = "text";
|
|
30
|
+
this.output = "text";
|
|
31
|
+
}
|
|
32
|
+
/* open node */
|
|
33
|
+
async open() {
|
|
34
|
+
/* provide text-to-text formatter */
|
|
35
|
+
const format = async (text) => {
|
|
36
|
+
text = (0, wrap_text_1.default)(text, this.params.width);
|
|
37
|
+
text = text.replace(/([^\n])$/, "$1\n");
|
|
38
|
+
return text;
|
|
39
|
+
};
|
|
40
|
+
/* establish a duplex stream and connect it to DeepL translation */
|
|
41
|
+
this.stream = new node_stream_1.default.Transform({
|
|
42
|
+
readableObjectMode: true,
|
|
43
|
+
writableObjectMode: true,
|
|
44
|
+
decodeStrings: false,
|
|
45
|
+
transform(chunk, encoding, callback) {
|
|
46
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
47
|
+
callback(new Error("invalid chunk payload type"));
|
|
48
|
+
else {
|
|
49
|
+
if (chunk.payload === "") {
|
|
50
|
+
this.push(chunk);
|
|
51
|
+
callback();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
format(chunk.payload).then((payload) => {
|
|
55
|
+
const chunkNew = chunk.clone();
|
|
56
|
+
chunkNew.payload = payload;
|
|
57
|
+
this.push(chunkNew);
|
|
58
|
+
callback();
|
|
59
|
+
}).catch((err) => {
|
|
60
|
+
callback(err);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
final(callback) {
|
|
66
|
+
this.push(null);
|
|
67
|
+
callback();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/* open node */
|
|
72
|
+
async close() {
|
|
73
|
+
/* close stream */
|
|
74
|
+
if (this.stream !== null) {
|
|
75
|
+
this.stream.destroy();
|
|
76
|
+
this.stream = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.default = SpeechFlowNodeFormat;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeGemma extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private ollama;
|
|
5
|
+
private setup;
|
|
6
|
+
constructor(id: string, cfg: {
|
|
7
|
+
[id: string]: any;
|
|
8
|
+
}, opts: {
|
|
9
|
+
[id: string]: any;
|
|
10
|
+
}, args: any[]);
|
|
11
|
+
open(): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
8
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
/* standard dependencies */
|
|
12
|
+
const node_stream_1 = __importDefault(require("node:stream"));
|
|
13
|
+
/* external dependencies */
|
|
14
|
+
const ollama_1 = require("ollama");
|
|
15
|
+
/* internal dependencies */
|
|
16
|
+
const speechflow_node_1 = __importDefault(require("./speechflow-node"));
|
|
17
|
+
/* SpeechFlow node for Gemma/Ollama text-to-text translation */
|
|
18
|
+
class SpeechFlowNodeGemma extends speechflow_node_1.default {
|
|
19
|
+
/* declare official node name */
|
|
20
|
+
static name = "gemma";
|
|
21
|
+
/* internal state */
|
|
22
|
+
ollama = null;
|
|
23
|
+
/* internal LLM setup */
|
|
24
|
+
setup = {
|
|
25
|
+
/* English (EN) spellchecking only */
|
|
26
|
+
"en-en": {
|
|
27
|
+
systemPrompt: "You are a proofreader and spellchecker for English.\n" +
|
|
28
|
+
"Output only the corrected text.\n" +
|
|
29
|
+
"Do NOT use markdown.\n" +
|
|
30
|
+
"Do NOT give any explanations.\n" +
|
|
31
|
+
"Do NOT give any introduction.\n" +
|
|
32
|
+
"Do NOT give any comments.\n" +
|
|
33
|
+
"Do NOT give any preamble.\n" +
|
|
34
|
+
"Do NOT give any prolog.\n" +
|
|
35
|
+
"Do NOT give any epilog.\n" +
|
|
36
|
+
"Do NOT change the gammar.\n" +
|
|
37
|
+
"Do NOT use synonyms for words.\n" +
|
|
38
|
+
"Keep all words.\n" +
|
|
39
|
+
"Fill in missing commas.\n" +
|
|
40
|
+
"Fill in missing points.\n" +
|
|
41
|
+
"Fill in missing question marks.\n" +
|
|
42
|
+
"Fill in missing hyphens.\n" +
|
|
43
|
+
"Focus ONLY on the word spelling.\n" +
|
|
44
|
+
"The text you have to correct is:\n",
|
|
45
|
+
chat: [
|
|
46
|
+
{ role: "user", content: "I luve my wyfe" },
|
|
47
|
+
{ role: "system", content: "I love my wife." },
|
|
48
|
+
{ role: "user", content: "The weether is wunderfull!" },
|
|
49
|
+
{ role: "system", content: "The weather is wonderful!" },
|
|
50
|
+
{ role: "user", content: "The live awesome but I'm hungry." },
|
|
51
|
+
{ role: "system", content: "The live is awesome, but I'm hungry." }
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
/* German (DE) spellchecking only */
|
|
55
|
+
"de-de": {
|
|
56
|
+
systemPrompt: "Du bist ein Korrekturleser und Rechtschreibprüfer für Deutsch.\n" +
|
|
57
|
+
"Gib nur den korrigierten Text aus.\n" +
|
|
58
|
+
"Benutze KEIN Markdown.\n" +
|
|
59
|
+
"Gib KEINE Erklärungen.\n" +
|
|
60
|
+
"Gib KEINE Einleitung.\n" +
|
|
61
|
+
"Gib KEINE Kommentare.\n" +
|
|
62
|
+
"Gib KEINE Preamble.\n" +
|
|
63
|
+
"Gib KEINEN Prolog.\n" +
|
|
64
|
+
"Gib KEINEN Epilog.\n" +
|
|
65
|
+
"Ändere NICHT die Grammatik.\n" +
|
|
66
|
+
"Verwende KEINE Synonyme für Wörter.\n" +
|
|
67
|
+
"Behalte alle Wörter bei.\n" +
|
|
68
|
+
"Füge fehlende Kommas ein.\n" +
|
|
69
|
+
"Füge fehlende Punkte ein.\n" +
|
|
70
|
+
"Füge fehlende Fragezeichen ein.\n" +
|
|
71
|
+
"Füge fehlende Bindestriche ein.\n" +
|
|
72
|
+
"Füge fehlende Gedankenstriche ein.\n" +
|
|
73
|
+
"Fokussiere dich NUR auf die Rechtschreibung der Wörter.\n" +
|
|
74
|
+
"Der von dir zu korrigierende Text ist:\n",
|
|
75
|
+
chat: [
|
|
76
|
+
{ role: "user", content: "Ich ljebe meine Frao" },
|
|
77
|
+
{ role: "system", content: "Ich liebe meine Frau." },
|
|
78
|
+
{ role: "user", content: "Die Wedter ist wunderschoen." },
|
|
79
|
+
{ role: "system", content: "Das Wetter ist wunderschön." },
|
|
80
|
+
{ role: "user", content: "Das Leben einfach großartig aber ich bin hungrig." },
|
|
81
|
+
{ role: "system", content: "Das Leben ist einfach großartig, aber ich bin hungrig." }
|
|
82
|
+
]
|
|
83
|
+
},
|
|
84
|
+
/* English (EN) to German (DE) translation */
|
|
85
|
+
"en-de": {
|
|
86
|
+
systemPrompt: "You are a translator.\n" +
|
|
87
|
+
"Output only the requested text.\n" +
|
|
88
|
+
"Do not use markdown.\n" +
|
|
89
|
+
"Do not chat.\n" +
|
|
90
|
+
"Do not show any explanations.\n" +
|
|
91
|
+
"Do not show any introduction.\n" +
|
|
92
|
+
"Do not show any preamble.\n" +
|
|
93
|
+
"Do not show any prolog.\n" +
|
|
94
|
+
"Do not show any epilog.\n" +
|
|
95
|
+
"Get to the point.\n" +
|
|
96
|
+
"Directly translate text from Enlish (EN) to German (DE) language.\n",
|
|
97
|
+
chat: [
|
|
98
|
+
{ role: "user", content: "I love my wife." },
|
|
99
|
+
{ role: "system", content: "Ich liebe meine Frau." },
|
|
100
|
+
{ role: "user", content: "The weather is wonderful." },
|
|
101
|
+
{ role: "system", content: "Das Wetter ist wunderschön." },
|
|
102
|
+
{ role: "user", content: "The live is awesome." },
|
|
103
|
+
{ role: "system", content: "Das Leben ist einfach großartig." }
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
/* German (DE) to English (EN) translation */
|
|
107
|
+
"de-en": {
|
|
108
|
+
systemPrompt: "You are a translator.\n" +
|
|
109
|
+
"Output only the requested text.\n" +
|
|
110
|
+
"Do not use markdown.\n" +
|
|
111
|
+
"Do not chat.\n" +
|
|
112
|
+
"Do not show any explanations. \n" +
|
|
113
|
+
"Do not show any introduction.\n" +
|
|
114
|
+
"Do not show any preamble. \n" +
|
|
115
|
+
"Do not show any prolog. \n" +
|
|
116
|
+
"Do not show any epilog. \n" +
|
|
117
|
+
"Get to the point.\n" +
|
|
118
|
+
"Directly translate text from German (DE) to English (EN) language.\n",
|
|
119
|
+
chat: [
|
|
120
|
+
{ role: "user", content: "Ich liebe meine Frau." },
|
|
121
|
+
{ role: "system", content: "I love my wife." },
|
|
122
|
+
{ role: "user", content: "Das Wetter ist wunderschön." },
|
|
123
|
+
{ role: "system", content: "The weather is wonderful." },
|
|
124
|
+
{ role: "user", content: "Das Leben ist einfach großartig." },
|
|
125
|
+
{ role: "system", content: "The live is awesome." }
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
/* construct node */
|
|
130
|
+
constructor(id, cfg, opts, args) {
|
|
131
|
+
super(id, cfg, opts, args);
|
|
132
|
+
/* declare node configuration parameters */
|
|
133
|
+
this.configure({
|
|
134
|
+
api: { type: "string", val: "http://127.0.0.1:11434", match: /^https?:\/\/.+?:\d+$/ },
|
|
135
|
+
src: { type: "string", pos: 0, val: "de", match: /^(?:de|en)$/ },
|
|
136
|
+
dst: { type: "string", pos: 1, val: "en", match: /^(?:de|en)$/ }
|
|
137
|
+
});
|
|
138
|
+
/* declare node input/output format */
|
|
139
|
+
this.input = "text";
|
|
140
|
+
this.output = "text";
|
|
141
|
+
}
|
|
142
|
+
/* open node */
|
|
143
|
+
async open() {
|
|
144
|
+
/* instantiate Ollama API */
|
|
145
|
+
this.ollama = new ollama_1.Ollama({ host: this.params.api });
|
|
146
|
+
/* provide text-to-text translation */
|
|
147
|
+
const translate = async (text) => {
|
|
148
|
+
const key = `${this.params.src}-${this.params.dst}`;
|
|
149
|
+
const cfg = this.setup[key];
|
|
150
|
+
const response = await this.ollama.chat({
|
|
151
|
+
model: "gemma3:4b-it-q4_K_M",
|
|
152
|
+
messages: [
|
|
153
|
+
{ role: "system", content: cfg.systemPrompt },
|
|
154
|
+
...cfg.chat,
|
|
155
|
+
{ role: "user", content: text }
|
|
156
|
+
],
|
|
157
|
+
keep_alive: "10m",
|
|
158
|
+
options: {
|
|
159
|
+
repeat_penalty: 1.1,
|
|
160
|
+
temperature: 0.7,
|
|
161
|
+
seed: 1,
|
|
162
|
+
top_k: 10,
|
|
163
|
+
top_p: 0.5
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return response.message.content;
|
|
167
|
+
};
|
|
168
|
+
/* establish a duplex stream and connect it to Ollama */
|
|
169
|
+
this.stream = new node_stream_1.default.Transform({
|
|
170
|
+
readableObjectMode: true,
|
|
171
|
+
writableObjectMode: true,
|
|
172
|
+
decodeStrings: false,
|
|
173
|
+
transform(chunk, encoding, callback) {
|
|
174
|
+
if (Buffer.isBuffer(chunk.payload))
|
|
175
|
+
callback(new Error("invalid chunk payload type"));
|
|
176
|
+
else {
|
|
177
|
+
if (chunk.payload === "") {
|
|
178
|
+
this.push(chunk);
|
|
179
|
+
callback();
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
translate(chunk.payload).then((payload) => {
|
|
183
|
+
const chunkNew = chunk.clone();
|
|
184
|
+
chunkNew.payload = payload;
|
|
185
|
+
this.push(chunkNew);
|
|
186
|
+
callback();
|
|
187
|
+
}).catch((err) => {
|
|
188
|
+
callback(err);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
final(callback) {
|
|
194
|
+
this.push(null);
|
|
195
|
+
callback();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/* close node */
|
|
200
|
+
async close() {
|
|
201
|
+
/* close stream */
|
|
202
|
+
if (this.stream !== null) {
|
|
203
|
+
this.stream.destroy();
|
|
204
|
+
this.stream = null;
|
|
205
|
+
}
|
|
206
|
+
/* shutdown Ollama */
|
|
207
|
+
if (this.ollama !== null) {
|
|
208
|
+
this.ollama.abort();
|
|
209
|
+
this.ollama = null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.default = SpeechFlowNodeGemma;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import SpeechFlowNode from "./speechflow-node";
|
|
2
|
+
export default class SpeechFlowNodeOPUS extends SpeechFlowNode {
|
|
3
|
+
static name: string;
|
|
4
|
+
private translator;
|
|
5
|
+
constructor(id: string, cfg: {
|
|
6
|
+
[id: string]: any;
|
|
7
|
+
}, opts: {
|
|
8
|
+
[id: string]: any;
|
|
9
|
+
}, args: any[]);
|
|
10
|
+
open(): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
}
|