shipmyagent 1.0.229 → 1.0.236
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/extensions/voice/Index.d.ts.map +1 -0
- package/bin/extensions/voice/Index.js +655 -0
- package/bin/extensions/voice/Index.js.map +1 -0
- package/bin/extensions/voice/runtime/Catalog.d.ts.map +1 -0
- package/bin/extensions/voice/runtime/Catalog.js +61 -0
- package/bin/extensions/voice/runtime/Catalog.js.map +1 -0
- package/bin/extensions/voice/runtime/ConfigStore.d.ts.map +1 -0
- package/bin/extensions/voice/runtime/ConfigStore.js +79 -0
- package/bin/extensions/voice/runtime/ConfigStore.js.map +1 -0
- package/bin/extensions/voice/runtime/DependencyInstaller.d.ts.map +1 -0
- package/bin/extensions/voice/runtime/DependencyInstaller.js +264 -0
- package/bin/extensions/voice/runtime/DependencyInstaller.js.map +1 -0
- package/bin/extensions/voice/runtime/Installer.d.ts.map +1 -0
- package/bin/extensions/voice/runtime/Installer.js +161 -0
- package/bin/extensions/voice/runtime/Installer.js.map +1 -0
- package/bin/extensions/voice/runtime/Transcriber.d.ts.map +1 -0
- package/bin/extensions/voice/runtime/Transcriber.js +280 -0
- package/bin/extensions/voice/runtime/Transcriber.js.map +1 -0
- package/bin/main/agent/Agent.d.ts.map +1 -1
- package/bin/main/agent/Agent.js +205 -437
- package/bin/main/agent/Agent.js.map +1 -1
- package/bin/main/agent/components/CompactorComponent.d.ts.map +1 -1
- package/bin/main/agent/components/CompactorComponent.js.map +1 -1
- package/bin/main/agent/components/OrchestratorComponent.d.ts.map +1 -1
- package/bin/main/agent/components/OrchestratorComponent.js.map +1 -1
- package/bin/main/agent/components/PrompterComponent.d.ts.map +1 -1
- package/bin/main/agent/components/PrompterComponent.js.map +1 -1
- package/bin/main/agent/helpers/AgentHelpers.d.ts.map +1 -0
- package/bin/main/agent/helpers/AgentHelpers.js +142 -0
- package/bin/main/agent/helpers/AgentHelpers.js.map +1 -0
- package/bin/main/context/context-agent/ContextAgent.d.ts.map +1 -1
- package/bin/main/context/context-agent/ContextAgent.js +2 -1
- package/bin/main/context/context-agent/ContextAgent.js.map +1 -1
- package/bin/main/context/context-agent/components/RuntimeOrchestrator.d.ts.map +1 -1
- package/bin/main/context/context-agent/components/RuntimeOrchestrator.js +86 -23
- package/bin/main/context/context-agent/components/RuntimeOrchestrator.js.map +1 -1
- package/bin/main/context/context-agent/components/SummaryCompactor.d.ts.map +1 -1
- package/bin/main/context/context-agent/components/SummaryCompactor.js +14 -0
- package/bin/main/context/context-agent/components/SummaryCompactor.js.map +1 -1
- package/bin/main/context/manager/RuntimeState.d.ts.map +1 -1
- package/bin/main/context/manager/RuntimeState.js +43 -0
- package/bin/main/context/manager/RuntimeState.js.map +1 -1
- package/bin/main/extension/ExtensionCommand.d.ts.map +1 -0
- package/bin/main/extension/ExtensionCommand.js +344 -0
- package/bin/main/extension/ExtensionCommand.js.map +1 -0
- package/bin/main/extension/ExtensionManager.d.ts.map +1 -0
- package/bin/main/extension/ExtensionManager.js +9 -0
- package/bin/main/extension/ExtensionManager.js.map +1 -0
- package/bin/main/extension/Extensions.d.ts.map +1 -0
- package/bin/main/extension/Extensions.js +10 -0
- package/bin/main/extension/Extensions.js.map +1 -0
- package/bin/main/extension/Manager.d.ts.map +1 -0
- package/bin/main/extension/Manager.js +449 -0
- package/bin/main/extension/Manager.js.map +1 -0
- package/bin/main/prompts/system/PromptSystem.d.ts.map +1 -1
- package/bin/main/prompts/system/PromptSystem.js +5 -3
- package/bin/main/prompts/system/PromptSystem.js.map +1 -1
- package/bin/main/prompts/system/assets/service.prompt.txt +1 -1
- package/bin/main/server/commands/Alias.js +1 -1
- package/bin/main/server/commands/Config.d.ts.map +1 -1
- package/bin/main/server/commands/Config.js +16 -1
- package/bin/main/server/commands/Config.js.map +1 -1
- package/bin/main/server/commands/Extensions.d.ts.map +1 -0
- package/bin/main/server/commands/Extensions.js +244 -0
- package/bin/main/server/commands/Extensions.js.map +1 -0
- package/bin/main/server/commands/Index.js +47 -38
- package/bin/main/server/commands/Index.js.map +1 -1
- package/bin/main/server/commands/Init.js +1 -1
- package/bin/main/server/commands/Init.js.map +1 -1
- package/bin/main/server/commands/Restart.js +1 -1
- package/bin/main/server/commands/Run.d.ts.map +1 -1
- package/bin/main/server/commands/Run.js +26 -4
- package/bin/main/server/commands/Run.js.map +1 -1
- package/bin/main/server/commands/Services.d.ts.map +1 -1
- package/bin/main/server/commands/Services.js +16 -17
- package/bin/main/server/commands/Services.js.map +1 -1
- package/bin/main/server/commands/Start.d.ts.map +1 -1
- package/bin/main/server/commands/Start.js +5 -3
- package/bin/main/server/commands/Start.js.map +1 -1
- package/bin/main/server/commands/Stop.d.ts.map +1 -1
- package/bin/main/server/commands/Stop.js +3 -1
- package/bin/main/server/commands/Stop.js.map +1 -1
- package/bin/main/server/constants/ShipSchema.d.ts.map +1 -1
- package/bin/main/server/constants/ShipSchema.js +53 -0
- package/bin/main/server/constants/ShipSchema.js.map +1 -1
- package/bin/main/server/daemon/CliArgs.d.ts.map +1 -1
- package/bin/main/server/daemon/CliArgs.js +3 -2
- package/bin/main/server/daemon/CliArgs.js.map +1 -1
- package/bin/main/server/daemon/Manager.js +3 -3
- package/bin/main/server/index.d.ts.map +1 -1
- package/bin/main/server/index.js +4 -0
- package/bin/main/server/index.js.map +1 -1
- package/bin/main/server/routes/extensions.d.ts.map +1 -0
- package/bin/main/server/routes/extensions.js +75 -0
- package/bin/main/server/routes/extensions.js.map +1 -0
- package/bin/main/service/ServiceCommand.d.ts.map +1 -1
- package/bin/main/service/ServiceCommand.js +41 -5
- package/bin/main/service/ServiceCommand.js.map +1 -1
- package/bin/main/service/ServiceRuntime.d.ts.map +1 -1
- package/bin/main/service/Services.d.ts.map +1 -1
- package/bin/main/service/Services.js +5 -1
- package/bin/main/service/Services.js.map +1 -1
- package/bin/main/types/Agent.d.ts.map +1 -1
- package/bin/main/types/Extensions.d.ts.map +1 -0
- package/bin/main/types/Extensions.js +9 -0
- package/bin/main/types/Extensions.js.map +1 -0
- package/bin/main/types/ShipConfig.d.ts.map +1 -1
- package/bin/main/types/Voice.d.ts.map +1 -0
- package/bin/main/types/Voice.js +9 -0
- package/bin/main/types/Voice.js.map +1 -0
- package/bin/services/chat/PROMPT.direct.txt +22 -17
- package/bin/services/chat/channels/feishu/PROMPT.direct.txt +1 -1
- package/bin/services/chat/channels/qq/PROMPT.direct.txt +1 -1
- package/bin/services/chat/channels/telegram/ApiClient.d.ts.map +1 -1
- package/bin/services/chat/channels/telegram/ApiClient.js +24 -33
- package/bin/services/chat/channels/telegram/ApiClient.js.map +1 -1
- package/bin/services/chat/channels/telegram/Bot.d.ts.map +1 -1
- package/bin/services/chat/channels/telegram/Bot.js +27 -4
- package/bin/services/chat/channels/telegram/Bot.js.map +1 -1
- package/bin/services/chat/channels/telegram/PROMPT.direct.txt +6 -10
- package/bin/services/chat/channels/telegram/PROMPT.txt +2 -1
- package/bin/services/chat/channels/telegram/Shared.d.ts.map +1 -1
- package/bin/services/chat/channels/telegram/Shared.js +14 -4
- package/bin/services/chat/channels/telegram/Shared.js.map +1 -1
- package/bin/services/chat/channels/telegram/VoiceInput.d.ts.map +1 -0
- package/bin/services/chat/channels/telegram/VoiceInput.js +52 -0
- package/bin/services/chat/channels/telegram/VoiceInput.js.map +1 -0
- package/bin/services/chat/runtime/ChatQueueWorker.js +1 -1
- package/bin/services/chat/runtime/DirectDispatchParser.d.ts.map +1 -1
- package/bin/services/chat/runtime/DirectDispatchParser.js +23 -56
- package/bin/services/chat/runtime/DirectDispatchParser.js.map +1 -1
- package/bin/services/chat/types/DirectDispatch.d.ts.map +1 -1
- package/bin/services/task/runtime/Runner.d.ts.map +1 -1
- package/bin/services/task/runtime/Runner.js +5 -4
- package/bin/services/task/runtime/Runner.js.map +1 -1
- package/package.json +1 -1
- package/scripts/count-src-lines.mjs +96 -0
- package/src/main/prompts/system/assets/service.prompt.txt +1 -1
- package/src/services/chat/PROMPT.direct.txt +22 -17
- package/src/services/chat/channels/feishu/PROMPT.direct.txt +1 -1
- package/src/services/chat/channels/qq/PROMPT.direct.txt +1 -1
- package/src/services/chat/channels/telegram/PROMPT.direct.txt +6 -10
- package/src/services/chat/channels/telegram/PROMPT.txt +2 -1
- package/test/chat/direct-dispatch-parser.test.mjs +12 -5
- package/test/chat/telegram-attachment-video.test.mjs +62 -0
- package/test/chat/telegram-voice-input.test.mjs +79 -0
- package/test/voice/extension-action-auto-start.test.mjs +168 -0
- package/test/voice/voice-dependency-installer.test.mjs +58 -0
- package/test/voice/voice-service-config.test.mjs +185 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Index.d.ts","sourceRoot":"","sources":["../../../src/extensions/voice/Index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAC;AA2UtE,eAAO,MAAM,cAAc,EAAE,SAuf5B,CAAC"}
|
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Voice extension。
|
|
3
|
+
*
|
|
4
|
+
* 关键点(中文)
|
|
5
|
+
* - 统一管理本地语音识别模型:选择、安装、启停、激活模型。
|
|
6
|
+
* - 通过 `extensions.voice` 落盘配置,作为 chat 渠道接入 STT 的单一事实源。
|
|
7
|
+
*/
|
|
8
|
+
import prompts from "prompts";
|
|
9
|
+
import { VOICE_MODEL_CATALOG, getVoiceModelCatalogItem, resolveVoiceModelId, } from "./runtime/Catalog.js";
|
|
10
|
+
import { dedupeVoiceModelIds, ensureVoiceExtensionConfig, persistShipConfig, resolveVoiceModelsRootDir, toPortableRelativePath, } from "./runtime/ConfigStore.js";
|
|
11
|
+
import { installVoiceModelFromHuggingFace, } from "./runtime/Installer.js";
|
|
12
|
+
import { installVoiceTranscribeDependencies, resolveVoiceRunnersByModels, resolveVoiceStrategyByModel, } from "./runtime/DependencyInstaller.js";
|
|
13
|
+
import { transcribeVoiceAudio } from "./runtime/Transcriber.js";
|
|
14
|
+
function getStringOpt(opts, key) {
|
|
15
|
+
const value = opts[key];
|
|
16
|
+
if (typeof value !== "string")
|
|
17
|
+
return undefined;
|
|
18
|
+
const trimmed = value.trim();
|
|
19
|
+
return trimmed || undefined;
|
|
20
|
+
}
|
|
21
|
+
function getBooleanOpt(opts, key, defaultValue) {
|
|
22
|
+
const value = opts[key];
|
|
23
|
+
if (typeof value === "boolean")
|
|
24
|
+
return value;
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
const normalized = value.trim().toLowerCase();
|
|
27
|
+
if (["1", "true", "yes", "y", "on"].includes(normalized))
|
|
28
|
+
return true;
|
|
29
|
+
if (["0", "false", "no", "n", "off"].includes(normalized))
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return defaultValue;
|
|
33
|
+
}
|
|
34
|
+
function getNumberOpt(opts, key) {
|
|
35
|
+
const value = opts[key];
|
|
36
|
+
if (typeof value === "number") {
|
|
37
|
+
if (!Number.isFinite(value) || Number.isNaN(value))
|
|
38
|
+
return undefined;
|
|
39
|
+
return Math.floor(value);
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const text = value.trim();
|
|
43
|
+
if (!text)
|
|
44
|
+
return undefined;
|
|
45
|
+
const parsed = Number.parseInt(text, 10);
|
|
46
|
+
if (!Number.isFinite(parsed) || Number.isNaN(parsed)) {
|
|
47
|
+
throw new Error(`Invalid numeric option "${key}": ${value}`);
|
|
48
|
+
}
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function parseVoiceModelIdOrThrow(input) {
|
|
54
|
+
const modelId = resolveVoiceModelId(input);
|
|
55
|
+
if (!modelId) {
|
|
56
|
+
const supported = VOICE_MODEL_CATALOG.map((item) => item.id).join(", ");
|
|
57
|
+
throw new Error(`Unsupported voice model: ${input}. Supported: ${supported}`);
|
|
58
|
+
}
|
|
59
|
+
return modelId;
|
|
60
|
+
}
|
|
61
|
+
function parseVoiceModelArgs(args) {
|
|
62
|
+
const parsed = [];
|
|
63
|
+
for (const arg of args) {
|
|
64
|
+
const text = String(arg || "").trim();
|
|
65
|
+
if (!text)
|
|
66
|
+
continue;
|
|
67
|
+
parsed.push(parseVoiceModelIdOrThrow(text));
|
|
68
|
+
}
|
|
69
|
+
return dedupeVoiceModelIds(parsed);
|
|
70
|
+
}
|
|
71
|
+
async function selectVoiceModelsInteractively(params) {
|
|
72
|
+
const answer = await prompts({
|
|
73
|
+
type: "multiselect",
|
|
74
|
+
name: "selectedModels",
|
|
75
|
+
message: params.message,
|
|
76
|
+
hint: "- Space 选择 · Enter 确认",
|
|
77
|
+
instructions: false,
|
|
78
|
+
min: 1,
|
|
79
|
+
choices: VOICE_MODEL_CATALOG.map((item) => ({
|
|
80
|
+
title: `${item.label} (${item.description})`,
|
|
81
|
+
value: item.id,
|
|
82
|
+
selected: item.id === "SenseVoiceSmall",
|
|
83
|
+
})),
|
|
84
|
+
});
|
|
85
|
+
const selected = Array.isArray(answer.selectedModels)
|
|
86
|
+
? answer.selectedModels
|
|
87
|
+
: [];
|
|
88
|
+
if (selected.length === 0) {
|
|
89
|
+
throw new Error("No voice model selected");
|
|
90
|
+
}
|
|
91
|
+
return dedupeVoiceModelIds(selected);
|
|
92
|
+
}
|
|
93
|
+
function readInstalledModelIds(raw) {
|
|
94
|
+
if (!Array.isArray(raw))
|
|
95
|
+
return [];
|
|
96
|
+
const parsed = [];
|
|
97
|
+
for (const item of raw) {
|
|
98
|
+
const modelId = resolveVoiceModelId(String(item || ""));
|
|
99
|
+
if (!modelId)
|
|
100
|
+
continue;
|
|
101
|
+
parsed.push(modelId);
|
|
102
|
+
}
|
|
103
|
+
return dedupeVoiceModelIds(parsed);
|
|
104
|
+
}
|
|
105
|
+
function toVoiceTranscribeJson(value) {
|
|
106
|
+
if (!value)
|
|
107
|
+
return null;
|
|
108
|
+
const out = {};
|
|
109
|
+
if (typeof value.strategy === "string")
|
|
110
|
+
out.strategy = value.strategy;
|
|
111
|
+
if (typeof value.command === "string")
|
|
112
|
+
out.command = value.command;
|
|
113
|
+
if (typeof value.timeoutMs === "number")
|
|
114
|
+
out.timeoutMs = value.timeoutMs;
|
|
115
|
+
if (typeof value.pythonBin === "string")
|
|
116
|
+
out.pythonBin = value.pythonBin;
|
|
117
|
+
if (typeof value.language === "string")
|
|
118
|
+
out.language = value.language;
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
function toVoiceConfigJson(value) {
|
|
122
|
+
if (!value)
|
|
123
|
+
return null;
|
|
124
|
+
const out = {};
|
|
125
|
+
if (typeof value.enabled === "boolean")
|
|
126
|
+
out.enabled = value.enabled;
|
|
127
|
+
if (typeof value.provider === "string")
|
|
128
|
+
out.provider = value.provider;
|
|
129
|
+
if (typeof value.activeModel === "string")
|
|
130
|
+
out.activeModel = value.activeModel;
|
|
131
|
+
if (typeof value.modelsDir === "string")
|
|
132
|
+
out.modelsDir = value.modelsDir;
|
|
133
|
+
if (Array.isArray(value.installedModels)) {
|
|
134
|
+
out.installedModels = value.installedModels.map((item) => String(item));
|
|
135
|
+
}
|
|
136
|
+
const transcribe = toVoiceTranscribeJson(value.transcribe);
|
|
137
|
+
if (transcribe)
|
|
138
|
+
out.transcribe = transcribe;
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
function toVoiceCatalogJson() {
|
|
142
|
+
return VOICE_MODEL_CATALOG.map((item) => ({
|
|
143
|
+
id: item.id,
|
|
144
|
+
label: item.label,
|
|
145
|
+
description: item.description,
|
|
146
|
+
huggingfaceRepo: item.huggingfaceRepo,
|
|
147
|
+
revision: item.revision,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
function toInstallProgressJson(event) {
|
|
151
|
+
return {
|
|
152
|
+
stage: event.stage,
|
|
153
|
+
...(typeof event.filePath === "string" ? { filePath: event.filePath } : {}),
|
|
154
|
+
...(typeof event.index === "number" ? { index: event.index } : {}),
|
|
155
|
+
...(typeof event.totalFiles === "number"
|
|
156
|
+
? { totalFiles: event.totalFiles }
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function toDependencyInstallJson(value) {
|
|
161
|
+
return {
|
|
162
|
+
pythonBin: value.pythonBin,
|
|
163
|
+
runners: value.runners,
|
|
164
|
+
usedVirtualEnv: value.usedVirtualEnv,
|
|
165
|
+
...(typeof value.venvDir === "string" ? { venvDir: value.venvDir } : {}),
|
|
166
|
+
...(typeof value.basePythonBin === "string"
|
|
167
|
+
? { basePythonBin: value.basePythonBin }
|
|
168
|
+
: {}),
|
|
169
|
+
items: value.items.map((item) => ({
|
|
170
|
+
runner: item.runner,
|
|
171
|
+
pythonBin: item.pythonBin,
|
|
172
|
+
args: item.args,
|
|
173
|
+
packages: item.packages,
|
|
174
|
+
command: item.command,
|
|
175
|
+
elapsedMs: item.elapsedMs,
|
|
176
|
+
...(typeof item.stdoutTail === "string" ? { stdoutTail: item.stdoutTail } : {}),
|
|
177
|
+
...(typeof item.stderrTail === "string" ? { stderrTail: item.stderrTail } : {}),
|
|
178
|
+
})),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export const voiceExtension = {
|
|
182
|
+
name: "voice",
|
|
183
|
+
actions: {
|
|
184
|
+
models: {
|
|
185
|
+
command: {
|
|
186
|
+
description: "列出内置语音模型目录",
|
|
187
|
+
mapInput() {
|
|
188
|
+
return {};
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
api: {
|
|
192
|
+
method: "GET",
|
|
193
|
+
},
|
|
194
|
+
execute() {
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
data: {
|
|
198
|
+
models: toVoiceCatalogJson(),
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
status: {
|
|
204
|
+
command: {
|
|
205
|
+
description: "查看 voice extension 当前配置",
|
|
206
|
+
mapInput() {
|
|
207
|
+
return {};
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
api: {
|
|
211
|
+
method: "GET",
|
|
212
|
+
},
|
|
213
|
+
execute(params) {
|
|
214
|
+
return {
|
|
215
|
+
success: true,
|
|
216
|
+
data: {
|
|
217
|
+
voice: toVoiceConfigJson(params.context.config.extensions?.voice || null),
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
on: {
|
|
223
|
+
command: {
|
|
224
|
+
description: "启用 voice extension,并可交互选择模型安装",
|
|
225
|
+
configure(command) {
|
|
226
|
+
command
|
|
227
|
+
.argument("[models...]")
|
|
228
|
+
.option("--models-dir <path>", "模型目录(默认 ~/.ship/models/voice)")
|
|
229
|
+
.option("--active-model <modelId>", "激活模型 ID(必须在所选模型中)")
|
|
230
|
+
.option("--no-install", "仅写入配置,不执行下载")
|
|
231
|
+
.option("--force", "强制覆盖并重下已存在模型文件")
|
|
232
|
+
.option("--hf-token <token>", "HuggingFace token(私有/Gated 模型场景)");
|
|
233
|
+
},
|
|
234
|
+
async mapInput({ args, opts }) {
|
|
235
|
+
const fromArgs = parseVoiceModelArgs(args);
|
|
236
|
+
const modelIds = fromArgs.length > 0
|
|
237
|
+
? fromArgs
|
|
238
|
+
: await selectVoiceModelsInteractively({
|
|
239
|
+
message: "请选择要启用/安装的语音识别模型(可多选)",
|
|
240
|
+
});
|
|
241
|
+
const activeModel = getStringOpt(opts, "activeModel")
|
|
242
|
+
? parseVoiceModelIdOrThrow(String(getStringOpt(opts, "activeModel")))
|
|
243
|
+
: modelIds[0];
|
|
244
|
+
if (!modelIds.includes(activeModel)) {
|
|
245
|
+
throw new Error(`active-model "${activeModel}" is not in selected models: ${modelIds.join(", ")}`);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
modelIds,
|
|
249
|
+
install: getBooleanOpt(opts, "install", true),
|
|
250
|
+
force: getBooleanOpt(opts, "force", false),
|
|
251
|
+
modelsDir: getStringOpt(opts, "modelsDir"),
|
|
252
|
+
activeModel,
|
|
253
|
+
hfToken: getStringOpt(opts, "hfToken"),
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
async execute(params) {
|
|
258
|
+
const payload = params.payload;
|
|
259
|
+
const modelsRootDir = resolveVoiceModelsRootDir({
|
|
260
|
+
projectRoot: params.context.rootPath,
|
|
261
|
+
modelsDir: payload.modelsDir,
|
|
262
|
+
});
|
|
263
|
+
const installResults = [];
|
|
264
|
+
if (payload.install) {
|
|
265
|
+
for (const modelId of payload.modelIds) {
|
|
266
|
+
const model = getVoiceModelCatalogItem(modelId);
|
|
267
|
+
if (!model) {
|
|
268
|
+
throw new Error(`Voice model catalog entry not found: ${modelId}`);
|
|
269
|
+
}
|
|
270
|
+
const progressEvents = [];
|
|
271
|
+
const installed = await installVoiceModelFromHuggingFace({
|
|
272
|
+
model,
|
|
273
|
+
modelsRootDir,
|
|
274
|
+
force: payload.force,
|
|
275
|
+
hfToken: payload.hfToken,
|
|
276
|
+
onProgress: (event) => {
|
|
277
|
+
progressEvents.push(toInstallProgressJson(event));
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
installResults.push({
|
|
281
|
+
modelId,
|
|
282
|
+
modelDir: installed.modelDir,
|
|
283
|
+
repoId: installed.repoId,
|
|
284
|
+
revision: installed.revision,
|
|
285
|
+
downloadedFiles: installed.downloadedFiles,
|
|
286
|
+
skippedFiles: installed.skippedFiles,
|
|
287
|
+
downloadedFilePaths: installed.downloadedFilePaths,
|
|
288
|
+
skippedFilePaths: installed.skippedFilePaths,
|
|
289
|
+
progressEvents,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const voiceConfig = ensureVoiceExtensionConfig(params.context.config);
|
|
294
|
+
const existingInstalled = readInstalledModelIds(voiceConfig.installedModels);
|
|
295
|
+
voiceConfig.enabled = true;
|
|
296
|
+
voiceConfig.provider = "local";
|
|
297
|
+
voiceConfig.activeModel = payload.activeModel;
|
|
298
|
+
voiceConfig.modelsDir = toPortableRelativePath(params.context.rootPath, modelsRootDir);
|
|
299
|
+
voiceConfig.installedModels = dedupeVoiceModelIds([
|
|
300
|
+
...existingInstalled,
|
|
301
|
+
...payload.modelIds,
|
|
302
|
+
]);
|
|
303
|
+
const shipJsonPath = await persistShipConfig({
|
|
304
|
+
projectRoot: params.context.rootPath,
|
|
305
|
+
config: params.context.config,
|
|
306
|
+
});
|
|
307
|
+
params.context.logger.info("Voice extension enabled", {
|
|
308
|
+
activeModel: voiceConfig.activeModel,
|
|
309
|
+
modelsDir: voiceConfig.modelsDir,
|
|
310
|
+
install: payload.install,
|
|
311
|
+
});
|
|
312
|
+
return {
|
|
313
|
+
success: true,
|
|
314
|
+
data: {
|
|
315
|
+
shipJsonPath,
|
|
316
|
+
voice: toVoiceConfigJson(voiceConfig),
|
|
317
|
+
install: payload.install,
|
|
318
|
+
installResults,
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
init: {
|
|
324
|
+
command: {
|
|
325
|
+
description: "从零初始化 voice(启用 + 安装模型 + 自动安装转写依赖)",
|
|
326
|
+
configure(command) {
|
|
327
|
+
command
|
|
328
|
+
.argument("[models...]")
|
|
329
|
+
.option("--models-dir <path>", "模型目录(默认 ~/.ship/models/voice)")
|
|
330
|
+
.option("--active-model <modelId>", "激活模型 ID(必须在所选模型中)")
|
|
331
|
+
.option("--no-install-model", "跳过模型下载,仅写入配置")
|
|
332
|
+
.option("--no-install-deps", "跳过 Python 转写依赖安装")
|
|
333
|
+
.option("--python <bin>", "Python 可执行文件(默认 python3)")
|
|
334
|
+
.option("--venv-dir <path>", "PEP 668 回退虚拟环境目录(默认 ~/.ship/venvs/voice)")
|
|
335
|
+
.option("--no-pip-upgrade", "安装依赖时不带 pip -U")
|
|
336
|
+
.option("--pip-timeout-ms <ms>", "pip 安装超时毫秒(默认 300000)")
|
|
337
|
+
.option("--force", "强制覆盖并重下已存在模型文件")
|
|
338
|
+
.option("--hf-token <token>", "HuggingFace token(私有/Gated 模型场景)");
|
|
339
|
+
},
|
|
340
|
+
mapInput({ args, opts }) {
|
|
341
|
+
const fromArgs = parseVoiceModelArgs(args);
|
|
342
|
+
const modelIds = fromArgs.length > 0 ? fromArgs : ["SenseVoiceSmall"];
|
|
343
|
+
const activeModel = getStringOpt(opts, "activeModel")
|
|
344
|
+
? parseVoiceModelIdOrThrow(String(getStringOpt(opts, "activeModel")))
|
|
345
|
+
: modelIds[0];
|
|
346
|
+
if (!modelIds.includes(activeModel)) {
|
|
347
|
+
throw new Error(`active-model "${activeModel}" is not in selected models: ${modelIds.join(", ")}`);
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
modelIds,
|
|
351
|
+
installModel: getBooleanOpt(opts, "installModel", true),
|
|
352
|
+
installDeps: getBooleanOpt(opts, "installDeps", true),
|
|
353
|
+
force: getBooleanOpt(opts, "force", false),
|
|
354
|
+
modelsDir: getStringOpt(opts, "modelsDir"),
|
|
355
|
+
activeModel,
|
|
356
|
+
hfToken: getStringOpt(opts, "hfToken"),
|
|
357
|
+
pythonBin: getStringOpt(opts, "python"),
|
|
358
|
+
pipUpgrade: getBooleanOpt(opts, "pipUpgrade", true),
|
|
359
|
+
pipTimeoutMs: getNumberOpt(opts, "pipTimeoutMs"),
|
|
360
|
+
venvDir: getStringOpt(opts, "venvDir"),
|
|
361
|
+
};
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
async execute(params) {
|
|
365
|
+
const payload = params.payload;
|
|
366
|
+
const modelsRootDir = resolveVoiceModelsRootDir({
|
|
367
|
+
projectRoot: params.context.rootPath,
|
|
368
|
+
modelsDir: payload.modelsDir,
|
|
369
|
+
});
|
|
370
|
+
const installResults = [];
|
|
371
|
+
if (payload.installModel) {
|
|
372
|
+
for (const modelId of payload.modelIds) {
|
|
373
|
+
const model = getVoiceModelCatalogItem(modelId);
|
|
374
|
+
if (!model) {
|
|
375
|
+
throw new Error(`Voice model catalog entry not found: ${modelId}`);
|
|
376
|
+
}
|
|
377
|
+
const progressEvents = [];
|
|
378
|
+
const installed = await installVoiceModelFromHuggingFace({
|
|
379
|
+
model,
|
|
380
|
+
modelsRootDir,
|
|
381
|
+
force: payload.force,
|
|
382
|
+
hfToken: payload.hfToken,
|
|
383
|
+
onProgress: (event) => {
|
|
384
|
+
progressEvents.push(toInstallProgressJson(event));
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
installResults.push({
|
|
388
|
+
modelId,
|
|
389
|
+
modelDir: installed.modelDir,
|
|
390
|
+
repoId: installed.repoId,
|
|
391
|
+
revision: installed.revision,
|
|
392
|
+
downloadedFiles: installed.downloadedFiles,
|
|
393
|
+
skippedFiles: installed.skippedFiles,
|
|
394
|
+
downloadedFilePaths: installed.downloadedFilePaths,
|
|
395
|
+
skippedFilePaths: installed.skippedFilePaths,
|
|
396
|
+
progressEvents,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const voiceConfig = ensureVoiceExtensionConfig(params.context.config);
|
|
401
|
+
const existingInstalled = readInstalledModelIds(voiceConfig.installedModels);
|
|
402
|
+
if (!payload.installModel &&
|
|
403
|
+
!existingInstalled.includes(payload.activeModel)) {
|
|
404
|
+
throw new Error(`active-model "${payload.activeModel}" is not installed. Remove --no-install-model or run "sma voice install ${payload.activeModel}" first.`);
|
|
405
|
+
}
|
|
406
|
+
const installedFromInit = payload.installModel ? payload.modelIds : [];
|
|
407
|
+
voiceConfig.enabled = true;
|
|
408
|
+
voiceConfig.provider = "local";
|
|
409
|
+
voiceConfig.activeModel = payload.activeModel;
|
|
410
|
+
voiceConfig.modelsDir = toPortableRelativePath(params.context.rootPath, modelsRootDir);
|
|
411
|
+
voiceConfig.installedModels = dedupeVoiceModelIds([
|
|
412
|
+
...existingInstalled,
|
|
413
|
+
...installedFromInit,
|
|
414
|
+
]);
|
|
415
|
+
let dependencyInstall;
|
|
416
|
+
if (payload.installDeps) {
|
|
417
|
+
const runners = resolveVoiceRunnersByModels(payload.modelIds);
|
|
418
|
+
const installedDeps = await installVoiceTranscribeDependencies({
|
|
419
|
+
pythonBin: payload.pythonBin,
|
|
420
|
+
runners,
|
|
421
|
+
upgrade: payload.pipUpgrade,
|
|
422
|
+
timeoutMs: payload.pipTimeoutMs,
|
|
423
|
+
venvDir: payload.venvDir,
|
|
424
|
+
});
|
|
425
|
+
dependencyInstall = toDependencyInstallJson(installedDeps);
|
|
426
|
+
voiceConfig.transcribe = {
|
|
427
|
+
...(voiceConfig.transcribe || {}),
|
|
428
|
+
pythonBin: installedDeps.pythonBin,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
voiceConfig.transcribe = {
|
|
432
|
+
...(voiceConfig.transcribe || {}),
|
|
433
|
+
strategy: resolveVoiceStrategyByModel(payload.activeModel),
|
|
434
|
+
};
|
|
435
|
+
const shipJsonPath = await persistShipConfig({
|
|
436
|
+
projectRoot: params.context.rootPath,
|
|
437
|
+
config: params.context.config,
|
|
438
|
+
});
|
|
439
|
+
params.context.logger.info("Voice extension initialized", {
|
|
440
|
+
activeModel: voiceConfig.activeModel,
|
|
441
|
+
modelsDir: voiceConfig.modelsDir,
|
|
442
|
+
installModel: payload.installModel,
|
|
443
|
+
installDeps: payload.installDeps,
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
success: true,
|
|
447
|
+
data: {
|
|
448
|
+
shipJsonPath,
|
|
449
|
+
voice: toVoiceConfigJson(voiceConfig),
|
|
450
|
+
installModel: payload.installModel,
|
|
451
|
+
installDeps: payload.installDeps,
|
|
452
|
+
installResults,
|
|
453
|
+
...(dependencyInstall ? { dependencyInstall } : {}),
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
off: {
|
|
459
|
+
command: {
|
|
460
|
+
description: "关闭 voice extension(保留已安装模型记录)",
|
|
461
|
+
mapInput() {
|
|
462
|
+
return {};
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
async execute(params) {
|
|
466
|
+
const voiceConfig = ensureVoiceExtensionConfig(params.context.config);
|
|
467
|
+
const previousEnabled = voiceConfig.enabled === true;
|
|
468
|
+
voiceConfig.enabled = false;
|
|
469
|
+
const shipJsonPath = await persistShipConfig({
|
|
470
|
+
projectRoot: params.context.rootPath,
|
|
471
|
+
config: params.context.config,
|
|
472
|
+
});
|
|
473
|
+
params.context.logger.info("Voice extension disabled");
|
|
474
|
+
return {
|
|
475
|
+
success: true,
|
|
476
|
+
data: {
|
|
477
|
+
shipJsonPath,
|
|
478
|
+
previousEnabled,
|
|
479
|
+
voice: toVoiceConfigJson(voiceConfig),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
install: {
|
|
485
|
+
command: {
|
|
486
|
+
description: "安装 voice 模型(不改变 enabled 状态)",
|
|
487
|
+
configure(command) {
|
|
488
|
+
command
|
|
489
|
+
.argument("[models...]")
|
|
490
|
+
.option("--models-dir <path>", "模型目录(默认 ~/.ship/models/voice)")
|
|
491
|
+
.option("--force", "强制覆盖并重下已存在模型文件")
|
|
492
|
+
.option("--hf-token <token>", "HuggingFace token(私有/Gated 模型场景)");
|
|
493
|
+
},
|
|
494
|
+
async mapInput({ args, opts }) {
|
|
495
|
+
const fromArgs = parseVoiceModelArgs(args);
|
|
496
|
+
const modelIds = fromArgs.length > 0
|
|
497
|
+
? fromArgs
|
|
498
|
+
: await selectVoiceModelsInteractively({
|
|
499
|
+
message: "请选择要安装的语音识别模型(可多选)",
|
|
500
|
+
});
|
|
501
|
+
return {
|
|
502
|
+
modelIds,
|
|
503
|
+
force: getBooleanOpt(opts, "force", false),
|
|
504
|
+
modelsDir: getStringOpt(opts, "modelsDir"),
|
|
505
|
+
hfToken: getStringOpt(opts, "hfToken"),
|
|
506
|
+
};
|
|
507
|
+
},
|
|
508
|
+
},
|
|
509
|
+
async execute(params) {
|
|
510
|
+
const payload = params.payload;
|
|
511
|
+
const modelsRootDir = resolveVoiceModelsRootDir({
|
|
512
|
+
projectRoot: params.context.rootPath,
|
|
513
|
+
modelsDir: payload.modelsDir,
|
|
514
|
+
});
|
|
515
|
+
const installResults = [];
|
|
516
|
+
for (const modelId of payload.modelIds) {
|
|
517
|
+
const model = getVoiceModelCatalogItem(modelId);
|
|
518
|
+
if (!model) {
|
|
519
|
+
throw new Error(`Voice model catalog entry not found: ${modelId}`);
|
|
520
|
+
}
|
|
521
|
+
const progressEvents = [];
|
|
522
|
+
const installed = await installVoiceModelFromHuggingFace({
|
|
523
|
+
model,
|
|
524
|
+
modelsRootDir,
|
|
525
|
+
force: payload.force,
|
|
526
|
+
hfToken: payload.hfToken,
|
|
527
|
+
onProgress: (event) => {
|
|
528
|
+
progressEvents.push(toInstallProgressJson(event));
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
installResults.push({
|
|
532
|
+
modelId,
|
|
533
|
+
modelDir: installed.modelDir,
|
|
534
|
+
repoId: installed.repoId,
|
|
535
|
+
revision: installed.revision,
|
|
536
|
+
downloadedFiles: installed.downloadedFiles,
|
|
537
|
+
skippedFiles: installed.skippedFiles,
|
|
538
|
+
downloadedFilePaths: installed.downloadedFilePaths,
|
|
539
|
+
skippedFilePaths: installed.skippedFilePaths,
|
|
540
|
+
progressEvents,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
const voiceConfig = ensureVoiceExtensionConfig(params.context.config);
|
|
544
|
+
const existingInstalled = readInstalledModelIds(voiceConfig.installedModels);
|
|
545
|
+
voiceConfig.provider = "local";
|
|
546
|
+
voiceConfig.modelsDir = toPortableRelativePath(params.context.rootPath, modelsRootDir);
|
|
547
|
+
voiceConfig.installedModels = dedupeVoiceModelIds([
|
|
548
|
+
...existingInstalled,
|
|
549
|
+
...payload.modelIds,
|
|
550
|
+
]);
|
|
551
|
+
if (!voiceConfig.activeModel && payload.modelIds.length > 0) {
|
|
552
|
+
voiceConfig.activeModel = payload.modelIds[0];
|
|
553
|
+
}
|
|
554
|
+
const shipJsonPath = await persistShipConfig({
|
|
555
|
+
projectRoot: params.context.rootPath,
|
|
556
|
+
config: params.context.config,
|
|
557
|
+
});
|
|
558
|
+
params.context.logger.info("Voice model install completed", {
|
|
559
|
+
models: payload.modelIds,
|
|
560
|
+
modelsDir: voiceConfig.modelsDir,
|
|
561
|
+
});
|
|
562
|
+
return {
|
|
563
|
+
success: true,
|
|
564
|
+
data: {
|
|
565
|
+
shipJsonPath,
|
|
566
|
+
voice: toVoiceConfigJson(voiceConfig),
|
|
567
|
+
installResults,
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
use: {
|
|
573
|
+
command: {
|
|
574
|
+
description: "切换 voice active 模型",
|
|
575
|
+
configure(command) {
|
|
576
|
+
command.argument("<modelId>");
|
|
577
|
+
},
|
|
578
|
+
mapInput({ args }) {
|
|
579
|
+
const modelId = parseVoiceModelIdOrThrow(String(args[0] || ""));
|
|
580
|
+
return { modelId };
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
async execute(params) {
|
|
584
|
+
const payload = params.payload;
|
|
585
|
+
const voiceConfig = ensureVoiceExtensionConfig(params.context.config);
|
|
586
|
+
const installed = readInstalledModelIds(voiceConfig.installedModels);
|
|
587
|
+
if (!installed.includes(payload.modelId)) {
|
|
588
|
+
return {
|
|
589
|
+
success: false,
|
|
590
|
+
error: `Model "${payload.modelId}" is not installed. Run "sma voice install ${payload.modelId}" first.`,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
voiceConfig.provider = "local";
|
|
594
|
+
voiceConfig.activeModel = payload.modelId;
|
|
595
|
+
const shipJsonPath = await persistShipConfig({
|
|
596
|
+
projectRoot: params.context.rootPath,
|
|
597
|
+
config: params.context.config,
|
|
598
|
+
});
|
|
599
|
+
params.context.logger.info("Voice active model switched", {
|
|
600
|
+
activeModel: payload.modelId,
|
|
601
|
+
});
|
|
602
|
+
return {
|
|
603
|
+
success: true,
|
|
604
|
+
data: {
|
|
605
|
+
shipJsonPath,
|
|
606
|
+
voice: toVoiceConfigJson(voiceConfig),
|
|
607
|
+
},
|
|
608
|
+
};
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
transcribe: {
|
|
612
|
+
command: {
|
|
613
|
+
description: "转写本地音频文件(用于联调)",
|
|
614
|
+
configure(command) {
|
|
615
|
+
command
|
|
616
|
+
.argument("<audioPath>")
|
|
617
|
+
.option("--language <code>", "语言提示(可选,例如 zh / en)");
|
|
618
|
+
},
|
|
619
|
+
mapInput({ args, opts }) {
|
|
620
|
+
const audioPath = String(args[0] || "").trim();
|
|
621
|
+
if (!audioPath) {
|
|
622
|
+
throw new Error("audioPath is required");
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
audioPath,
|
|
626
|
+
language: getStringOpt(opts, "language"),
|
|
627
|
+
};
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
api: {
|
|
631
|
+
method: "POST",
|
|
632
|
+
},
|
|
633
|
+
async execute(params) {
|
|
634
|
+
const payload = params.payload;
|
|
635
|
+
const result = await transcribeVoiceAudio({
|
|
636
|
+
runtime: params.context,
|
|
637
|
+
audioPath: payload.audioPath,
|
|
638
|
+
language: payload.language,
|
|
639
|
+
});
|
|
640
|
+
return {
|
|
641
|
+
success: true,
|
|
642
|
+
data: {
|
|
643
|
+
text: result.text,
|
|
644
|
+
modelId: result.modelId,
|
|
645
|
+
provider: result.provider,
|
|
646
|
+
elapsedMs: result.elapsedMs,
|
|
647
|
+
runner: result.runner,
|
|
648
|
+
audioPath: result.audioPath,
|
|
649
|
+
},
|
|
650
|
+
};
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
//# sourceMappingURL=Index.js.map
|