@z-qinghui/migpt-claw 1.0.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/LICENSE +21 -0
- package/README.md +690 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-entry.d.ts +3 -0
- package/dist/setup-entry.js +7 -0
- package/dist/setup-entry.js.map +1 -0
- package/dist/src/channel.d.ts +10 -0
- package/dist/src/channel.js +444 -0
- package/dist/src/channel.js.map +1 -0
- package/dist/src/config.d.ts +125 -0
- package/dist/src/config.js +146 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/message.d.ts +51 -0
- package/dist/src/message.js +145 -0
- package/dist/src/message.js.map +1 -0
- package/dist/src/mi/account.d.ts +5 -0
- package/dist/src/mi/account.js +162 -0
- package/dist/src/mi/account.js.map +1 -0
- package/dist/src/mi/common.d.ts +15 -0
- package/dist/src/mi/common.js +80 -0
- package/dist/src/mi/common.js.map +1 -0
- package/dist/src/mi/index.d.ts +4 -0
- package/dist/src/mi/index.js +10 -0
- package/dist/src/mi/index.js.map +1 -0
- package/dist/src/mi/mina.d.ts +66 -0
- package/dist/src/mi/mina.js +225 -0
- package/dist/src/mi/mina.js.map +1 -0
- package/dist/src/mi/miot.d.ts +35 -0
- package/dist/src/mi/miot.js +168 -0
- package/dist/src/mi/miot.js.map +1 -0
- package/dist/src/mi/typing.d.ts +90 -0
- package/dist/src/mi/typing.js +1 -0
- package/dist/src/mi/typing.js.map +1 -0
- package/dist/src/onboarding.d.ts +5 -0
- package/dist/src/onboarding.js +118 -0
- package/dist/src/onboarding.js.map +1 -0
- package/dist/src/openclaw-plugin-sdk.d.d.ts +185 -0
- package/dist/src/openclaw-plugin-sdk.d.js +1 -0
- package/dist/src/openclaw-plugin-sdk.d.js.map +1 -0
- package/dist/src/outbound.d.ts +5 -0
- package/dist/src/outbound.js +108 -0
- package/dist/src/outbound.js.map +1 -0
- package/dist/src/runtime.d.ts +6 -0
- package/dist/src/runtime.js +15 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/service.d.ts +70 -0
- package/dist/src/service.js +200 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/speaker.d.ts +62 -0
- package/dist/src/speaker.js +211 -0
- package/dist/src/speaker.js.map +1 -0
- package/dist/src/tts/mimo.d.ts +50 -0
- package/dist/src/tts/mimo.js +214 -0
- package/dist/src/tts/mimo.js.map +1 -0
- package/dist/src/types.d.ts +30 -0
- package/dist/src/types.js +1 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/utils/codec.d.ts +31 -0
- package/dist/src/utils/codec.js +144 -0
- package/dist/src/utils/codec.js.map +1 -0
- package/dist/src/utils/debug.d.ts +10 -0
- package/dist/src/utils/debug.js +15 -0
- package/dist/src/utils/debug.js.map +1 -0
- package/dist/src/utils/hash.d.ts +40 -0
- package/dist/src/utils/hash.js +75 -0
- package/dist/src/utils/hash.js.map +1 -0
- package/dist/src/utils/http.d.ts +24 -0
- package/dist/src/utils/http.js +151 -0
- package/dist/src/utils/http.js.map +1 -0
- package/dist/src/utils/index.d.ts +6 -0
- package/dist/src/utils/index.js +10 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/io.d.ts +26 -0
- package/dist/src/utils/io.js +53 -0
- package/dist/src/utils/io.js.map +1 -0
- package/dist/src/utils/parse.d.ts +26 -0
- package/dist/src/utils/parse.js +51 -0
- package/dist/src/utils/parse.js.map +1 -0
- package/index.ts +26 -0
- package/openclaw.plugin.json +344 -0
- package/package.json +106 -0
- package/setup-entry.ts +12 -0
- package/skills/migpt-volume/SKILL.md +182 -0
- package/skills/migpt-volume/index.ts +50 -0
- package/src/channel.ts +519 -0
- package/src/config.ts +299 -0
- package/src/message.ts +186 -0
- package/src/mi/account.ts +184 -0
- package/src/mi/common.ts +105 -0
- package/src/mi/index.ts +4 -0
- package/src/mi/mina.ts +261 -0
- package/src/mi/miot.ts +193 -0
- package/src/mi/typing.ts +93 -0
- package/src/onboarding.ts +136 -0
- package/src/openclaw-plugin-sdk.d.ts +185 -0
- package/src/outbound.ts +137 -0
- package/src/runtime.ts +14 -0
- package/src/service.ts +246 -0
- package/src/speaker.ts +264 -0
- package/src/tts/mimo.ts +300 -0
- package/src/types.ts +34 -0
- package/src/utils/codec.ts +206 -0
- package/src/utils/debug.ts +16 -0
- package/src/utils/hash.ts +104 -0
- package/src/utils/http.ts +193 -0
- package/src/utils/index.ts +5 -0
- package/src/utils/io.ts +68 -0
- package/src/utils/parse.ts +64 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { getMiService } from "./mi/common.js";
|
|
2
|
+
import { assert, sleep } from "./utils/parse.js";
|
|
3
|
+
import { Debugger } from "./utils/debug.js";
|
|
4
|
+
class _MiService {
|
|
5
|
+
MiNA;
|
|
6
|
+
MiOT;
|
|
7
|
+
_initialized = false;
|
|
8
|
+
_initializing = false;
|
|
9
|
+
_speakerControl = "mina";
|
|
10
|
+
/**
|
|
11
|
+
* 使用 MIoT 发送 TTS 播报
|
|
12
|
+
*/
|
|
13
|
+
async playWithMiot(text) {
|
|
14
|
+
if (!this.MiOT) {
|
|
15
|
+
console.warn("\u26A0\uFE0F MIoT \u670D\u52A1\u4E0D\u53EF\u7528");
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const result = await this.MiOT.doAction(5, 1, [text]);
|
|
20
|
+
return result;
|
|
21
|
+
} catch (e) {
|
|
22
|
+
console.error("\u274C MIoT TTS \u5931\u8D25:", e?.message || e);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 使用 MiNA 发送 TTS 播报
|
|
28
|
+
*/
|
|
29
|
+
async playWithMina(text) {
|
|
30
|
+
if (!this.MiNA) {
|
|
31
|
+
console.warn("\u26A0\uFE0F MiNA \u670D\u52A1\u4E0D\u53EF\u7528");
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const result = await this.MiNA.play({ text });
|
|
36
|
+
return result;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error("\u274C MiNA TTS \u5931\u8D25:", e?.message || e);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 发送 TTS 播报(根据配置选择 MiNA 或 MIoT)
|
|
44
|
+
*/
|
|
45
|
+
async play(text) {
|
|
46
|
+
if (this._speakerControl === "miot") {
|
|
47
|
+
return this.playWithMiot(text);
|
|
48
|
+
} else {
|
|
49
|
+
return this.playWithMina(text);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 初始化服务
|
|
54
|
+
*/
|
|
55
|
+
async init(config, did) {
|
|
56
|
+
if (this._initialized) {
|
|
57
|
+
console.log("\u2705 MiService \u5DF2\u521D\u59CB\u5316\uFF0C\u8DF3\u8FC7");
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (this._initializing) {
|
|
61
|
+
let waitCount = 0;
|
|
62
|
+
while (this._initializing && waitCount < 30) {
|
|
63
|
+
await sleep(100);
|
|
64
|
+
waitCount++;
|
|
65
|
+
}
|
|
66
|
+
return this._initialized;
|
|
67
|
+
}
|
|
68
|
+
this._initializing = true;
|
|
69
|
+
try {
|
|
70
|
+
console.log("\u{1F680} \u5F00\u59CB\u521D\u59CB\u5316 MiService...");
|
|
71
|
+
console.log("\u{1F4CB} \u914D\u7F6E\u4FE1\u606F:", {
|
|
72
|
+
did,
|
|
73
|
+
userId: config.userId,
|
|
74
|
+
hasPassword: !!config.password,
|
|
75
|
+
hasPassToken: !!config.passToken,
|
|
76
|
+
debug: config.debug,
|
|
77
|
+
timeout: config.timeout,
|
|
78
|
+
speakerControl: config.speakerControl
|
|
79
|
+
});
|
|
80
|
+
assert(!!did, "\u274C Speaker \u7F3A\u5C11 did \u53C2\u6570");
|
|
81
|
+
assert(
|
|
82
|
+
!!config.passToken || !!config.userId && !!config.password,
|
|
83
|
+
"\u274C Speaker \u7F3A\u5C11 passToken \u6216 userId \u548C password"
|
|
84
|
+
);
|
|
85
|
+
Debugger.debug = config.debug ?? false;
|
|
86
|
+
this._speakerControl = config.speakerControl ?? "mina";
|
|
87
|
+
const serviceConfig = {
|
|
88
|
+
...config,
|
|
89
|
+
did,
|
|
90
|
+
timeout: Math.max(1e3, config.timeout ?? 5e3)
|
|
91
|
+
};
|
|
92
|
+
console.log("\u{1F50C} \u6B63\u5728\u8FDE\u63A5 MiNA \u670D\u52A1...");
|
|
93
|
+
this.MiNA = await getMiService({ ...serviceConfig, service: "mina" });
|
|
94
|
+
console.log("\u{1F50C} \u67E5\u8BE2 MiNA \u670D\u52A1\u7ED3\u679C\uFF1A", { mina: !!this.MiNA });
|
|
95
|
+
console.log("\u{1F50C} \u6B63\u5728\u8FDE\u63A5 MIoT \u670D\u52A1...");
|
|
96
|
+
this.MiOT = await getMiService({ ...serviceConfig, service: "miot" });
|
|
97
|
+
console.log("\u{1F50C} \u67E5\u8BE2 MIoT \u670D\u52A1\u7ED3\u679C\uFF1A", { miot: !!this.MiOT });
|
|
98
|
+
assert(!!this.MiNA, "\u274C \u521D\u59CB\u5316 MiNA \u670D\u52A1\u5931\u8D25");
|
|
99
|
+
if (!this.MiOT) {
|
|
100
|
+
console.warn("\u26A0\uFE0F MIoT \u670D\u52A1\u521D\u59CB\u5316\u5931\u8D25\uFF0C\u90E8\u5206\u8BBE\u5907\u63A7\u5236\u529F\u80FD\u53EF\u80FD\u4E0D\u53EF\u7528");
|
|
101
|
+
}
|
|
102
|
+
if (Debugger.debug) {
|
|
103
|
+
const device = this.MiNA?.account?.device;
|
|
104
|
+
console.debug(
|
|
105
|
+
"\u{1F41B} \u8BBE\u5907\u4FE1\u606F\uFF1A",
|
|
106
|
+
JSON.stringify(
|
|
107
|
+
{
|
|
108
|
+
name: device?.name,
|
|
109
|
+
desc: device?.desc,
|
|
110
|
+
model: device?.model,
|
|
111
|
+
rom: device?.extra?.fw_version
|
|
112
|
+
},
|
|
113
|
+
null,
|
|
114
|
+
2
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
this._initialized = true;
|
|
119
|
+
console.log("\u2705 MiService \u521D\u59CB\u5316\u6210\u529F");
|
|
120
|
+
console.log(`\u{1F50A} \u97F3\u7BB1\u63A7\u5236\u65B9\u5F0F\uFF1A${this._speakerControl}`);
|
|
121
|
+
const announceOnStart = config.announceOnStart ?? true;
|
|
122
|
+
if (announceOnStart) {
|
|
123
|
+
const startupMessage = config.startupMessage ?? "\u60A8\u7684\u5C0F\u9F99\u867E\u5DF2\u4E0A\u7EBF\uFF0C\u968F\u65F6\u4E3A\u60A8\u670D\u52A1";
|
|
124
|
+
try {
|
|
125
|
+
console.log("\u{1F50A} \u6B63\u5728\u53D1\u9001\u542F\u52A8\u64AD\u62A5:", startupMessage);
|
|
126
|
+
await this.play(startupMessage);
|
|
127
|
+
console.log("\u2705 \u542F\u52A8\u64AD\u62A5\u53D1\u9001\u6210\u529F");
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.warn("\u26A0\uFE0F \u542F\u52A8\u64AD\u62A5\u53D1\u9001\u5931\u8D25:", e?.message || e);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.error("\u274C \u521D\u59CB\u5316\u5931\u8D25:", { err, message: err.message });
|
|
135
|
+
return false;
|
|
136
|
+
} finally {
|
|
137
|
+
this._initializing = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 获取设备列表
|
|
142
|
+
*/
|
|
143
|
+
async getDevices(config) {
|
|
144
|
+
try {
|
|
145
|
+
const tempService = await getMiService({ ...config, service: "mina", relogin: false });
|
|
146
|
+
if (!tempService) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const devices = await tempService.getDevices();
|
|
150
|
+
return (devices || []).map((d) => ({
|
|
151
|
+
did: d.name || d.alias || d.deviceID,
|
|
152
|
+
name: d.alias || d.name,
|
|
153
|
+
model: d.model
|
|
154
|
+
}));
|
|
155
|
+
} catch (err) {
|
|
156
|
+
console.error("\u274C \u83B7\u53D6\u8BBE\u5907\u5217\u8868\u5931\u8D25:", err);
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 重新登录
|
|
162
|
+
*/
|
|
163
|
+
async relogin(config, did) {
|
|
164
|
+
this._initialized = false;
|
|
165
|
+
return this.init(config, did);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 唤醒小爱音箱(进入监听状态)
|
|
169
|
+
* 使用 MIoT spec 调用唤醒动作 (siid=5, aiid=3)
|
|
170
|
+
* 基于 mi-gpt 项目的实现
|
|
171
|
+
*/
|
|
172
|
+
async wakeUp() {
|
|
173
|
+
if (!this.MiOT) {
|
|
174
|
+
console.warn("\u26A0\uFE0F MIoT \u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5\u5524\u9192\u97F3\u7BB1");
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const result = await this.MiOT.doAction(5, 3, []);
|
|
179
|
+
return result;
|
|
180
|
+
} catch (e) {
|
|
181
|
+
console.warn("\u26A0\uFE0F \u5524\u9192\u97F3\u7BB1\u5931\u8D25:", e?.message || e);
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 退出监听状态(取消唤醒)
|
|
187
|
+
* 使用 MiNA 暂停使小爱退出监听
|
|
188
|
+
*/
|
|
189
|
+
async unWakeUp() {
|
|
190
|
+
if (this.MiNA) {
|
|
191
|
+
await this.MiNA.pause().catch(() => {
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const MiService = new _MiService();
|
|
197
|
+
export {
|
|
198
|
+
MiService
|
|
199
|
+
};
|
|
200
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/service.ts"],"sourcesContent":["import { MiNA } from './mi/mina.js';\nimport { MIoT } from './mi/miot.js';\nimport { getMiService } from './mi/common.js';\nimport { assert, sleep } from './utils/parse.js';\nimport { Debugger } from './utils/debug.js';\n\nexport interface MiServiceConfig {\n /** 小米 ID(数字) */\n userId?: string;\n /** 密码 */\n password?: string;\n /** 登录凭证 */\n passToken?: string;\n /** 是否开启调试模式 */\n debug?: boolean;\n /** 网络请求超时时长(毫秒) */\n timeout?: number;\n /** 音箱控制方式:mina/miot */\n speakerControl?: 'mina' | 'miot';\n}\n\nclass _MiService {\n MiNA?: MiNA;\n MiOT?: MIoT;\n private _initialized = false;\n private _initializing = false;\n private _speakerControl: 'mina' | 'miot' = 'mina';\n\n /**\n * 使用 MIoT 发送 TTS 播报\n */\n async playWithMiot(text: string): Promise<boolean> {\n if (!this.MiOT) {\n console.warn('⚠️ MIoT 服务不可用');\n return false;\n }\n try {\n // 使用 MIoT spec 调用 TTS 动作 (siid=5, aiid=1 是常见的 TTS 动作)\n // 不同设备可能不同,需要根据设备 spec 调整\n const result = await this.MiOT.doAction(5, 1, [text]);\n return result;\n } catch (e: any) {\n console.error('❌ MIoT TTS 失败:', e?.message || e);\n return false;\n }\n }\n\n /**\n * 使用 MiNA 发送 TTS 播报\n */\n async playWithMina(text: string): Promise<boolean> {\n if (!this.MiNA) {\n console.warn('⚠️ MiNA 服务不可用');\n return false;\n }\n try {\n const result = await this.MiNA.play({ text });\n return result;\n } catch (e: any) {\n console.error('❌ MiNA TTS 失败:', e?.message || e);\n return false;\n }\n }\n\n /**\n * 发送 TTS 播报(根据配置选择 MiNA 或 MIoT)\n */\n async play(text: string): Promise<boolean> {\n if (this._speakerControl === 'miot') {\n return this.playWithMiot(text);\n } else {\n return this.playWithMina(text);\n }\n }\n\n /**\n * 初始化服务\n */\n async init(config: MiServiceConfig & { \n announceOnStart?: boolean; \n startupMessage?: string; \n }, did: string): Promise<boolean> {\n if (this._initialized) {\n console.log('✅ MiService 已初始化,跳过');\n return true;\n }\n\n if (this._initializing) {\n // 等待初始化完成\n let waitCount = 0;\n while (this._initializing && waitCount < 30) {\n await sleep(100);\n waitCount++;\n }\n return this._initialized;\n }\n\n this._initializing = true;\n\n try {\n console.log('🚀 开始初始化 MiService...');\n console.log('📋 配置信息:', {\n did,\n userId: config.userId,\n hasPassword: !!config.password,\n hasPassToken: !!config.passToken,\n debug: config.debug,\n timeout: config.timeout,\n speakerControl:config.speakerControl\n });\n\n assert(!!did, '❌ Speaker 缺少 did 参数');\n assert(\n !!config.passToken || (!!config.userId && !!config.password),\n '❌ Speaker 缺少 passToken 或 userId 和 password',\n );\n\n Debugger.debug = config.debug ?? false;\n this._speakerControl = config.speakerControl ?? 'mina';\n\n const serviceConfig = {\n ...config,\n did,\n timeout: Math.max(1000, config.timeout ?? 5000),\n };\n\n console.log('🔌 正在连接 MiNA 服务...');\n this.MiNA = (await getMiService({ ...serviceConfig, service: 'mina' })) as MiNA | undefined;\n console.log('🔌 查询 MiNA 服务结果:', { mina: !!this.MiNA });\n\n // MiOT 对于音箱设备是可选的,只记录警告不阻断\n console.log('🔌 正在连接 MIoT 服务...');\n this.MiOT = (await getMiService({ ...serviceConfig, service: 'miot' })) as MIoT | undefined;\n console.log('🔌 查询 MIoT 服务结果:', { miot: !!this.MiOT });\n\n // 对于音箱设备,MiNA 是必需的,MiOT 是可选的\n assert(!!this.MiNA, '❌ 初始化 MiNA 服务失败');\n if (!this.MiOT) {\n console.warn('⚠️ MIoT 服务初始化失败,部分设备控制功能可能不可用');\n }\n\n if (Debugger.debug) {\n const device: any = this.MiNA?.account?.device;\n console.debug(\n '🐛 设备信息:',\n JSON.stringify(\n {\n name: device?.name,\n desc: device?.desc,\n model: device?.model,\n rom: device?.extra?.fw_version,\n },\n null,\n 2,\n ),\n );\n }\n\n this._initialized = true;\n console.log('✅ MiService 初始化成功');\n console.log(`🔊 音箱控制方式:${this._speakerControl}`);\n\n // 初始化成功后发送 TTS 播报(如果启用了启动播报)\n const announceOnStart = config.announceOnStart ?? true;\n if (announceOnStart) {\n const startupMessage = config.startupMessage ?? '您的小龙虾已上线,随时为您服务';\n try {\n console.log('🔊 正在发送启动播报:', startupMessage);\n await this.play(startupMessage);\n console.log('✅ 启动播报发送成功');\n } catch (e: any) {\n console.warn('⚠️ 启动播报发送失败:', e?.message || e);\n }\n }\n\n return true;\n } catch (err: any) {\n console.error('❌ 初始化失败:',{err:err,message: err.message});\n return false;\n } finally {\n this._initializing = false;\n }\n }\n\n /**\n * 获取设备列表\n */\n async getDevices(config: MiServiceConfig): Promise<Array<{ did: string; name: string; model?: string }>> {\n try {\n // 临时初始化以获取设备列表\n const tempService = await getMiService({ ...config, service: 'mina', relogin: false });\n if (!tempService) {\n return [];\n }\n\n const devices = await (tempService as MiNA).getDevices();\n return (devices || []).map((d: any) => ({\n did: d.name || d.alias || d.deviceID,\n name: d.alias || d.name,\n model: d.model,\n }));\n } catch (err) {\n console.error('❌ 获取设备列表失败:', err);\n return [];\n }\n }\n\n /**\n * 重新登录\n */\n async relogin(config: MiServiceConfig, did: string): Promise<boolean> {\n this._initialized = false;\n return this.init(config, did);\n }\n\n /**\n * 唤醒小爱音箱(进入监听状态)\n * 使用 MIoT spec 调用唤醒动作 (siid=5, aiid=3)\n * 基于 mi-gpt 项目的实现\n */\n async wakeUp(): Promise<boolean> {\n if (!this.MiOT) {\n console.warn('⚠️ MIoT 服务不可用,无法唤醒音箱');\n return false;\n }\n try {\n const result = await this.MiOT.doAction(5, 3, []);\n return result;\n } catch (e: any) {\n console.warn('⚠️ 唤醒音箱失败:', e?.message || e);\n return false;\n }\n }\n\n /**\n * 退出监听状态(取消唤醒)\n * 使用 MiNA 暂停使小爱退出监听\n */\n async unWakeUp(): Promise<void> {\n if (this.MiNA) {\n await this.MiNA.pause().catch(() => {});\n }\n }\n}\n\nexport const MiService = new _MiService();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { MiMoTTS } from './tts/mimo.js';
|
|
2
|
+
|
|
3
|
+
interface IPlayOptions {
|
|
4
|
+
text?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
duration?: number;
|
|
7
|
+
}
|
|
8
|
+
interface IPlayResult {
|
|
9
|
+
success: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
duration?: number;
|
|
12
|
+
}
|
|
13
|
+
declare class _MiSpeaker {
|
|
14
|
+
/** MiMo TTS 实例(延迟注入) */
|
|
15
|
+
private _mimoTTS;
|
|
16
|
+
/**
|
|
17
|
+
* 注入 MiMo TTS 提供者(自动清理旧实例,防止端口泄漏)
|
|
18
|
+
*/
|
|
19
|
+
setMiMoTTS(tts: MiMoTTS): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* 清理 MiMo TTS 资源(释放端口和临时文件)
|
|
22
|
+
*/
|
|
23
|
+
cleanupMiMoTTS(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* 播放文字、音频链接
|
|
26
|
+
* 优先使用 MiMo TTS(如果已配置),否则回退到小米原生 TTS
|
|
27
|
+
*/
|
|
28
|
+
play(options: IPlayOptions): Promise<IPlayResult>;
|
|
29
|
+
/**
|
|
30
|
+
* 自定义 TTS 播放前的抑制处理
|
|
31
|
+
* 暂停当前播放,防止小爱将 TTS 音频误识别为用户语音指令
|
|
32
|
+
*/
|
|
33
|
+
private _suppressBeforeCustomTTS;
|
|
34
|
+
/**
|
|
35
|
+
* 设置音量
|
|
36
|
+
*/
|
|
37
|
+
setVolume(volume: number): Promise<IPlayResult>;
|
|
38
|
+
/**
|
|
39
|
+
* 获取音量
|
|
40
|
+
*/
|
|
41
|
+
getVolume(): Promise<number | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* 暂停播放
|
|
44
|
+
*/
|
|
45
|
+
pause(): Promise<IPlayResult>;
|
|
46
|
+
/**
|
|
47
|
+
* 停止播放
|
|
48
|
+
*/
|
|
49
|
+
stop(): Promise<IPlayResult>;
|
|
50
|
+
/**
|
|
51
|
+
* 播放或暂停
|
|
52
|
+
*/
|
|
53
|
+
playOrPause(): Promise<IPlayResult>;
|
|
54
|
+
/**
|
|
55
|
+
* 中断小爱音箱的运行(重启设备)
|
|
56
|
+
* 注意:重启需要大约 1-2s 的时间,在此期间无法使用小爱音箱自带的 TTS 服务
|
|
57
|
+
*/
|
|
58
|
+
abortXiaoAI(): Promise<boolean>;
|
|
59
|
+
}
|
|
60
|
+
declare const MiSpeaker: _MiSpeaker;
|
|
61
|
+
|
|
62
|
+
export { type IPlayOptions, type IPlayResult, MiSpeaker };
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { networkInterfaces } from "node:os";
|
|
2
|
+
import { MiService } from "./service.js";
|
|
3
|
+
function getHostLANIP() {
|
|
4
|
+
const envIP = process.env.MIMO_TTS_HOST_IP;
|
|
5
|
+
if (envIP) return envIP;
|
|
6
|
+
const nets = networkInterfaces();
|
|
7
|
+
for (const name of Object.keys(nets)) {
|
|
8
|
+
for (const net of nets[name] ?? []) {
|
|
9
|
+
if (net.internal || net.family !== "IPv4") continue;
|
|
10
|
+
if (net.address.startsWith("172.17.")) continue;
|
|
11
|
+
if (net.address.startsWith("192.168.") || net.address.startsWith("10.") || net.address.startsWith("172.16.") || net.address.startsWith("172.18.") || net.address.startsWith("172.19.") || net.address.startsWith("172.2") || net.address.startsWith("172.3")) {
|
|
12
|
+
return net.address;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const name of Object.keys(nets)) {
|
|
17
|
+
for (const net of nets[name] ?? []) {
|
|
18
|
+
if (!net.internal && net.family === "IPv4") return net.address;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return "127.0.0.1";
|
|
22
|
+
}
|
|
23
|
+
class _MiSpeaker {
|
|
24
|
+
/** MiMo TTS 实例(延迟注入) */
|
|
25
|
+
_mimoTTS = null;
|
|
26
|
+
/**
|
|
27
|
+
* 注入 MiMo TTS 提供者(自动清理旧实例,防止端口泄漏)
|
|
28
|
+
*/
|
|
29
|
+
async setMiMoTTS(tts) {
|
|
30
|
+
if (this._mimoTTS) {
|
|
31
|
+
console.log("\u{1F50A} \u6E05\u7406\u65E7 MiMo TTS \u5B9E\u4F8B...");
|
|
32
|
+
await this._mimoTTS.destroy().catch(() => {
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
this._mimoTTS = tts;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 清理 MiMo TTS 资源(释放端口和临时文件)
|
|
39
|
+
*/
|
|
40
|
+
async cleanupMiMoTTS() {
|
|
41
|
+
if (this._mimoTTS) {
|
|
42
|
+
console.log("\u{1F50A} \u6E05\u7406 MiMo TTS \u8D44\u6E90...");
|
|
43
|
+
await this._mimoTTS.destroy().catch(() => {
|
|
44
|
+
});
|
|
45
|
+
this._mimoTTS = null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 播放文字、音频链接
|
|
50
|
+
* 优先使用 MiMo TTS(如果已配置),否则回退到小米原生 TTS
|
|
51
|
+
*/
|
|
52
|
+
async play(options) {
|
|
53
|
+
const { text, url } = options;
|
|
54
|
+
console.log(`\u{1F50A} Speaker.play called: text=${text?.slice(0, 50)}..., url=${url}`);
|
|
55
|
+
if (!MiService.MiNA && !MiService.MiOT) {
|
|
56
|
+
console.error("\u274C Speaker.play failed: MiNA/MiOT service not initialized");
|
|
57
|
+
return { success: false, error: "MiNA/MiOT service not initialized" };
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
let result;
|
|
61
|
+
if (url) {
|
|
62
|
+
if (!MiService.MiNA) {
|
|
63
|
+
return { success: false, error: "MiNA service not initialized for URL playback" };
|
|
64
|
+
}
|
|
65
|
+
console.log(`\u{1F50A} Playing URL: ${url}`);
|
|
66
|
+
result = await MiService.MiNA.play({ url });
|
|
67
|
+
} else if (text) {
|
|
68
|
+
if (this._mimoTTS) {
|
|
69
|
+
console.log(`\u{1F50A} Using MiMo TTS for: ${text.slice(0, 30)}...`);
|
|
70
|
+
const ttsResult = await this._mimoTTS.synthesize(text);
|
|
71
|
+
if (ttsResult.success && ttsResult.url) {
|
|
72
|
+
await this._suppressBeforeCustomTTS();
|
|
73
|
+
const hostIP = getHostLANIP();
|
|
74
|
+
const externalUrl = ttsResult.url.replace(/0\.0\.0\.0|127\.0\.0\.1|localhost/g, hostIP);
|
|
75
|
+
console.log(`\u{1F50A} MiMo TTS \u64AD\u653E: ${externalUrl} (\u65F6\u957F: ${ttsResult.duration?.toFixed(1)}s)`);
|
|
76
|
+
const duration = ttsResult.duration;
|
|
77
|
+
result = await MiService.MiNA.play({ url: externalUrl });
|
|
78
|
+
console.log(`\u{1F50A} MiMo TTS play result: ${result}`);
|
|
79
|
+
return { success: result, duration };
|
|
80
|
+
} else {
|
|
81
|
+
console.warn("\u26A0\uFE0F MiMo TTS \u5931\u8D25\uFF0C\u56DE\u9000\u5230\u539F\u751F TTS:", ttsResult.error);
|
|
82
|
+
result = await MiService.play(text);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
console.log(`\u{1F50A} Using native TTS for: ${text.slice(0, 30)}...`);
|
|
86
|
+
result = await MiService.play(text);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
return { success: false, error: "text or url is required" };
|
|
90
|
+
}
|
|
91
|
+
if (result) {
|
|
92
|
+
return { success: true, duration: options.duration };
|
|
93
|
+
} else {
|
|
94
|
+
return { success: false, error: "Playback failed" };
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return { success: false, error: err.message };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 自定义 TTS 播放前的抑制处理
|
|
102
|
+
* 暂停当前播放,防止小爱将 TTS 音频误识别为用户语音指令
|
|
103
|
+
*/
|
|
104
|
+
async _suppressBeforeCustomTTS() {
|
|
105
|
+
if (MiService.MiNA) {
|
|
106
|
+
await MiService.MiNA.pause().catch(() => {
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 设置音量
|
|
112
|
+
*/
|
|
113
|
+
async setVolume(volume) {
|
|
114
|
+
if (!MiService.MiNA && !MiService.MiOT) {
|
|
115
|
+
return { success: false, error: "MiNA/MiOT service not initialized" };
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
if (MiService.MiNA) {
|
|
119
|
+
const result = await MiService.MiNA.setVolume(volume);
|
|
120
|
+
if (result) {
|
|
121
|
+
return { success: true };
|
|
122
|
+
} else {
|
|
123
|
+
return { success: false, error: "Failed to set volume" };
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
return { success: false, error: "MiNA service required for volume control" };
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
return { success: false, error: err.message };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* 获取音量
|
|
134
|
+
*/
|
|
135
|
+
async getVolume() {
|
|
136
|
+
if (!MiService.MiNA) {
|
|
137
|
+
return void 0;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
return await MiService.MiNA.getVolume();
|
|
141
|
+
} catch {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 暂停播放
|
|
147
|
+
*/
|
|
148
|
+
async pause() {
|
|
149
|
+
if (!MiService.MiNA && !MiService.MiOT) {
|
|
150
|
+
return { success: false, error: "MiNA/MiOT service not initialized" };
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
if (MiService.MiNA) {
|
|
154
|
+
const result = await MiService.MiNA.pause();
|
|
155
|
+
return { success: result };
|
|
156
|
+
} else {
|
|
157
|
+
return { success: false, error: "MiNA service required for pause" };
|
|
158
|
+
}
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return { success: false, error: err.message };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 停止播放
|
|
165
|
+
*/
|
|
166
|
+
async stop() {
|
|
167
|
+
if (!MiService.MiNA && !MiService.MiOT) {
|
|
168
|
+
return { success: false, error: "MiNA/MiOT service not initialized" };
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
if (MiService.MiNA) {
|
|
172
|
+
const result = await MiService.MiNA.stop();
|
|
173
|
+
return { success: result };
|
|
174
|
+
} else {
|
|
175
|
+
return { success: false, error: "MiNA service required for stop" };
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return { success: false, error: err.message };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 播放或暂停
|
|
183
|
+
*/
|
|
184
|
+
async playOrPause() {
|
|
185
|
+
if (!MiService.MiNA && !MiService.MiOT) {
|
|
186
|
+
return { success: false, error: "MiNA/MiOT service not initialized" };
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
if (MiService.MiNA) {
|
|
190
|
+
const result = await MiService.MiNA.playOrPause();
|
|
191
|
+
return { success: result };
|
|
192
|
+
} else {
|
|
193
|
+
return { success: false, error: "MiNA service required for playOrPause" };
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
return { success: false, error: err.message };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* 中断小爱音箱的运行(重启设备)
|
|
201
|
+
* 注意:重启需要大约 1-2s 的时间,在此期间无法使用小爱音箱自带的 TTS 服务
|
|
202
|
+
*/
|
|
203
|
+
async abortXiaoAI() {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const MiSpeaker = new _MiSpeaker();
|
|
208
|
+
export {
|
|
209
|
+
MiSpeaker
|
|
210
|
+
};
|
|
211
|
+
//# sourceMappingURL=speaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/speaker.ts"],"sourcesContent":["import { networkInterfaces } from 'node:os';\nimport { MiService } from './service.js';\nimport type { MiMoTTS } from './tts/mimo.js';\n\n/**\n * 获取小爱音箱可访问的主机 IP\n * 优先使用环境变量 MIMO_TTS_HOST_IP,否则自动检测\n */\nfunction getHostLANIP(): string {\n // 优先使用环境变量(适用于云服务器场景)\n const envIP = process.env.MIMO_TTS_HOST_IP;\n if (envIP) return envIP;\n\n const nets = networkInterfaces();\n for (const name of Object.keys(nets)) {\n for (const net of nets[name] ?? []) {\n if (net.internal || net.family !== 'IPv4') continue;\n if (net.address.startsWith('172.17.')) continue;\n if (\n net.address.startsWith('192.168.') ||\n net.address.startsWith('10.') ||\n net.address.startsWith('172.16.') ||\n net.address.startsWith('172.18.') ||\n net.address.startsWith('172.19.') ||\n net.address.startsWith('172.2') ||\n net.address.startsWith('172.3')\n ) {\n return net.address;\n }\n }\n }\n for (const name of Object.keys(nets)) {\n for (const net of nets[name] ?? []) {\n if (!net.internal && net.family === 'IPv4') return net.address;\n }\n }\n return '127.0.0.1';\n}\n\nexport interface IPlayOptions {\n text?: string;\n url?: string;\n duration?: number;\n}\n\nexport interface IPlayResult {\n success: boolean;\n error?: string;\n duration?: number;\n}\n\nclass _MiSpeaker {\n /** MiMo TTS 实例(延迟注入) */\n private _mimoTTS: MiMoTTS | null = null;\n\n /**\n * 注入 MiMo TTS 提供者(自动清理旧实例,防止端口泄漏)\n */\n async setMiMoTTS(tts: MiMoTTS) {\n // 清理旧实例,释放端口\n if (this._mimoTTS) {\n console.log('🔊 清理旧 MiMo TTS 实例...');\n await this._mimoTTS.destroy().catch(() => {});\n }\n this._mimoTTS = tts;\n }\n\n /**\n * 清理 MiMo TTS 资源(释放端口和临时文件)\n */\n async cleanupMiMoTTS() {\n if (this._mimoTTS) {\n console.log('🔊 清理 MiMo TTS 资源...');\n await this._mimoTTS.destroy().catch(() => {});\n this._mimoTTS = null;\n }\n }\n\n /**\n * 播放文字、音频链接\n * 优先使用 MiMo TTS(如果已配置),否则回退到小米原生 TTS\n */\n async play(options: IPlayOptions): Promise<IPlayResult> {\n const { text, url } = options;\n\n console.log(`🔊 Speaker.play called: text=${text?.slice(0, 50)}..., url=${url}`);\n\n if (!MiService.MiNA && !MiService.MiOT) {\n console.error('❌ Speaker.play failed: MiNA/MiOT service not initialized');\n return { success: false, error: 'MiNA/MiOT service not initialized' };\n }\n\n try {\n let result: boolean;\n if (url) {\n // URL 播放只支持 MiNA\n if (!MiService.MiNA) {\n return { success: false, error: 'MiNA service not initialized for URL playback' };\n }\n console.log(`🔊 Playing URL: ${url}`);\n result = await MiService.MiNA.play({ url });\n } else if (text) {\n // 优先使用 MiMo TTS\n if (this._mimoTTS) {\n console.log(`🔊 Using MiMo TTS for: ${text.slice(0, 30)}...`);\n const ttsResult = await this._mimoTTS.synthesize(text);\n if (ttsResult.success && ttsResult.url) {\n await this._suppressBeforeCustomTTS();\n const hostIP = getHostLANIP();\n const externalUrl = ttsResult.url.replace(/0\\.0\\.0\\.0|127\\.0\\.0\\.1|localhost/g, hostIP);\n console.log(`🔊 MiMo TTS 播放: ${externalUrl} (时长: ${ttsResult.duration?.toFixed(1)}s)`);\n const duration = ttsResult.duration; // 保存 duration\n result = await MiService.MiNA!.play({ url: externalUrl });\n console.log(`🔊 MiMo TTS play result: ${result}`);\n // 无论播放是否成功,都返回 duration\n return { success: result, duration };\n } else {\n console.warn('⚠️ MiMo TTS 失败,回退到原生 TTS:', ttsResult.error);\n result = await MiService.play(text);\n }\n } else {\n // 文字播报使用配置的播放方式\n console.log(`🔊 Using native TTS for: ${text.slice(0, 30)}...`);\n result = await MiService.play(text);\n }\n } else {\n return { success: false, error: 'text or url is required' };\n }\n\n if (result) {\n return { success: true, duration: options.duration };\n } else {\n return { success: false, error: 'Playback failed' };\n }\n } catch (err: any) {\n return { success: false, error: err.message };\n }\n }\n\n /**\n * 自定义 TTS 播放前的抑制处理\n * 暂停当前播放,防止小爱将 TTS 音频误识别为用户语音指令\n */\n private async _suppressBeforeCustomTTS() {\n if (MiService.MiNA) {\n await MiService.MiNA.pause().catch(() => {});\n }\n }\n\n /**\n * 设置音量\n */\n async setVolume(volume: number): Promise<IPlayResult> {\n if (!MiService.MiNA && !MiService.MiOT) {\n return { success: false, error: 'MiNA/MiOT service not initialized' };\n }\n\n try {\n // 音量控制只支持 MiNA\n if (MiService.MiNA) {\n const result = await MiService.MiNA.setVolume(volume);\n if (result) {\n return { success: true };\n } else {\n return { success: false, error: 'Failed to set volume' };\n }\n } else {\n return { success: false, error: 'MiNA service required for volume control' };\n }\n } catch (err: any) {\n return { success: false, error: err.message };\n }\n }\n\n /**\n * 获取音量\n */\n async getVolume(): Promise<number | undefined> {\n if (!MiService.MiNA) {\n return undefined;\n }\n\n try {\n return await MiService.MiNA.getVolume();\n } catch {\n return undefined;\n }\n }\n\n /**\n * 暂停播放\n */\n async pause(): Promise<IPlayResult> {\n if (!MiService.MiNA && !MiService.MiOT) {\n return { success: false, error: 'MiNA/MiOT service not initialized' };\n }\n\n try {\n // 暂停只支持 MiNA\n if (MiService.MiNA) {\n const result = await MiService.MiNA.pause();\n return { success: result };\n } else {\n return { success: false, error: 'MiNA service required for pause' };\n }\n } catch (err: any) {\n return { success: false, error: err.message };\n }\n }\n\n /**\n * 停止播放\n */\n async stop(): Promise<IPlayResult> {\n if (!MiService.MiNA && !MiService.MiOT) {\n return { success: false, error: 'MiNA/MiOT service not initialized' };\n }\n\n try {\n // 停止只支持 MiNA\n if (MiService.MiNA) {\n const result = await MiService.MiNA.stop();\n return { success: result };\n } else {\n return { success: false, error: 'MiNA service required for stop' };\n }\n } catch (err: any) {\n return { success: false, error: err.message };\n }\n }\n\n /**\n * 播放或暂停\n */\n async playOrPause(): Promise<IPlayResult> {\n if (!MiService.MiNA && !MiService.MiOT) {\n return { success: false, error: 'MiNA/MiOT service not initialized' };\n }\n\n try {\n // 播放或暂停只支持 MiNA\n if (MiService.MiNA) {\n const result = await MiService.MiNA.playOrPause();\n return { success: result };\n } else {\n return { success: false, error: 'MiNA service required for playOrPause' };\n }\n } catch (err: any) {\n return { success: false, error: err.message };\n }\n }\n\n /**\n * 中断小爱音箱的运行(重启设备)\n * 注意:重启需要大约 1-2s 的时间,在此期间无法使用小爱音箱自带的 TTS 服务\n */\n async abortXiaoAI(): Promise<boolean> {\n // 无法通过 MiOT 中断小爱运行\n // 可以通过 MiOT 重启设备,但这会影响所有功能\n return false;\n }\n}\n\nexport const MiSpeaker = new _MiSpeaker();\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
interface MiMoTTSConfig {
|
|
2
|
+
/** MiMo API Key */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** MiMo API Base URL,默认 https://api.xiaomimimo.com/v1 */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** TTS 模型,默认 mimo-v2.5-tts */
|
|
7
|
+
model?: string;
|
|
8
|
+
/** 预设音色 ID,默认 mimo_default */
|
|
9
|
+
voice?: string;
|
|
10
|
+
/** 风格指令(放在 user role 中) */
|
|
11
|
+
style?: string;
|
|
12
|
+
/** 是否启用流式传输(减少首字延迟),默认 true */
|
|
13
|
+
stream?: boolean;
|
|
14
|
+
/** 本地服务器监听端口,0 = 自动分配 */
|
|
15
|
+
port?: number;
|
|
16
|
+
/** 本地服务器监听地址,默认 0.0.0.0 */
|
|
17
|
+
host?: string;
|
|
18
|
+
}
|
|
19
|
+
declare class MiMoTTS {
|
|
20
|
+
private _server;
|
|
21
|
+
private _serverUrl;
|
|
22
|
+
private _audioDir;
|
|
23
|
+
private _config;
|
|
24
|
+
private _ready;
|
|
25
|
+
constructor(config: MiMoTTSConfig);
|
|
26
|
+
/**
|
|
27
|
+
* 初始化:创建临时目录 + 启动 HTTP 服务器
|
|
28
|
+
*/
|
|
29
|
+
init(): Promise<boolean>;
|
|
30
|
+
/**
|
|
31
|
+
* 生成语音并返回可播放的 URL
|
|
32
|
+
*/
|
|
33
|
+
synthesize(text: string, options?: {
|
|
34
|
+
voice?: string;
|
|
35
|
+
style?: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
url: string;
|
|
38
|
+
success: boolean;
|
|
39
|
+
error?: string;
|
|
40
|
+
duration?: number;
|
|
41
|
+
}>;
|
|
42
|
+
/**
|
|
43
|
+
* 清理临时文件和关闭服务器
|
|
44
|
+
*/
|
|
45
|
+
destroy(): Promise<void>;
|
|
46
|
+
get ready(): boolean;
|
|
47
|
+
get serverUrl(): string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { MiMoTTS, type MiMoTTSConfig };
|