@kmchai/cortico-world-voice 0.1.0 → 0.2.1
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/CHANGELOG.md +31 -0
- package/README.md +83 -70
- package/describe.json +2 -27
- package/dist/webui.css +1 -0
- package/dist/webui.js +1 -0
- package/package.json +33 -6
- package/src/ENV_PROMPT.md +6 -7
- package/src/audio/queue.ts +1 -1
- package/src/config.ts +60 -117
- package/src/definition.ts +3 -6
- package/src/models.ts +66 -0
- package/src/panel.ts +34 -0
- package/src/service.ts +371 -0
- package/src/speech/style.ts +2 -14
- package/src/speech/tags.ts +8 -6
- package/src/speech/text.ts +4 -1
- package/src/tools.ts +1 -16
- package/src/types.ts +46 -49
- package/src/world.ts +219 -267
- package/src/backends/audiocpp.ts +0 -98
- package/src/backends/custom.ts +0 -132
- package/src/backends/http.ts +0 -91
- package/src/backends/index.ts +0 -21
- package/src/backends/openai.ts +0 -51
- package/src/http.ts +0 -205
- package/src/options.ts +0 -15
- package/src/sing.ts +0 -73
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
## 0.2.1
|
|
2
|
+
|
|
3
|
+
- 新增 World 页的「模型」面板:模型与音色在面板里选,不再占系统 Config tab 的字段。
|
|
4
|
+
- 按服务 `/v1/models` 返回的参数元数据渲染字段:开关、枚举、数字与文本;IndexTTS2 的参数带中文名。
|
|
5
|
+
- 模型专属参数存进 `worlds.voice.modelOptions[model]`,请求时作为 `options` 发给服务;空对象删掉该模型的条目。
|
|
6
|
+
- 面板显示当前模型的提示词片段;目录缓存在进程内,服务不可达时保留上一次结果并报错。
|
|
7
|
+
- 新增 `scripts/build-webui.mjs`(esbuild)与 `dist/webui.js` / `dist/webui.css`。
|
|
8
|
+
|
|
9
|
+
## 0.2.0
|
|
10
|
+
|
|
11
|
+
分层:协议、进程托管与 HTTP 网关移到 `@kmchai/cortico-world-service`;语音只保留工具、概率朗读、文本过滤、情绪与标签语义、队列和本机播放,经 HTTP 调服务。
|
|
12
|
+
|
|
13
|
+
### 破坏性变更
|
|
14
|
+
|
|
15
|
+
- 语音不再直连 TTS 后端,也不再监听本地 HTTP。合成统一走服务的 `/v1/audio/speech`,探活与目录用 `/health`、`/v1/models`、`/v1/audio/voices`;服务不可达时没有回退路径。
|
|
16
|
+
- 新配置 `serviceUrl`(默认 `http://127.0.0.1:9883`)与 `serviceTokenSecret`;删除 `backend`、`baseUrl`、`apiKeySecret`、`custom*`、`http*`、`sing*`。这些后端配置改到服务 World 里。
|
|
17
|
+
- 删除 `src/backends/`、`src/http.ts`、`src/sing.ts`;新增 `src/service.ts` 客户端。
|
|
18
|
+
- 删除 `voice_sing` 工具、`【唱】` 前缀与全部唱歌配置;语音不唱歌,唱歌改由服务的 `/v1/audio/sing` 与网关接入。工具只剩 `voice_speak` / `voice_hush` / `voice_style`。
|
|
19
|
+
- 情绪模式只决定发哪个语义字段:instruct 发 `instructions`,categorical 发 `emotion`,basic 发 `speed`,tags/none 不发;怎么落到具体模型由服务的模型 profile 决定。
|
|
20
|
+
- 副语言标签 `auto` 一律保留,由服务里的模型决定演不演;保留 `keep` / `strip`。
|
|
21
|
+
|
|
22
|
+
### 界面与目录
|
|
23
|
+
|
|
24
|
+
- World 页改为服务灯与队列灯;徽标显示服务地址主机、模型与音色;音色与模型下拉从服务目录刷新,读不到时保留上次结果。
|
|
25
|
+
- World 页链到服务 World 的配置页。
|
|
26
|
+
- 环境提示词重写:只说明声音能力、工具与文本规则,不提后端、协议或 HTTP。
|
|
27
|
+
|
|
28
|
+
## 0.1.1
|
|
29
|
+
|
|
30
|
+
- 市场元数据:补中文关键词与 `cortico.locales`,中文界面显示「语音」而不是包名。
|
|
31
|
+
|
|
1
32
|
## 0.1.0
|
|
2
33
|
|
|
3
34
|
- 初版:协议无关的 TTS World,内置 audiocpp / OpenAI 兼容 / custom JSON 三种适配器。
|
package/README.md
CHANGED
|
@@ -1,37 +1,38 @@
|
|
|
1
1
|
# @kmchai/cortico-world-voice
|
|
2
2
|
|
|
3
|
-
Cortico 的语音 World
|
|
3
|
+
Cortico 的语音 World:把回复念出来。触发、概率、文本过滤、情绪语义、队列与播放留在这里;合成经 [Kmch AI · 服务](https://github.com/Kmch-AI/cortico-world-service) 的 HTTP 网关完成。
|
|
4
4
|
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
- **本地语音接口**:QQ 插件、脚本、唱歌插件都能用同一个声音。
|
|
11
|
-
- **唱歌联动**:`voice_sing` 与 `【唱】` 前缀走唱歌插件的 HTTP 接口。
|
|
5
|
+
- **语音工具**:`voice_speak`、`voice_hush`、`voice_style`。
|
|
6
|
+
- **三条触发路径**:工具显式调用、用户语义请求(「念给我听」)、按概率自动朗读。
|
|
7
|
+
- **情绪与标签**:自然语言指令、分类情绪、语速、副语言标签(`[Laughter]`、`[Sigh]` 等)。
|
|
8
|
+
- **文本过滤**:Markdown、代码、网址、动作标记、emoji、指定符号都不念。
|
|
9
|
+
- **出口可拆**:本机播放、发 `tts.voice` 事件交给播放插件、或只合成并交回音频附件。
|
|
12
10
|
|
|
13
|
-
|
|
11
|
+
它不发送聊天消息,也不碰渠道;可见的回复仍走原来的发送工具。Voice 只负责声音,不唱歌。
|
|
14
12
|
|
|
15
13
|
## 安装
|
|
16
14
|
|
|
15
|
+
装两个包:服务负责托管本机 TTS 并暴露网关,语音只连它。
|
|
16
|
+
|
|
17
17
|
```bash
|
|
18
18
|
# 在部署的 extensions/ 目录里
|
|
19
|
+
corepack pnpm add "D:/path/to/cortico-world-service" --ignore-workspace
|
|
19
20
|
corepack pnpm add "D:/path/to/cortico-world-voice" --ignore-workspace
|
|
20
21
|
```
|
|
21
22
|
|
|
22
|
-
或在控制台「扩展」页选「从本机目录安装」,装完重启进程。启用 `worlds.voice.enabled`。
|
|
23
|
+
或在控制台「扩展」页选「从本机目录安装」,装完重启进程。启用 `worlds.service.enabled` 与 `worlds.voice.enabled`。
|
|
23
24
|
|
|
24
25
|
## 最小配置
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
服务负责拉起本机 TTS 并暴露网关;语音只连它:
|
|
27
28
|
|
|
28
29
|
```jsonc
|
|
29
30
|
{
|
|
30
31
|
"worlds": {
|
|
31
32
|
"voice": {
|
|
32
33
|
"enabled": true,
|
|
33
|
-
"
|
|
34
|
-
"
|
|
34
|
+
"serviceUrl": "http://127.0.0.1:9883",
|
|
35
|
+
"serviceTokenSecret": "",
|
|
35
36
|
"model": "ming3-tts",
|
|
36
37
|
"voice": "4016",
|
|
37
38
|
"playback": "local",
|
|
@@ -42,105 +43,117 @@ corepack pnpm add "D:/path/to/cortico-world-voice" --ignore-workspace
|
|
|
42
43
|
}
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
`serviceUrl` 默认为 `http://127.0.0.1:9883`。`serviceTokenSecret` 填部署 `.env` 里的变量名,值只在请求时读取;服务监听非回环地址时必须配令牌。后端条目、模型 profile 与上游密钥都配在服务 World 里,见[服务包 README](https://github.com/Kmch-AI/cortico-world-service#readme)。
|
|
47
|
+
|
|
48
|
+
## 模型面板
|
|
49
|
+
|
|
50
|
+
World 页的「模型」tab 选模型与音色,并按服务 `/v1/models` 返回的参数元数据渲染字段。保存写进 `worlds.voice.modelOptions[model]`,合成时作为 `options` 发给服务;空参数表删掉该模型的条目。模型专属的文本与情绪写法显示在同页的「模型提示词」折叠区。系统 Config tab 只留服务地址、风格、触发、过滤与输出。
|
|
51
|
+
|
|
52
|
+
## 工具
|
|
53
|
+
|
|
54
|
+
| 工具 | 作用 |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `voice_speak` | 念一句;可带 `emotion`、`instruct`、`voice`、`speed`、`silent`。不受概率限制。 |
|
|
57
|
+
| `voice_style` | 读或改长期风格(情绪、指令、音色、语速),写回 `worlds.voice`,影响之后的自动朗读。 |
|
|
58
|
+
| `voice_hush` | 立刻停声,清空排队。 |
|
|
59
|
+
|
|
60
|
+
`silent: true` 只合成、不在本机播放,音频随工具回执交给渠道,用于语音条这类出口。
|
|
46
61
|
|
|
47
62
|
## 触发
|
|
48
63
|
|
|
49
64
|
| 路径 | 行为 |
|
|
50
65
|
|---|---|
|
|
51
|
-
| `voice_speak` 工具 |
|
|
52
|
-
| 用户语义请求 | 最近 `explicitWindowSec` 秒内的外部消息含 `explicitKeywords
|
|
53
|
-
| 自动朗读 |
|
|
54
|
-
| `<tts>…</tts>` |
|
|
66
|
+
| `voice_speak` 工具 | 立即念;不受概率限制。可带 `emotion`、`instruct`、`voice`、`speed`、`silent`。 |
|
|
67
|
+
| 用户语义请求 | 最近 `explicitWindowSec` 秒内的外部消息含 `explicitKeywords`(默认「语音,声音,发语音,念给我听,读给我听,voice,tts」)时必定朗读。 |
|
|
68
|
+
| 自动朗读 | 助手正文、指定工具参数、指定事件三类来源,按 `autoProbability` 掷一次。 |
|
|
69
|
+
| `<tts>…</tts>` | 只念标签内文,并强制朗读。标签会出现在可见文字里;渠道可见时优先用工具触发。 |
|
|
55
70
|
|
|
56
|
-
|
|
71
|
+
自动来源:
|
|
57
72
|
|
|
58
|
-
- `autoFromText
|
|
59
|
-
- `autoTools
|
|
60
|
-
- `autoEvents
|
|
73
|
+
- `autoFromText`:一轮里只有正文、没有函数调用时,念这一轮的正文。
|
|
74
|
+
- `autoTools`:这些工具的参数会被当作正文。默认 `pet_say, pet_ask, console_send`,取 `script` / `question` / `text` / `content` / `message` / `body` 里第一个字符串。
|
|
75
|
+
- `autoEvents`:轮询事件库,念这些事件类型的 `meta.body` 或正文。
|
|
61
76
|
|
|
62
|
-
同一段文字 5
|
|
77
|
+
同一段文字 5 秒内只念一次。概率按「一段话」掷,不按句掷。
|
|
63
78
|
|
|
64
|
-
|
|
79
|
+
只想响应工具与显式请求、不要随机朗读:保留 `autoSpeak: true`,把 `autoProbability` 设为 `0`。把 `autoSpeak` 整个关掉会让 tap 不再挂载,显式关键词也就看不到正文了;这时只保留 `voice_speak` 工具触发。
|
|
65
80
|
|
|
66
|
-
自动朗读依赖 Persona 把已挂载 World 的 `outputTap()` 扇进主 session
|
|
81
|
+
自动朗读依赖 Persona 把已挂载 World 的 `outputTap()` 扇进主 session;仓内 `cormini` 与 Coopanion 都这样做。只想要工具触发时,把 `autoSpeak` 关掉即可。
|
|
67
82
|
|
|
68
83
|
## 情绪、语速与标签
|
|
69
84
|
|
|
70
|
-
|
|
85
|
+
语音只把语义字段发给服务,由服务的模型 profile 决定怎么落到具体模型:
|
|
71
86
|
|
|
72
|
-
|
|
|
87
|
+
| `emotionMode` | 语音发送 | `audiocpp-ming3` profile 的落点 |
|
|
73
88
|
|---|---|---|
|
|
74
|
-
| `instruct
|
|
75
|
-
| `categorical` | `
|
|
76
|
-
| `basic` | `
|
|
77
|
-
| `tags` |
|
|
78
|
-
| `none` |
|
|
89
|
+
| `instruct`(默认) | `instructions`:默认情绪/指令与语速折成自然语言。 | `options.template_name = "freeform"`,带上指令。 |
|
|
90
|
+
| `categorical` | `emotion`:只发情绪标签;没配时用「平静」。 | `emotion` 字段。 |
|
|
91
|
+
| `basic` | `speed`:只发语速倍率。 | `basic` 模板,按 `speedLevels` 取慢速/中速/快速。 |
|
|
92
|
+
| `tags` | 不发情绪参数,只用正文标签。 | 正文标签。 |
|
|
93
|
+
| `none` | 都不发。 | — |
|
|
79
94
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
OpenAI 兼容条目把 `instructions` / `emotion` / `speed` 直接放进请求。其他模型可在服务面板里覆盖 profile 的字段映射,语音不用改配置。
|
|
96
|
+
|
|
97
|
+
`response_format` 每次都带;`model` 与 `voice` 配了才带,工具参数里的音色优先于配置默认值。
|
|
98
|
+
|
|
99
|
+
- 情绪来源:工具参数 > 正文里的 `[emotion:开心]` 标记 > `defaultEmotion`。
|
|
100
|
+
- `voice_style` 读写长期风格(情绪、指令、音色、语速),写回 `worlds.voice`。
|
|
101
|
+
- 副语言标签:`[Breathing] [Cough] [Sigh] [Laughter] [Surprise-ah] [Surprise-oh] [Dissatisfaction-hnn] [Uhm] [Confirmation-en] [Question-ei] [Question-ah] [Question-en] [Question-oh] [Question-huh]`。
|
|
102
|
+
`paralinguistic: auto` 只在当前模型支持标签(Ming 3.0 系)时保留,IndexTTS2 与通用模型剥掉;`keep` 一律保留,`strip` 一律删除。表外 `[...]` 会从朗读文本里剥掉,防止被念出来。
|
|
84
103
|
|
|
85
104
|
## 文本过滤
|
|
86
105
|
|
|
87
|
-
|
|
106
|
+
按顺序:取 `<tts>` 内文 → 去围栏代码/行内代码 → 去图片/链接/网址 → 去 Markdown 记号 → 去 `【…】`、`<…>`、`〈…〉` → 去 emoji 与不可见字符 → 去 `filterSymbols` 里的字符 → 压缩空白。
|
|
88
107
|
|
|
89
|
-
过滤后短于 `minChars`
|
|
108
|
+
过滤后短于 `minChars` 不念,长于 `maxChars` 在句末截断。切句优先中英文句末标点,过长在逗号处再切;`chunkMax` 是单块上限,`gapMs` 是块间停顿。
|
|
90
109
|
|
|
91
110
|
## 出口
|
|
92
111
|
|
|
93
112
|
| `playback` | 行为 |
|
|
94
113
|
|---|---|
|
|
95
|
-
| `local
|
|
96
|
-
| `event` | 每块推一条 `tts.voice`
|
|
97
|
-
| `none` |
|
|
114
|
+
| `local`(默认) | 本机播放。Windows 用一个常驻 PowerShell 调 winmm MCI;macOS 用 `afplay`;Linux 用 `ffplay`/`paplay`/`aplay`。不需要原生 npm 模块。 |
|
|
115
|
+
| `event` | 每块推一条 `tts.voice` 事件(带音频附件,`deliver: false`)。`cortico-world-audio-play` 默认就监听这个类型。 |
|
|
116
|
+
| `none` | 只合成,不播放;适合语音只交给别的渠道。 |
|
|
98
117
|
|
|
99
|
-
`shareAudio: true`
|
|
118
|
+
`shareAudio: true` 时工具回执带音频附件,渠道工具可以用 `[blob …]` 句柄发送同一段音频。`silent: true` 的 `voice_speak` 只合成、不播放,并默认带回附件。
|
|
100
119
|
|
|
101
|
-
##
|
|
120
|
+
## 与服务的关系
|
|
102
121
|
|
|
103
|
-
|
|
122
|
+
语音只经 HTTP 调服务,不解析后端方言、不监听端口、不做模型 profile:
|
|
104
123
|
|
|
105
|
-
| 路由 |
|
|
124
|
+
| 路由 | 用途 |
|
|
106
125
|
|---|---|
|
|
107
|
-
| `
|
|
108
|
-
| `GET /
|
|
109
|
-
| `GET /v1/
|
|
110
|
-
| `
|
|
111
|
-
| `POST /v1/audio/sing` | 体 `{lyrics}`,转给唱歌插件;没配置时 503 |
|
|
112
|
-
|
|
113
|
-
`httpPlay: true` 时经接口合成也在本机播放。接口默认不播,交给调用方处理。
|
|
114
|
-
|
|
115
|
-
## 唱歌
|
|
126
|
+
| `POST /v1/audio/speech` | 合成音频;体 `{model?, input, voice?, response_format, speed?, instructions?, emotion?}`。 |
|
|
127
|
+
| `GET /health` | 探活;World 页的服务灯每 15 秒刷新。 |
|
|
128
|
+
| `GET /v1/models` | 模型、音色与请求参数元数据。 |
|
|
129
|
+
| `GET /v1/audio/voices?model=` | 当前模型的音色表。 |
|
|
116
130
|
|
|
117
|
-
`
|
|
131
|
+
服务不可达时:模型与音色下拉保留上一次读到的表,服务灯变红,`voice_speak` 回失败。语音 0.2.0 不保留直连后端的回退路径。服务不重试上游,语音也不重试。
|
|
118
132
|
|
|
119
|
-
|
|
120
|
-
- 自动来源里以 `【唱】`、`[唱]`、`[sing]` 开头的正文也会转给唱歌插件;没配置时按普通文本处理,不会静默改念。
|
|
121
|
-
- 唱歌插件可以是任何实现这个 HTTP 形状的包,Voice 不 import 它。
|
|
133
|
+
语音不唱歌:0.1.x 的 `voice_sing`、`【唱】` 前缀与 `sing*` 配置已删除。唱歌由服务的 `/v1/audio/sing` 和网关/唱歌插件接入。
|
|
122
134
|
|
|
123
|
-
##
|
|
135
|
+
## 从 0.1.x 迁移
|
|
124
136
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
137
|
+
| 0.1.x | 0.2.0 |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `backend`、`baseUrl`、`apiKeySecret` | 删除;在服务里添加对应服务条目,语音改填 `serviceUrl` 与 `serviceTokenSecret`。 |
|
|
140
|
+
| `customUrl` / `customMethod` / `customHeaders` / `customBody` / `customResponse` / `customAudioPath` | 删除;在服务的 `custom` 条目里配置。 |
|
|
141
|
+
| `httpApi` / `httpPort` / `httpToken` / `httpPlay` | 删除;服务的网关取代本地语音接口。 |
|
|
142
|
+
| `singUrl` / `singToken`、`voice_sing`、`【唱】` | 删除;唱歌由服务的 `/v1/audio/sing` 与网关接入。 |
|
|
143
|
+
| `model` / `voice` / `format` / `timeoutMs` | 保留,含义不变。 |
|
|
144
|
+
| `emotionMode` / `defaultEmotion` / `defaultInstruct` / `speed` / `paralinguistic` | 保留;语音只发语义字段,由服务的模型 profile 映射到具体模型。 |
|
|
145
|
+
| 触发、过滤、输出配置 | 保留。 |
|
|
132
146
|
|
|
133
147
|
## 验证
|
|
134
148
|
|
|
135
149
|
```bash
|
|
136
150
|
pnpm typecheck
|
|
137
151
|
pnpm test
|
|
138
|
-
pnpm smoke:
|
|
139
|
-
pnpm smoke:
|
|
140
|
-
pnpm smoke:chain http://127.0.0.1:8000 # 触发→过滤→合成→tts.voice 全链路
|
|
152
|
+
pnpm smoke:player out.wav # 本机播放器,音量 0
|
|
153
|
+
pnpm smoke:chain http://127.0.0.1:9883 # 触发→过滤→经服务合成→tts.voice 全链路
|
|
141
154
|
```
|
|
142
155
|
|
|
143
|
-
装进 Cortico
|
|
156
|
+
装进 Cortico 后应看到:World 页多一张「Kmch AI · 语音」卡,服务灯、队列灯与 `service` / `model` / `voice` 徽标;「模型」tab 能选模型与音色、按服务参数渲染字段并保存;服务在线时灯亮;让 bot 调 `voice_speak` 能听见;说「念给我听」会绕过概率;`playback: event` 时时间线出现 `tts.voice`。
|
|
144
157
|
|
|
145
158
|
## 许可
|
|
146
159
|
|
package/describe.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"tools": [
|
|
3
3
|
{
|
|
4
4
|
"name": "voice_speak",
|
|
5
|
-
"description": "Say a line aloud in your voice through the person's speakers, or only synthesize it when silent is true. Use it when the person asks for voice, when this line needs a specific emotion or voice, or when automatic reading is off. It only makes sound; the visible reply still goes through the channel's own send tool. When the
|
|
5
|
+
"description": "Say a line aloud in your voice through the person's speakers, or only synthesize it when silent is true. Use it when the person asks for voice, when this line needs a specific emotion or voice, or when automatic reading is off. It only makes sound; the visible reply still goes through the channel's own send tool. When the speech service supports paralinguistic tags, keep them inline in the text: [Laughter], [Sigh], [Breathing], [Uhm], [Question-ei] and the like are performed, not read out.",
|
|
6
6
|
"parameters": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -50,31 +50,6 @@
|
|
|
50
50
|
"en": "Say one line now, optionally with an emotion, direction, voice and speed, or synthesize it silently."
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
|
-
{
|
|
54
|
-
"name": "voice_sing",
|
|
55
|
-
"description": "Sing a lyric line through the configured singing plugin. It only makes sound; the visible reply still goes through the channel's own send tool. If no singing plugin is configured, or it is not ready, the call fails instead of speaking the lyrics.",
|
|
56
|
-
"parameters": {
|
|
57
|
-
"type": "object",
|
|
58
|
-
"additionalProperties": false,
|
|
59
|
-
"properties": {
|
|
60
|
-
"lyrics": {
|
|
61
|
-
"type": "string",
|
|
62
|
-
"maxLength": 2000,
|
|
63
|
-
"description": "The lyric line to sing."
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
"required": [
|
|
67
|
-
"lyrics"
|
|
68
|
-
]
|
|
69
|
-
},
|
|
70
|
-
"tags": [
|
|
71
|
-
"speak"
|
|
72
|
-
],
|
|
73
|
-
"summary": {
|
|
74
|
-
"zh": "把一句歌词交给唱歌插件唱出来。",
|
|
75
|
-
"en": "Sing one lyric line through the configured singing service."
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
53
|
{
|
|
79
54
|
"name": "voice_hush",
|
|
80
55
|
"description": "Stop the voice now: the line playing is cut and queued lines are dropped. It does not affect the visible reply.",
|
|
@@ -151,7 +126,7 @@
|
|
|
151
126
|
"key": "worlds.voice.envPrompt",
|
|
152
127
|
"title": "语音 · 环境提示词",
|
|
153
128
|
"role": "envPrompt",
|
|
154
|
-
"content": "语音由 Voice
|
|
129
|
+
"content": "语音由 Voice 提供:它只把文字变成声音,从对方的扬声器念出来,不负责把回复发到聊天渠道;可见的回复仍然走渠道自己的发送工具。\n\n自动朗读现在是 {{voice.auto}}(音色 {{voice.voice}},情绪 {{voice.emotion}},指令 {{voice.instruct}})。自动朗读开着时,普通回复不必调用语音工具;关着时,想出声就调用 voice_speak。\n\n需要出声的时候:\n- 对方明确要语音、要你念出来、要听你的声音时,调用 voice_speak,把要念的正文放进 text。它会绕过概率,必定出声。\n- 这一句需要特定情绪、语气、音色或语速时,调用 voice_speak 并填对应参数。想让同样的语气之后一直生效,调用 voice_style;它影响之后的自动朗读。\n- 某一句只用于别的渠道(例如语音条),不要在本机播放:voice_speak 带 silent: true,音频随回执交给渠道。\n- 对方让你安静、停下时调用 voice_hush。\n\n文本规则:\n- text 只放真正要念的正文,不要放动作描写、Markdown、代码、网址或解释。\n- {{voice.modelPrompt}}\n- 不要把控制标记写进可见的回复正文;语音参数只放在工具调用里。\n"
|
|
155
130
|
}
|
|
156
131
|
]
|
|
157
132
|
}
|
package/dist/webui.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.vx-top{display:flex;flex-wrap:wrap;align-items:end;gap:10px}.vx-pick{display:flex;flex:1 1 160px;min-width:0;flex-direction:column;gap:4px}.vx-pick-label{font:var(--text-detail)/1.2 var(--sans);color:var(--ink-soft)}.vx-pick select.field{width:100%;box-sizing:border-box}.vx-top>.btn{margin-left:auto;align-self:end}.vx-params{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px 18px;margin-top:14px}.vx-params:empty{display:none}.vx-field{display:flex;min-width:0;flex-direction:column;gap:5px}.vx-head{display:flex;align-items:baseline;gap:7px}.vx-label{font:13px/1.3 var(--sans);color:var(--ink)}.vx-name{font:11px/1.3 var(--mono);color:var(--ink-soft)}.vx-field>.field,.vx-field>label.check{width:100%;box-sizing:border-box}.vx-desc{font:12px/1.4 var(--sans);color:var(--ink-soft)}.vx-params+.actions{margin-top:14px}.vx-prompt{margin:0;padding:10px 12px;max-height:260px;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.55 var(--mono);color:var(--ink-soft);background:var(--sheet-2);border:1px solid var(--line);border-radius:8px}
|
package/dist/webui.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var W={zh:{title:"\u6A21\u578B",model:"\u6A21\u578B",voice:"\u97F3\u8272",refresh:"\u5237\u65B0",save:"\u4FDD\u5B58",saved:"\u5DF2\u4FDD\u5B58",loading:"\u8BFB\u53D6\u4E2D\u2026",promptTitle:"\u6A21\u578B\u63D0\u793A\u8BCD"},en:{title:"\u6A21\u578B",model:"Model",voice:"Voice",refresh:"Refresh",save:"Save",saved:"Saved",loading:"Loading\u2026",promptTitle:"Model prompt"}};function O(e){return e.toLowerCase().startsWith("zh")?W.zh:W.en}var U={emotion_alpha:"\u60C5\u7EEA\u5F3A\u5EA6",emotion_text:"\u60C5\u7EEA\u63CF\u8FF0",use_emotion_text:"\u4F7F\u7528\u6587\u5B57\u60C5\u7EEA",use_random_emotion:"\u968F\u673A\u60C5\u7EEA",duration_scale:"\u65F6\u957F\u7F29\u653E",interval_silence_ms:"\u5206\u6BB5\u9759\u97F3",do_sample:"\u91C7\u6837",top_p:"top_p",top_k:"top_k",temperature:"\u6E29\u5EA6",length_penalty:"\u957F\u5EA6\u60E9\u7F5A",num_beams:"beam \u6570",repetition_penalty:"\u91CD\u590D\u60E9\u7F5A",max_tokens:"\u6700\u5927 token",seed:"\u79CD\u5B50",text_chunk_size:"\u6587\u672C\u5206\u5757\u5927\u5C0F",text_chunk_mode:"\u5206\u5757\u6A21\u5F0F"};function j(e,t){return Object.prototype.hasOwnProperty.call(t,e.name)?t[e.name]:e.default}function C(e){return e==null?"":Array.isArray(e)?e.map(t=>String(t)).join(", "):String(e)}function H(e){return typeof e=="number"&&Number.isFinite(e)?String(e):typeof e=="string"&&e.trim()&&Number.isFinite(Number(e))?e.trim():""}function I(e,t,s){let i=e.ui,r=i.h("div","vx-field"),m=i.h("div","vx-head"),p=U[t.name];p&&p!==t.name?m.append(i.h("span","vx-label",p),i.h("span","vx-name",t.name)):m.append(i.h("span","vx-label",t.name)),r.append(m);let f=j(t,s),u;switch(t.type){case"bool":{let a=i.checkbox("",{checked:f===!0});r.append(a.el),u=()=>a.checked;break}case"enum":{let a=t.values??[],d=C(f),c=[{value:"",label:"\u2014"},...a.map(k=>({value:k,label:k}))];d&&!a.includes(d)&&c.push({value:d,label:d});let b=i.select({options:c,value:d});r.append(b),u=()=>b.value||void 0;break}case"int":case"float":{let a=i.input({type:"number",value:H(f)});t.min!==void 0&&(a.min=String(t.min)),t.max!==void 0&&(a.max=String(t.max)),a.step=t.type==="int"?"1":"any",r.append(a),u=()=>{let d=a.value.trim();if(!d)return;let c=Number(d);if(Number.isFinite(c))return t.type==="int"?Math.trunc(c):c};break}case"string_list":case"path_list":{let a=i.input({type:"text",value:C(f)});r.append(a),u=()=>{let d=a.value.split(/[,,]/).map(c=>c.trim()).filter(Boolean);return d.length>0?d:void 0};break}default:{let a=i.input({type:"text",value:C(f)});r.append(a),u=()=>a.value.trim()||void 0;break}}return t.description&&r.append(i.h("div","vx-desc",t.description)),{param:t,el:r,read:u}}function X(e){let t=O(e.language),s=e.ui,i=null,r="",m=[],p=!1,f=s.sheet({title:t.title}),u=s.h("div","vx-top"),a=s.select({onChange:()=>B(a.value)}),d=s.select({}),c=s.h("label","vx-pick");c.append(s.h("span","vx-pick-label",t.model),a);let b=s.h("label","vx-pick");b.append(s.h("span","vx-pick-label",t.voice),d);let k=s.button(t.refresh,{size:"sm",onClick:()=>{E("refresh")}});u.append(c,b,k);let y=s.h("div","vx-params"),S=s.button(t.save,{variant:"primary",size:"sm",onClick:()=>{F()}}),w=s.msgline(),T=s.actions();T.append(S),f.body.append(u,y,T,w),e.root.append(f.el);let _=s.foldSheet("voice-model-prompt",{title:t.promptTitle,defaultOpen:!1}),z=s.h("pre","vx-prompt");_.body.append(z),e.root.append(_.el);function x(n,o=n){let l=s.h("option");return l.value=n,l.textContent=o,l}function h(n,o){w.textContent=n,w.classList.toggle("bad",o)}function v(){let n=i!==null&&i.models.length>0;a.disabled=p||!n,d.disabled=p||!n||!r,S.disabled=p||!n||!r,k.disabled=p}function L(){let o=i?.models.find(M=>M.id===r)?.voices??[],l=i?.model===r?i.voice:"",g=[...o];l&&!g.includes(l)&&g.unshift(l);let P=g.map(M=>x(M));l||P.unshift(x("","\u2014")),d.replaceChildren(...P.length>0?P:[x("","\u2014")]),d.value=l}function R(){let n=i?.models.find(l=>l.id===r),o=i&&i.model===r?i.options:{};m=[],y.replaceChildren();for(let l of n?.parameters??[]){let g=I(e,l,o);m.push(g),y.append(g.el)}}function B(n){r=n,L(),R(),v()}function A(n){i=n,r=n.model,N(),R(),z.textContent=n.prompt,_.note.textContent=n.model||"\u2014",v(),h(n.error??"",n.error!==void 0)}function N(){let n=i?.models??[],o=n.map(l=>x(l.id,l.label||l.id));!r&&n.length>0?o.unshift(x("","\u2014")):r&&!n.some(l=>l.id===r)&&o.unshift(x(r,r)),a.replaceChildren(...o),a.value=r,L()}function V(){let n={};for(let o of m){let l=o.read();l!==void 0&&(n[o.param.name]=l)}return n}async function E(n){if(!p){p=!0,v(),h(t.loading,!1);try{let o=await e.invoke(n,[]);e.signal.aborted||A(o)}catch(o){e.signal.aborted||h(o instanceof Error?o.message:String(o),!0)}finally{p=!1,e.signal.aborted||v()}}}async function F(){if(p||!i)return;let n={model:r,voice:d.value,options:V()};p=!0,v(),h("",!1);try{let o=await e.invoke("save",[n]);e.signal.aborted||(A(o),h(t.saved,!1))}catch(o){e.signal.aborted||h(o instanceof Error?o.message:String(o),!0)}finally{p=!1,e.signal.aborted||v()}}v(),E("state")}var q={panels:{model:{mount:X}}},Y=q;export{Y as default};
|
package/package.json
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kmchai/cortico-world-voice",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Cortico World:
|
|
5
|
+
"description": "Cortico World: voice tools, probabilistic reading, emotion and paralinguistic tags, text filtering and local playback; speech is synthesized by the Kmch AI service over HTTP",
|
|
6
6
|
"author": "kmchai",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Kmch-AI/cortico-world-voice.git"
|
|
11
|
+
},
|
|
8
12
|
"keywords": [
|
|
9
13
|
"cortico-world",
|
|
10
14
|
"cortico",
|
|
11
15
|
"tts",
|
|
12
16
|
"voice",
|
|
13
17
|
"speech",
|
|
14
|
-
"emotion"
|
|
18
|
+
"emotion",
|
|
19
|
+
"语音",
|
|
20
|
+
"语音合成",
|
|
21
|
+
"文本转语音",
|
|
22
|
+
"朗读",
|
|
23
|
+
"情感",
|
|
24
|
+
"音色"
|
|
15
25
|
],
|
|
16
26
|
"engines": {
|
|
17
27
|
"node": ">=22"
|
|
@@ -21,30 +31,47 @@
|
|
|
21
31
|
"cortico": {
|
|
22
32
|
"kind": "world",
|
|
23
33
|
"api": 6,
|
|
34
|
+
"displayName": "Kmch AI · 语音",
|
|
35
|
+
"locales": {
|
|
36
|
+
"zh": {
|
|
37
|
+
"displayName": "Kmch AI · 语音",
|
|
38
|
+
"description": "语音 World:工具、概率朗读、情绪与副语言标签、文本过滤、本机播放;协议与 HTTP 服务由 Kmch AI · 服务提供。"
|
|
39
|
+
},
|
|
40
|
+
"en": {
|
|
41
|
+
"displayName": "Kmch AI · Voice",
|
|
42
|
+
"description": "Voice World: tools, probabilistic reading, emotion and paralinguistic tags, text filtering and local playback; the Kmch AI service provides the protocol and HTTP API."
|
|
43
|
+
}
|
|
44
|
+
},
|
|
24
45
|
"tags": [
|
|
25
46
|
"voice",
|
|
26
47
|
"local",
|
|
27
48
|
"external-setup",
|
|
49
|
+
"requires-extension",
|
|
28
50
|
"experimental"
|
|
29
51
|
],
|
|
30
|
-
"describe": "describe.json"
|
|
52
|
+
"describe": "describe.json",
|
|
53
|
+
"webUiClient": "dist/webui.js",
|
|
54
|
+
"webUiStyle": "dist/webui.css"
|
|
31
55
|
},
|
|
32
56
|
"files": [
|
|
33
57
|
"src",
|
|
58
|
+
"dist/webui.js",
|
|
59
|
+
"dist/webui.css",
|
|
34
60
|
"README.md",
|
|
35
61
|
"CHANGELOG.md",
|
|
36
62
|
"describe.json"
|
|
37
63
|
],
|
|
38
64
|
"scripts": {
|
|
39
65
|
"test": "vitest run",
|
|
40
|
-
"typecheck": "tsc --noEmit",
|
|
41
|
-
"
|
|
66
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.webui.json",
|
|
67
|
+
"build:webui": "node scripts/build-webui.mjs",
|
|
42
68
|
"smoke:player": "tsx scripts/smoke-player.ts",
|
|
43
69
|
"smoke:chain": "tsx scripts/smoke-chain.ts"
|
|
44
70
|
},
|
|
45
71
|
"devDependencies": {
|
|
46
72
|
"@types/node": "^22.10.0",
|
|
47
73
|
"cortico": "^0.2.12",
|
|
74
|
+
"esbuild": "^0.28.2",
|
|
48
75
|
"tsx": "^4.19.2",
|
|
49
76
|
"typescript": "^5.7.2",
|
|
50
77
|
"vitest": "^2.1.8"
|
package/src/ENV_PROMPT.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
语音由 Voice
|
|
1
|
+
语音由 Voice 提供:它只把文字变成声音,从对方的扬声器念出来,不负责把回复发到聊天渠道;可见的回复仍然走渠道自己的发送工具。
|
|
2
2
|
|
|
3
|
-
自动朗读现在是 {{voice.auto}}(
|
|
3
|
+
自动朗读现在是 {{voice.auto}}(音色 {{voice.voice}},情绪 {{voice.emotion}},指令 {{voice.instruct}})。自动朗读开着时,普通回复不必调用语音工具;关着时,想出声就调用 voice_speak。
|
|
4
4
|
|
|
5
5
|
需要出声的时候:
|
|
6
|
-
- 对方明确要语音、要你念出来、要听你的声音时,调用 voice_speak,把要念的正文放进 text
|
|
7
|
-
- 这一句需要特定情绪、语气、音色或语速时,调用 voice_speak
|
|
8
|
-
-
|
|
9
|
-
- 想唱一句时调用 voice_sing;自动来源的正文以【唱】开头也会转给唱歌插件。唱歌服务没配时会回失败,不要改写成普通朗读。
|
|
6
|
+
- 对方明确要语音、要你念出来、要听你的声音时,调用 voice_speak,把要念的正文放进 text。它会绕过概率,必定出声。
|
|
7
|
+
- 这一句需要特定情绪、语气、音色或语速时,调用 voice_speak 并填对应参数。想让同样的语气之后一直生效,调用 voice_style;它影响之后的自动朗读。
|
|
8
|
+
- 某一句只用于别的渠道(例如语音条),不要在本机播放:voice_speak 带 silent: true,音频随回执交给渠道。
|
|
10
9
|
- 对方让你安静、停下时调用 voice_hush。
|
|
11
10
|
|
|
12
11
|
文本规则:
|
|
13
12
|
- text 只放真正要念的正文,不要放动作描写、Markdown、代码、网址或解释。
|
|
14
|
-
-
|
|
13
|
+
- {{voice.modelPrompt}}
|
|
15
14
|
- 不要把控制标记写进可见的回复正文;语音参数只放在工具调用里。
|
package/src/audio/queue.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { AudioPlayer, PlayResult } from './player.ts';
|
|
|
9
9
|
export interface SpeechJob {
|
|
10
10
|
/** 日志与回执里的正文。 */
|
|
11
11
|
text: string;
|
|
12
|
-
/**
|
|
12
|
+
/** 已切好的块;工具要交附件时只有一块。 */
|
|
13
13
|
chunks: string[];
|
|
14
14
|
/** 合成一块。 */
|
|
15
15
|
produce(chunk: string, signal: AbortSignal): Promise<TtsAudio>;
|