@downcity/plugins 1.0.56 → 1.0.57
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/bin/BuiltinPlugins.d.ts.map +1 -1
- package/bin/BuiltinPlugins.js +0 -4
- package/bin/BuiltinPlugins.js.map +1 -1
- package/bin/asr/Plugin.d.ts +67 -7
- package/bin/asr/Plugin.d.ts.map +1 -1
- package/bin/asr/Plugin.js +229 -461
- package/bin/asr/Plugin.js.map +1 -1
- package/bin/asr/types/AsrPlugin.d.ts +114 -0
- package/bin/asr/types/AsrPlugin.d.ts.map +1 -0
- package/bin/asr/types/AsrPlugin.js +10 -0
- package/bin/asr/types/AsrPlugin.js.map +1 -0
- package/bin/image/ImagePlugin.d.ts +1 -1
- package/bin/image/ImagePlugin.d.ts.map +1 -1
- package/bin/image/ImagePlugin.js +23 -5
- package/bin/image/ImagePlugin.js.map +1 -1
- package/bin/index.d.ts +2 -0
- package/bin/index.d.ts.map +1 -1
- package/bin/tts/Plugin.d.ts +53 -6
- package/bin/tts/Plugin.d.ts.map +1 -1
- package/bin/tts/Plugin.js +197 -474
- package/bin/tts/Plugin.js.map +1 -1
- package/bin/tts/types/TtsPlugin.d.ts +63 -102
- package/bin/tts/types/TtsPlugin.d.ts.map +1 -1
- package/bin/tts/types/TtsPlugin.js +4 -3
- package/bin/tts/types/TtsPlugin.js.map +1 -1
- package/bin/web/PROMPT.d.ts +1 -1
- package/bin/web/PROMPT.d.ts.map +1 -1
- package/bin/web/PROMPT.js +1 -1
- package/bin/web/PROMPT.js.map +1 -1
- package/bin/web/Plugin.d.ts +66 -5
- package/bin/web/Plugin.d.ts.map +1 -1
- package/bin/web/Plugin.js +126 -450
- package/bin/web/Plugin.js.map +1 -1
- package/bin/web/WebPromptAssets.d.ts +1 -9
- package/bin/web/WebPromptAssets.d.ts.map +1 -1
- package/bin/web/WebPromptAssets.js +1 -11
- package/bin/web/WebPromptAssets.js.map +1 -1
- package/bin/web/runtime/Install.d.ts +19 -0
- package/bin/web/runtime/Install.d.ts.map +1 -0
- package/bin/web/runtime/Install.js +178 -0
- package/bin/web/runtime/Install.js.map +1 -0
- package/bin/web/types/WebPlugin.d.ts +37 -109
- package/bin/web/types/WebPlugin.d.ts.map +1 -1
- package/bin/web/types/WebPlugin.js +5 -7
- package/bin/web/types/WebPlugin.js.map +1 -1
- package/package.json +2 -2
- package/src/BuiltinPlugins.ts +0 -4
- package/src/asr/Plugin.ts +264 -483
- package/src/asr/types/AsrPlugin.ts +118 -0
- package/src/image/ImagePlugin.ts +23 -5
- package/src/index.ts +12 -0
- package/src/tts/Plugin.ts +225 -492
- package/src/tts/types/TtsPlugin.ts +67 -102
- package/src/web/PROMPT.ts +1 -1
- package/src/web/PROMPT.ts.txt +32 -6
- package/src/web/Plugin.ts +119 -453
- package/src/web/WebPromptAssets.ts +1 -13
- package/src/web/runtime/Install.ts +241 -0
- package/src/web/types/WebPlugin.ts +37 -113
- package/bin/asr/Config.d.ts +0 -43
- package/bin/asr/Config.d.ts.map +0 -1
- package/bin/asr/Config.js +0 -107
- package/bin/asr/Config.js.map +0 -1
- package/bin/asr/Dependency.d.ts +0 -77
- package/bin/asr/Dependency.d.ts.map +0 -1
- package/bin/asr/Dependency.js +0 -238
- package/bin/asr/Dependency.js.map +0 -1
- package/bin/asr/InboundAugment.d.ts +0 -17
- package/bin/asr/InboundAugment.d.ts.map +0 -1
- package/bin/asr/InboundAugment.js +0 -47
- package/bin/asr/InboundAugment.js.map +0 -1
- package/bin/asr/ModelCatalog.d.ts +0 -29
- package/bin/asr/ModelCatalog.d.ts.map +0 -1
- package/bin/asr/ModelCatalog.js +0 -25
- package/bin/asr/ModelCatalog.js.map +0 -1
- package/bin/tts/Dependency.d.ts +0 -90
- package/bin/tts/Dependency.d.ts.map +0 -1
- package/bin/tts/Dependency.js +0 -344
- package/bin/tts/Dependency.js.map +0 -1
- package/bin/tts/PluginSupport.d.ts +0 -25
- package/bin/tts/PluginSupport.d.ts.map +0 -1
- package/bin/tts/PluginSupport.js +0 -72
- package/bin/tts/PluginSupport.js.map +0 -1
- package/bin/tts/runtime/Catalog.d.ts +0 -21
- package/bin/tts/runtime/Catalog.d.ts.map +0 -1
- package/bin/tts/runtime/Catalog.js +0 -90
- package/bin/tts/runtime/Catalog.js.map +0 -1
- package/bin/tts/runtime/DependencyInstaller.d.ts +0 -143
- package/bin/tts/runtime/DependencyInstaller.d.ts.map +0 -1
- package/bin/tts/runtime/DependencyInstaller.js +0 -261
- package/bin/tts/runtime/DependencyInstaller.js.map +0 -1
- package/bin/tts/runtime/Installer.d.ts +0 -89
- package/bin/tts/runtime/Installer.d.ts.map +0 -1
- package/bin/tts/runtime/Installer.js +0 -188
- package/bin/tts/runtime/Installer.js.map +0 -1
- package/bin/tts/runtime/Paths.d.ts +0 -20
- package/bin/tts/runtime/Paths.d.ts.map +0 -1
- package/bin/tts/runtime/Paths.js +0 -32
- package/bin/tts/runtime/Paths.js.map +0 -1
- package/bin/tts/runtime/Synthesizer.d.ts +0 -44
- package/bin/tts/runtime/Synthesizer.d.ts.map +0 -1
- package/bin/tts/runtime/Synthesizer.js +0 -363
- package/bin/tts/runtime/Synthesizer.js.map +0 -1
- package/bin/tts/types/Tts.d.ts +0 -91
- package/bin/tts/types/Tts.d.ts.map +0 -1
- package/bin/tts/types/Tts.js +0 -9
- package/bin/tts/types/Tts.js.map +0 -1
- package/bin/voice/Config.d.ts +0 -43
- package/bin/voice/Config.d.ts.map +0 -1
- package/bin/voice/Config.js +0 -104
- package/bin/voice/Config.js.map +0 -1
- package/bin/voice/Dependency.d.ts +0 -77
- package/bin/voice/Dependency.d.ts.map +0 -1
- package/bin/voice/Dependency.js +0 -237
- package/bin/voice/Dependency.js.map +0 -1
- package/bin/voice/InboundAugment.d.ts +0 -17
- package/bin/voice/InboundAugment.d.ts.map +0 -1
- package/bin/voice/InboundAugment.js +0 -47
- package/bin/voice/InboundAugment.js.map +0 -1
- package/bin/voice/ModelCatalog.d.ts +0 -29
- package/bin/voice/ModelCatalog.d.ts.map +0 -1
- package/bin/voice/ModelCatalog.js +0 -25
- package/bin/voice/ModelCatalog.js.map +0 -1
- package/bin/voice/runtime/Catalog.d.ts +0 -18
- package/bin/voice/runtime/Catalog.d.ts.map +0 -1
- package/bin/voice/runtime/Catalog.js +0 -61
- package/bin/voice/runtime/Catalog.js.map +0 -1
- package/bin/voice/runtime/DependencyInstaller.d.ts +0 -145
- package/bin/voice/runtime/DependencyInstaller.d.ts.map +0 -1
- package/bin/voice/runtime/DependencyInstaller.js +0 -309
- package/bin/voice/runtime/DependencyInstaller.js.map +0 -1
- package/bin/voice/runtime/Installer.d.ts +0 -94
- package/bin/voice/runtime/Installer.d.ts.map +0 -1
- package/bin/voice/runtime/Installer.js +0 -200
- package/bin/voice/runtime/Installer.js.map +0 -1
- package/bin/voice/runtime/Paths.d.ts +0 -8
- package/bin/voice/runtime/Paths.d.ts.map +0 -1
- package/bin/voice/runtime/Paths.js +0 -26
- package/bin/voice/runtime/Paths.js.map +0 -1
- package/bin/voice/runtime/Transcriber.d.ts +0 -57
- package/bin/voice/runtime/Transcriber.d.ts.map +0 -1
- package/bin/voice/runtime/Transcriber.js +0 -329
- package/bin/voice/runtime/Transcriber.js.map +0 -1
- package/bin/voice/types/Voice.d.ts +0 -58
- package/bin/voice/types/Voice.d.ts.map +0 -1
- package/bin/voice/types/Voice.js +0 -9
- package/bin/voice/types/Voice.js.map +0 -1
- package/bin/voice/types/VoicePlugin.d.ts +0 -190
- package/bin/voice/types/VoicePlugin.d.ts.map +0 -1
- package/bin/voice/types/VoicePlugin.js +0 -9
- package/bin/voice/types/VoicePlugin.js.map +0 -1
- package/bin/web/Dependency.d.ts +0 -10
- package/bin/web/Dependency.d.ts.map +0 -1
- package/bin/web/Dependency.js +0 -10
- package/bin/web/Dependency.js.map +0 -1
- package/bin/web/PROMPT.agent-browser.d.ts +0 -7
- package/bin/web/PROMPT.agent-browser.d.ts.map +0 -1
- package/bin/web/PROMPT.agent-browser.js +0 -8
- package/bin/web/PROMPT.agent-browser.js.map +0 -1
- package/bin/web/PROMPT.web-access.d.ts +0 -7
- package/bin/web/PROMPT.web-access.d.ts.map +0 -1
- package/bin/web/PROMPT.web-access.js +0 -8
- package/bin/web/PROMPT.web-access.js.map +0 -1
- package/bin/web/runtime/Config.d.ts +0 -21
- package/bin/web/runtime/Config.d.ts.map +0 -1
- package/bin/web/runtime/Config.js +0 -79
- package/bin/web/runtime/Config.js.map +0 -1
- package/bin/web/runtime/Source.d.ts +0 -29
- package/bin/web/runtime/Source.d.ts.map +0 -1
- package/bin/web/runtime/Source.js +0 -209
- package/bin/web/runtime/Source.js.map +0 -1
- package/src/asr/Config.ts +0 -138
- package/src/asr/Dependency.ts +0 -336
- package/src/asr/InboundAugment.ts +0 -59
- package/src/asr/ModelCatalog.ts +0 -43
- package/src/tts/Dependency.ts +0 -473
- package/src/tts/PluginSupport.ts +0 -85
- package/src/tts/runtime/Catalog.ts +0 -97
- package/src/tts/runtime/DependencyInstaller.ts +0 -436
- package/src/tts/runtime/Installer.ts +0 -297
- package/src/tts/runtime/Paths.ts +0 -39
- package/src/tts/runtime/Synthesizer.ts +0 -480
- package/src/tts/types/Tts.ts +0 -99
- package/src/voice/Config.ts +0 -135
- package/src/voice/Dependency.ts +0 -329
- package/src/voice/InboundAugment.ts +0 -59
- package/src/voice/ModelCatalog.ts +0 -43
- package/src/voice/runtime/Catalog.ts +0 -68
- package/src/voice/runtime/DependencyInstaller.ts +0 -505
- package/src/voice/runtime/Installer.ts +0 -324
- package/src/voice/runtime/Paths.ts +0 -26
- package/src/voice/runtime/Transcriber.ts +0 -467
- package/src/voice/types/Voice.ts +0 -68
- package/src/voice/types/VoicePlugin.ts +0 -194
- package/src/web/Dependency.ts +0 -17
- package/src/web/PROMPT.agent-browser.ts +0 -9
- package/src/web/PROMPT.agent-browser.ts.txt +0 -17
- package/src/web/PROMPT.web-access.ts +0 -9
- package/src/web/PROMPT.web-access.ts.txt +0 -13
- package/src/web/runtime/Config.ts +0 -105
- package/src/web/runtime/Source.ts +0 -257
|
@@ -1,436 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TTS 依赖安装器。
|
|
3
|
-
*
|
|
4
|
-
* 关键点(中文)
|
|
5
|
-
* - 统一管理 `town tts` 的 Python 依赖安装逻辑。
|
|
6
|
-
* - 根据模型自动推导 runner(qwen3 / kokoro)并执行 `python -m pip install`。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { execFile as execFileCb } from "node:child_process";
|
|
10
|
-
import os from "node:os";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { promisify } from "node:util";
|
|
13
|
-
import fs from "fs-extra";
|
|
14
|
-
import type { TtsModelId, TtsRuntimeFamily } from "@/tts/types/Tts.js";
|
|
15
|
-
|
|
16
|
-
const execFileAsync = promisify(execFileCb);
|
|
17
|
-
|
|
18
|
-
const DEFAULT_PIP_TIMEOUT_MS = 300_000;
|
|
19
|
-
const OUTPUT_TAIL_LIMIT = 1200;
|
|
20
|
-
const DEFAULT_VENV_DIR = path.join(os.homedir(), ".downcity", "venvs", "tts");
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* TTS 依赖 runner 类型。
|
|
24
|
-
*/
|
|
25
|
-
export type TtsDependencyRunner = TtsRuntimeFamily;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 单个 runner 的安装摘要。
|
|
29
|
-
*/
|
|
30
|
-
export interface TtsDependencyInstallItem {
|
|
31
|
-
/**
|
|
32
|
-
* 当前安装 runner。
|
|
33
|
-
*/
|
|
34
|
-
runner: TtsDependencyRunner;
|
|
35
|
-
/**
|
|
36
|
-
* Python 可执行文件。
|
|
37
|
-
*/
|
|
38
|
-
pythonBin: string;
|
|
39
|
-
/**
|
|
40
|
-
* 执行参数(不含 pythonBin)。
|
|
41
|
-
*/
|
|
42
|
-
args: string[];
|
|
43
|
-
/**
|
|
44
|
-
* 安装包列表。
|
|
45
|
-
*/
|
|
46
|
-
packages: string[];
|
|
47
|
-
/**
|
|
48
|
-
* 实际执行的命令行。
|
|
49
|
-
*/
|
|
50
|
-
command: string;
|
|
51
|
-
/**
|
|
52
|
-
* 执行耗时(毫秒)。
|
|
53
|
-
*/
|
|
54
|
-
elapsedMs: number;
|
|
55
|
-
/**
|
|
56
|
-
* 是否跳过安装。
|
|
57
|
-
*/
|
|
58
|
-
skipped?: boolean;
|
|
59
|
-
/**
|
|
60
|
-
* 跳过原因(可选)。
|
|
61
|
-
*/
|
|
62
|
-
skipReason?: string;
|
|
63
|
-
/**
|
|
64
|
-
* stdout 尾部(可选)。
|
|
65
|
-
*/
|
|
66
|
-
stdoutTail?: string;
|
|
67
|
-
/**
|
|
68
|
-
* stderr 尾部(可选)。
|
|
69
|
-
*/
|
|
70
|
-
stderrTail?: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 依赖安装结果。
|
|
75
|
-
*/
|
|
76
|
-
export interface TtsDependencyInstallResult {
|
|
77
|
-
/**
|
|
78
|
-
* 本次使用的 Python 可执行文件。
|
|
79
|
-
*/
|
|
80
|
-
pythonBin: string;
|
|
81
|
-
/**
|
|
82
|
-
* 本次处理的 runner 列表。
|
|
83
|
-
*/
|
|
84
|
-
runners: TtsDependencyRunner[];
|
|
85
|
-
/**
|
|
86
|
-
* 各 runner 的安装摘要。
|
|
87
|
-
*/
|
|
88
|
-
items: TtsDependencyInstallItem[];
|
|
89
|
-
/**
|
|
90
|
-
* 是否使用了虚拟环境安装。
|
|
91
|
-
*/
|
|
92
|
-
usedVirtualEnv: boolean;
|
|
93
|
-
/**
|
|
94
|
-
* 虚拟环境目录(仅 `usedVirtualEnv=true` 时存在)。
|
|
95
|
-
*/
|
|
96
|
-
venvDir?: string;
|
|
97
|
-
/**
|
|
98
|
-
* 初始尝试使用的 Python(仅进入虚拟环境回退时存在)。
|
|
99
|
-
*/
|
|
100
|
-
basePythonBin?: string;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 依赖安装输入参数。
|
|
105
|
-
*/
|
|
106
|
-
export interface TtsDependencyInstallInput {
|
|
107
|
-
/**
|
|
108
|
-
* Python 可执行文件(默认 `python3`)。
|
|
109
|
-
*/
|
|
110
|
-
pythonBin?: string;
|
|
111
|
-
/**
|
|
112
|
-
* 需要安装的 runner 列表。
|
|
113
|
-
*/
|
|
114
|
-
runners: TtsDependencyRunner[];
|
|
115
|
-
/**
|
|
116
|
-
* 是否使用 `pip -U` 升级。
|
|
117
|
-
*/
|
|
118
|
-
upgrade?: boolean;
|
|
119
|
-
/**
|
|
120
|
-
* pip 安装超时(毫秒)。
|
|
121
|
-
*/
|
|
122
|
-
timeoutMs?: number;
|
|
123
|
-
/**
|
|
124
|
-
* 虚拟环境目录(可选)。
|
|
125
|
-
*/
|
|
126
|
-
venvDir?: string;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function normalizePythonBin(input?: string): string {
|
|
130
|
-
const text = String(input || "").trim();
|
|
131
|
-
return text || "python3";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function normalizeTimeoutMs(value?: number): number {
|
|
135
|
-
if (!Number.isFinite(value as number)) return DEFAULT_PIP_TIMEOUT_MS;
|
|
136
|
-
const ms = Math.floor(Number(value));
|
|
137
|
-
if (ms < 5_000) return 5_000;
|
|
138
|
-
if (ms > 1_800_000) return 1_800_000;
|
|
139
|
-
return ms;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function normalizeVenvDir(input?: string): string {
|
|
143
|
-
const text = String(input || "").trim();
|
|
144
|
-
if (!text) return DEFAULT_VENV_DIR;
|
|
145
|
-
return path.resolve(text);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function tailText(value: string): string | undefined {
|
|
149
|
-
const text = String(value || "").trim();
|
|
150
|
-
if (!text) return undefined;
|
|
151
|
-
if (text.length <= OUTPUT_TAIL_LIMIT) return text;
|
|
152
|
-
return text.slice(text.length - OUTPUT_TAIL_LIMIT);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function resolveTtsVenvPythonBin(venvDir: string): string {
|
|
156
|
-
return path.join(venvDir, "bin", "python");
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 返回 TTS 默认虚拟环境目录。
|
|
161
|
-
*/
|
|
162
|
-
export function resolveDefaultTtsVenvDir(): string {
|
|
163
|
-
return DEFAULT_VENV_DIR;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 返回 TTS 默认虚拟环境 Python 路径。
|
|
168
|
-
*/
|
|
169
|
-
export function resolveDefaultTtsVenvPythonBin(): string {
|
|
170
|
-
return resolveTtsVenvPythonBin(DEFAULT_VENV_DIR);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* 判断是否为 PEP 668(externally-managed-environment)错误。
|
|
175
|
-
*/
|
|
176
|
-
export function isPep668InstallError(text: string): boolean {
|
|
177
|
-
const msg = String(text || "").toLowerCase();
|
|
178
|
-
if (!msg) return false;
|
|
179
|
-
return (
|
|
180
|
-
msg.includes("externally-managed-environment") ||
|
|
181
|
-
msg.includes("this environment is externally managed") ||
|
|
182
|
-
msg.includes("pep 668") ||
|
|
183
|
-
msg.includes("--break-system-packages")
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function dedupeRunners(runners: TtsDependencyRunner[]): TtsDependencyRunner[] {
|
|
188
|
-
const seen = new Set<TtsDependencyRunner>();
|
|
189
|
-
const out: TtsDependencyRunner[] = [];
|
|
190
|
-
for (const runner of runners) {
|
|
191
|
-
if (seen.has(runner)) continue;
|
|
192
|
-
seen.add(runner);
|
|
193
|
-
out.push(runner);
|
|
194
|
-
}
|
|
195
|
-
return out;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function getRunnerPackages(runner: TtsDependencyRunner): string[] {
|
|
199
|
-
if (runner === "qwen3") {
|
|
200
|
-
return ["qwen-tts", "soundfile", "torch", "torchaudio", "transformers", "accelerate"];
|
|
201
|
-
}
|
|
202
|
-
return ["kokoro==0.7.16", "soundfile", "misaki[zh]", "torch"];
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async function checkRunnerPackagesInstalled(params: {
|
|
206
|
-
pythonBin: string;
|
|
207
|
-
packages: string[];
|
|
208
|
-
timeoutMs: number;
|
|
209
|
-
}): Promise<{
|
|
210
|
-
installed: boolean;
|
|
211
|
-
args: string[];
|
|
212
|
-
command: string;
|
|
213
|
-
}> {
|
|
214
|
-
const args = ["-m", "pip", "show", ...params.packages.map((item) => item.replace(/\[.*\]$/, "").replace(/==.+$/, ""))];
|
|
215
|
-
const command = [params.pythonBin, ...args].join(" ");
|
|
216
|
-
try {
|
|
217
|
-
await execFileAsync(params.pythonBin, args, {
|
|
218
|
-
timeout: params.timeoutMs,
|
|
219
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
220
|
-
});
|
|
221
|
-
return {
|
|
222
|
-
installed: true,
|
|
223
|
-
args,
|
|
224
|
-
command,
|
|
225
|
-
};
|
|
226
|
-
} catch {
|
|
227
|
-
return {
|
|
228
|
-
installed: false,
|
|
229
|
-
args,
|
|
230
|
-
command,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
type RunnerInstallFailure = {
|
|
236
|
-
runner: TtsDependencyRunner;
|
|
237
|
-
command: string;
|
|
238
|
-
reason: string;
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
type RunnerInstallAttempt =
|
|
242
|
-
| {
|
|
243
|
-
success: true;
|
|
244
|
-
item: TtsDependencyInstallItem;
|
|
245
|
-
}
|
|
246
|
-
| {
|
|
247
|
-
success: false;
|
|
248
|
-
failure: RunnerInstallFailure;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
async function installRunnerWithPython(params: {
|
|
252
|
-
pythonBin: string;
|
|
253
|
-
runner: TtsDependencyRunner;
|
|
254
|
-
upgrade: boolean;
|
|
255
|
-
timeoutMs: number;
|
|
256
|
-
}): Promise<RunnerInstallAttempt> {
|
|
257
|
-
const packages = getRunnerPackages(params.runner);
|
|
258
|
-
const checkStartedAt = Date.now();
|
|
259
|
-
const checkResult = await checkRunnerPackagesInstalled({
|
|
260
|
-
pythonBin: params.pythonBin,
|
|
261
|
-
packages,
|
|
262
|
-
timeoutMs: params.timeoutMs,
|
|
263
|
-
});
|
|
264
|
-
if (checkResult.installed) {
|
|
265
|
-
return {
|
|
266
|
-
success: true,
|
|
267
|
-
item: {
|
|
268
|
-
runner: params.runner,
|
|
269
|
-
pythonBin: params.pythonBin,
|
|
270
|
-
args: checkResult.args,
|
|
271
|
-
packages,
|
|
272
|
-
command: checkResult.command,
|
|
273
|
-
elapsedMs: Date.now() - checkStartedAt,
|
|
274
|
-
skipped: true,
|
|
275
|
-
skipReason: "already-installed",
|
|
276
|
-
},
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const args = [
|
|
281
|
-
"-m",
|
|
282
|
-
"pip",
|
|
283
|
-
"install",
|
|
284
|
-
...(params.upgrade ? ["-U"] : []),
|
|
285
|
-
...packages,
|
|
286
|
-
];
|
|
287
|
-
const command = [params.pythonBin, ...args].join(" ");
|
|
288
|
-
const startedAt = Date.now();
|
|
289
|
-
try {
|
|
290
|
-
const { stdout, stderr } = await execFileAsync(params.pythonBin, args, {
|
|
291
|
-
timeout: params.timeoutMs,
|
|
292
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
293
|
-
});
|
|
294
|
-
return {
|
|
295
|
-
success: true,
|
|
296
|
-
item: {
|
|
297
|
-
runner: params.runner,
|
|
298
|
-
pythonBin: params.pythonBin,
|
|
299
|
-
args,
|
|
300
|
-
packages,
|
|
301
|
-
command,
|
|
302
|
-
elapsedMs: Date.now() - startedAt,
|
|
303
|
-
stdoutTail: tailText(String(stdout || "")),
|
|
304
|
-
stderrTail: tailText(String(stderr || "")),
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
} catch (error) {
|
|
308
|
-
const errorLike = error as {
|
|
309
|
-
stdout?: string;
|
|
310
|
-
stderr?: string;
|
|
311
|
-
message?: string;
|
|
312
|
-
};
|
|
313
|
-
const reason =
|
|
314
|
-
tailText(String(errorLike.stderr || "")) ||
|
|
315
|
-
tailText(String(errorLike.stdout || "")) ||
|
|
316
|
-
String(errorLike.message || error);
|
|
317
|
-
return {
|
|
318
|
-
success: false,
|
|
319
|
-
failure: {
|
|
320
|
-
runner: params.runner,
|
|
321
|
-
command,
|
|
322
|
-
reason,
|
|
323
|
-
},
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export async function ensureTtsVirtualEnv(params: {
|
|
329
|
-
/**
|
|
330
|
-
* 虚拟环境目录。
|
|
331
|
-
*/
|
|
332
|
-
venvDir?: string;
|
|
333
|
-
/**
|
|
334
|
-
* 用于创建 venv 的基础 Python。
|
|
335
|
-
*/
|
|
336
|
-
basePythonBin?: string;
|
|
337
|
-
}): Promise<string> {
|
|
338
|
-
const venvDir = normalizeVenvDir(params.venvDir);
|
|
339
|
-
const basePythonBin = normalizePythonBin(params.basePythonBin);
|
|
340
|
-
const pythonBin = resolveTtsVenvPythonBin(venvDir);
|
|
341
|
-
if (await fs.pathExists(pythonBin)) {
|
|
342
|
-
return pythonBin;
|
|
343
|
-
}
|
|
344
|
-
await fs.ensureDir(venvDir);
|
|
345
|
-
await execFileAsync(basePythonBin, ["-m", "venv", venvDir], {
|
|
346
|
-
timeout: DEFAULT_PIP_TIMEOUT_MS,
|
|
347
|
-
maxBuffer: 8 * 1024 * 1024,
|
|
348
|
-
});
|
|
349
|
-
return pythonBin;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async function installAllRunners(params: {
|
|
353
|
-
pythonBin: string;
|
|
354
|
-
runners: TtsDependencyRunner[];
|
|
355
|
-
upgrade: boolean;
|
|
356
|
-
timeoutMs: number;
|
|
357
|
-
}): Promise<{
|
|
358
|
-
success: boolean;
|
|
359
|
-
items: TtsDependencyInstallItem[];
|
|
360
|
-
failures: RunnerInstallFailure[];
|
|
361
|
-
}> {
|
|
362
|
-
const items: TtsDependencyInstallItem[] = [];
|
|
363
|
-
const failures: RunnerInstallFailure[] = [];
|
|
364
|
-
for (const runner of params.runners) {
|
|
365
|
-
const result = await installRunnerWithPython({
|
|
366
|
-
pythonBin: params.pythonBin,
|
|
367
|
-
runner,
|
|
368
|
-
upgrade: params.upgrade,
|
|
369
|
-
timeoutMs: params.timeoutMs,
|
|
370
|
-
});
|
|
371
|
-
if (result.success) {
|
|
372
|
-
items.push(result.item);
|
|
373
|
-
} else {
|
|
374
|
-
failures.push(result.failure);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
success: failures.length === 0,
|
|
379
|
-
items,
|
|
380
|
-
failures,
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* 安装 TTS Python 依赖。
|
|
386
|
-
*/
|
|
387
|
-
export async function installTtsDependencies(
|
|
388
|
-
input: TtsDependencyInstallInput,
|
|
389
|
-
): Promise<TtsDependencyInstallResult> {
|
|
390
|
-
const runners = dedupeRunners(input.runners);
|
|
391
|
-
const basePythonBin = normalizePythonBin(input.pythonBin);
|
|
392
|
-
const timeoutMs = normalizeTimeoutMs(input.timeoutMs);
|
|
393
|
-
const upgrade = input.upgrade === true;
|
|
394
|
-
const venvDir = normalizeVenvDir(input.venvDir);
|
|
395
|
-
const venvPythonBin = await ensureTtsVirtualEnv({
|
|
396
|
-
venvDir,
|
|
397
|
-
basePythonBin,
|
|
398
|
-
});
|
|
399
|
-
const venvAttempt = await installAllRunners({
|
|
400
|
-
pythonBin: venvPythonBin,
|
|
401
|
-
runners,
|
|
402
|
-
upgrade,
|
|
403
|
-
timeoutMs,
|
|
404
|
-
});
|
|
405
|
-
if (!venvAttempt.success) {
|
|
406
|
-
throw new Error(
|
|
407
|
-
venvAttempt.failures
|
|
408
|
-
.map((item) => `${item.runner}: ${item.reason}`)
|
|
409
|
-
.join("\n"),
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
return {
|
|
414
|
-
pythonBin: venvPythonBin,
|
|
415
|
-
runners,
|
|
416
|
-
items: venvAttempt.items,
|
|
417
|
-
usedVirtualEnv: true,
|
|
418
|
-
venvDir,
|
|
419
|
-
basePythonBin,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* 根据模型列表推导需要安装的 runner 集合。
|
|
425
|
-
*/
|
|
426
|
-
export function resolveTtsRunnersByModels(modelIds: TtsModelId[]): TtsDependencyRunner[] {
|
|
427
|
-
const runners = new Set<TtsDependencyRunner>();
|
|
428
|
-
for (const modelId of modelIds) {
|
|
429
|
-
if (modelId === "qwen3-tts-0.6b") {
|
|
430
|
-
runners.add("qwen3");
|
|
431
|
-
continue;
|
|
432
|
-
}
|
|
433
|
-
runners.add("kokoro");
|
|
434
|
-
}
|
|
435
|
-
return [...runners];
|
|
436
|
-
}
|
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TTS 模型安装器。
|
|
3
|
-
*
|
|
4
|
-
* 关键点(中文)
|
|
5
|
-
* - 统一把 HuggingFace 资源下载到本地模型目录。
|
|
6
|
-
* - 支持“整仓下载”与“仅下载指定文件”两种模式。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import fs from "node:fs";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
import { Readable } from "node:stream";
|
|
12
|
-
import { pipeline } from "node:stream/promises";
|
|
13
|
-
import fsExtra from "fs-extra";
|
|
14
|
-
import type { TtsModelCatalogItem, TtsModelId } from "@/tts/types/Tts.js";
|
|
15
|
-
|
|
16
|
-
const HUGGINGFACE_HOST = "https://huggingface.co";
|
|
17
|
-
const TTS_INSTALL_MANIFEST = "downcity.tts.install.json";
|
|
18
|
-
|
|
19
|
-
type HuggingFaceModelApiResponse = {
|
|
20
|
-
siblings?: Array<{
|
|
21
|
-
rfilename?: string;
|
|
22
|
-
}>;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* TTS 模型安装结果。
|
|
27
|
-
*/
|
|
28
|
-
export interface TtsModelInstallResult {
|
|
29
|
-
/**
|
|
30
|
-
* 模型目录(绝对路径)。
|
|
31
|
-
*/
|
|
32
|
-
modelDir: string;
|
|
33
|
-
/**
|
|
34
|
-
* 本次下载文件数。
|
|
35
|
-
*/
|
|
36
|
-
downloadedFiles: number;
|
|
37
|
-
/**
|
|
38
|
-
* 本次跳过文件数(已存在且未强制覆盖)。
|
|
39
|
-
*/
|
|
40
|
-
skippedFiles: number;
|
|
41
|
-
/**
|
|
42
|
-
* 本次实际下载的文件路径(相对模型目录)。
|
|
43
|
-
*/
|
|
44
|
-
downloadedFilePaths: string[];
|
|
45
|
-
/**
|
|
46
|
-
* 本次跳过的文件路径(相对模型目录)。
|
|
47
|
-
*/
|
|
48
|
-
skippedFilePaths: string[];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function encodePathSegments(input: string): string {
|
|
52
|
-
return input
|
|
53
|
-
.split("/")
|
|
54
|
-
.map((segment) => encodeURIComponent(segment))
|
|
55
|
-
.join("/");
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function buildAuthHeaders(token?: string): Record<string, string> | undefined {
|
|
59
|
-
const t = String(token || "").trim();
|
|
60
|
-
if (!t) return undefined;
|
|
61
|
-
return { Authorization: `Bearer ${t}` };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function resolveSafeTargetPath(baseDir: string, relativePath: string): string {
|
|
65
|
-
const normalized = relativePath.replace(/\\/g, "/");
|
|
66
|
-
const target = path.resolve(baseDir, normalized);
|
|
67
|
-
const base = path.resolve(baseDir);
|
|
68
|
-
if (target !== base && !target.startsWith(`${base}${path.sep}`)) {
|
|
69
|
-
throw new Error(`Unsafe file path from HuggingFace: ${relativePath}`);
|
|
70
|
-
}
|
|
71
|
-
return target;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async function readRepoFiles(input: {
|
|
75
|
-
repoId: string;
|
|
76
|
-
hfToken?: string;
|
|
77
|
-
}): Promise<string[]> {
|
|
78
|
-
const repoUrlPath = encodePathSegments(input.repoId);
|
|
79
|
-
const metadataUrl = `${HUGGINGFACE_HOST}/api/models/${repoUrlPath}`;
|
|
80
|
-
const response = await fetch(metadataUrl, {
|
|
81
|
-
headers: {
|
|
82
|
-
Accept: "application/json",
|
|
83
|
-
...(buildAuthHeaders(input.hfToken) || {}),
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
if (!response.ok) {
|
|
87
|
-
const responseText = await response.text().catch(() => "");
|
|
88
|
-
throw new Error(
|
|
89
|
-
`Failed to fetch model metadata (${input.repoId}): HTTP ${response.status}${responseText ? ` ${responseText.slice(0, 300)}` : ""}`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
const payload = (await response.json()) as HuggingFaceModelApiResponse;
|
|
93
|
-
const files = (Array.isArray(payload.siblings) ? payload.siblings : [])
|
|
94
|
-
.map((item) => String(item?.rfilename || "").trim())
|
|
95
|
-
.filter(Boolean)
|
|
96
|
-
.filter((item) => item !== ".gitattributes");
|
|
97
|
-
if (files.length === 0) {
|
|
98
|
-
throw new Error(`No downloadable files found in repo: ${input.repoId}`);
|
|
99
|
-
}
|
|
100
|
-
return files;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function downloadModelFile(input: {
|
|
104
|
-
repoId: string;
|
|
105
|
-
revision: string;
|
|
106
|
-
filePath: string;
|
|
107
|
-
targetPath: string;
|
|
108
|
-
hfToken?: string;
|
|
109
|
-
}): Promise<void> {
|
|
110
|
-
const repoPath = encodePathSegments(input.repoId);
|
|
111
|
-
const revision = encodeURIComponent(input.revision);
|
|
112
|
-
const filePath = encodePathSegments(input.filePath);
|
|
113
|
-
const url = `${HUGGINGFACE_HOST}/${repoPath}/resolve/${revision}/${filePath}?download=1`;
|
|
114
|
-
const response = await fetch(url, {
|
|
115
|
-
headers: {
|
|
116
|
-
...(buildAuthHeaders(input.hfToken) || {}),
|
|
117
|
-
},
|
|
118
|
-
});
|
|
119
|
-
if (!response.ok) {
|
|
120
|
-
const responseText = await response.text().catch(() => "");
|
|
121
|
-
throw new Error(
|
|
122
|
-
`Failed to download ${input.filePath}: HTTP ${response.status}${responseText ? ` ${responseText.slice(0, 300)}` : ""}`,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
if (!response.body) {
|
|
126
|
-
throw new Error(`Empty response body for file: ${input.filePath}`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const tempPath = `${input.targetPath}.downloading`;
|
|
130
|
-
await fsExtra.ensureDir(path.dirname(input.targetPath));
|
|
131
|
-
try {
|
|
132
|
-
const body = Readable.fromWeb(
|
|
133
|
-
response.body as unknown as globalThis.ReadableStream<Uint8Array>,
|
|
134
|
-
);
|
|
135
|
-
await pipeline(body, fs.createWriteStream(tempPath));
|
|
136
|
-
await fsExtra.move(tempPath, input.targetPath, { overwrite: true });
|
|
137
|
-
} catch (error) {
|
|
138
|
-
await fsExtra.remove(tempPath).catch(() => undefined);
|
|
139
|
-
throw error;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* 判断模型在本地是否已安装。
|
|
145
|
-
*
|
|
146
|
-
* 关键点(中文)
|
|
147
|
-
* - 优先识别安装清单 `downcity.tts.install.json`。
|
|
148
|
-
* - 若无清单但目录非空,也视为已存在模型,避免重复下载。
|
|
149
|
-
*/
|
|
150
|
-
export async function detectLocalTtsModelInstallState(input: {
|
|
151
|
-
/**
|
|
152
|
-
* 模型 ID。
|
|
153
|
-
*/
|
|
154
|
-
modelId: TtsModelId;
|
|
155
|
-
/**
|
|
156
|
-
* TTS 模型根目录。
|
|
157
|
-
*/
|
|
158
|
-
modelsRootDir: string;
|
|
159
|
-
}): Promise<{
|
|
160
|
-
/**
|
|
161
|
-
* 模型目录(绝对路径)。
|
|
162
|
-
*/
|
|
163
|
-
modelDir: string;
|
|
164
|
-
/**
|
|
165
|
-
* 是否判断为“已安装”。
|
|
166
|
-
*/
|
|
167
|
-
installed: boolean;
|
|
168
|
-
/**
|
|
169
|
-
* 已安装来源。
|
|
170
|
-
*/
|
|
171
|
-
source?: "manifest" | "directory";
|
|
172
|
-
}> {
|
|
173
|
-
const modelDir = path.resolve(input.modelsRootDir, input.modelId);
|
|
174
|
-
const manifestPath = path.join(modelDir, TTS_INSTALL_MANIFEST);
|
|
175
|
-
const hasManifest = await fsExtra.pathExists(manifestPath);
|
|
176
|
-
if (hasManifest) {
|
|
177
|
-
return {
|
|
178
|
-
modelDir,
|
|
179
|
-
installed: true,
|
|
180
|
-
source: "manifest",
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const hasModelDir = await fsExtra.pathExists(modelDir);
|
|
185
|
-
if (!hasModelDir) {
|
|
186
|
-
return {
|
|
187
|
-
modelDir,
|
|
188
|
-
installed: false,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const entries = await fsExtra.readdir(modelDir).catch(() => []);
|
|
193
|
-
const hasPersistedFiles = entries.some(
|
|
194
|
-
(entry) => !String(entry).endsWith(".downloading"),
|
|
195
|
-
);
|
|
196
|
-
if (!hasPersistedFiles) {
|
|
197
|
-
return {
|
|
198
|
-
modelDir,
|
|
199
|
-
installed: false,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
return {
|
|
204
|
-
modelDir,
|
|
205
|
-
installed: true,
|
|
206
|
-
source: "directory",
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* 安装指定 TTS 模型到本地目录。
|
|
212
|
-
*
|
|
213
|
-
* 关键点(中文)
|
|
214
|
-
* - 使用 HTTP 文件级下载,不依赖 git-lfs。
|
|
215
|
-
* - 默认复用已有文件;`force=true` 时覆盖重下。
|
|
216
|
-
*/
|
|
217
|
-
export async function installTtsModelFromHuggingFace(input: {
|
|
218
|
-
/**
|
|
219
|
-
* 目标模型目录项。
|
|
220
|
-
*/
|
|
221
|
-
model: TtsModelCatalogItem;
|
|
222
|
-
/**
|
|
223
|
-
* TTS 模型根目录。
|
|
224
|
-
*/
|
|
225
|
-
modelsRootDir: string;
|
|
226
|
-
/**
|
|
227
|
-
* 是否强制覆盖。
|
|
228
|
-
*/
|
|
229
|
-
force?: boolean;
|
|
230
|
-
/**
|
|
231
|
-
* HuggingFace Token(可选)。
|
|
232
|
-
*/
|
|
233
|
-
hfToken?: string;
|
|
234
|
-
}): Promise<TtsModelInstallResult> {
|
|
235
|
-
const modelDir = path.resolve(input.modelsRootDir, input.model.id);
|
|
236
|
-
await fsExtra.ensureDir(modelDir);
|
|
237
|
-
|
|
238
|
-
let downloadedFiles = 0;
|
|
239
|
-
let skippedFiles = 0;
|
|
240
|
-
const downloadedFilePaths: string[] = [];
|
|
241
|
-
const skippedFilePaths: string[] = [];
|
|
242
|
-
|
|
243
|
-
for (const asset of input.model.assets) {
|
|
244
|
-
const assetFiles =
|
|
245
|
-
Array.isArray(asset.files) && asset.files.length > 0
|
|
246
|
-
? asset.files
|
|
247
|
-
: await readRepoFiles({
|
|
248
|
-
repoId: asset.repoId,
|
|
249
|
-
hfToken: input.hfToken,
|
|
250
|
-
});
|
|
251
|
-
const assetBaseDir = asset.targetSubdir
|
|
252
|
-
? path.resolve(modelDir, asset.targetSubdir)
|
|
253
|
-
: modelDir;
|
|
254
|
-
await fsExtra.ensureDir(assetBaseDir);
|
|
255
|
-
|
|
256
|
-
for (const filePath of assetFiles) {
|
|
257
|
-
const relativeToModel = asset.targetSubdir
|
|
258
|
-
? path.posix.join(asset.targetSubdir.replace(/\\/g, "/"), filePath)
|
|
259
|
-
: filePath;
|
|
260
|
-
const targetPath = resolveSafeTargetPath(assetBaseDir, filePath);
|
|
261
|
-
const exists = await fsExtra.pathExists(targetPath);
|
|
262
|
-
if (exists && input.force !== true) {
|
|
263
|
-
skippedFiles += 1;
|
|
264
|
-
skippedFilePaths.push(relativeToModel);
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
await downloadModelFile({
|
|
268
|
-
repoId: asset.repoId,
|
|
269
|
-
revision: asset.revision,
|
|
270
|
-
filePath,
|
|
271
|
-
targetPath,
|
|
272
|
-
hfToken: input.hfToken,
|
|
273
|
-
});
|
|
274
|
-
downloadedFiles += 1;
|
|
275
|
-
downloadedFilePaths.push(relativeToModel);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const manifestPath = path.join(modelDir, TTS_INSTALL_MANIFEST);
|
|
280
|
-
await fsExtra.writeJson(
|
|
281
|
-
manifestPath,
|
|
282
|
-
{
|
|
283
|
-
modelId: input.model.id,
|
|
284
|
-
installedAt: new Date().toISOString(),
|
|
285
|
-
assets: input.model.assets,
|
|
286
|
-
},
|
|
287
|
-
{ spaces: 2 },
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
return {
|
|
291
|
-
modelDir,
|
|
292
|
-
downloadedFiles,
|
|
293
|
-
skippedFiles,
|
|
294
|
-
downloadedFilePaths,
|
|
295
|
-
skippedFilePaths,
|
|
296
|
-
};
|
|
297
|
-
}
|