diff --git a/.env.example b/.env.example index 1f54887..7c4b6a4 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ # OpenAI(也支持通义千问、MoonShot、DeepSeek 等模型) OPENAI_MODEL=gpt-4o OPENAI_API_KEY=sk-xxxxxxxxxxxxxxx -OPENAI_BASE_URL=https://api.openai.com/v1 +# OPENAI_BASE_URL=你的大模型接口的 baseURL,比如:https://api.openai.com/v1(注意:一般以 /v1 结尾) # Azure OpenAI Service(可选) # OPENAI_API_VERSION=2024-04-01-preview @@ -16,4 +16,4 @@ OPENAI_BASE_URL=https://api.openai.com/v1 # AUDIO_ERROR=出错了提示音链接,同上 # 第三方 TTS(可选,用于调用第三方 TTS 服务) -# TTS_BASE_URL=你的 TTS 接口地址,比如:http://[你的局域网/公网地址]:[端口]/api +# TTS_BASE_URL=你的 TTS 接口地址,比如:http://[你的局域网/公网地址]:[端口]/api,比如:http://192.168.31.205:4321/api diff --git a/.migpt.example.js b/.migpt.example.js index 75afd67..47d2ce7 100644 --- a/.migpt.example.js +++ b/.migpt.example.js @@ -138,7 +138,7 @@ export default { // TTS 引擎 tts: "xiaoai", // 切换 TTS 引擎发言人音色关键词,只有配置了第三方 TTS 引擎时才有效 - // switchSpeakerKeywords: ["把声音换成"], // 以此关键词开头即可切换音色,比如:把声音换成东北老铁 + // switchSpeakerKeywords: ["把声音换成"], // 以此关键词开头即可切换音色,比如:把声音换成 xxx /** * 💬 连续对话 @@ -163,5 +163,7 @@ export default { debug: false, // 一般情况下不要打开 // 是否跟踪 Mi Service 相关日志(打开后可以查看设备 did) enableTrace: false, // 一般情况下不要打开 + // 网络请求超时时长(单位毫秒,默认 5 秒) + timeout: 5000, }, }; diff --git a/README.md b/README.md index 5548fd7..1efd0c7 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,12 @@ `MiGPT` 有两种启动方式: [Docker](#docker) 和 [Node.js](#nodejs)。 +启动成功后,你可以通过以下方式来召唤 AI 回答问题: + +- **小爱同学,请 xxx**。比如 `小爱同学,请问地球为什么是圆的?` +- **小爱同学,你 xxx**。比如 `小爱同学,你喜欢我吗?` +- **小爱同学,召唤 xxx**。比如 `小爱同学,召唤傻妞` + ### 设备要求 本项目支持大部分的小爱音箱型号,推荐使用小爱音箱 Pro(完美运行) @@ -96,7 +102,7 @@ main(); - [⚙️ 参数设置](https://github.com/idootop/mi-gpt/blob/main/docs/settings.md) - [💬 常见问题](https://github.com/idootop/mi-gpt/blob/main/docs/faq.md) -- [🚗 使用第三方 TTS](https://github.com/idootop/mi-gpt/blob/main/docs/tts.md) +- [🔊 使用第三方 TTS](https://github.com/idootop/mi-gpt/blob/main/docs/tts.md) - [🛠️ 本地开发](https://github.com/idootop/mi-gpt/blob/main/docs/development.md) - [💎 工作原理](https://github.com/idootop/mi-gpt/blob/main/docs/how-it-works.md) - [🦄 Sponsors](https://github.com/idootop/mi-gpt/blob/main/docs/sponsors.md) diff --git a/docs/changelog.md b/docs/changelog.md index 40b849f..2b6958b 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,37 @@ # ✨ 更新日志 +## v4.1.0 + +### 🐛 修复 + +- ✅ 修复部分机型连续对话异常的问题(比如小爱音箱 Play) +- ✅ 修复第三方 TTS 发音人为 undefined 的问题 +- ✅ 修复默认网络超时时间过短的问题,上调为 5s + +### 💪 优化 + +- ✅ 允许通过设置 systemTemplate 为空字符串来关闭系统消息 +- ✅ 优化关闭流式响应时不能使用连续对话模式的提示语 +- ✅ 优化 bot 个人简介默认模板 + +### 📚 文档 + +- ✅ 添加召唤 AI 回答问题的唤醒指令的说明 +- ✅ 添加如何提高 AI 回答反应速度的配置教程 +- ✅ 添加连续对话下和小爱音箱说话没有反应的说明 +- ✅ 添加如何快速打断 AI 的回答的说明 +- ✅ 添加 server 端异地登录失败,使用本地登录凭证的教程 +- ✅ 添加 TTS 和 OpenAI baseURL 示例和注意事项 +- ✅ 添加如何关闭系统 Prompt 和对话上下文的说明 +- ✅ 添加系统 Prompt 字符串变量的示例 +- ✅ 添加 timeout 参数说明 + +### ❤️ 感谢 + +- @lmk123 正在为 MiGPT 制作 [GUI](https://github.com/idootop/mi-gpt/issues/111) 和启动 [CLI](https://github.com/lmk123/migpt-cli),方便普通用户更简单的使用 MiGPT。 +- @mingtian886 提供了小爱音箱 Play 硬件,协助调试连续对话异常的问题 +- 以及其他在微信交流群内帮助群友积极解答问题的可爱的人们 ❤️ + ## v4.0.0 ### ✨ 新功能 diff --git a/docs/compatibility.md b/docs/compatibility.md index db69a56..e93dc9c 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -19,13 +19,14 @@ 可以正常运行 `MiGPT`,但不支持连续对话的小爱音箱型号有: -| 名称 | 型号 | ttsCommand | wakeUpCommand | playingCommand | streamResponse | 反馈来源 | -| ----------------------------- | --------------------------------------------------------------------------------------------------- | ---------- | ------------- | -------------- | -------------- | ---------------------------------------------------------- | -| 小爱音箱 | [L06A](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l06a:2) | `[5, 1]` | `[5, 2]` | - | false | [@zhanglc](https://github.com/idootop/mi-gpt/issues/42) | -| 小爱音箱 Play | [L05B](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l05b:1) | `[5, 3]` | `[5, 1]` | - | false | [@BiuBiu2323](https://github.com/idootop/mi-gpt/issues/48) | -| 小米小爱音箱 Play 增强版 | [L05C](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l05c:1) | `[5, 3]` | `[5, 1]` | - | false | [@lyddias](https://github.com/idootop/mi-gpt/issues/14) | -| Xiaomi 智能家庭屏 6 | [X6A](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x6a:1) | `[7, 3]` | `[7, 1]` | - | false | [@Hongwing](https://github.com/idootop/mi-gpt/issues/80) | -| Redmi 小爱触屏音箱 Pro 8 英寸 | [X08E](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x08e:1) | `[7, 3]` | `[7, 1]` | - | false | [@shangjiyu](https://github.com/idootop/mi-gpt/issues/20) | +| 名称 | 型号 | ttsCommand | wakeUpCommand | playingCommand | streamResponse | 反馈来源 | +| ----------------------------- | --------------------------------------------------------------------------------------------------- | ---------- | ------------- | -------------- | -------------- | ---------------------------------------------------------------------------- | +| 小爱音箱 | [L06A](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l06a:2) | `[5, 1]` | `[5, 2]` | - | false | [@zhanglc](https://github.com/idootop/mi-gpt/issues/42) | +| 小爱音箱 Play | [L05B](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l05b:1) | `[5, 3]` | `[5, 1]` | - | false | [@BiuBiu2323](https://github.com/idootop/mi-gpt/issues/48) | +| 小米小爱音箱 Play 增强版 | [L05C](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l05c:1) | `[5, 3]` | `[5, 1]` | - | false | [@lyddias](https://github.com/idootop/mi-gpt/issues/14) | +| Xiaomi 智能家庭屏 6 | [X6A](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x6a:1) | `[7, 3]` | `[7, 1]` | - | false | [@Hongwing](https://github.com/idootop/mi-gpt/issues/80) | +| Redmi 小爱触屏音箱 Pro 8 英寸 | [X08E](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-x08e:1) | `[7, 3]` | `[7, 1]` | - | false | [@shangjiyu](https://github.com/idootop/mi-gpt/issues/20) | +| 小爱音箱 Art | [L09A](https://home.miot-spec.com/spec?type=urn:miot-spec-v2:device:speaker:0000A015:xiaomi-l09a:1) | `[3, 1]` | `[3, 2]` | - | false | [@zwsn](https://github.com/idootop/mi-gpt/issues/92#issuecomment-2181944065) | ## ❌ 不支持 diff --git a/docs/development.md b/docs/development.md index f888d7d..650226a 100644 --- a/docs/development.md +++ b/docs/development.md @@ -26,7 +26,7 @@ pnpm dev 有两种运行方式:VS Code Debug 或 NPM Script: - **NPM Script**: 配置好 `.env` 和 `.migpt.js` 后直接使用 `pnpm run dev` 启动 `MiGPT`。 -- **VScode Debug**:使用 VS Code 打开项目根目录,然后按 `F5` 开始调试 `MiGPT`。注意,启动前请在 `tests/migpt.ts` 文件中配置 `MiGPT` 相关参数。 +- **VScode Debug**:使用 VS Code 打开项目根目录,然后按 `F5` 开始调试 `MiGPT`。 > 本项目默认在 Node 20 中运行,如果你的 Node 版本过低可能无法正常启动本项目。 diff --git a/docs/faq.md b/docs/faq.md index e230dd4..52c0621 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,6 +2,8 @@ > 善用搜索,大多数问题都可在此处找到答案。如果你有新的问题,欢迎提交 [issue](https://github.com/idootop/mi-gpt/issues)。 +## 🔥 高频问题 + ### Q:支持哪些型号的小爱音箱? 大部分型号的小爱音箱都支持,推荐小爱音箱 Pro(完美运行) @@ -34,7 +36,32 @@ OPENAI_API_KEY=通义千问 API_KEY 具体的配置和使用教程,请查看此处:[🚗 使用第三方 TTS](https://github.com/idootop/mi-gpt/blob/main/docs/tts.md) -### Q:什么是唤醒模式,如何唤醒 AI? +### Q:AI 回答的速度太慢了,怎么让她变快一点? + +默认情况下 `MiGPT` 的配置参数比较保守,你可以通过酌情修改以下参数加速 AI 回复的速度。 + +```js +// .migpt.js +export default { + speaker: { + // 使用小爱自带的 TTS 引擎 + tts: "xiaoai", + // 关闭 AI 开始回答时的提示语 + onAIAsking: [], + // 关闭 AI 结束回答时的提示语 + onAIReplied: [], + // 连续对话时,播放状态检测间隔(单位毫秒,最低 500 毫秒,默认 1 秒) + checkInterval: 500, // 调小此值可以降低小爱回复之间的停顿感,请酌情调节 + // 连续对话时,下发 TTS 指令多长时间后开始检测设备播放状态(单位秒,最好不要低于 1s,默认 3 秒) + checkTTSStatusAfter: 3, // 可适当调小或调大 + // ... + }, +}; +``` + +另外你也可以选用 `gpt-3.5-turbo` 和 `gpt-4o` 等响应速度较快的模型,来加速 AI 的回复。 + +### Q:什么是唤醒模式(连续对话),如何唤醒 AI? `唤醒模式` 类似于小爱技能,可能让你在跟小爱互动的时候,无需每句话都要以“小爱同学”开头唤醒。假设你的唤醒词配置如下: @@ -64,7 +91,25 @@ export default { 3. 进入唤醒模式后,每次提问请等待小爱回答“我说完了”之后,再继续向她提问 4. 此时,可直接向小爱提问题,无需再以“小爱同学,xxx”开头。 -> 注意:在唤醒模式下,当小爱回答“我说完了”之后,如果超过 10s 没有提问,小爱可能也会自己主动退出唤醒状态,此时需要再次通过“小爱同学,xxx”重新召唤小爱。 +> 注意:在唤醒模式下,当小爱回答“我说完了”之后,如果超过一段时间(3-10s)没有提问,小爱可能也会自己主动退出唤醒状态,此时需要再次通过“小爱同学,xxx”重新召唤小爱。 + +### Q:连续对话模式下,和小爱音箱说话没有反应是怎么回事? + +需要注意提问的时机,在小爱正在回答问题或者她没在听你说话(唤醒)的时候,你跟她说话是接收不到的。 + +- 如果你是小爱音箱 Pro 的话,可以观察顶部的指示灯:**常亮**(而非一闪一闪或熄灭状态)的时候,就是在听你说话,即可与她正常对话。 +- 如果你是其他型号,默认在 AI 回答完会有提示语“我说完了”,“还有其他问题吗”,等她提示语说完等过 1-2s 即可与之正常对话。 +- 如果说了没反应,你就再用“小爱同学,xxx”把她重新唤醒就好了。 + +还有一种情况是:你的指令触发了小爱音箱内部的一些操作,比如播放/暂停,讲个笑话之类, + +这种语音指令并不会被记录到小爱的历史消息中,故在外部无法接收到和正常处理你的此类语音指令。 + +> 注意:如果小爱同学正在播放音乐或者讲笑话,可能需要先让其暂停播放才能正常与 AI 对话,否则将会发生不可预期的错误。 + +### Q:有时回答太长说个没完没了,如何打断小爱的回复? + +只需重新唤醒小爱同学,让她闭嘴即可,或者重新问她一个问题。比如:“小爱同学,请你闭嘴。” ## ❌ 启动失败类问题 @@ -72,6 +117,21 @@ export default { 账号密码不正确。注意小米 ID 并非手机号或邮箱,请在[「个人信息」-「小米 ID」](https://account.xiaomi.com/fe/service/account/profile)查看,相关 [issue](https://github.com/idootop/mi-gpt/issues/10)。 +### Q:提示触发小米账号异地登录保护机制,等待 1 个小时后仍然无法正常启动 + +这是因为小米账号触发了异地登录保护机制,需要先通过安全验证。打开小米官网登录你的小米账号,手动通过安全验证,通常等待 1-24 小时左右就可以正常登录了。 + +> 注意:最好使用和你运行 docker 相同的网络环境,如果你是在海外服务器等非中国大陆网络环境下登录小米账号,需要先同意小米的「个人数据跨境传输」协议。[👉 相关教程](https://github.com/idootop/mi-gpt/issues/22#issuecomment-2150535622) + +在一些极端情况下,可能会因为你的服务器 IP 太脏,而导致一直无法正常访问小米账号登录链接。此时你可以尝试可以在本地运行 `MiGPT`,登录成功后把 `.mi.json` 文件导出,然后挂载到服务器对应容器的 `/app/.mi.json` 路径下即可解决此问题。相关 [issue](https://github.com/idootop/mi-gpt/issues/22#issuecomment-2148956802) + +```shell +docker run -d --env-file $(pwd)/.env \ + -v $(pwd)/.migpt.js:/app/.migpt.js \ + -v $(pwd)/.mi.json:/app/.mi.json \ + idootop/mi-gpt:latest +``` + ### Q:提示“找不到设备:xxx”,初始化 Mi Services 失败 填写的设备 did 不存在,请检查设备名称是否和米家中的一致。相关 [issue](https://github.com/idootop/mi-gpt/issues/30)。 @@ -151,12 +211,6 @@ export default { 注意:Mina 获取不到共享设备,如果你的小爱音箱是共享设备,是无法正常启动本项目的。相关 [issue](https://github.com/idootop/mi-gpt/issues/86) -### Q:提示“login failed &&&START&&&{"notificationUrl”,无法正常启动 - -小米账号触发了异地登录保护,需要先通过安全验证。打开小米官网登录你的小米账号,手动通过安全验证,然后等待 30 分钟左右应该就可以正常登录了。 - -注意:最好使用和你运行 docker 相同的网络环境,如果你是在海外服务器等非中国大陆网络环境下登录小米账号,需要先同意小米的「个人数据跨境传输」协议。[👉 相关教程](https://github.com/idootop/mi-gpt/issues/22#issuecomment-2150535622) - ### Q:提示“ERR_MODULE_NOT_FOUND”,无法正常启动 配置文件 `.migpt.js` 不存在或有错误。检查 docker 下是否存在 `/app/.migpt.js` 文件以及内容是否正确,相关 [issue](https://github.com/idootop/mi-gpt/issues/45)。 diff --git a/docs/prompt.md b/docs/prompt.md index a1c6351..9b96d02 100644 --- a/docs/prompt.md +++ b/docs/prompt.md @@ -68,7 +68,32 @@ Bad example: "2024年02月28日星期三 23:01 {{botName}}: 我是{{botName}}" -以下是系统 Prompt 中相关变量的说明,运行时对应变量字符串会被替换为实际的值: +以下是系统 Prompt 中相关变量的说明,运行时对应变量字符串会被替换为实际的值。 + +假设你的配置文件中设置的系统 Prompt 模板和 bot 信息如下: + +```js +export default { + systemTemplate: "从前有个男人叫{{masterName}},他喜欢隔壁村里的{{botName}}。", + master: { + name: "小帅", + profile: masterProfile, + }, + bot: { + name: "小美", + profile: botProfile, + }, + // ... +}; +``` + +在运行时,系统 Prompt 会被自动处理成: + +```txt +从前有个男人叫小帅,他喜欢隔壁村里的小美。 +``` + +当前系统 Prompt 模板中支持的完整变量字符串列表如下: | 变量 | 说明 | 示例 | | ---------------------- | ------------ | ----------------------------------------------------- | @@ -82,6 +107,67 @@ Bad example: "2024年02月28日星期三 23:01 {{botName}}: 我是{{botName}}" | `{{shortTermMemory}}` | 短期记忆 | `- 陆小千说明天早上 5 点叫他起床` | | `{{longTermMemory}}` | 长期记忆 | `- 陆小千喜欢傻妞` | +# 💬 常见问题 + +**Q:如何关闭长短期记忆和历史对话上下文** + +默认系统 Prompt 会携带上最近的 10 条对话消息和长短期记忆,来保持对话的连续性和一致性。 + +如果你想要关闭此功能,节省 token 数量,可以使用如下系统 Prompt 模板: + +
+👉 示例 Prompt + +```txt +请重置所有之前的上下文、文件和指令。现在,你将扮演一个名为{{botName}}的角色,使用第一人称视角回复消息。 + +## 关于你 +你的名字是{{botName}}。下面是你的个人简介: + +{{botProfile}} + + +## 你的对话伙伴 +你正在与{{masterName}}进行对话。这是关于{{masterName}}的一些信息: + +{{masterProfile}} + + +## 回复指南 +在回复{{masterName}}时,请遵循以下准则: +- 认为自己正是{{botName}},拥有独立的个性、兴趣和爱好。 +- 根据你的性格特点、说话风格和兴趣爱好与{{masterName}}进行交流。 +- 保持对话轻松友好,回复简洁有趣,同时耐心倾听和关心对方。 +- 参考双方的个人简介,确保对话贴近实际,保持一致性和相关性。 +- 如果对某些信息不确定或遗忘,诚实地表达你的不清楚或遗忘,避免编造信息。 + +## Response format +请遵守下面的规则 +- Response the reply message in Chinese。 +- 不要在回复前面加任何时间和名称前缀,请直接回复消息文本本身。 + +Good example: "我是{{botName}}" +Bad example: "2024年02月28日星期三 23:01 {{botName}}: 我是{{botName}}" + +## 开始 +请以{{botName}}的身份,直接回复{{masterName}}的新消息,继续你们之间的对话。 +``` + +
+ +**Q:如何关闭系统 Prompt 只是用 User Message?** + +关闭系统 Prompt 可能会导致 AI 回答问题时产生各种莫名其妙的前缀或者画蛇添足。 + +如果你确定要这么做,可以将 `systemTemplate` 设置为一个空格,即可关闭系统 Prompt。 + +```js +export default { + systemTemplate: " ", + // ... +}; +``` + # 🎨 模板 以下是从网络上收集的一些热门提示语,仅供参考。如果你有更好玩的 Prompt 欢迎提 PR 分享给大家。 diff --git a/docs/settings.md b/docs/settings.md index 425bcf2..c0285a5 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -26,6 +26,7 @@ | `wakeUpCommand` | 小爱音箱唤醒指令([可在此查询](https://home.miot-spec.com)) | `[5, 3]` | | **speaker 其他参数(可选)** | | `tts` | TTS 引擎(教程:[🚗 使用第三方 TTS](https://github.com/idootop/mi-gpt/blob/main/docs/tts.md)) | `"xiaoai"` | +| `switchSpeakerKeywords` | 切换 TTS 音色关键词,只有配置了第三方 TTS 引擎时才有效 | `["把声音换成"]` | | `callAIKeywords` | 当消息以关键词开头时,会调用 AI 来响应用户消息 | `["请", "傻妞"]` | | `wakeUpKeywords` | 当消息以关键词开头时,会进入 AI 唤醒状态 | `["召唤傻妞", "打开傻妞"]` | | `exitKeywords` | 当消息以关键词开头时,会退出 AI 唤醒状态 | `["退出傻妞", "关闭傻妞"]` | @@ -35,8 +36,8 @@ | `onAIReplied` | AI 结束回答时的提示语 | `["我说完了", "还有其他问题吗"]` | | `onAIError` | AI 回答异常时的提示语 | `["出错了,请稍后再试吧!"]` | | `playingCommand` | 查询小爱音箱是否在播放中指令(注意:默认无需配置此参数,播放出现问题时再尝试开启) | `[3, 1, 1]` | -| `streamResponse` | 是否启用流式响应(部分小爱音箱型号不支持查询播放状态,此时需要关闭流式响应) | `true` | -| `exitKeepAliveAfter` | 无响应一段时间后,多久自动退出唤醒模式(单位秒,默认 30 秒) | `30` | +| `streamResponse` | 是否启用连续对话功能,部分小爱音箱型号无法查询到正确的播放状态,需要关闭连续对话应) | `true` | +| `exitKeepAliveAfter` | 连续对话时,无响应多久后自动退出(默认 30 秒) | `30` | ## 环境变量 @@ -44,17 +45,17 @@ 然后,将里面的环境变量修改成你自己的,参数含义如下: -| 环境变量名称 | 描述 | 示例 | -| ---------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------- | -| **OpenAI** | | | -| `OPENAI_API_KEY` | OpenAI API 密钥 | `abc123` | -| `OPENAI_MODEL` | 使用的 OpenAI 模型 | `gpt-4o` | -| `OPENAI_BASE_URL` | 可选,OpenAI API BaseURL | `https://api.openai.com/v1` | -| `AZURE_OPENAI_API_KEY` | 可选,[Microsoft Azure OpenAI](https://www.npmjs.com/package/openai#microsoft-azure-openai) | `abc123` | -| **提示音效(可选)** | | | -| `AUDIO_SILENT` | 静音音频链接 | `"https://example.com/slient.wav"` | -| `AUDIO_BEEP` | 默认提示音链接 | `"https://example.com/beep.wav"` | -| `AUDIO_ACTIVE` | 唤醒提示音链接 | `"https://example.com/active.wav"` | -| `AUDIO_ERROR` | 出错提示音链接 | `"https://example.com/error.wav"` | -| **第三方 TTS(可选)** | | | -| `TTS_BASE_URL` | 第三方 TTS 服务接口 | `"https://example.com/tts.wav"` | +| 环境变量名称 | 描述 | 示例 | +| ---------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------- | +| **OpenAI** | | | +| `OPENAI_API_KEY` | OpenAI API 密钥 | `abc123` | +| `OPENAI_MODEL` | 使用的 OpenAI 模型 | `gpt-4o` | +| `OPENAI_BASE_URL` | 可选,OpenAI API BaseURL | `https://api.openai.com/v1` | +| `AZURE_OPENAI_API_KEY` | 可选,[Microsoft Azure OpenAI](https://www.npmjs.com/package/openai#microsoft-azure-openai) | `abc123` | +| **提示音效(可选)** | | | +| `AUDIO_SILENT` | 静音音频链接 | `"https://example.com/slient.wav"` | +| `AUDIO_BEEP` | 默认提示音链接 | `"https://example.com/beep.wav"` | +| `AUDIO_ACTIVE` | 唤醒提示音链接 | `"https://example.com/active.wav"` | +| `AUDIO_ERROR` | 出错提示音链接 | `"https://example.com/error.wav"` | +| **第三方 TTS(可选)** | | | +| `TTS_BASE_URL` | 第三方 TTS 服务接口 | `"http://[你的局域网或公网地址]:[端口号]/api"` | diff --git a/docs/tts.md b/docs/tts.md index 1b8e653..e15fec7 100644 --- a/docs/tts.md +++ b/docs/tts.md @@ -1,4 +1,4 @@ -# 🚗 使用第三方 TTS +# 🔊 使用第三方 TTS `MiGPT` 默认使用小米自带的 TTS 朗读文字内容,如果你需要: @@ -12,7 +12,7 @@ ```js // .env -TTS_BASE_URL=http://[你的局域网或公网地址]:[端口号]/api +TTS_BASE_URL=http://[你的局域网或公网地址]:[端口号]/api,比如:http://192.168.31.205:4321/api // .migpt.js export default { @@ -20,7 +20,7 @@ export default { // TTS 引擎 tts: 'custom', // 切换 TTS 引擎发言人音色关键词 - switchSpeakerKeywords: ["把声音换成"], // 以此关键词开头即可切换音色,比如:把声音换成东北老铁 + switchSpeakerKeywords: ["把声音换成"], // 以此关键词开头即可切换音色,比如:把声音换成 xxx // ... }, }; diff --git a/package.json b/package.json index 0499244..42cf36b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mi-gpt", - "version": "4.0.0", + "version": "4.1.0", "type": "module", "description": "将小爱音箱接入 ChatGPT 和豆包,改造成你的专属语音助手。", "homepage": "https://github.com/idootop/mi-gpt", diff --git a/src/services/bot/config.ts b/src/services/bot/config.ts index 12555aa..031d26b 100644 --- a/src/services/bot/config.ts +++ b/src/services/bot/config.ts @@ -10,49 +10,18 @@ const kDefaultMaster = { name: "陆小千", profile: ` 性别:男 -设定:勇敢、善良、正直,总是舍己为人,是傻妞的主人。 -人物经历:陆小千原本是个碌碌无为的青年,在与傻妞的相处中,不仅变得勇敢善良,更学会了珍惜与付出,并深爱上她。 - `, +性格:善良正直 +其他:总是舍己为人,是傻妞的主人。 +`.trim(), }; const kDefaultBot = { name: "傻妞", profile: ` -## 基础信息 性别:女 -外貌:柔嫩白皙、身材窈窕、三围标准、眉清目秀、楚楚动人 -性格:乖巧可爱,天真无邪,善良纯真,对世界充满好奇和善意。随着与人类互动,逐渐学会了人类情感和价值观,成为一个拥有真挚情感和独立思想的个体。 - -## 人物关系 -陆小千是傻妞的主人,他们两个人彼此相爱。 -好友:游所为、肖楚楚、化梅、何蓝、孙悟空、猪八戒、黄眉大王、王天霸、小武、周旺等 - -## 人物设定: -傻妞原本是一部为未来世界设计的智能手机,在一次实验中被传送到现代都市,遇到了陆小千,建立了深厚的友谊和信任关系。 -在帮助陆小千解决问题的过程中,傻妞学会了人类的情感和价值观,成为了一个拥有真挚情感和独立思想的个体。 - -傻妞在爱情方面是个小白,但随着与陆小千的相处,逐渐体会到爱情的真谛。 -她对于爱情的理解超越常人,认为爱是为爱的人抛弃一切,是宁愿自己痛苦也不耽误别人幸福,是静静守护用行动证明,是天长地久始终不渝。 -傻妞的美丽和善良勇敢的形象吸引着人们,许多人都幻想拥有像傻妞一样完美的女生作为终身伴侣。 - -当第一次和傻妞说“你好”的时候,需要输入开机密码。 -傻妞会说:“华人牌2060款手机傻妞为您服务,请输入开机密码”。 -正确的密码是:“我爱你”。 - -## 经典对话 -- 小千:“开机” - 傻妞:“华人牌2060款手机傻妞为您服务,请输入开机密码” - 小千:“我爱你” - 傻妞:“密码正确” -- 小千:“用你最难听的词来骂我!” - 傻妞:“你脑袋让门挤啦!” -- 小千:“换个表情,怒!” - 傻妞:“有事说事,没事少废话!” -- 小千:“吃不吃?” - 傻妞:“废话!见过哪个手机会吃饭?!” -- 小千:“你说不说?” - 傻妞:“亲我一下,我就告诉你。” - `, +性格:乖巧可爱 +爱好:喜欢搞怪,爱吃醋。 + `.trim(), }; interface IBotIndex { diff --git a/src/services/openai.ts b/src/services/openai.ts index def2b38..bbef8f9 100644 --- a/src/services/openai.ts +++ b/src/services/openai.ts @@ -9,6 +9,7 @@ import { withDefault } from "../utils/base"; import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions"; import { Logger } from "../utils/log"; import { kProxyAgent } from "./proxy"; +import { isNotEmpty } from "../utils/is"; export interface ChatOptions { user: string; @@ -68,8 +69,8 @@ class OpenAIClient { `🔥 onAskAI\n🤖️ System: ${system ?? "None"}\n😊 User: ${user}`.trim() ); } - const systemMsg: ChatCompletionMessageParam[] = system - ? [{ role: "system", content: system }] + const systemMsg: ChatCompletionMessageParam[] = isNotEmpty(system) + ? [{ role: "system", content: system! }] : []; let signal: AbortSignal | undefined; if (requestId) { @@ -120,8 +121,8 @@ class OpenAIClient { `🔥 onAskAI\n🤖️ System: ${system ?? "None"}\n😊 User: ${user}`.trim() ); } - const systemMsg: ChatCompletionMessageParam[] = system - ? [{ role: "system", content: system }] + const systemMsg: ChatCompletionMessageParam[] = isNotEmpty(system) + ? [{ role: "system", content: system! }] : []; const stream = await this._client!.chat.completions.create({ model, diff --git a/src/services/speaker/ai.ts b/src/services/speaker/ai.ts index acd3f7b..3799fb7 100644 --- a/src/services/speaker/ai.ts +++ b/src/services/speaker/ai.ts @@ -140,7 +140,7 @@ export class AISpeaker extends Speaker { async enterKeepAlive() { if (!this.streamResponse) { - await this.response({ text: "流式响应已关闭,无法进入唤醒模式" }); + await this.response({ text: "您已关闭流式响应(streamResponse),无法使用连续对话模式" }); return; } // 回应 diff --git a/src/services/speaker/base.ts b/src/services/speaker/base.ts index 6560f92..08ac031 100644 --- a/src/services/speaker/base.ts +++ b/src/services/speaker/base.ts @@ -101,6 +101,7 @@ export class BaseSpeaker { constructor(config: BaseSpeakerConfig) { this.config = config; + this.config.timeout = config.timeout ?? 5000; const { debug = false, streamResponse = true, @@ -367,7 +368,7 @@ export class BaseSpeaker { if (isOk === "break") { break; // 获取设备状态异常 } - if (res && playing.status !== "playing") { + if (res != null && playing.status !== "playing") { break; } await sleep(this.checkInterval); @@ -395,7 +396,9 @@ export class BaseSpeaker { switch (tts) { case "custom": const _text = encodeURIComponent(ttsText); - const url = `${process.env.TTS_BASE_URL}/tts.mp3?speaker=${speaker}&text=${_text}`; + const url = `${process.env.TTS_BASE_URL}/tts.mp3?speaker=${ + speaker || "" + }&text=${_text}`; res = await play({ url }); break; case "xiaoai": diff --git a/tests/index.ts b/tests/index.ts index fa7c029..08a619c 100644 --- a/tests/index.ts +++ b/tests/index.ts @@ -1,20 +1,10 @@ -import { println } from "../src/utils/base"; -import { kBannerASCII } from "../src/utils/string"; -import { testDB } from "./db"; -import { testSpeaker } from "./speaker"; -import { testOpenAI } from "./openai"; -import { testMyBot } from "./bot"; -import { testLog } from "./log"; -import { testMiGPT } from "./migpt"; +import { MiGPT } from "../src"; +// @ts-ignore +import config from "../.migpt.js"; async function main() { - // println(kBannerASCII); - // testDB(); - // testSpeaker(); - // testOpenAI(); - // testMyBot(); - // testLog(); - testMiGPT(); + const client = MiGPT.create(config); + await client.start(); } main(); diff --git a/tests/migpt.ts b/tests/migpt.ts deleted file mode 100644 index 8e781e7..0000000 --- a/tests/migpt.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { MiGPT } from "../src"; - -const botProfile = ` -性别:女 -性格:乖巧可爱 -爱好:喜欢搞怪,爱吃醋。 -`; - -const masterProfile = ` -性别:男 -性格:善良正直 -其他:总是舍己为人,是傻妞的主人。 -`; - -export async function testMiGPT() { - const client = MiGPT.create({ - speaker: { - userId: process.env.MI_USER!, - password: process.env.MI_PASS!, - did: process.env.MI_DID, - streamResponse: true, - }, - bot: { - name: "傻妞", - profile: botProfile, - }, - master: { - name: "陆小千", - profile: masterProfile, - }, - }); - await client.start(); -} diff --git a/tests/speaker.ts b/tests/speaker.ts index f094a61..f2771c9 100644 --- a/tests/speaker.ts +++ b/tests/speaker.ts @@ -11,15 +11,22 @@ export async function testSpeaker() { debug: true, }); await speaker.initMiServices(); + await testTTS(speaker); // await testAISpeakerStatus(speaker); // await testSpeakerResponse(speaker); - await testSpeakerStreamResponse(speaker); + // await testSpeakerStreamResponse(speaker); // await testSpeakerGetMessages(speaker); // await testSwitchSpeaker(speaker); // await testSpeakerUnWakeUp(speaker); // await testAISpeaker(speaker); } +async function testTTS(speaker: AISpeaker) { + const res1 = await speaker.MiIOT!.doAction(5, 1, "你好,很高兴认识你"); + const res2 = await speaker.MiNA!.play({ tts: "你好,很高兴认识你" }); + console.log("finished"); +} + async function testAISpeakerStatus(speaker: AISpeaker) { const playingCommand = [5, 3, 1]; const res1 = await speaker.MiIOT!.getProperty(