@fugood/buttress-server 2.25.2 → 2.25.3
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/README.md +75 -3
- package/config/sample.toml +21 -0
- package/lib/functions/templates.d.ts +1 -1
- package/lib/index.mjs +47 -44
- package/lib/routes/tts-shared.d.ts +2 -2
- package/lib/services/ggml-tts.d.ts +42 -0
- package/lib/services/index.d.ts +3 -0
- package/lib/types.d.ts +1 -1
- package/lib/utils/fileResponse.d.ts +14 -0
- package/package.json +3 -3
- package/public/status.html +101 -1
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { GeneratorCacheEntry } from './generator-cache';
|
|
10
10
|
export declare const TTS_TYPES: string[];
|
|
11
|
-
/** Get the TTS backend API for a generator type
|
|
12
|
-
export declare function getTtsBackend(backend: any,
|
|
11
|
+
/** Get the TTS backend API for a generator type. */
|
|
12
|
+
export declare function getTtsBackend(backend: any, type: string): any;
|
|
13
13
|
export declare function getOrCreateTtsGenerator(backend: any, config: any, requestedModel?: string, logPrefix?: string): Promise<GeneratorCacheEntry>;
|
|
14
14
|
export declare function releaseTtsGenerator(backend: any, entry: GeneratorCacheEntry | null | undefined, logPrefix?: string): Promise<void>;
|
|
15
15
|
export type SynthesizeRequest = {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { ReadableStream } from 'node:stream/web';
|
|
3
|
+
import type { ServiceContext, EventStream, Expand } from '../types';
|
|
4
|
+
export declare const schemas: {
|
|
5
|
+
initContext: z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>], null>;
|
|
6
|
+
synthesize: z.ZodTuple<[z.ZodString, z.ZodObject<{
|
|
7
|
+
text: z.ZodString;
|
|
8
|
+
options: z.ZodOptional<z.ZodObject<{
|
|
9
|
+
speaker: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
10
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
11
|
+
speaker: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
12
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
13
|
+
speaker: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
14
|
+
}, z.ZodTypeAny, "passthrough">>>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
text: string;
|
|
17
|
+
options?: z.objectOutputType<{
|
|
18
|
+
speaker: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
19
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
text: string;
|
|
22
|
+
options?: z.objectInputType<{
|
|
23
|
+
speaker: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
24
|
+
}, z.ZodTypeAny, "passthrough"> | undefined;
|
|
25
|
+
}>], null>;
|
|
26
|
+
releaseContext: z.ZodTuple<[z.ZodString], null>;
|
|
27
|
+
};
|
|
28
|
+
export interface SynthesizeResult {
|
|
29
|
+
filename: string;
|
|
30
|
+
sampling_rate: number;
|
|
31
|
+
channels: number;
|
|
32
|
+
}
|
|
33
|
+
export interface Service {
|
|
34
|
+
initContext: (ctx: ServiceContext, ...args: z.infer<typeof schemas.initContext>) => ReadableStream<Expand<EventStream>>;
|
|
35
|
+
synthesize: (ctx: ServiceContext, ...args: z.infer<typeof schemas.synthesize>) => Promise<SynthesizeResult>;
|
|
36
|
+
releaseContext: (ctx: ServiceContext, ...args: z.infer<typeof schemas.releaseContext>) => Promise<{
|
|
37
|
+
released: boolean;
|
|
38
|
+
alreadyReleased?: boolean;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
declare const _default: Service;
|
|
42
|
+
export default _default;
|
package/lib/services/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ServiceContext } from '../types';
|
|
|
2
2
|
import type { Service as CommonService } from './common';
|
|
3
3
|
import type { Service as GgmlLlmService } from './ggml-llm';
|
|
4
4
|
import type { Service as GgmlSttService } from './ggml-stt';
|
|
5
|
+
import type { Service as GgmlTtsService } from './ggml-tts';
|
|
5
6
|
import type { Service as MlxLlmService } from './mlx-llm';
|
|
6
7
|
import type { Service as OnnxSttService } from './onnx-stt';
|
|
7
8
|
import type { Service as OnnxTtsService } from './onnx-tts';
|
|
@@ -19,6 +20,7 @@ declare const services: {
|
|
|
19
20
|
}, id: any, audioData: any, options: any): Promise<any>;
|
|
20
21
|
releaseContext: ({ backend, session }: ServiceContext, id: any, force: any) => Promise<any>;
|
|
21
22
|
};
|
|
23
|
+
ggmlTts: GgmlTtsService;
|
|
22
24
|
mlxLlm: MlxLlmService;
|
|
23
25
|
onnxStt: OnnxSttService;
|
|
24
26
|
onnxTts: OnnxTtsService;
|
|
@@ -32,6 +34,7 @@ export type Services = {
|
|
|
32
34
|
common: ClientService<CommonService>;
|
|
33
35
|
ggmlLlm: ClientService<GgmlLlmService>;
|
|
34
36
|
ggmlStt: ClientService<GgmlSttService>;
|
|
37
|
+
ggmlTts: ClientService<GgmlTtsService>;
|
|
35
38
|
mlxLlm: ClientService<MlxLlmService>;
|
|
36
39
|
onnxStt: ClientService<OnnxSttService>;
|
|
37
40
|
onnxTts: ClientService<OnnxTtsService>;
|
package/lib/types.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export type RuntimeConfig = {
|
|
|
25
25
|
huggingface_token?: string;
|
|
26
26
|
session_cache?: SessionCacheConfig;
|
|
27
27
|
} & Record<string, any>;
|
|
28
|
-
export type GeneratorType = 'ggml-llm' | 'ggml-stt' | 'mlx-llm' | 'onnx-stt' | 'onnx-tts';
|
|
28
|
+
export type GeneratorType = 'ggml-llm' | 'ggml-stt' | 'ggml-tts' | 'mlx-llm' | 'onnx-stt' | 'onnx-tts';
|
|
29
29
|
export type GeneratorConfig = {
|
|
30
30
|
type: GeneratorType;
|
|
31
31
|
} & Record<string, any>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serve a local file from a route.
|
|
3
|
+
*
|
|
4
|
+
* Not elysia's `file()` on Bun: it returns an ElysiaFile, which elysia's
|
|
5
|
+
* adapter dispatches by constructor *name* — and bundling renames the class
|
|
6
|
+
* (always under identifier minification, and under plain bundling whenever
|
|
7
|
+
* another module declares the same symbol). The compiled distribution shipped
|
|
8
|
+
* that way, degrading every file response to `String(response)` — HTTP 200
|
|
9
|
+
* "[object Object]". A BunFile dispatches through the global `Blob` case onto
|
|
10
|
+
* the same `handleFile` path, and globals survive any bundler. The Node
|
|
11
|
+
* fallback runs unbundled from lib/, where `file()` is safe.
|
|
12
|
+
* Covered by scripts/__tests__/bundle-file-response.test.js.
|
|
13
|
+
*/
|
|
14
|
+
export declare const fileResponse: (path: string) => any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/buttress-server",
|
|
3
|
-
"version": "2.25.
|
|
3
|
+
"version": "2.25.3",
|
|
4
4
|
"main": "lib/index.mjs",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@elysiajs/cors": "^1.1.1",
|
|
41
41
|
"@elysiajs/node": "^1.4.2",
|
|
42
|
-
"@fugood/llama.node": "1.
|
|
42
|
+
"@fugood/llama.node": "1.8.0-rc.2",
|
|
43
43
|
"@fugood/whisper.node": "^1.1.1",
|
|
44
44
|
"@huggingface/gguf": "^0.3.2",
|
|
45
45
|
"@iarna/toml": "^3.0.0",
|
|
@@ -69,5 +69,5 @@
|
|
|
69
69
|
"tsdown": "^0.22.4",
|
|
70
70
|
"typescript": "^7.0.2"
|
|
71
71
|
},
|
|
72
|
-
"gitHead": "
|
|
72
|
+
"gitHead": "0a712b850bf86dc30defb32658122438a38d39ba"
|
|
73
73
|
}
|
package/public/status.html
CHANGED
|
@@ -582,6 +582,27 @@
|
|
|
582
582
|
</div>
|
|
583
583
|
</div>
|
|
584
584
|
|
|
585
|
+
<!-- GGML-TTS Status (hidden unless active) -->
|
|
586
|
+
<div class="card" id="ggmlTtsCard" style="display:none">
|
|
587
|
+
<div class="card-header">
|
|
588
|
+
<span class="card-title">GGML-TTS Status</span>
|
|
589
|
+
<span class="badge badge-info" id="ggmlTtsGeneratorCount">0 generators</span>
|
|
590
|
+
</div>
|
|
591
|
+
<div id="ggmlTtsGenerators"></div>
|
|
592
|
+
<div class="section">
|
|
593
|
+
<div class="section-title collapsible" onclick="toggleSection(this)">Model Load History</div>
|
|
594
|
+
<div class="collapsible-content" id="ggmlTtsModelHistory">
|
|
595
|
+
<div class="empty-state">No model load history</div>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
<div class="section">
|
|
599
|
+
<div class="section-title collapsible" onclick="toggleSection(this)">Synthesis History</div>
|
|
600
|
+
<div class="collapsible-content" id="ggmlTtsSynthesisHistory">
|
|
601
|
+
<div class="empty-state">No synthesis history</div>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
|
|
585
606
|
<!-- Local Functions (hidden unless enabled) -->
|
|
586
607
|
<div class="card" id="functionsCard" style="display:none">
|
|
587
608
|
<div class="card-header">
|
|
@@ -972,6 +993,52 @@
|
|
|
972
993
|
})
|
|
973
994
|
}
|
|
974
995
|
|
|
996
|
+
function renderGgmlTtsGenerators(generators) {
|
|
997
|
+
const container = document.getElementById('ggmlTtsGenerators')
|
|
998
|
+
document.getElementById('ggmlTtsGeneratorCount').textContent =
|
|
999
|
+
`${generators.length} generator${generators.length !== 1 ? 's' : ''}`
|
|
1000
|
+
|
|
1001
|
+
withScrollPreserve(container, () => {
|
|
1002
|
+
if (generators.length === 0) {
|
|
1003
|
+
container.innerHTML = '<div class="empty-state">No GGML TTS generators loaded</div>'
|
|
1004
|
+
return
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
container.innerHTML = `
|
|
1008
|
+
<div class="table-wrapper">
|
|
1009
|
+
<div class="table-inner">
|
|
1010
|
+
<table>
|
|
1011
|
+
<thead>
|
|
1012
|
+
<tr>
|
|
1013
|
+
<th>ID</th>
|
|
1014
|
+
<th>Model</th>
|
|
1015
|
+
<th>Quant</th>
|
|
1016
|
+
<th>Variant</th>
|
|
1017
|
+
<th>GPU</th>
|
|
1018
|
+
<th>Model Size</th>
|
|
1019
|
+
<th>Context</th>
|
|
1020
|
+
</tr>
|
|
1021
|
+
</thead>
|
|
1022
|
+
<tbody>
|
|
1023
|
+
${generators.map(g => `
|
|
1024
|
+
<tr>
|
|
1025
|
+
<td class="mono">${escapeHtml(g.id?.slice(0, 8) || '-')}</td>
|
|
1026
|
+
<td>${formatModelName(g.repoId)}${g.vocoderRepoId ? '<br><span style="font-size:11px;color:var(--text-muted)">+ ' + formatModelName(g.vocoderRepoId) + '</span>' : ''}</td>
|
|
1027
|
+
<td>${escapeHtml(g.quantization || '-')}</td>
|
|
1028
|
+
<td>${escapeHtml(g.variant || '-')}</td>
|
|
1029
|
+
<td><span class="badge ${g.useGpu ? 'badge-success' : 'badge-info'}">${g.useGpu ? 'yes' : 'no'}</span></td>
|
|
1030
|
+
<td>${formatBytes(g.modelBytes)}</td>
|
|
1031
|
+
<td>${g.hasContext ? '<span class="badge badge-success">loaded</span>' : '-'}</td>
|
|
1032
|
+
</tr>
|
|
1033
|
+
`).join('')}
|
|
1034
|
+
</tbody>
|
|
1035
|
+
</table>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
`
|
|
1039
|
+
})
|
|
1040
|
+
}
|
|
1041
|
+
|
|
975
1042
|
// Escape HTML
|
|
976
1043
|
function escapeHtml(str) {
|
|
977
1044
|
if (str == null) return ''
|
|
@@ -999,6 +1066,7 @@
|
|
|
999
1066
|
const mlx = status.mlxLlm || {}
|
|
1000
1067
|
const onnxStt = status.onnxStt || {}
|
|
1001
1068
|
const onnxTts = status.onnxTts || {}
|
|
1069
|
+
const ggmlTts = status.ggmlTts || {}
|
|
1002
1070
|
|
|
1003
1071
|
// LLM
|
|
1004
1072
|
renderLlmGenerators(llm.generators || [])
|
|
@@ -1178,6 +1246,38 @@
|
|
|
1178
1246
|
},
|
|
1179
1247
|
])
|
|
1180
1248
|
|
|
1249
|
+
// GGML-TTS
|
|
1250
|
+
const hasGgmlTts = (ggmlTts.generators?.length > 0) ||
|
|
1251
|
+
(ggmlTts.history?.modelLoads?.length > 0) ||
|
|
1252
|
+
(ggmlTts.history?.syntheses?.length > 0)
|
|
1253
|
+
document.getElementById('ggmlTtsCard').style.display = hasGgmlTts ? '' : 'none'
|
|
1254
|
+
renderGgmlTtsGenerators(ggmlTts.generators || [])
|
|
1255
|
+
|
|
1256
|
+
renderHistory('ggmlTtsModelHistory', ggmlTts.history?.modelLoads || [], [
|
|
1257
|
+
{ label: 'Time', render: i => `<span class="timestamp">${formatRelativeTime(i.timestamp)}</span>` },
|
|
1258
|
+
{ label: 'Model', render: i => formatModelName(i.repoId) },
|
|
1259
|
+
{ label: 'Quant', render: i => escapeHtml(i.quantization || '-') },
|
|
1260
|
+
{ label: 'Variant', render: i => escapeHtml(i.variant || '-') },
|
|
1261
|
+
{ label: 'Vocoder', render: i => i.vocoderRepoId ? formatModelName(i.vocoderRepoId) : '-' },
|
|
1262
|
+
{ label: 'Duration', render: i => `${(i.durationMs / 1000).toFixed(1)}s` },
|
|
1263
|
+
{ label: 'Status', render: i => i.success ?
|
|
1264
|
+
'<span class="badge badge-success">Success</span>' :
|
|
1265
|
+
`<span class="badge badge-error">Failed: ${escapeHtml(i.error || 'Unknown')}</span>`
|
|
1266
|
+
},
|
|
1267
|
+
])
|
|
1268
|
+
|
|
1269
|
+
renderHistory('ggmlTtsSynthesisHistory', ggmlTts.history?.syntheses || [], [
|
|
1270
|
+
{ label: 'Time', render: i => `<span class="timestamp">${formatRelativeTime(i.timestamp)}</span>` },
|
|
1271
|
+
{ label: 'Model', render: i => formatModelName(i.repoId) },
|
|
1272
|
+
{ label: 'Variant', render: i => escapeHtml(i.variant || '-') },
|
|
1273
|
+
{ label: 'Text Len', render: i => i.textLength ?? '-' },
|
|
1274
|
+
{ label: 'Duration', render: i => i.cached ? '<span class="badge badge-info">Cached</span>' : `${(i.durationMs / 1000).toFixed(1)}s` },
|
|
1275
|
+
{ label: 'Status', render: i => i.success ?
|
|
1276
|
+
'<span class="badge badge-success">Success</span>' :
|
|
1277
|
+
`<span class="badge badge-error">Failed</span>`
|
|
1278
|
+
},
|
|
1279
|
+
])
|
|
1280
|
+
|
|
1181
1281
|
renderFunctionsStatus(status.functions || {})
|
|
1182
1282
|
}
|
|
1183
1283
|
|
|
@@ -1271,7 +1371,7 @@
|
|
|
1271
1371
|
const response = await fetch('/buttress/status')
|
|
1272
1372
|
const data = await response.json()
|
|
1273
1373
|
// Direct JSON response (not tRPC wrapped)
|
|
1274
|
-
if (data.ggmlLlm || data.ggmlStt || data.mlxLlm || data.onnxStt || data.onnxTts) {
|
|
1374
|
+
if (data.ggmlLlm || data.ggmlStt || data.mlxLlm || data.onnxStt || data.onnxTts || data.ggmlTts) {
|
|
1275
1375
|
updateUI(data)
|
|
1276
1376
|
setConnectionStatus('connected')
|
|
1277
1377
|
}
|