beast-agent 2.76.5 → 2.77.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.
- package/package.json +1 -1
- package/src/agent/bots.js +669 -664
- package/src/agent/customtools.js +6 -19
- package/src/agent/engine.js +55 -307
- package/src/agent/seeds/tool-yazma/SKILL.md +13 -16
- package/src/agent/skills.js +14 -35
- package/src/agent/supertonic.js +622 -0
- package/src/agent/tools.js +3 -7
- package/src/main.js +77 -6068
- package/src/preload.js +2 -17
- package/src/renderer/i18n.js +14 -8
- package/src/renderer/index.html +0 -297
- package/src/renderer/renderer.js +142 -2280
- package/src/renderer/style.css +3 -825
- package/src/agent/defaulttools/mt5_barlar2/run.js +0 -188
- package/src/agent/defaulttools/mt5_barlar2/tool.json +0 -23
- package/src/agent/defaulttools/mt5_bekleyen/run.js +0 -105
- package/src/agent/defaulttools/mt5_bekleyen/tool.json +0 -18
- package/src/agent/defaulttools/mt5_durum/run.js +0 -93
- package/src/agent/defaulttools/mt5_durum/tool.json +0 -10
- package/src/agent/defaulttools/mt5_emir/run.js +0 -98
- package/src/agent/defaulttools/mt5_emir/tool.json +0 -17
- package/src/agent/defaulttools/mt5_emir_iptal/run.js +0 -90
- package/src/agent/defaulttools/mt5_emir_iptal/tool.json +0 -12
- package/src/agent/defaulttools/mt5_fiyat/run.js +0 -87
- package/src/agent/defaulttools/mt5_fiyat/tool.json +0 -16
- package/src/agent/defaulttools/mt5_gecmis/run.js +0 -87
- package/src/agent/defaulttools/mt5_gecmis/tool.json +0 -12
- package/src/agent/defaulttools/mt5_kapat/run.js +0 -108
- package/src/agent/defaulttools/mt5_kapat/tool.json +0 -15
- package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +0 -60
- package/src/agent/defaulttools/mt5_m15_m5/run.js +0 -73
- package/src/agent/defaulttools/mt5_m15_m5/tool.json +0 -10
- package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +0 -144
- package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +0 -12
- package/src/agent/defaulttools/mt5_shot/run.js +0 -649
- package/src/agent/defaulttools/mt5_shot/stitch.js +0 -151
- package/src/agent/defaulttools/mt5_shot/tool.json +0 -25
- package/src/agent/defaulttools/mt5_sltp/run.js +0 -92
- package/src/agent/defaulttools/mt5_sltp/tool.json +0 -14
- package/src/agent/financetools.js +0 -1025
- package/src/agent/finindicators.js +0 -254
- package/src/agent/finrisk.js +0 -289
- package/src/agent/finstats.js +0 -239
- package/src/agent/finwatch.js +0 -302
- package/src/agent/mt5/BeastFinance.mq5 +0 -498
- package/src/agent/mt5/BeastShot.tpl +0 -0
- package/src/agent/mt5setup.js +0 -542
- package/src/agent/scripts/mt5_bridge.py +0 -575
- package/src/agent/seeds/haber-duygu/SKILL.md +0 -49
- package/src/agent/seeds/mql5/SKILL.md +0 -154
- package/src/agent/seeds/price-action/SKILL.md +0 -60
- package/src/agent/seeds/risk-yonetimi/SKILL.md +0 -52
- package/src/mt5bridge.js +0 -215
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* Beast Supertonic 3 TTS: tamamen YEREL/offline, 31 dil destekli neural
|
|
4
|
+
seslendirme (Supertone/supertonic-3, ONNX Runtime CPU).
|
|
5
|
+
- Model: %APPDATA%\beast\supertonic\onnx (toplam ~400MB, ilk kullanımda
|
|
6
|
+
indirilir; tts.json + unicode_indexer.json + 4 ONNX oturumu).
|
|
7
|
+
- Sesler: M1-M5 (erkek) / F1-F5 (kadın) voice style JSON'ları
|
|
8
|
+
%APPDATA%\beast\supertonic\voice_styles altına iner.
|
|
9
|
+
- Çıktı: 44.1kHz mono 16-bit WAV buffer — WhatsApp sesli not yolu ffmpeg
|
|
10
|
+
ile ogg/opus'a çevirir, masaüstü oynatıcı WAV'ı doğrudan çalar.
|
|
11
|
+
- Oturumlar ilk sentezde yüklenir ve 10 dk boşta kalınca bellekten
|
|
12
|
+
bırakılır; indirmeler kilitli, ilerleme progressbus'a akar.
|
|
13
|
+
- Implementasyon resmi nodejs örneğinin (helper.js) CommonJS portu. */
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const { Readable } = require('stream');
|
|
19
|
+
const { pipeline } = require('stream/promises');
|
|
20
|
+
const { beastRoot } = require('./memory');
|
|
21
|
+
const log = require('./logger');
|
|
22
|
+
const { emitInstallProgress } = require('./progressbus');
|
|
23
|
+
|
|
24
|
+
const HF_BASE = 'https://huggingface.co/Supertone/supertonic-3/resolve/main';
|
|
25
|
+
const DEFAULT_VOICE = 'M1';
|
|
26
|
+
const DEFAULT_LANG = 'tr';
|
|
27
|
+
const DEFAULT_SPEED = 1.05;
|
|
28
|
+
const DEFAULT_STEPS = 8;
|
|
29
|
+
const IDLE_UNLOAD_MS = 10 * 60 * 1000;
|
|
30
|
+
|
|
31
|
+
/* onnx/ klasörü: config + indexer + 4 model (sıra indirme sırası) */
|
|
32
|
+
const MODEL_FILES = [
|
|
33
|
+
'tts.json',
|
|
34
|
+
'unicode_indexer.json',
|
|
35
|
+
'duration_predictor.onnx',
|
|
36
|
+
'text_encoder.onnx',
|
|
37
|
+
'vector_estimator.onnx',
|
|
38
|
+
'vocoder.onnx',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const VOICES = {
|
|
42
|
+
M1: { name: 'M1 — Erkek 1' },
|
|
43
|
+
M2: { name: 'M2 — Erkek 2' },
|
|
44
|
+
M3: { name: 'M3 — Erkek 3' },
|
|
45
|
+
M4: { name: 'M4 — Erkek 4' },
|
|
46
|
+
M5: { name: 'M5 — Erkek 5' },
|
|
47
|
+
F1: { name: 'F1 — Kadın 1' },
|
|
48
|
+
F2: { name: 'F2 — Kadın 2' },
|
|
49
|
+
F3: { name: 'F3 — Kadın 3' },
|
|
50
|
+
F4: { name: 'F4 — Kadın 4' },
|
|
51
|
+
F5: { name: 'F5 — Kadın 5' },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/* Desteklenen 31 dil + na (bilinmeyen/dil bağımsız) — UI sıralaması önemli */
|
|
55
|
+
const LANGS = {
|
|
56
|
+
tr: 'Türkçe',
|
|
57
|
+
en: 'English',
|
|
58
|
+
de: 'Deutsch',
|
|
59
|
+
fr: 'Français',
|
|
60
|
+
es: 'Español',
|
|
61
|
+
it: 'Italiano',
|
|
62
|
+
ru: 'Русский',
|
|
63
|
+
ar: 'العربية',
|
|
64
|
+
ja: '日本語',
|
|
65
|
+
ko: '한국어',
|
|
66
|
+
pt: 'Português',
|
|
67
|
+
nl: 'Nederlands',
|
|
68
|
+
pl: 'Polski',
|
|
69
|
+
hi: 'हिन्दी',
|
|
70
|
+
id: 'Bahasa Indonesia',
|
|
71
|
+
vi: 'Tiếng Việt',
|
|
72
|
+
uk: 'Українська',
|
|
73
|
+
bg: 'Bulgarian',
|
|
74
|
+
cs: 'Čeština',
|
|
75
|
+
da: 'Dansk',
|
|
76
|
+
el: 'Ελληνικά',
|
|
77
|
+
et: 'Eesti',
|
|
78
|
+
fi: 'Suomi',
|
|
79
|
+
hr: 'Hrvatski',
|
|
80
|
+
hu: 'Magyar',
|
|
81
|
+
lt: 'Lietuvių',
|
|
82
|
+
lv: 'Latviešu',
|
|
83
|
+
ro: 'Română',
|
|
84
|
+
sk: 'Slovenčina',
|
|
85
|
+
sl: 'Slovenščina',
|
|
86
|
+
sv: 'Svenska',
|
|
87
|
+
na: 'Otomatik (dil bilinmiyor)',
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/* ---------- yollar ---------- */
|
|
91
|
+
|
|
92
|
+
function rootDir() {
|
|
93
|
+
return path.join(beastRoot(), 'supertonic');
|
|
94
|
+
}
|
|
95
|
+
function onnxDir() {
|
|
96
|
+
return path.join(rootDir(), 'onnx');
|
|
97
|
+
}
|
|
98
|
+
function stylesDir() {
|
|
99
|
+
return path.join(rootDir(), 'voice_styles');
|
|
100
|
+
}
|
|
101
|
+
function modelPath(name) {
|
|
102
|
+
return path.join(onnxDir(), name);
|
|
103
|
+
}
|
|
104
|
+
function stylePath(id) {
|
|
105
|
+
return path.join(stylesDir(), String(id) + '.json');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function resolveVoice(id) {
|
|
109
|
+
const v = String(id || '').trim().toUpperCase();
|
|
110
|
+
return VOICES[v] ? v : DEFAULT_VOICE;
|
|
111
|
+
}
|
|
112
|
+
function resolveLang(id) {
|
|
113
|
+
const l = String(id || '').trim().toLowerCase();
|
|
114
|
+
return LANGS[l] ? l : DEFAULT_LANG;
|
|
115
|
+
}
|
|
116
|
+
function clampNum(n, lo, hi) {
|
|
117
|
+
return Math.max(lo, Math.min(hi, n));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function bigEnough(p, min) {
|
|
121
|
+
try {
|
|
122
|
+
const st = fs.statSync(p);
|
|
123
|
+
return st.isFile() && st.size >= min;
|
|
124
|
+
} catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function modelInstalled() {
|
|
129
|
+
return MODEL_FILES.every((f) => bigEnough(modelPath(f), 1000));
|
|
130
|
+
}
|
|
131
|
+
function voiceInstalled(id) {
|
|
132
|
+
return !!VOICES[id] && bigEnough(stylePath(id), 1000);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ---------- indirme (yüzde akışlı, .part → atomik rename) ---------- */
|
|
136
|
+
|
|
137
|
+
async function download(url, dest, tag, label) {
|
|
138
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
139
|
+
if (!res.ok || !res.body) throw new Error('indirilemedi (http ' + res.status + ')');
|
|
140
|
+
const total = Number(res.headers.get('content-length')) || 0;
|
|
141
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
142
|
+
const tmp = dest + '.part';
|
|
143
|
+
let loaded = 0;
|
|
144
|
+
let last = 0;
|
|
145
|
+
const rs = Readable.fromWeb(res.body);
|
|
146
|
+
rs.on('data', (c) => {
|
|
147
|
+
loaded += c.length;
|
|
148
|
+
const now = Date.now();
|
|
149
|
+
if (now - last > 500) {
|
|
150
|
+
last = now;
|
|
151
|
+
emitInstallProgress(tag, {
|
|
152
|
+
pct: total ? Math.round((loaded / total) * 100) : 0,
|
|
153
|
+
loaded,
|
|
154
|
+
total,
|
|
155
|
+
file: label,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
await pipeline(rs, fs.createWriteStream(tmp));
|
|
160
|
+
const st = fs.statSync(tmp);
|
|
161
|
+
if (st.size < 1000) {
|
|
162
|
+
fs.rmSync(tmp, { force: true });
|
|
163
|
+
throw new Error('eksik indirme: ' + label);
|
|
164
|
+
}
|
|
165
|
+
fs.renameSync(tmp, dest);
|
|
166
|
+
emitInstallProgress(tag, { pct: 100, loaded, total: total || loaded, file: label });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* ---------- kurulum (kilitli) ---------- */
|
|
170
|
+
|
|
171
|
+
let modelPromise = null;
|
|
172
|
+
const voicePromises = new Map();
|
|
173
|
+
|
|
174
|
+
function ensureModel() {
|
|
175
|
+
if (modelInstalled()) return Promise.resolve(true);
|
|
176
|
+
if (modelPromise) return modelPromise;
|
|
177
|
+
modelPromise = (async () => {
|
|
178
|
+
log.info('supertonic', 'model indiriliyor (~400MB): ' + HF_BASE);
|
|
179
|
+
for (const f of MODEL_FILES) {
|
|
180
|
+
const dest = modelPath(f);
|
|
181
|
+
if (bigEnough(dest, 1000)) continue;
|
|
182
|
+
emitInstallProgress('supertonic', { pct: 0, file: f });
|
|
183
|
+
await download(HF_BASE + '/onnx/' + f, dest, 'supertonic', f);
|
|
184
|
+
}
|
|
185
|
+
if (!modelInstalled()) throw new Error('supertonic model dosyaları eksik');
|
|
186
|
+
log.info('supertonic', 'model hazır: ' + onnxDir());
|
|
187
|
+
return true;
|
|
188
|
+
})().catch((e) => {
|
|
189
|
+
log.error('supertonic', 'model indirme başarısız: ' + String((e && e.message) || e));
|
|
190
|
+
throw e;
|
|
191
|
+
}).finally(() => {
|
|
192
|
+
modelPromise = null; // kilit çözülür; kuruluysa yukarıdaki kısa yol devrede
|
|
193
|
+
});
|
|
194
|
+
return modelPromise;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function ensureVoice(id) {
|
|
198
|
+
const vid = resolveVoice(id);
|
|
199
|
+
if (voiceInstalled(vid)) return Promise.resolve(true);
|
|
200
|
+
if (voicePromises.has(vid)) return voicePromises.get(vid);
|
|
201
|
+
const p = (async () => {
|
|
202
|
+
log.info('supertonic', 'ses stili indiriliyor: ' + vid);
|
|
203
|
+
await download(HF_BASE + '/voice_styles/' + vid + '.json', stylePath(vid), 'supertonic-voice', vid + '.json');
|
|
204
|
+
if (!voiceInstalled(vid)) throw new Error('ses stili indirilemedi: ' + vid);
|
|
205
|
+
log.info('supertonic', 'ses hazır: ' + vid);
|
|
206
|
+
return true;
|
|
207
|
+
})().catch((e) => {
|
|
208
|
+
log.error('supertonic', 'ses indirme başarısız (' + vid + '): ' + String((e && e.message) || e));
|
|
209
|
+
throw e;
|
|
210
|
+
}).finally(() => {
|
|
211
|
+
voicePromises.delete(vid);
|
|
212
|
+
});
|
|
213
|
+
voicePromises.set(vid, p);
|
|
214
|
+
return p;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* UI için durum: { installed, model, voice, voiceId, installing } */
|
|
218
|
+
function status(voiceId) {
|
|
219
|
+
const vid = resolveVoice(voiceId);
|
|
220
|
+
return {
|
|
221
|
+
installed: modelInstalled() && voiceInstalled(vid),
|
|
222
|
+
model: modelInstalled(),
|
|
223
|
+
voice: voiceInstalled(vid),
|
|
224
|
+
voiceId: vid,
|
|
225
|
+
installing: !!modelPromise || voicePromises.has(vid),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* elle/otomatik kurulum: model + ses (hata fırlatmaz, {ok} döner) */
|
|
230
|
+
async function install(voiceId) {
|
|
231
|
+
const vid = resolveVoice(voiceId);
|
|
232
|
+
try {
|
|
233
|
+
await ensureModel();
|
|
234
|
+
await ensureVoice(vid);
|
|
235
|
+
return { ok: true, voice: vid };
|
|
236
|
+
} catch (e) {
|
|
237
|
+
return { ok: false, voice: vid, error: String((e && e.message) || e) };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* ---------- ONNX motoru (lazy yükleme + boşta bellek bırakma) ---------- */
|
|
242
|
+
|
|
243
|
+
let engine = null;
|
|
244
|
+
let enginePromise = null;
|
|
245
|
+
let busy = 0;
|
|
246
|
+
let idleTimer = null;
|
|
247
|
+
|
|
248
|
+
function touchIdle() {
|
|
249
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
250
|
+
idleTimer = setTimeout(releaseEngine, IDLE_UNLOAD_MS);
|
|
251
|
+
if (idleTimer.unref) idleTimer.unref();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function releaseEngine() {
|
|
255
|
+
if (busy > 0 || !engine) {
|
|
256
|
+
if (engine) touchIdle();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
for (const s of [engine.dp, engine.textEnc, engine.vectorEst, engine.vocoder]) {
|
|
260
|
+
try { s.release(); } catch {}
|
|
261
|
+
}
|
|
262
|
+
engine = null;
|
|
263
|
+
log.info('supertonic', 'model boşta — bellekten bırakıldı');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* onnxruntime-node'un EN YENİ kopyasını seç: kökteki eski sürüm (1.14)
|
|
267
|
+
Windows'ta delay-load hatasıyla süreci çökertebiliyor; @huggingface/
|
|
268
|
+
transformers kendi yeni kopyasını (1.21+) nested taşır. */
|
|
269
|
+
function ortVersion(dir) {
|
|
270
|
+
try {
|
|
271
|
+
return String(JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).version || '0');
|
|
272
|
+
} catch {
|
|
273
|
+
return '0';
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
function verNum(v) {
|
|
277
|
+
const p = String(v).split('.').map((x) => parseInt(x, 10) || 0);
|
|
278
|
+
return (p[0] || 0) * 1000000 + (p[1] || 0) * 1000 + (p[2] || 0);
|
|
279
|
+
}
|
|
280
|
+
function loadOrt() {
|
|
281
|
+
const candidates = [];
|
|
282
|
+
try {
|
|
283
|
+
let dir = path.dirname(require.resolve('@huggingface/transformers'));
|
|
284
|
+
while (dir && dir !== path.dirname(dir)) {
|
|
285
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) break;
|
|
286
|
+
dir = path.dirname(dir);
|
|
287
|
+
}
|
|
288
|
+
if (dir) candidates.push(path.join(dir, 'node_modules', 'onnxruntime-node'));
|
|
289
|
+
} catch {}
|
|
290
|
+
try {
|
|
291
|
+
candidates.push(path.dirname(require.resolve('onnxruntime-node/package.json')));
|
|
292
|
+
} catch {}
|
|
293
|
+
const found = candidates
|
|
294
|
+
.filter((c) => fs.existsSync(path.join(c, 'package.json')))
|
|
295
|
+
.map((c) => ({ dir: c, v: ortVersion(c) }))
|
|
296
|
+
.sort((a, b) => verNum(b.v) - verNum(a.v));
|
|
297
|
+
for (const c of found) {
|
|
298
|
+
try {
|
|
299
|
+
const m = require(c.dir);
|
|
300
|
+
if (m && typeof m.InferenceSession === 'function') {
|
|
301
|
+
log.info('supertonic', 'onnxruntime-node ' + c.v + ' kullanılıyor');
|
|
302
|
+
return m;
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {
|
|
305
|
+
log.info('supertonic', 'onnxruntime adayı yüklenemedi (' + c.v + '): ' + String((e && e.message) || e));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return require('onnxruntime-node');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function doLoadEngine() {
|
|
312
|
+
await ensureModel();
|
|
313
|
+
const ort = loadOrt();
|
|
314
|
+
const cfg = JSON.parse(fs.readFileSync(modelPath('tts.json'), 'utf8'));
|
|
315
|
+
const indexer = JSON.parse(fs.readFileSync(modelPath('unicode_indexer.json'), 'utf8'));
|
|
316
|
+
const threads = Math.max(1, Math.min(4, (os.cpus() && os.cpus().length || 2) - 1));
|
|
317
|
+
const opts = { intraOpNumThreads: threads, interOpNumThreads: 1, executionProviders: ['cpu'] };
|
|
318
|
+
log.info('supertonic', 'model yükleniyor (cpu, ' + threads + ' iş parçacığı)…');
|
|
319
|
+
const [dp, textEnc, vectorEst, vocoder] = await Promise.all([
|
|
320
|
+
ort.InferenceSession.create(modelPath('duration_predictor.onnx'), opts),
|
|
321
|
+
ort.InferenceSession.create(modelPath('text_encoder.onnx'), opts),
|
|
322
|
+
ort.InferenceSession.create(modelPath('vector_estimator.onnx'), opts),
|
|
323
|
+
ort.InferenceSession.create(modelPath('vocoder.onnx'), opts),
|
|
324
|
+
]);
|
|
325
|
+
log.info('supertonic', 'model yüklendi');
|
|
326
|
+
return {
|
|
327
|
+
ort,
|
|
328
|
+
cfg,
|
|
329
|
+
indexer,
|
|
330
|
+
dp,
|
|
331
|
+
textEnc,
|
|
332
|
+
vectorEst,
|
|
333
|
+
vocoder,
|
|
334
|
+
styles: new Map(),
|
|
335
|
+
sampleRate: cfg.ae.sample_rate,
|
|
336
|
+
baseChunkSize: cfg.ae.base_chunk_size,
|
|
337
|
+
chunkCompressFactor: cfg.ttl.chunk_compress_factor,
|
|
338
|
+
ldim: cfg.ttl.latent_dim,
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function loadEngine() {
|
|
343
|
+
if (engine) return Promise.resolve(engine);
|
|
344
|
+
if (!enginePromise) {
|
|
345
|
+
enginePromise = doLoadEngine().then(
|
|
346
|
+
(e) => { engine = e; enginePromise = null; return e; },
|
|
347
|
+
(e) => { enginePromise = null; throw e; }
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
return enginePromise;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/* ses stili JSON → { ttl, dp } tensorları (motor başına önbellek) */
|
|
354
|
+
function loadStyle(eng, id) {
|
|
355
|
+
const cached = eng.styles.get(id);
|
|
356
|
+
if (cached) return cached;
|
|
357
|
+
const raw = JSON.parse(fs.readFileSync(stylePath(id), 'utf8'));
|
|
358
|
+
const ttl = new eng.ort.Tensor('float32', Float32Array.from(raw.style_ttl.data.flat(Infinity)), raw.style_ttl.dims);
|
|
359
|
+
const dp = new eng.ort.Tensor('float32', Float32Array.from(raw.style_dp.data.flat(Infinity)), raw.style_dp.dims);
|
|
360
|
+
const style = { ttl, dp };
|
|
361
|
+
eng.styles.set(id, style);
|
|
362
|
+
return style;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* ---------- metin ön işleme (resmi helper.js portu) ---------- */
|
|
366
|
+
|
|
367
|
+
function preprocessText(text, lang) {
|
|
368
|
+
let t = String(text || '').normalize('NFKD');
|
|
369
|
+
t = t.replace(/[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{1F1E6}-\u{1F1FF}]+/gu, '');
|
|
370
|
+
const replacements = {
|
|
371
|
+
'–': '-', '‑': '-', '—': '-', _: ' ',
|
|
372
|
+
'\u201C': '"', '\u201D': '"', '\u2018': "'", '\u2019': "'",
|
|
373
|
+
'´': "'", '`': "'", '[': ' ', ']': ' ', '|': ' ', '/': ' ', '#': ' ', '→': ' ', '←': ' ',
|
|
374
|
+
};
|
|
375
|
+
for (const [k, v] of Object.entries(replacements)) t = t.split(k).join(v);
|
|
376
|
+
t = t.replace(/[♥☆♡©\\]/g, '');
|
|
377
|
+
t = t.replace(/@/g, ' at ').replace(/e\.g\.,/g, 'for example, ').replace(/i\.e\.,/g, 'that is, ');
|
|
378
|
+
t = t.replace(/ ,/g, ',').replace(/ \./g, '.').replace(/ !/g, '!').replace(/ \?/g, '?').replace(/ ;/g, ';').replace(/ :/g, ':').replace(/ '/g, "'");
|
|
379
|
+
while (t.includes('""')) t = t.replace('""', '"');
|
|
380
|
+
while (t.includes("''")) t = t.replace("''", "'");
|
|
381
|
+
while (t.includes('``')) t = t.replace('``', '`');
|
|
382
|
+
t = t.replace(/\s+/g, ' ').trim();
|
|
383
|
+
if (!/[.!?;:,'\"')\]}…。」』】〉》›»]$/.test(t)) t += '.';
|
|
384
|
+
return '<' + lang + '>' + t + '</' + lang + '>';
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function chunkText(text, maxLen) {
|
|
388
|
+
const paragraphs = String(text).trim().split(/\n\s*\n+/).filter((p) => p.trim());
|
|
389
|
+
const chunks = [];
|
|
390
|
+
for (let paragraph of paragraphs) {
|
|
391
|
+
paragraph = paragraph.trim();
|
|
392
|
+
if (!paragraph) continue;
|
|
393
|
+
const sentences = paragraph.split(/(?<!Mr\.|Mrs\.|Ms\.|Dr\.|Prof\.|Sr\.|Jr\.|Ph\.D\.|etc\.|e\.g\.|i\.e\.|vs\.|Inc\.|Ltd\.|Co\.|Corp\.|St\.|Ave\.|Blvd\.)(?<!\b[A-Z]\.)(?<=[.!?])\s+/);
|
|
394
|
+
let current = '';
|
|
395
|
+
for (const sentence of sentences) {
|
|
396
|
+
if (current.length + sentence.length + 1 <= maxLen) current += (current ? ' ' : '') + sentence;
|
|
397
|
+
else {
|
|
398
|
+
if (current) chunks.push(current.trim());
|
|
399
|
+
current = sentence;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (current) chunks.push(current.trim());
|
|
403
|
+
}
|
|
404
|
+
return chunks.length ? chunks : [String(text).trim()];
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* ---------- tensör yardımcıları ---------- */
|
|
408
|
+
|
|
409
|
+
function lengthToMask(lengths, maxLen) {
|
|
410
|
+
maxLen = maxLen || Math.max(...lengths);
|
|
411
|
+
const mask = [];
|
|
412
|
+
for (let i = 0; i < lengths.length; i++) {
|
|
413
|
+
const row = [];
|
|
414
|
+
for (let j = 0; j < maxLen; j++) row.push(j < lengths[i] ? 1.0 : 0.0);
|
|
415
|
+
mask.push([row]);
|
|
416
|
+
}
|
|
417
|
+
return mask;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getLatentMask(wavLengths, baseChunkSize, chunkCompressFactor) {
|
|
421
|
+
const latentSize = baseChunkSize * chunkCompressFactor;
|
|
422
|
+
return lengthToMask(wavLengths.map((len) => Math.floor((len + latentSize - 1) / latentSize)));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function arrayToTensor(ort, array, dims) {
|
|
426
|
+
return new ort.Tensor('float32', Float32Array.from(array.flat(Infinity)), dims);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function intArrayToTensor(ort, array, dims) {
|
|
430
|
+
return new ort.Tensor('int64', BigInt64Array.from(array.flat(Infinity).map((x) => BigInt(x))), dims);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/* ---------- çıkarım ---------- */
|
|
434
|
+
|
|
435
|
+
function sampleNoisyLatent(eng, duration) {
|
|
436
|
+
const wavLenMax = Math.max(...duration) * eng.sampleRate;
|
|
437
|
+
const wavLengths = duration.map((d) => Math.floor(d * eng.sampleRate));
|
|
438
|
+
const chunkSize = eng.baseChunkSize * eng.chunkCompressFactor;
|
|
439
|
+
const latentLen = Math.floor((wavLenMax + chunkSize - 1) / chunkSize);
|
|
440
|
+
const latentDim = eng.ldim * eng.chunkCompressFactor;
|
|
441
|
+
|
|
442
|
+
const noisyLatent = [];
|
|
443
|
+
for (let b = 0; b < duration.length; b++) {
|
|
444
|
+
const batch = [];
|
|
445
|
+
for (let d = 0; d < latentDim; d++) {
|
|
446
|
+
const row = [];
|
|
447
|
+
for (let t = 0; t < latentLen; t++) {
|
|
448
|
+
const eps = 1e-10;
|
|
449
|
+
const u1 = Math.max(eps, Math.random());
|
|
450
|
+
const u2 = Math.random();
|
|
451
|
+
row.push(Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2)); // Box-Muller
|
|
452
|
+
}
|
|
453
|
+
batch.push(row);
|
|
454
|
+
}
|
|
455
|
+
noisyLatent.push(batch);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const latentMask = getLatentMask(wavLengths, eng.baseChunkSize, eng.chunkCompressFactor);
|
|
459
|
+
for (let b = 0; b < noisyLatent.length; b++) {
|
|
460
|
+
for (let d = 0; d < noisyLatent[b].length; d++) {
|
|
461
|
+
for (let t = 0; t < noisyLatent[b][d].length; t++) {
|
|
462
|
+
noisyLatent[b][d][t] *= latentMask[b][0][t];
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return { noisyLatent, latentMask };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function inferOne(eng, text, lang, style, steps, speed) {
|
|
470
|
+
const ort = eng.ort;
|
|
471
|
+
const processed = preprocessText(text, lang);
|
|
472
|
+
const textIdsLen = processed.length;
|
|
473
|
+
const textIdsRow = new Array(textIdsLen).fill(0);
|
|
474
|
+
const unicodeVals = Array.from(processed).map((c) => c.charCodeAt(0));
|
|
475
|
+
for (let j = 0; j < unicodeVals.length; j++) {
|
|
476
|
+
const idx = eng.indexer[unicodeVals[j]];
|
|
477
|
+
textIdsRow[j] = Number.isFinite(idx) ? idx : -1;
|
|
478
|
+
}
|
|
479
|
+
const textIds = [textIdsRow];
|
|
480
|
+
const textMask = lengthToMask([textIdsLen]);
|
|
481
|
+
const textIdsShape = [1, textIdsLen];
|
|
482
|
+
const textMaskShape = [1, 1, textMask[0][0].length];
|
|
483
|
+
|
|
484
|
+
const textMaskTensor = arrayToTensor(ort, textMask, textMaskShape);
|
|
485
|
+
|
|
486
|
+
const dpResult = await eng.dp.run({
|
|
487
|
+
text_ids: intArrayToTensor(ort, textIds, textIdsShape),
|
|
488
|
+
style_dp: style.dp,
|
|
489
|
+
text_mask: textMaskTensor,
|
|
490
|
+
});
|
|
491
|
+
const durOnnx = Array.from(dpResult.duration.data);
|
|
492
|
+
for (let i = 0; i < durOnnx.length; i++) durOnnx[i] /= speed;
|
|
493
|
+
|
|
494
|
+
const textEncResult = await eng.textEnc.run({
|
|
495
|
+
text_ids: intArrayToTensor(ort, textIds, textIdsShape),
|
|
496
|
+
style_ttl: style.ttl,
|
|
497
|
+
text_mask: textMaskTensor,
|
|
498
|
+
});
|
|
499
|
+
const textEmbTensor = textEncResult.text_emb;
|
|
500
|
+
|
|
501
|
+
const { noisyLatent, latentMask } = sampleNoisyLatent(eng, durOnnx);
|
|
502
|
+
const latentShape = [1, noisyLatent[0].length, noisyLatent[0][0].length];
|
|
503
|
+
const latentMaskShape = [1, 1, latentMask[0][0].length];
|
|
504
|
+
const latentMaskTensor = arrayToTensor(ort, latentMask, latentMaskShape);
|
|
505
|
+
|
|
506
|
+
const totalStepArray = new Array(1).fill(steps);
|
|
507
|
+
const scalarShape = [1];
|
|
508
|
+
const totalStepTensor = arrayToTensor(ort, totalStepArray, scalarShape);
|
|
509
|
+
|
|
510
|
+
for (let step = 0; step < steps; step++) {
|
|
511
|
+
const currentStepArray = new Array(1).fill(step);
|
|
512
|
+
const vectorEstResult = await eng.vectorEst.run({
|
|
513
|
+
noisy_latent: arrayToTensor(ort, noisyLatent, latentShape),
|
|
514
|
+
text_emb: textEmbTensor,
|
|
515
|
+
style_ttl: style.ttl,
|
|
516
|
+
text_mask: textMaskTensor,
|
|
517
|
+
latent_mask: latentMaskTensor,
|
|
518
|
+
total_step: totalStepTensor,
|
|
519
|
+
current_step: arrayToTensor(ort, currentStepArray, scalarShape),
|
|
520
|
+
});
|
|
521
|
+
const denoised = vectorEstResult.denoised_latent.data;
|
|
522
|
+
let idx = 0;
|
|
523
|
+
for (let b = 0; b < noisyLatent.length; b++) {
|
|
524
|
+
for (let d = 0; d < noisyLatent[b].length; d++) {
|
|
525
|
+
for (let t = 0; t < noisyLatent[b][d].length; t++) {
|
|
526
|
+
noisyLatent[b][d][t] = denoised[idx++];
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const vocoderResult = await eng.vocoder.run({
|
|
533
|
+
latent: arrayToTensor(ort, noisyLatent, latentShape),
|
|
534
|
+
});
|
|
535
|
+
const wavAll = vocoderResult.wav_tts.data;
|
|
536
|
+
const wavLen = Math.floor(durOnnx[0] * eng.sampleRate);
|
|
537
|
+
return wavAll.slice(0, wavLen);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function runInference(eng, text, lang, style, steps, speed) {
|
|
541
|
+
const maxLen = lang === 'ko' || lang === 'ja' ? 120 : 300;
|
|
542
|
+
const chunks = chunkText(text, maxLen);
|
|
543
|
+
let wav = null;
|
|
544
|
+
const silenceLen = Math.floor(0.3 * eng.sampleRate);
|
|
545
|
+
for (const chunk of chunks) {
|
|
546
|
+
const part = await inferOne(eng, chunk, lang, style, steps, speed);
|
|
547
|
+
if (!wav) {
|
|
548
|
+
wav = part;
|
|
549
|
+
} else if (part.length) {
|
|
550
|
+
const merged = new Float32Array(wav.length + silenceLen + part.length);
|
|
551
|
+
merged.set(wav, 0);
|
|
552
|
+
merged.set(part, wav.length + silenceLen);
|
|
553
|
+
wav = merged;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return wav || new Float32Array(0);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/* ---------- WAV (16-bit PCM mono) ---------- */
|
|
560
|
+
|
|
561
|
+
function encodeWav(samples, sampleRate) {
|
|
562
|
+
const dataSize = samples.length * 2;
|
|
563
|
+
const buf = Buffer.alloc(44 + dataSize);
|
|
564
|
+
buf.write('RIFF', 0);
|
|
565
|
+
buf.writeUInt32LE(36 + dataSize, 4);
|
|
566
|
+
buf.write('WAVE', 8);
|
|
567
|
+
buf.write('fmt ', 12);
|
|
568
|
+
buf.writeUInt32LE(16, 16);
|
|
569
|
+
buf.writeUInt16LE(1, 20);
|
|
570
|
+
buf.writeUInt16LE(1, 22);
|
|
571
|
+
buf.writeUInt32LE(sampleRate, 24);
|
|
572
|
+
buf.writeUInt32LE(sampleRate * 2, 28);
|
|
573
|
+
buf.writeUInt16LE(2, 32);
|
|
574
|
+
buf.writeUInt16LE(16, 34);
|
|
575
|
+
buf.write('data', 36);
|
|
576
|
+
buf.writeUInt32LE(dataSize, 40);
|
|
577
|
+
for (let i = 0; i < samples.length; i++) {
|
|
578
|
+
const s = Math.max(-1, Math.min(1, samples[i]));
|
|
579
|
+
buf.writeInt16LE(Math.floor(s * 32767), 44 + i * 2);
|
|
580
|
+
}
|
|
581
|
+
return buf;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* ---------- sentez: metin → { audio: WAV buffer, mime } ---------- */
|
|
585
|
+
|
|
586
|
+
async function synthesize(text, opts = {}) {
|
|
587
|
+
const t = String(text || '').trim();
|
|
588
|
+
if (!t) throw new Error('boş metin');
|
|
589
|
+
const vid = resolveVoice(opts.voice);
|
|
590
|
+
const lang = resolveLang(opts.lang);
|
|
591
|
+
const speed = clampNum(Number(opts.speed) || DEFAULT_SPEED, 0.7, 2);
|
|
592
|
+
const steps = Math.round(clampNum(Number(opts.steps) || DEFAULT_STEPS, 5, 12));
|
|
593
|
+
await ensureModel();
|
|
594
|
+
await ensureVoice(vid);
|
|
595
|
+
const eng = await loadEngine();
|
|
596
|
+
const style = loadStyle(eng, vid);
|
|
597
|
+
busy++;
|
|
598
|
+
try {
|
|
599
|
+
const pcm = await runInference(eng, t.slice(0, 4000), lang, style, steps, speed);
|
|
600
|
+
if (!pcm.length) throw new Error('ses üretilemedi');
|
|
601
|
+
return { audio: encodeWav(pcm, eng.sampleRate), mime: 'audio/wav' };
|
|
602
|
+
} finally {
|
|
603
|
+
busy--;
|
|
604
|
+
if (busy <= 0) touchIdle();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
module.exports = {
|
|
609
|
+
VOICES,
|
|
610
|
+
LANGS,
|
|
611
|
+
DEFAULT_VOICE,
|
|
612
|
+
DEFAULT_LANG,
|
|
613
|
+
DEFAULT_SPEED,
|
|
614
|
+
DEFAULT_STEPS,
|
|
615
|
+
synthesize,
|
|
616
|
+
install,
|
|
617
|
+
status,
|
|
618
|
+
ensureModel,
|
|
619
|
+
ensureVoice,
|
|
620
|
+
modelInstalled,
|
|
621
|
+
voiceInstalled,
|
|
622
|
+
};
|
package/src/agent/tools.js
CHANGED
|
@@ -14,8 +14,6 @@ const gittools = require('./gittools');
|
|
|
14
14
|
const repomap = require('./repomap');
|
|
15
15
|
const xlsxtools = require('./xlsxtools');
|
|
16
16
|
const typesafe = require('./typesafe');
|
|
17
|
-
let financetools = null;
|
|
18
|
-
try { financetools = require('./financetools'); } catch {}
|
|
19
17
|
|
|
20
18
|
const MAX_CMD_OUTPUT = 16000;
|
|
21
19
|
const MAX_FILE_CHARS = 200000;
|
|
@@ -1794,11 +1792,10 @@ function readCacheDrop(abs) {
|
|
|
1794
1792
|
}
|
|
1795
1793
|
|
|
1796
1794
|
const definitions = [
|
|
1797
|
-
/* yerleşik modül araçları: git, repo haritası, excel,
|
|
1795
|
+
/* yerleşik modül araçları: git, repo haritası, excel, TypeSafe */
|
|
1798
1796
|
...gittools.definitions,
|
|
1799
1797
|
...repomap.definitions,
|
|
1800
1798
|
...xlsxtools.definitions,
|
|
1801
|
-
...(financetools ? financetools.definitions : []),
|
|
1802
1799
|
...typesafe.definitions,
|
|
1803
1800
|
{
|
|
1804
1801
|
type: 'function',
|
|
@@ -2082,13 +2079,12 @@ const definitions = [
|
|
|
2082
2079
|
|
|
2083
2080
|
async function exec(name, args, ctx) {
|
|
2084
2081
|
const cwd = ctx.cwd;
|
|
2085
|
-
/* modül araçları: gittools / repomap / xlsxtools /
|
|
2082
|
+
/* modül araçları: gittools / repomap / xlsxtools / typesafe — kendi handler'larında */
|
|
2086
2083
|
const modHandler =
|
|
2087
2084
|
gittools.handlers[name] ||
|
|
2088
2085
|
repomap.handlers[name] ||
|
|
2089
2086
|
xlsxtools.handlers[name] ||
|
|
2090
|
-
typesafe.handlers[name]
|
|
2091
|
-
(financetools && financetools.handlers[name]);
|
|
2087
|
+
typesafe.handlers[name];
|
|
2092
2088
|
if (modHandler) {
|
|
2093
2089
|
try {
|
|
2094
2090
|
return JSON.stringify(await modHandler(args, ctx));
|