cortico-world-asr 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phantivia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # cortico-world-asr
2
+
3
+ Owner: `src/definition.ts`
4
+
5
+ [Cortico](https://github.com/Pal-AI-Lab/Cortico) 的语音识别 World,以独立 npm 包发布。
6
+
7
+ 麦克风里的话经切分、识别、打包,作为外部事件进入上下文。 World **没有工具**:听是纯入站的事,
8
+ 表达通道是嘴(cortico-world-vtuber)或文字,不在这儿。
9
+
10
+ ## 与 Cortico 的关系
11
+
12
+ 这是一个扩展包。它按 Cortico 的扩展契约声明自己:
13
+
14
+ ```jsonc
15
+ "cortico": { "kind": "world", "api": 4, "consoleClient": "dist/console.js", "consoleStyle": "dist/console.css" }
16
+ ```
17
+
18
+ 运行时它以 `cortico/<框架 src 下的路径>` import 框架(`cortico/world.ts`、`cortico/core/types.ts`、
19
+ `cortico/paths.ts` …),由框架 `src/extensions/runtime.ts` 注册的模块钩子解析到框架源码本身,扩展与框架
20
+ 共用同一份实例,所以包必须是 `"type": "module"`。开发期 `tsconfig.json` 与 `vitest.config.ts` 把同一前缀
21
+ 指到同级的框架 checkout(`../BOT/src/`)。浏览器侧(`src/console/**`)对 `cortico/*` 只 `import type`。
22
+
23
+ ## 安装
24
+
25
+ 先在本目录构建面板产物(`dist/` 不进版本库,没有它控制台的语音识别页是空的):
26
+
27
+ ```bash
28
+ corepack pnpm install
29
+ corepack pnpm build
30
+ ```
31
+
32
+ 然后二选一:控制台「扩展」页手动安装,填本目录的绝对路径;或在 `<Cortico>/extensions/` 下
33
+ `corepack pnpm add --ignore-workspace <本目录绝对路径>`。装完整进程重启 Cortico。
34
+
35
+ 识别后端的运行时与权重另装,见下一节。
36
+
37
+ ## 运行时与权重
38
+
39
+ World 不带推理代码、Python 环境,也不带权重;控制台「收听」面板下面那张「运行时与权重」卡负责把它们取来:
40
+
41
+ - **运行时**是 FireRedASR2S 的 Python 环境,装到 `<运行时根>/firered-asr/<commit>/cu128/`:
42
+ `git` 取上游代码并钉在 `4e7d9aaf`,`uv` 建 Python 3.12 环境、装 PyTorch 2.9.1(cu128 wheel,约 3 GB)
43
+ 与 `src/firered-requirements.txt`。装在 `<目录>.partial` 里,写完 `cortico-runtime.json` 才整个改名到位。
44
+ 本机要有 `git` 与 `uv` 在 PATH 上;World 不装它们,也不碰 NVIDIA 驱动。CPU 档用同一份环境。
45
+ - **权重**是 FireRedASR2-AED 的四个文件(`model.pth.tar` 4.7 GB、`cmvn.ark`、`dict.txt`、`train_bpe1000.model`),
46
+ 从 HuggingFace 钉住的 revision 下到 `<模型根>/asr/FireRedASR2-AED/`。每个文件下到 `.partial` 再改名,
47
+ 下一半的文件不会冒充齐了。来源与许可见 [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)。
48
+
49
+ 运行时根与模型根是部署根下的 `runtimes/` 与 `models/`,多份部署共用;约定见框架 `docs/runtimes.md`。
50
+
51
+ 自备的优先:`config.json` 的 `worlds.asr.backend` 节里
52
+
53
+ ```jsonc
54
+ "runtimeDir": "D:/firered", // 含 .venv/ 与 FireRedASR2S/ 的目录;填了就不安装
55
+ "modelFile": "D:/weights/FireRedASR2-AED/model.pth.tar", // 同目录须有另外三个文件;填了就不下载
56
+ "runtimeRevision": "" // 托管安装取哪个 commit;空 = 包里钉住的
57
+ ```
58
+
59
+ `modelFile` 也可以是相对 `<模型根>/asr/` 的路径;留空时面板下拉框列出该目录下每一组完整的权重,
60
+ 默认挑 `FireRedASR2-AED`。运行时目录、权重都在了,后端才起得来;缺哪样,面板上那一行写得出来。
61
+
62
+ 真机跑一遍全流程(联网、要 git 与 uv、要显卡):
63
+
64
+ ```bash
65
+ tsx scripts/check-asr-runtime.ts # 装运行时、下权重、起 server、转写一段合成音
66
+ tsx scripts/check-asr-runtime.ts --cpu # CPU 档
67
+ ```
68
+
69
+ 默认装在 `scratch/asr-runtime-check/` 下,不碰真部署。
70
+
71
+ ## 开发
72
+
73
+ ```bash
74
+ corepack pnpm typecheck # tsc --noEmit,Node 侧与浏览器侧一份配置一起 check
75
+ corepack pnpm test # vitest run
76
+ corepack pnpm build # esbuild → dist/console.{js,css}
77
+ ```
78
+
79
+ 测试全程不联网、不开声卡、不起 Python:运行时安装用假的命令执行器断言每一步与目录协议,
80
+ 权重下载用假的 fetch,后端进程管理用 Node 自己当替身。`firered-server.py` 的协议测试是
81
+ `python -m unittest discover -s tests/asr -p 'test_firered_server.py'`。
82
+
83
+ ## 结构
84
+
85
+ ```
86
+ capture.ts 声卡:RtAudio 输入,16k 单声道,20ms 一帧
87
+ segmenter.ts 切分(能量门限状态机)+ 打包(相邻句子并成一条投递)——纯逻辑
88
+ asr-client.ts 转写端点客户端(OpenAI 兼容 /audio/transcriptions)+ WAV 封装 + 幻觉过滤
89
+ asr-server.ts FireRedASR2-AED 的进程管理(gpu / cpu 两档)
90
+ firered-server.py 本地端点:把 /v1/audio/transcriptions 接到 FireRedASR2-AED
91
+ runtime/env.ts 运行时安装:git + uv 建出的 Python 环境,一个 commit 一个目录
92
+ runtime/models.ts 权重下载:四个文件,固定 revision
93
+ stream.ts /overlay 字幕页 + SSE /stream(自有端口)
94
+ overlay/ 字幕页三件套(OBS 浏览器源与控制台预览是同一份)
95
+ console/ 控制台扩展:收听(含运行时与权重)/ Overlay 字幕 两个面板
96
+ world.ts 装配:配置、面板、事件投递
97
+ ```
98
+
99
+ 一条链四段,每段都能单独换掉:
100
+
101
+ ```
102
+ 声卡 → 切分 → 识别端点 → 打包投递
103
+ ```
104
+
105
+ World 跑在主进程里,不像 cortico-world-vtuber 那样开子进程:这条链上唯一的常驻计算是每帧一次
106
+ 均方根(20ms 一帧、320 个样本),识别本身在另一个进程/另一台机器上。没有 60Hz 的
107
+ 同步工作要隔离,子进程只会多一层 IPC。
108
+
109
+ ## 后端:FireRedASR2-AED
110
+
111
+ 客户端使用 `POST /v1/audio/transcriptions`:multipart 字段 `file` 是 16 kHz、单声道、PCM16 WAV,响应为
112
+ `{ "text": "..." }`。`firered-server.py` 将该接口接到 FireRedASR2-AED。麦克风采集、切分、简体转换、纠错、
113
+ 字幕和事件投递共用同一条链路,换成别的兼容端点只改 `baseUrl`。
114
+
115
+ `worlds.asr.backend.model` 默认为 `FireRedASR2-AED`,只作标签;本地服务实际加载的模型由 `modelFile`
116
+ 决定。`baseUrl` 默认 `http://127.0.0.1:8793/v1`,自带后端就起在这个端口上。显卡档使用 CUDA,CPU 档使用
117
+ 相同权重;`threads=0` 使用 4 线程。AED 按自身能力识别中英文,没有强制语言参数;接口接受 `language`
118
+ 以兼容现有客户端,但该提示不改变 AED 解码。
119
+
120
+ 推理参数为 FP32、beam=3、softmax_smoothing=1.25、aed_length_penalty=0.6、eos_penalty=1,无时间戳和外部
121
+ 语言模型。请求独立,不携带前一句文本。输入直接在内存中解码,不写入临时录音文件;单段最长 60 秒,
122
+ 与切分配置上限一致。
123
+
124
+ 管理器沿用 `state/start/stop` 和 `autoStart`。模型加载与预热结束后才开放 HTTP,因此成功探活表示可识别。
125
+ Windows 停止托管后端会结束 Python 子进程树。已由外部启动的兼容端点仍可复用,管理器只停止自己启动的进程。
126
+
127
+ ## 切分:哪一段值得送去识别
128
+
129
+ 声卡给的是每 20ms 一帧,而识别的输入单位是"一句"。中间这层是能量门限状态机:
130
+
131
+ - `thresholdDb` 判为"有人在说"的门槛。安静房间底噪常在 -60 上下,人声峰值在 -20 上下。
132
+ - `minSpeechMs` 连续超过门槛多久才算开口——咳嗽、键盘、鼠标点击挡在这一关。
133
+ - `dispatchSilenceMs` 停这么久就把手上这段**送去转写**(短门限,默认 250)。
134
+ - `silenceMs` 停这么久才算**一句说完**(长门限,默认 500)——发不发车看它。
135
+ - `preRollMs` 触发点往前多带一段:**人开口的第一个字总在能量越线之前**。
136
+ - `maxUtteranceMs` 一直说不停时到点强切,切完接着收——她不该等一个人讲完五分钟
137
+ 才听见第一个字。
138
+ - `minUtteranceMs` 短于它的碎片直接丢。
139
+
140
+ 门槛调得对不对,「收听」面板上那条带刻度的电平条一眼就能看出来:说话时越过刻度、
141
+ 安静时不越过。这是这个面板存在的主要理由,数字本身说不清这件事。
142
+
143
+ 短门限让识别与剩余的收尾静音重叠。转写结果返回且收尾静音结束后才投递;
144
+ 若识别耗时超过剩余静音,继续等待识别完成。
145
+
146
+ 短门限不会把句子切碎,因为**它切的是音频、不是发车**:长门限没到之前
147
+ `settleRemainingMs` 一直报"还没说完",打包层照旧按着;人在窗口里接着说,后半段自然接上,
148
+ 两半在打包层并回一条。给到 `dispatchSilenceMs >= silenceMs` 就退回原来的单级行为。
149
+
150
+ ## 打包:几句话算一次开口
151
+
152
+ 识别结果**不逐句投递**。人说话是一串短句,逐句唤醒等于把一段话拆成五次打断——但
153
+ "几句算一次开口"这件事已经由切分层的 `silenceMs` 定住了:停顿短于收尾静音的两句根本
154
+ 不会变成两批。所以这一层只剩三个数:
155
+
156
+ - `joinGapMs` 转写落地后**额外**再空等多久(默认 0)。给多少就直接加多少响应延迟;
157
+ 收尾静音之外真正需要它的场合已经没有了,后到的句子由总线下一批带走。
158
+ - `maxHoldMs` 一条最多攒这么久。只在 `joinGapMs > 0` 时才有机会当判据。
159
+ - `minChars` 少于这么多字的结果丢掉:噪声与语气词识别出来常是一两个字。
160
+
161
+ 打包发车不用 100ms 轮询。每次转写落地都会立即检查一次并按精确 deadline 下钟。持有的
162
+ 判据有三条:麦克风正在收音、有音频待转写或转写在途、收尾静音还没到点。前两条各自会在
163
+ 自己完事时再叫一次;第三条有确定的到点时刻,所以由 `flushPackedIfDue` 自己下钟——短门限
164
+ 早就把音频交出去了,那一段最后一个帧回调可能已经过去了。
165
+
166
+ ## 出口纠错表
167
+
168
+ `worlds.asr.corrections` 每条为 `错=对`,用换行或分号分隔;在简体化与幻觉过滤之后、投递之前作整段替换。
169
+ 适用于人名或专有词的固定误识别,左侧匹配文本较长的规则先执行。
170
+
171
+ ## 幻觉过滤
172
+
173
+ whisper 系模型在纯噪声上会稳定地吐出训练集里的高频片段("谢谢观看"、字幕组落款、
174
+ `[音乐]` 这类标注)。它们与真话在文本上无从分辨,只能按名单挡
175
+ (`asr-client.ts` 的 `looksHallucinated`)。漏一条的代价是她收到一句没人说过的话,
176
+ 并且会当真去回应——所以这道过滤宁可误杀。被挡下的在面板上标成 `[丢弃]`,不静默。
177
+
178
+ ## 事件面
179
+
180
+ | 事件 | 触发 | 说明 |
181
+ | --- | --- | --- |
182
+ | `asr.speech` | `flush`(`worlds.asr.wake` 关掉则 `debounce`) | `[语音] <说话人>:<话>` |
183
+
184
+ 识别分不出是谁在说,`worlds.asr.speaker` 就是事件里那个"谁"。
185
+
186
+ `flush` 保持事件 FIFO,整批立即发车:到达就冲洗合批窗口,把积压一起带走,
187
+ 不打断在途的那一轮——后到的话等这轮说完再进下一批。
188
+
189
+ ## Overlay 字幕
190
+
191
+ `stream.ts` 起一个自己的 HTTP 口(`worlds.asr.streamPort`,被占顺延):
192
+ `/overlay` 是字幕页,`/stream` 是它的 SSE 数据面。OBS 加一个浏览器源指到 `/overlay`,
193
+ 控制台的预览用 iframe 嵌**同一个页面**(带 `?bg=dim` 给个暗底)——两份渲染意味着
194
+ "预览里好看、OBS 里不对"这类问题永远查不清。
195
+
196
+ URL 参数按订阅方覆盖:`?lines=3` 只留三行、`?partial=0` 不显示"正在说…"。
197
+ 样式(字号、字重、颜色、描边、底板、字体、保留行数、停留时长)在面板上改,
198
+ 服务端钳完值经 SSE 热推给所有订阅者,OBS 那份不用重开;同时经装配层写回 config.json。
199
+
200
+ ## 控制台面板
201
+
202
+ - **收听(listen)**:麦克风选择、带门槛刻度的电平条、识别后端启停、实时识别文本,以及下面
203
+ 那张「运行时与权重」卡(安装运行时、下载权重、看进度)。电平与文本走 `ctx.stream`
204
+ (World 的 `WorldConsoleDecl.stream`)实时推,计数与后端状态五秒轮一次。
205
+ - **Overlay 字幕(overlay)**:链接、样式、试显、预览。
206
+
207
+ ## 挂载
208
+
209
+ ```ts
210
+ if (cfg.worlds.asr.enabled) {
211
+ worlds.push(new AsrWorld({
212
+ cfg: cfg.worlds.asr,
213
+ timezone: cfg.timezone,
214
+ onDevice: (device) => { /* 写回 config.json */ },
215
+ onOverlayConfig: (overlay) => { /* 同上 */ },
216
+ onModelFile: (modelFile) => { /* 同上 */ },
217
+ }));
218
+ }
219
+ ```
220
+
221
+ 麦克风、权重与字幕样式是在面板上调的,调完要留住:World 只管当场生效,落盘经装配层
222
+ (与 cortico-world-vtuber 的 overlay/TTS 档案同一套办法)。
223
+
224
+ ## 许可
225
+
226
+ MIT,见 [LICENSE](LICENSE)。框架 Cortico 也是 MIT,两者经扩展契约相连,许可各归各。
227
+ 第三方(FireRedASR2S、权重、PyTorch)见 [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md)。
@@ -0,0 +1,32 @@
1
+ # 第三方声明
2
+
3
+ 这个包自己不带推理代码、Python 环境,也不带模型权重。下面是它在运行时会去取、或者要求操作者
4
+ 自己装好的东西,以及各自的许可。
5
+
6
+ ## 识别运行时
7
+
8
+ FireRedASR2S 的推理代码,钉在一个 commit 上,由「收听」面板取到
9
+ `<运行时根>/firered-asr/<commit>/cu128/FireRedASR2S/`,并在同目录 `.venv/` 里建 Python 环境。
10
+
11
+ - 上游:[FireRedTeam/FireRedASR2S](https://github.com/FireRedTeam/FireRedASR2S),Apache-2.0
12
+ - Python 3.12 由 uv 取用;PyTorch、torchaudio、torchvision 取自 PyTorch 官方 cu128 wheel 源,BSD-3-Clause;
13
+ 其余依赖见 `src/firered-requirements.txt`,各随其上游许可
14
+ - `git` 与 `uv` 由操作者自己装好放进 PATH;本项目不修改本机的驱动、运行库与应用控制策略
15
+
16
+ ## 模型权重
17
+
18
+ 放在 `<模型根>/asr/FireRedASR2-AED/`,由面板按固定 revision 从 HuggingFace 下载。
19
+
20
+ | 文件 | 来源 | 许可 |
21
+ |---|---|---|
22
+ | `model.pth.tar`、`cmvn.ark`、`dict.txt`、`train_bpe1000.model` | [FireRedTeam/FireRedASR2-AED](https://huggingface.co/FireRedTeam/FireRedASR2-AED) @ `2304afe` | Apache-2.0(模型卡所载) |
23
+
24
+ ## 其他运行时依赖
25
+
26
+ - **声卡**:`audify`(RtAudio 的 Node 绑定)随包安装,MIT
27
+ - **简繁转换**:`opencc-js`,MIT AND Apache-2.0
28
+
29
+ ## 本包的许可
30
+
31
+ MIT,见 `LICENSE`。框架 [Cortico](https://github.com/Pal-AI-Lab/Cortico) 也是 MIT,两者经扩展契约相连,
32
+ 许可各归各。
@@ -0,0 +1,2 @@
1
+ .mountrow .mstate.asr-on{color:var(--ok)}.mountrow .mstate.asr-off{color:var(--danger)}.mountrow .mstate.asr-warn{color:var(--ink-soft)}.asr-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:8px 14px;margin:6px 0 2px}.asr-form .fieldrow{margin:0}.asr-form .fieldrow>input.field{width:8em}.asr-form .fieldrow>select.field{min-width:12em}.asr-meterrow{display:flex;align-items:center;gap:10px;margin:8px 0 4px}.asr-meter{position:relative;flex:1;height:14px;border-radius:7px;background:var(--sheet-3);border:1px solid var(--line-2);overflow:hidden}.asr-meterfill{height:100%;width:0;background:var(--line-strong);transition:width 90ms linear}.asr-meterfill.on{background:var(--accent)}.asr-metermark{position:absolute;top:0;bottom:0;width:2px;background:var(--ink);opacity:.55}.asr-readout{min-width:8.5em;font-size:12px;color:var(--ink-soft);font-variant-numeric:tabular-nums}.asr-preview{width:100%;aspect-ratio:16 / 9;border:1px solid var(--line-2);border-radius:10px;background:#1c1a17;margin-top:6px}
2
+ /*# sourceMappingURL=console.css.map */
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/console/style.css"],
4
+ "sourcesContent": ["/*\n * \u8BED\u97F3\u8BC6\u522B\u9762\u677F\u7684\u4E13\u6709\u6837\u5F0F\u3002\n *\n * \u53EA\u653E `ctx.ui` \u6CA1\u6709\u5BF9\u5E94\u539F\u8BED\u7684\u90A3\u4E09\u5757:\u7535\u5E73\u6761(\u5E26\u95E8\u69DB\u523B\u5EA6)\u3001\u4E00\u884C\u6392\u5F00\u7684\u8868\u5355\u5757\u3001\n * \u5B57\u5E55\u9884\u89C8\u6846\u3002\u5176\u4F59\u4E00\u5F8B\u7528\u539F\u8BED(sheet / rowbar / section / btn / kv / log / stat /\n * field / checkbox / select),\u90A3\u4E9B class \u5F52\u63A7\u5236\u53F0\u7BA1,\u8FD9\u91CC\u4E00\u4E2A\u5B57\u90FD\u4E0D\u8986\u76D6\u3002\n *\n * \u4E24\u6761\u81EA\u5F8B(\u4E0E mc\u3001QQ \u90A3\u4E24\u4EFD\u540C\u6E90):\u7C7B\u540D\u4E00\u5F8B `asr-` \u524D\u7F00;\u989C\u8272\u53EA\u7528\u63A7\u5236\u53F0\u7684\u4E3B\u9898\u53D8\u91CF\u3002\n *\n * \u7535\u5E73\u6761\u4E0D\u7528 `ui.progress`:\u90A3\u662F\"\u8FD9\u4E2A\u6570\u5360\u591A\u5C11\"\u7684\u8BFB\u6570\u6761,\u800C\u8FD9\u91CC\u8981\u7684\u662F**\u95E8\u69DB\u523B\u5EA6**\n * \u2014\u2014\u6761\u8D70\u5230\u54EA\u4E0D\u91CD\u8981,\u8FC7\u6CA1\u8FC7\u90A3\u9053\u7EBF\u624D\u662F\u552F\u4E00\u8981\u770B\u7684\u4E8B\u3002\n */\n\n/* ---- \u8BC6\u522B\u540E\u7AEF\u90A3\u4E00\u884C\u7684\u72B6\u6001\u8BCD\u914D\u8272(\u501F\u7528 .mountrow \u7684\u7248\u5F0F) ---- */\n.mountrow .mstate.asr-on { color: var(--ok); }\n.mountrow .mstate.asr-off { color: var(--danger); }\n.mountrow .mstate.asr-warn { color: var(--ink-soft); }\n\n/* ---- \u4E00\u884C\u6392\u5F00\u7684\u8868\u5355\u5757 ---- */\n.asr-form {\n display: flex;\n flex-wrap: wrap;\n align-items: flex-end;\n gap: 8px 14px;\n margin: 6px 0 2px;\n}\n.asr-form .fieldrow { margin: 0; }\n.asr-form .fieldrow > input.field { width: 8em; }\n.asr-form .fieldrow > select.field { min-width: 12em; }\n\n/* ---- \u7535\u5E73\u6761 ---- */\n.asr-meterrow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 8px 0 4px;\n}\n.asr-meter {\n position: relative;\n flex: 1;\n height: 14px;\n border-radius: 7px;\n background: var(--sheet-3);\n border: 1px solid var(--line-2);\n overflow: hidden;\n}\n.asr-meterfill {\n height: 100%;\n width: 0;\n background: var(--line-strong);\n transition: width 90ms linear;\n}\n/* \u8D8A\u8FC7\u95E8\u69DB(\u5224\u4E3A\"\u5728\u8BF4\u8BDD\")\u65F6\u6362\u6210\u5F3A\u8C03\u8272:\u8FD9\u4E00\u773C\u5C31\u662F\u95E8\u69DB\u8C03\u5BF9\u6CA1\u6709 */\n.asr-meterfill.on { background: var(--accent); }\n/* \u95E8\u69DB\u523B\u5EA6 */\n.asr-metermark {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 2px;\n background: var(--ink);\n opacity: 0.55;\n}\n.asr-readout {\n min-width: 8.5em;\n font-size: 12px;\n color: var(--ink-soft);\n font-variant-numeric: tabular-nums;\n}\n\n/* ---- \u5B57\u5E55\u9884\u89C8:\u5D4C\u7684\u5C31\u662F OBS \u91CC\u90A3\u4E00\u4EFD overlay \u9875 ---- */\n.asr-preview {\n width: 100%;\n aspect-ratio: 16 / 9;\n border: 1px solid var(--line-2);\n border-radius: 10px;\n background: #1c1a17;\n margin-top: 6px;\n}\n"],
5
+ "mappings": "AAcA,CAAC,SAAS,CAAC,MAAM,CAAC,OAAS,MAAO,IAAI,KAAO,CAC7C,CADC,SACS,CADC,MACM,CAAC,QAAU,MAAO,IAAI,SAAW,CAClD,CAFC,SAES,CAFC,MAEM,CAAC,SAAW,MAAO,IAAI,WAAa,CAGrD,CAAC,SACC,QAAS,KACT,UAAW,KACX,YAAa,SACb,IAAK,IAAI,KAvBX,OAwBU,IAAI,EAAE,GAChB,CACA,CAPC,SAOS,CAAC,SA1BX,OA0B8B,CAAG,CACjC,CARC,SAQS,CADC,QACS,CAAE,KAAK,CAAC,MAAQ,MAAO,GAAK,CAChD,CATC,SASS,CAFC,QAES,CAAE,MAAM,CADA,MACS,UAAW,IAAM,CAGtD,CAAC,aACC,QAAS,KACT,YAAa,OACb,IAAK,KAlCP,OAmCU,IAAI,EAAE,GAChB,CACA,CAAC,UACC,SAAU,SACV,KAAM,EACN,OAAQ,KAxCV,cAyCiB,IACf,WAAY,IAAI,WAChB,OAAQ,IAAI,MAAM,IAAI,UACtB,SAAU,MACZ,CACA,CAAC,cACC,OAAQ,KACR,MAAO,EACP,WAAY,IAAI,eAChB,WAAY,MAAM,KAAK,MACzB,CAEA,CAPC,aAOa,CAAC,GAAK,WAAY,IAAI,SAAW,CAE/C,CAAC,cACC,SAAU,SACV,IAAK,EACL,OAAQ,EACR,MAAO,IACP,WAAY,IAAI,OAChB,QAAS,GACX,CACA,CAAC,YACC,UAAW,MACX,UAAW,KACX,MAAO,IAAI,YACX,qBAAsB,YACxB,CAGA,CAAC,YACC,MAAO,KACP,aAAc,GAAG,EAAE,EACnB,OAAQ,IAAI,MAAM,IAAI,UA1ExB,cA2EiB,KACf,WAAY,QACZ,WAAY,GACd",
6
+ "names": []
7
+ }
@@ -0,0 +1,2 @@
1
+ var oe="http://www.w3.org/2000/svg",ue={"folder-open":[["path",{d:"M3 6h6l2 2h10"}],["path",{d:"M3 6v13h15l3-8H6l-3 8"}]],download:[["path",{d:"M12 3v12"}],["path",{d:"m7 10 5 5 5-5"}],["path",{d:"M5 21h14"}]]};function K(t,e,y="icon"){let s=t.createElementNS(oe,"svg");s.setAttribute("viewBox","0 0 24 24"),s.setAttribute("fill","none"),s.setAttribute("stroke","currentColor"),s.setAttribute("stroke-width","1.8"),s.setAttribute("stroke-linecap","round"),s.setAttribute("stroke-linejoin","round"),s.setAttribute("aria-hidden","true"),s.classList.add(y);for(let[v,a]of ue[e]){let h=t.createElementNS(oe,v);for(let[S,C]of Object.entries(a))h.setAttribute(S,C);s.appendChild(h)}return s}var me="\u8FD0\u884C\u65F6\u662F FireRedASR2S \u7684 Python \u73AF\u5883(git \u53D6\u4EE3\u7801\u3001uv \u5EFA\u73AF\u5883\u5E76\u88C5 PyTorch),\u88C5\u5230\u90E8\u7F72\u6839\u7684 runtimes/firered-asr/ \u4E0B;\u6743\u91CD\u662F FireRedASR2-AED \u7684\u56DB\u4E2A\u6587\u4EF6,\u4E0B\u5230 models/asr/FireRedASR2-AED/\u3002\u914D\u7F6E\u91CC\u586B\u4E86\u300C\u8FD0\u884C\u65F6\u76EE\u5F55(\u81EA\u5907)\u300D\u6216\u300C\u6743\u91CD\u6587\u4EF6(\u81EA\u5907)\u300D\u7684\u90A3\u4E00\u6837\u5C31\u4E0D\u518D\u88C5\u3002\u5B89\u88C5\u8981\u672C\u673A\u6709 git \u4E0E uv\u3002";function j(t){return t>=1e9?`${(t/1e9).toFixed(2)} GB`:t>=1e6?`${Math.round(t/1e6)} MB`:`${t} B`}function ve(t,e){return e?`${j(t)} / ${j(e)}(${Math.round(t/e*100)}%)`:j(t)}function ie(t){let{ui:e}=t,y=e.sheet({title:"\u8FD0\u884C\u65F6\u4E0E\u6743\u91CD",en:"runtime",desc:me}),s=e.msgline(""),v=e.chip("\u2014"),a=e.button("\u5B89\u88C5\u8FD0\u884C\u65F6",{size:"sm",onClick:()=>{b()}}),h=e.rowbar();h.append(e.pill("\u8FD0\u884C\u65F6","plain"),v,s,e.h("span","grow"),a);let S=e.h("div","pagedesc"),C=e.h("div","pagedesc"),c=e.msgline(""),P=e.chip("\u2014"),D=e.button("\u4E0B\u8F7D\u6743\u91CD",{size:"sm",onClick:()=>{w()}}),$=e.rowbar();$.append(e.pill("\u6743\u91CD","plain"),P,c,e.h("span","grow"),D);let R=e.h("div","pagedesc"),x=e.h("div");y.body.append(h,S,C,e.section("\u6743\u91CD","FireRedASR2-AED,\u56DB\u4E2A\u6587\u4EF6\u7F3A\u4E00\u4E0D\u53EF"),$,R,x),t.root.appendChild(y.el);let g=(o,u,k=!1)=>{o.textContent=u,o.classList.toggle("bad",k)},L=!1;async function d(){let o;try{o=await t.invoke("runtime")}catch(p){t.signal.aborted||g(s,A(p),!0);return}if(t.signal.aborted)return;let u=o.install.phase,k=o.models.some(p=>p.phase==="downloading");L=u==="installing"||k,v.textContent=o.own?"\u81EA\u5907\u76EE\u5F55":o.supported?u==="installed"?o.revision.slice(0,7):u==="installing"?`\u5B89\u88C5\u4E2D \xB7 ${o.install.step??""}`:u==="error"?"\u88C5\u5931\u8D25":"\u672A\u5B89\u88C5":"\u672C\u5E73\u53F0\u65E0\u6258\u7BA1\u65B9\u6848",S.textContent=o.dir||"(\u8FD8\u6CA1\u6709\u76EE\u5F55)",C.textContent=u==="installing"?o.install.line??"":"",a.disabled=L||o.own||!o.supported,a.textContent=u==="installed"?"\u91CD\u88C5\u8FD0\u884C\u65F6":"\u5B89\u88C5\u8FD0\u884C\u65F6",o.install.detail&&g(s,o.install.detail,!0),P.textContent=o.modelsComplete?"\u9F50\u4E86":k?"\u4E0B\u8F7D\u4E2D":"\u7F3A\u6587\u4EF6",R.textContent=`${o.modelsDir} \xB7 \u6765\u6E90 ${o.modelsSource}`,D.disabled=L||o.modelsComplete,x.replaceChildren();for(let p of o.models){let O=e.rowbar(),l=p.phase==="present"?j(p.bytes):p.phase==="downloading"?ve(p.done,p.total):p.phase==="error"?p.detail??"\u4E0B\u8F7D\u5931\u8D25":"\u672A\u4E0B\u8F7D";O.append(e.chip(p.file),e.h("span",p.phase==="error"?"bad":"",l)),x.appendChild(O)}}async function b(){g(s,"\u5F00\u59CB\u5B89\u88C5:\u53D6\u4EE3\u7801\u3001\u5EFA\u73AF\u5883\u3001\u88C5 PyTorch(\u51E0 GB),\u522B\u5173\u9875\u9762"),M();try{await t.invoke("installRuntime"),g(s,"\u8FD0\u884C\u65F6\u88C5\u597D\u4E86")}catch(o){t.signal.aborted||g(s,A(o),!0)}d()}async function w(){g(c,"\u5F00\u59CB\u4E0B\u8F7D,model.pth.tar \u6709 4.7 GB"),M();try{await t.invoke("downloadModels"),g(c,"\u6743\u91CD\u9F50\u4E86")}catch(o){t.signal.aborted||g(c,A(o),!0)}d()}async function M(){await d(),L&&!t.signal.aborted&&t.timeout(()=>{M()},1e3)}d()}var ge='\u9009\u9EA6\u514B\u98CE\u3001\u770B\u7535\u5E73\u3001\u770B\u8BC6\u522B\u7ED3\u679C\u3002\u7535\u5E73\u6761\u4E0A\u90A3\u9053\u7AD6\u7EBF\u662F"\u8BF4\u8BDD\u95E8\u69DB":\u8BF4\u8BDD\u65F6\u8D8A\u8FC7\u5B83\u3001\u5B89\u9759\u65F6\u4E0D\u8D8A\u8FC7,\u95E8\u69DB\u5C31\u8C03\u5BF9\u4E86\u3002\u8BC6\u522B\u540E\u7AEF\u5728\u8FD9\u513F\u542F\u505C:FireRed \u663E\u5361\u6863\u9002\u5408\u5B9E\u65F6\u8BC6\u522B\u3001CPU \u6863\u4E0D\u5360\u663E\u5B58,\u6863\u4F4D\u5728\u672C World \u914D\u7F6E\u91CC\u5207\u3002',ae=-60;function se(t){return Math.max(0,Math.min(100,(t-ae)/-ae*100))}var le={mount(t){let{ui:e,root:y}=t,s=e.sheet({title:"\u6536\u542C",en:"listen",desc:ge}),v=s.body;y.appendChild(s.el);let a=U(t,"grow"),h=e.button("\u5F00\u59CB\u6536\u542C",{size:"sm",variant:"primary"}),S=e.button("\u505C\u6B62",{size:"sm",variant:"danger"}),C=e.rowbar();C.append(h,S,a.el),v.append(C);let c=e.select(),P=e.h("div","asr-form");P.append(e.field("\u9EA6\u514B\u98CE",c));let D=e.h("div","asr-meter"),$=e.h("div","asr-meterfill"),R=e.h("div","asr-metermark"),x=e.h("span","asr-readout","\u2014");D.append($,R);let g=e.h("div","asr-meterrow");g.append(D,x);let L=e.h("div","pagedesc");v.append(e.section("\u9EA6\u514B\u98CE","\u9009\u8BBE\u5907\u4E0E\u770B\u7535\u5E73;\u6362\u8BBE\u5907\u4F1A\u91CD\u5F00\u4E00\u6B21\u8F93\u5165\u6D41"),P,g,L);let d=e.select(),b=e.button("",{size:"sm"});b.className+=" pathpick",b.title="\u9009\u62E9\u6743\u91CD\u6587\u4EF6",b.setAttribute("aria-label",b.title),b.appendChild(K(b.ownerDocument,"folder-open"));let w=e.h("a","pathdownload");w.href="https://huggingface.co/FireRedTeam/FireRedASR2-AED/tree/2304afed56eacfee6256dee5937ed22ffa0b64ec",w.target="_blank",w.rel="noopener noreferrer",w.title="FireRedASR2-AED \u6A21\u578B\u6587\u4EF6",w.append(K(w.ownerDocument,"download"),e.h("span",null,"\u4E0B\u8F7D\u6743\u91CD"));let M=e.h("div","asr-form"),o=e.rowbar();o.append(d,b);let u=e.h("div","pathfield-meta",""),k=e.h("span",null,"");u.append(k,w);let p=e.h("div","pathfield");p.append(o,u),M.append(e.field("\u6743\u91CD",p));let O=e.h("div","mountrow"),l=e.h("span","navdot"),f=e.h("span","mstate","\u2014"),B=e.h("span","mdetail"),Q=e.h("span","macts"),I=e.button("\u505C\u6B62",{size:"sm",variant:"danger"}),V=e.button("\u542F\u52A8",{size:"sm",variant:"primary"});Q.append(I,V),O.append(l,e.h("span","mname","\u8BC6\u522B\u540E\u7AEF"),f,B,Q),v.append(e.section("\u8BC6\u522B\u540E\u7AEF","\u6743\u91CD\u6362\u5B8C\u4F1A\u628A\u540E\u7AEF\u91CD\u8D77\u4E00\u6B21;\u6863\u4F4D(\u663E\u5361/CPU)\u5728\u672C World \u914D\u7F6E\u91CC\u5207"),M,O);let J=e.statgrid(),z=e.log({max:200}),q=e.button("\u6E05\u7A7A",{size:"sm"}),X=e.actions();X.append(e.h("span","grow"),q),v.append(e.section("\u8BC6\u522B\u7ED3\u679C","\u5B9E\u65F6\u63A8\u9001;\u4E22\u6389\u7684\u90A3\u4E9B(\u592A\u77ED\u3001\u7591\u4F3C\u5E7B\u89C9)\u4E5F\u6807\u51FA\u6765"),J,z.el,X);let E=null,G=0,Y="",Z="";function H(n){let r=E?.modelFile??"",i=`${n.models.join("\0")}\0${r}`;if(i!==Z){Z=i,d.replaceChildren();let ne=e.h("option",null,"\u81EA\u52A8(\u6309\u6863\u4F4D\u6311)");ne.value="",d.appendChild(ne);for(let T of n.models){let re=e.h("option",null,T.replace(/[/\\]model\.pth\.tar$/,""));re.value=T,d.appendChild(re)}if(r&&!n.models.includes(r)){let T=e.h("option",null,r);T.value=r,d.appendChild(T)}}d.value=r,d.disabled=n.models.length===0&&!r,k.textContent=`\u6743\u91CD\u76EE\u5F55\uFF1A${n.modelsDir}`;let m=n.reachable,F=!n.installed||n.model===null,ce=m?"on":n.phase==="starting"?"warn":"off";l.className=`navdot${m?" on":n.phase==="starting"?" warn":" bad"}`,f.className=`mstate asr-${ce}`,f.textContent=m?n.pid?"\u8FD0\u884C\u4E2D":"\u8FD0\u884C\u4E2D(\u5916\u90E8)":F?"\u672A\u5B89\u88C5":{starting:"\u542F\u52A8\u4E2D",error:"\u5F02\u5E38",stopped:"\u672A\u542F\u52A8"}[n.phase]??n.phase;let te=F&&!m?n.detail??`${n.profile.toUpperCase()} \u6863\u7F3A${n.installed?"\u6743\u91CD":"\u8FD0\u884C\u73AF\u5883"}:\u5728\u4E0B\u9762\u300C\u8FD0\u884C\u65F6\u4E0E\u6743\u91CD\u300D\u5B89\u88C5`:[n.pid?`pid ${n.pid}`:null,n.url,n.model,`${n.profile.toUpperCase()} \u6863`,n.detail].filter(Boolean).join(" \xB7 ");B.textContent=te,B.title=te,V.disabled=n.phase==="starting"||m||F,I.disabled=n.pid===null&&n.phase!=="starting"}function W(n){if(t.signal.aborted)return;if(E=n,!n){a.say("\u6536\u542C\u9762\u677F\u4E0D\u53EF\u7528",!0);return}let r=n.devices.map(i=>i.name).join("\0");if(r!==Y){Y=r,c.replaceChildren();let i=e.h("option",null,"\u7CFB\u7EDF\u9ED8\u8BA4");i.value="",c.appendChild(i);for(let m of n.devices){let F=e.h("option",null,`${m.name}${m.isDefault?"(\u9ED8\u8BA4)":""}`);F.value=m.name,c.appendChild(F)}}c.value=n.devices.some(i=>i.name===n.deviceSetting)?n.deviceSetting:"",L.textContent=n.device?`\u5B9E\u9645:${n.device}`:"",n.audioAvailable||a.say("\u672C\u673A\u6CA1\u6709\u53EF\u7528\u7684\u5F55\u97F3\u8BBE\u5907(\u6216\u539F\u751F\u58F0\u5361\u6A21\u5757\u6CA1\u88C5\u4E0A)",!0),R.style.left=`${se(n.thresholdDb)}%`,ee(n.level,n.speaking,n.listening),h.disabled=n.listening||!n.audioAvailable,S.disabled=!n.listening,c.disabled=!n.audioAvailable,H(n.server),J.replaceChildren(e.stat({k:"\u8BC6\u522B\u53E5\u6570",v:n.counts.utterances}),e.stat({k:"\u5DF2\u6295\u9012",v:n.counts.delivered,accent:!0}),e.stat({k:"\u4E22\u5F03",v:n.counts.dropped}),e.stat({k:"\u7AEF\u70B9",v:n.endpoint.replace(/^https?:\/\//,"")}));for(let i of[...n.recent].reverse())i.at<=G||(G=i.at,z.append(`${new Date(i.at).toTimeString().slice(0,8)} ${i.text} (${i.ms}ms)`));n.detail&&a.say(n.detail,n.detail.includes("\u5931\u8D25"))}function ee(n,r,i){$.style.width=`${se(n)}%`,$.classList.toggle("on",r),x.textContent=i?`${Math.round(n)} dB${r?" \xB7 \u6B63\u5728\u8BF4":""}`:"\u672A\u6536\u542C"}let _=()=>t.invoke("state").then(W,()=>W(null));async function N(n,r){a.say(r);let i=e.disable(h,S),m=null;try{m=await n()}catch(F){t.signal.aborted||a.say(`\u64CD\u4F5C\u5931\u8D25: ${A(F)}`,!0)}finally{i.dispose()}m&&W(m)}h.addEventListener("click",()=>{N(()=>t.invoke("start"),"\u6253\u5F00\u9EA6\u514B\u98CE\u2026")},{signal:t.signal}),S.addEventListener("click",()=>{N(()=>t.invoke("stop"),"\u505C\u6B62\u4E2D\u2026")},{signal:t.signal}),q.addEventListener("click",()=>{z.clear(),N(()=>t.invoke("clear"),"")},{signal:t.signal}),c.addEventListener("change",()=>{N(()=>t.invoke("setDevice",[c.value]),"\u6362\u8BBE\u5907\u2026")},{signal:t.signal}),d.addEventListener("change",()=>{N(()=>t.invoke("setModel",[d.value]),"\u6362\u6743\u91CD(\u8981\u91CD\u8D77\u540E\u7AEF)\u2026")},{signal:t.signal}),b.addEventListener("click",()=>{(async()=>{let n=e.disable(b,d);try{let r=await t.pickPath({kind:"file",title:"\u9009\u62E9 FireRedASR2-AED \u7684 model.pth.tar",extensions:[".tar"],...E?.server.modelsDir?{recommendedDir:E.server.modelsDir}:{},...E?.modelFile?{currentPath:E.modelFile}:{}});if(!r||t.signal.aborted)return;let i=await t.invoke("setModel",[r]);W(i),a.say(`\u6743\u91CD\u8DEF\u5F84\u5DF2\u4FDD\u5B58: ${r}`)}catch(r){t.signal.aborted||a.say(`\u9009\u62E9\u6743\u91CD\u5931\u8D25: ${A(r)}`,!0)}finally{n.dispose()}})()},{signal:t.signal}),V.addEventListener("click",()=>{a.say("\u8BC6\u522B\u540E\u7AEF\u542F\u52A8\u4E2D\u2026(\u8981\u52A0\u8F7D\u6743\u91CD)"),t.invoke("server.start").then(n=>{H(n),pe()},n=>{t.signal.aborted||a.say(`\u542F\u52A8\u5931\u8D25: ${A(n)}`,!0)})},{signal:t.signal}),I.addEventListener("click",()=>{t.invoke("server.stop").then(n=>{H(n),a.say("\u8BC6\u522B\u540E\u7AEF\u5DF2\u505C\u6B62")},n=>{t.signal.aborted||a.say(`\u505C\u6B62\u5931\u8D25: ${A(n)}`,!0)})},{signal:t.signal});function pe(){let n=0,r=t.interval(()=>{if(++n>60){r.dispose(),a.say("\u8BC6\u522B\u540E\u7AEF\u542F\u52A8\u8D85\u65F6",!0);return}t.invoke("server.state").then(i=>{H(i),i.phase!=="starting"&&(r.dispose(),a.say(i.reachable?"\u8BC6\u522B\u540E\u7AEF\u5C31\u7EEA":`\u8BC6\u522B\u540E\u7AEF ${i.detail||i.phase}`,!i.reachable))})},2e3)}t.stream({message:n=>{let r;try{r=JSON.parse(n)}catch{return}if(r.type==="level"){ee(r.level,r.speaking,r.listening),E&&E.listening!==r.listening&&_();return}if(r.type!=="text"&&r.type!=="dropped")return;let i=r.type==="text"?r.at:Date.now();G=Math.max(G,i);let m=new Date(i).toTimeString().slice(0,8);z.append(r.type==="dropped"?`${m} [\u4E22\u5F03] ${r.text}`:`${m} ${r.text} (${r.ms}ms)`)}}),_(),t.interval(()=>{_()},5e3),ie(t)}};var he="OBS \u91CC\u52A0\u4E00\u4E2A\u6D4F\u89C8\u5668\u6E90,\u5730\u5740\u586B\u4E0B\u9762\u8FD9\u6761,\u5C3A\u5BF8\u6309\u753B\u5E03\u6765\u3002\u5B57\u5E55\u6837\u5F0F\u6539\u5B8C\u5373\u65F6\u751F\u6548,OBS \u90A3\u4EFD\u4E0D\u7528\u91CD\u5F00\u3002\u5B57\u4F53\u586B OBS \u90A3\u53F0\u673A\u5668\u4E0A\u88C5\u4E86\u7684\u5B57\u4F53\u540D\u3002",de={mount(t){let{ui:e,root:y}=t,s=e.sheet({title:"Overlay \u5B57\u5E55",en:"subtitle overlay",desc:he}),v=s.body;y.appendChild(s.el);let a=U(t,"grow"),h=e.h("div");v.append(e.section("\u63A8\u6D41\u94FE\u63A5"),h);let S=e.input({type:"number",value:"1"}),C=e.select({options:["300","400","500","600","700","800"]}),c=e.input({type:"number",value:"2"}),P=e.input({type:"number",value:"6"}),D=e.input({value:"#fffef8",cls:"mono"}),$=e.input({value:"#000000",cls:"mono"}),R=e.input({type:"number",value:"1.5"}),x=e.input({type:"number",value:"0.35"}),g=e.input({placeholder:"\u7559\u7A7A = \u9875\u9762\u9ED8\u8BA4"}),L=e.checkbox('\u663E\u793A"\u6B63\u5728\u8BF4\u2026"',{checked:!0}),d=e.h("div","asr-form");d.append(e.field("\u5B57\u53F7\u500D\u7387",S),e.field("\u5B57\u91CD",C),e.field("\u4FDD\u7559\u884C\u6570",c),e.field("\u505C\u7559(\u79D2)",P),e.field("\u5B57\u8272",D),e.field("\u63CF\u8FB9\u8272",$),e.field("\u63CF\u8FB9\u5BBD(px)",R),e.field("\u5E95\u677F\u4E0D\u900F\u660E\u5EA6",x),e.field("\u5B57\u4F53",g));let b=e.button("\u5E94\u7528",{size:"sm",variant:"primary"}),w=e.input({placeholder:"\u8BD5\u663E\u4E00\u6761\u5B57\u5E55"}),M=e.button("\u8BD5\u663E",{size:"sm"}),o=e.actions();o.append(L.el,e.h("span","grow"),w,M,b),v.append(e.section("\u5B57\u5E55\u6837\u5F0F"),d,o,a.el);let u=e.h("iframe","asr-preview");u.setAttribute("title","\u5B57\u5E55\u9884\u89C8"),v.append(e.section("\u9884\u89C8","\u4E0E OBS \u91CC\u90A3\u4EFD\u662F\u540C\u4E00\u4E2A\u9875\u9762,\u53EA\u662F\u52A0\u4E86\u6697\u5E95"),u);function k(l){if(t.signal.aborted)return;if(!l){a.say("\u5B57\u5E55\u9762\u677F\u4E0D\u53EF\u7528",!0),h.replaceChildren(e.placeholder("\u8BC6\u522B\u6D41\u6CA1\u8D77\u6765"));return}h.replaceChildren(e.kv([{k:"\u5B57\u5E55\u9875",v:l.url??"(\u672A\u542F\u52A8)"},{k:"\u8BA2\u9605\u8005",v:`${l.subscribers} \u4E2A(OBS \u6D4F\u89C8\u5668\u6E90 / \u9884\u89C8\u5404\u7B97\u4E00\u4E2A)`},{k:"\u590D\u5236",v:l.url?e.copyButton(l.url,{label:"\u590D\u5236\u94FE\u63A5"}):"\u2014"}]));let f=l.config;S.value=String(f.subtitle.scale),C.value=String(f.subtitle.weight),c.value=String(f.maxLines),P.value=String(Math.round(f.holdMs/1e3)),D.value=f.subtitle.color,$.value=f.subtitle.strokeColor,R.value=String(f.subtitle.strokeW),x.value=String(f.subtitle.plate),g.value=f.subtitle.fontFamily,L.setChecked(f.showPartial);let B=l.url?`${l.url}?bg=dim`:"";B&&u.getAttribute("src")!==B&&u.setAttribute("src",B)}let p=()=>({maxLines:Number(c.value),holdMs:Math.round(Number(P.value)*1e3),showPartial:L.checked,subtitle:{scale:Number(S.value),weight:Number(C.value),color:D.value.trim(),strokeColor:$.value.trim(),strokeW:Number(R.value),plate:Number(x.value),fontFamily:g.value.trim()}}),O=()=>t.invoke("state").then(k,()=>k(null));b.addEventListener("click",()=>{a.say("\u5E94\u7528\u4E2D\u2026"),t.invoke("setConfig",[p()]).then(l=>{k(l),a.say("\u5DF2\u751F\u6548\u5E76\u843D\u76D8;\u8BA2\u9605\u4E2D\u7684\u9875\u9762\u5DF2\u70ED\u66F4\u65B0")},l=>{t.signal.aborted||a.say(`\u5E94\u7528\u5931\u8D25: ${A(l)}`,!0)})},{signal:t.signal}),M.addEventListener("click",()=>{t.invoke("test",[w.value]).then(l=>{k(l),a.say("\u5DF2\u6295\u4E00\u6761\u5230\u5B57\u5E55\u6D41")},l=>{t.signal.aborted||a.say(`\u8BD5\u663E\u5931\u8D25: ${A(l)}`,!0)})},{signal:t.signal}),O()}};function A(t){return t instanceof Error?t.message:String(t)}function U(t,e){let y=t.ui.msgline("");return e&&y.classList.add(e),{el:y,say(s,v){y.textContent=s??"",y.classList.toggle("bad",v===!0)}}}var be={panels:{listen:le,overlay:de}},Ee=be;export{Ee as default,A as errText,U as msgLine};
2
+ //# sourceMappingURL=console.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/console/icons.ts", "../src/console/runtime.ts", "../src/console/listen.ts", "../src/console/overlay.ts", "../src/console/client.ts"],
4
+ "sourcesContent": ["/**\n * \u9762\u677F\u7528\u5230\u7684\u4E24\u679A\u56FE\u6807\u3002\u5305\u5185\u81EA\u5E26\u800C\u4E0D\u4ECE `cortico/web/client/ui/icons.ts` \u53D6:\u6D4F\u89C8\u5668\u4FA7\u5BF9\n * \u6846\u67B6\u53EA\u5141\u8BB8 `import type`,\u9762\u677F bundle \u4E0D\u6253\u8FDB\u6846\u67B6\u7684\u524D\u7AEF\u4EE3\u7801\u3002\u5F62\u72B6\u4E0E\u6846\u67B6\u90A3\u4EFD\u76F8\u540C\u3002\n */\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\ntype Shape = readonly [tag: 'path' | 'circle' | 'rect' | 'line', attrs: Readonly<Record<string, string>>];\n\nconst SHAPES = {\n 'folder-open': [\n ['path', { d: 'M3 6h6l2 2h10' }],\n ['path', { d: 'M3 6v13h15l3-8H6l-3 8' }],\n ],\n download: [\n ['path', { d: 'M12 3v12' }],\n ['path', { d: 'm7 10 5 5 5-5' }],\n ['path', { d: 'M5 21h14' }],\n ],\n} as const satisfies Readonly<Record<string, readonly Shape[]>>;\n\nexport type AsrIconName = keyof typeof SHAPES;\n\nexport function icon(doc: Document, name: AsrIconName, cls = 'icon'): SVGSVGElement {\n const svg = doc.createElementNS(SVG_NS, 'svg');\n svg.setAttribute('viewBox', '0 0 24 24');\n svg.setAttribute('fill', 'none');\n svg.setAttribute('stroke', 'currentColor');\n svg.setAttribute('stroke-width', '1.8');\n svg.setAttribute('stroke-linecap', 'round');\n svg.setAttribute('stroke-linejoin', 'round');\n svg.setAttribute('aria-hidden', 'true');\n svg.classList.add(cls);\n for (const [tag, attrs] of SHAPES[name]) {\n const child = doc.createElementNS(SVG_NS, tag);\n for (const [key, value] of Object.entries(attrs)) child.setAttribute(key, value);\n svg.appendChild(child);\n }\n return svg;\n}\n", "/**\n * \u300C\u8FD0\u884C\u65F6\u4E0E\u6743\u91CD\u300D:\u63A5\u5728\u300C\u6536\u542C\u300D\u9762\u677F\u4E0B\u9762\u7684\u7B2C\u4E8C\u5F20\u5361\u3002\u8FD0\u884C\u65F6\u662F FireRedASR2S \u7684 Python \u73AF\u5883,\n * \u6743\u91CD\u662F FireRedASR2-AED \u7684\u56DB\u4E2A\u6587\u4EF6;\u4E24\u6837\u90FD\u88C5\u5230\u90E8\u7F72\u6839\u4E0B,\u5B89\u88C5\u4E0E\u4E0B\u8F7D\u5404\u4E00\u9897\u952E\u3002\n */\nimport type { ConsolePanelContext } from 'cortico/web/shared/client-panel.ts';\nimport { errText, type AsrRuntimeState } from './client.ts';\n\nconst DESC =\n '\u8FD0\u884C\u65F6\u662F FireRedASR2S \u7684 Python \u73AF\u5883(git \u53D6\u4EE3\u7801\u3001uv \u5EFA\u73AF\u5883\u5E76\u88C5 PyTorch),\u88C5\u5230\u90E8\u7F72\u6839\u7684 runtimes/firered-asr/ \u4E0B;'\n + '\u6743\u91CD\u662F FireRedASR2-AED \u7684\u56DB\u4E2A\u6587\u4EF6,\u4E0B\u5230 models/asr/FireRedASR2-AED/\u3002'\n + '\u914D\u7F6E\u91CC\u586B\u4E86\u300C\u8FD0\u884C\u65F6\u76EE\u5F55(\u81EA\u5907)\u300D\u6216\u300C\u6743\u91CD\u6587\u4EF6(\u81EA\u5907)\u300D\u7684\u90A3\u4E00\u6837\u5C31\u4E0D\u518D\u88C5\u3002\u5B89\u88C5\u8981\u672C\u673A\u6709 git \u4E0E uv\u3002';\n\nfunction gb(bytes: number): string {\n return bytes >= 1e9 ? `${(bytes / 1e9).toFixed(2)} GB` : bytes >= 1e6 ? `${Math.round(bytes / 1e6)} MB` : `${bytes} B`;\n}\n\nfunction progressText(done: number, total: number | null): string {\n return total ? `${gb(done)} / ${gb(total)}(${Math.round((done / total) * 100)}%)` : gb(done);\n}\n\nexport function mountRuntimeSection(ctx: ConsolePanelContext): void {\n const { ui } = ctx;\n const card = ui.sheet({ title: '\u8FD0\u884C\u65F6\u4E0E\u6743\u91CD', en: 'runtime', desc: DESC });\n\n const msg = ui.msgline('');\n const chip = ui.chip('\u2014');\n const btnInstall = ui.button('\u5B89\u88C5\u8FD0\u884C\u65F6', { size: 'sm', onClick: () => void install() });\n const head = ui.rowbar();\n head.append(ui.pill('\u8FD0\u884C\u65F6', 'plain'), chip, msg, ui.h('span', 'grow'), btnInstall);\n const dirLine = ui.h('div', 'pagedesc');\n const stepLine = ui.h('div', 'pagedesc');\n\n const modelsMsg = ui.msgline('');\n const modelsChip = ui.chip('\u2014');\n const btnDownload = ui.button('\u4E0B\u8F7D\u6743\u91CD', { size: 'sm', onClick: () => void download() });\n const modelsHead = ui.rowbar();\n modelsHead.append(ui.pill('\u6743\u91CD', 'plain'), modelsChip, modelsMsg, ui.h('span', 'grow'), btnDownload);\n const modelsDirLine = ui.h('div', 'pagedesc');\n const rows = ui.h('div');\n\n card.body.append(head, dirLine, stepLine, ui.section('\u6743\u91CD', 'FireRedASR2-AED,\u56DB\u4E2A\u6587\u4EF6\u7F3A\u4E00\u4E0D\u53EF'), modelsHead, modelsDirLine, rows);\n ctx.root.appendChild(card.el);\n\n const say = (el: HTMLElement, text: string, bad = false): void => {\n el.textContent = text;\n el.classList.toggle('bad', bad);\n };\n\n /** \u6709\u6D3B\u5728\u8DD1\u5C31\u63D0\u9AD8\u8F6E\u8BE2\u9891\u7387,\u9759\u6B62\u65F6\u4E00\u6B21\u5C31\u591F */\n let busy = false;\n\n async function refresh(): Promise<void> {\n let st: AsrRuntimeState;\n try {\n st = await ctx.invoke<AsrRuntimeState>('runtime');\n } catch (error) {\n if (!ctx.signal.aborted) say(msg, errText(error), true);\n return;\n }\n if (ctx.signal.aborted) return;\n const phase = st.install.phase;\n const downloading = st.models.some((m) => m.phase === 'downloading');\n busy = phase === 'installing' || downloading;\n\n chip.textContent = st.own\n ? '\u81EA\u5907\u76EE\u5F55'\n : !st.supported\n ? '\u672C\u5E73\u53F0\u65E0\u6258\u7BA1\u65B9\u6848'\n : phase === 'installed'\n ? st.revision.slice(0, 7)\n : phase === 'installing'\n ? `\u5B89\u88C5\u4E2D \u00B7 ${st.install.step ?? ''}`\n : phase === 'error'\n ? '\u88C5\u5931\u8D25'\n : '\u672A\u5B89\u88C5';\n dirLine.textContent = st.dir || '(\u8FD8\u6CA1\u6709\u76EE\u5F55)';\n stepLine.textContent = phase === 'installing' ? (st.install.line ?? '') : '';\n btnInstall.disabled = busy || st.own || !st.supported;\n btnInstall.textContent = phase === 'installed' ? '\u91CD\u88C5\u8FD0\u884C\u65F6' : '\u5B89\u88C5\u8FD0\u884C\u65F6';\n if (st.install.detail) say(msg, st.install.detail, true);\n\n modelsChip.textContent = st.modelsComplete ? '\u9F50\u4E86' : downloading ? '\u4E0B\u8F7D\u4E2D' : '\u7F3A\u6587\u4EF6';\n modelsDirLine.textContent = `${st.modelsDir} \u00B7 \u6765\u6E90 ${st.modelsSource}`;\n btnDownload.disabled = busy || st.modelsComplete;\n rows.replaceChildren();\n for (const m of st.models) {\n const row = ui.rowbar();\n const state = m.phase === 'present'\n ? gb(m.bytes)\n : m.phase === 'downloading'\n ? progressText(m.done, m.total)\n : m.phase === 'error'\n ? (m.detail ?? '\u4E0B\u8F7D\u5931\u8D25')\n : '\u672A\u4E0B\u8F7D';\n row.append(ui.chip(m.file), ui.h('span', m.phase === 'error' ? 'bad' : '', state));\n rows.appendChild(row);\n }\n }\n\n async function install(): Promise<void> {\n say(msg, '\u5F00\u59CB\u5B89\u88C5:\u53D6\u4EE3\u7801\u3001\u5EFA\u73AF\u5883\u3001\u88C5 PyTorch(\u51E0 GB),\u522B\u5173\u9875\u9762');\n void poll();\n try {\n await ctx.invoke('installRuntime');\n say(msg, '\u8FD0\u884C\u65F6\u88C5\u597D\u4E86');\n } catch (error) {\n if (!ctx.signal.aborted) say(msg, errText(error), true);\n }\n void refresh();\n }\n\n async function download(): Promise<void> {\n say(modelsMsg, '\u5F00\u59CB\u4E0B\u8F7D,model.pth.tar \u6709 4.7 GB');\n void poll();\n try {\n await ctx.invoke('downloadModels');\n say(modelsMsg, '\u6743\u91CD\u9F50\u4E86');\n } catch (error) {\n if (!ctx.signal.aborted) say(modelsMsg, errText(error), true);\n }\n void refresh();\n }\n\n /** \u5B89\u88C5\u4E0E\u4E0B\u8F7D\u7684 invoke \u8981\u7B49\u5230\u7ED3\u675F\u624D\u8FD4\u56DE,\u8FDB\u5EA6\u9760\u8FD9\u91CC\u6309\u79D2\u5237 */\n async function poll(): Promise<void> {\n await refresh();\n if (busy && !ctx.signal.aborted) ctx.timeout(() => void poll(), 1000);\n }\n\n void refresh();\n}\n", "/**\n * \u9762\u677F `listen` \u2014\u2014 \u6536\u542C:\u9EA6\u514B\u98CE\u3001\u8BC6\u522B\u540E\u7AEF\u3001\u7535\u5E73\u4E0E\u5B9E\u65F6\u8BC6\u522B\u6587\u672C\u3002\n *\n * \u8FD9\u4E00\u5C4F\u662F**\u8C03\u95E8\u69DB**\u7684\u5730\u65B9\u3002\u7535\u5E73\u6761\u4E0A\u90A3\u9053\u7AD6\u7EBF\u5C31\u662F `worlds.asr.segment.thresholdDb`:\n * \u8BF4\u8BDD\u65F6\u6761\u8981\u8D8A\u8FC7\u5B83\u3001\u5B89\u9759\u65F6\u4E0D\u8D8A\u8FC7\u2014\u2014\u8FD9\u6761\u5224\u636E\u4E00\u53E5\u8BDD\u8BF4\u5F97\u6E05,\u800C\u5149\u770B\u6570\u5B57\u8BF4\u4E0D\u6E05\u3002\n * \u6240\u4EE5\u7535\u5E73\u8D70 `ctx.stream` \u5B9E\u65F6\u63A8(10Hz),\u4E0D\u662F\u8F6E\u8BE2\u56DE\u6765\u7684\u4E00\u4E2A\u9759\u6B62\u8BFB\u6570\u3002\n *\n * \u8BC6\u522B\u6587\u672C\u7528 `ui.log`(\u53EA\u5F80\u672B\u5C3E\u957F\u7684\u4E8B\u4EF6\u6D41\u3001\u5C3E\u90E8\u7C98\u6EDE),\u4E0D\u662F `ui.table`:\n * \u4E00\u6761\u6761\u6765\u7684\u4E1C\u897F\u6574\u8868\u91CD\u753B\u4F1A\u5237\u6389\u6EDA\u52A8\u4F4D\u7F6E\u3002\n */\n\nimport type {\n ConsolePanelContext,\n ConsolePanel,\n} from 'cortico/web/shared/client-panel.ts';\nimport {\n errText,\n msgLine,\n type AsrListenState,\n type AsrPanelFrame,\n type AsrServerState,\n} from './client.ts';\nimport { icon } from './icons.ts';\nimport { mountRuntimeSection } from './runtime.ts';\n\nconst DESC =\n '\u9009\u9EA6\u514B\u98CE\u3001\u770B\u7535\u5E73\u3001\u770B\u8BC6\u522B\u7ED3\u679C\u3002\u7535\u5E73\u6761\u4E0A\u90A3\u9053\u7AD6\u7EBF\u662F\"\u8BF4\u8BDD\u95E8\u69DB\":\u8BF4\u8BDD\u65F6\u8D8A\u8FC7\u5B83\u3001'\n + '\u5B89\u9759\u65F6\u4E0D\u8D8A\u8FC7,\u95E8\u69DB\u5C31\u8C03\u5BF9\u4E86\u3002\u8BC6\u522B\u540E\u7AEF\u5728\u8FD9\u513F\u542F\u505C:FireRed \u663E\u5361\u6863\u9002\u5408\u5B9E\u65F6\u8BC6\u522B\u3001'\n + 'CPU \u6863\u4E0D\u5360\u663E\u5B58,\u6863\u4F4D\u5728\u672C World \u914D\u7F6E\u91CC\u5207\u3002';\n\n/** \u7535\u5E73\u6761\u7684\u663E\u793A\u8303\u56F4:-60 dBFS \u4EE5\u4E0B\u90FD\u5F53\u9759\u97F3 */\nconst FLOOR_DB = -60;\n\nfunction pct(db: number): number {\n return Math.max(0, Math.min(100, ((db - FLOOR_DB) / -FLOOR_DB) * 100));\n}\n\nexport const listenPanel: ConsolePanel = {\n mount(ctx: ConsolePanelContext) {\n const { ui, root } = ctx;\n const card = ui.sheet({ title: '\u6536\u542C', en: 'listen', desc: DESC });\n const s = card.body;\n root.appendChild(card.el);\n\n const msg = msgLine(ctx, 'grow');\n const btnListen = ui.button('\u5F00\u59CB\u6536\u542C', { size: 'sm', variant: 'primary' });\n const btnStop = ui.button('\u505C\u6B62', { size: 'sm', variant: 'danger' });\n const bar = ui.rowbar();\n bar.append(btnListen, btnStop, msg.el);\n s.append(bar);\n\n // ---- \u9EA6\u514B\u98CE ----\n const devSel = ui.select();\n const devForm = ui.h('div', 'asr-form');\n devForm.append(ui.field('\u9EA6\u514B\u98CE', devSel));\n const meter = ui.h('div', 'asr-meter');\n const fill = ui.h('div', 'asr-meterfill');\n const mark = ui.h('div', 'asr-metermark');\n const readout = ui.h('span', 'asr-readout', '\u2014');\n meter.append(fill, mark);\n const meterRow = ui.h('div', 'asr-meterrow');\n meterRow.append(meter, readout);\n const actual = ui.h('div', 'pagedesc');\n s.append(ui.section('\u9EA6\u514B\u98CE', '\u9009\u8BBE\u5907\u4E0E\u770B\u7535\u5E73;\u6362\u8BBE\u5907\u4F1A\u91CD\u5F00\u4E00\u6B21\u8F93\u5165\u6D41'), devForm, meterRow, actual);\n\n // ---- \u8BC6\u522B\u540E\u7AEF ----\n const modelSel = ui.select();\n const btnModelFile = ui.button('', { size: 'sm' });\n btnModelFile.className += ' pathpick';\n btnModelFile.title = '\u9009\u62E9\u6743\u91CD\u6587\u4EF6';\n btnModelFile.setAttribute('aria-label', btnModelFile.title);\n btnModelFile.appendChild(icon(btnModelFile.ownerDocument, 'folder-open'));\n const modelDownload = ui.h('a', 'pathdownload');\n modelDownload.href = 'https://huggingface.co/FireRedTeam/FireRedASR2-AED/tree/2304afed56eacfee6256dee5937ed22ffa0b64ec';\n modelDownload.target = '_blank';\n modelDownload.rel = 'noopener noreferrer';\n modelDownload.title = 'FireRedASR2-AED \u6A21\u578B\u6587\u4EF6';\n modelDownload.append(icon(modelDownload.ownerDocument, 'download'), ui.h('span', null, '\u4E0B\u8F7D\u6743\u91CD'));\n const modelForm = ui.h('div', 'asr-form');\n const modelRow = ui.rowbar();\n modelRow.append(modelSel, btnModelFile);\n const modelMeta = ui.h('div', 'pathfield-meta', '');\n const modelDirNote = ui.h('span', null, '');\n modelMeta.append(modelDirNote, modelDownload);\n const modelField = ui.h('div', 'pathfield');\n modelField.append(modelRow, modelMeta);\n modelForm.append(ui.field('\u6743\u91CD', modelField));\n const srvLine = ui.h('div', 'mountrow');\n const srvDot = ui.h('span', 'navdot');\n const srvState = ui.h('span', 'mstate', '\u2014');\n const srvDetail = ui.h('span', 'mdetail');\n const srvActs = ui.h('span', 'macts');\n const btnSrvStop = ui.button('\u505C\u6B62', { size: 'sm', variant: 'danger' });\n const btnSrvStart = ui.button('\u542F\u52A8', { size: 'sm', variant: 'primary' });\n srvActs.append(btnSrvStop, btnSrvStart);\n srvLine.append(srvDot, ui.h('span', 'mname', '\u8BC6\u522B\u540E\u7AEF'), srvState, srvDetail, srvActs);\n s.append(\n ui.section('\u8BC6\u522B\u540E\u7AEF', '\u6743\u91CD\u6362\u5B8C\u4F1A\u628A\u540E\u7AEF\u91CD\u8D77\u4E00\u6B21;\u6863\u4F4D(\u663E\u5361/CPU)\u5728\u672C World \u914D\u7F6E\u91CC\u5207'),\n modelForm,\n srvLine,\n );\n\n // ---- \u8BC6\u522B\u6587\u672C ----\n const stats = ui.statgrid();\n const logView = ui.log({ max: 200 });\n const btnClear = ui.button('\u6E05\u7A7A', { size: 'sm' });\n const logBar = ui.actions();\n logBar.append(ui.h('span', 'grow'), btnClear);\n s.append(ui.section('\u8BC6\u522B\u7ED3\u679C', '\u5B9E\u65F6\u63A8\u9001;\u4E22\u6389\u7684\u90A3\u4E9B(\u592A\u77ED\u3001\u7591\u4F3C\u5E7B\u89C9)\u4E5F\u6807\u51FA\u6765'), stats, logView.el, logBar);\n\n // -----------------------------------------------------------------------\n\n let cur: AsrListenState | null = null;\n let seenAt = 0;\n /** \u4E0A\u4E00\u6B21\u94FA\u9009\u9879\u65F6\u7684\u8BBE\u5907\u540D\u5355;\u53D8\u4E86\u624D\u91CD\u94FA,\u514D\u5F97\u5237\u6389\u7528\u6237\u6B63\u5728\u9009\u7684\u90A3\u4E00\u4E0B */\n let deviceKey = '';\n /** \u540C\u4E0A,\u6743\u91CD\u90A3\u4E00\u4EFD */\n let modelKey = '';\n\n function renderServer(st: AsrServerState): void {\n const configured = cur?.modelFile ?? '';\n const key = `${st.models.join('\\0')}\\0${configured}`;\n if (key !== modelKey) {\n modelKey = key;\n modelSel.replaceChildren();\n const auto = ui.h('option', null, '\u81EA\u52A8(\u6309\u6863\u4F4D\u6311)');\n auto.value = '';\n modelSel.appendChild(auto);\n for (const m of st.models) {\n const op = ui.h('option', null, m.replace(/[/\\\\]model\\.pth\\.tar$/, ''));\n op.value = m;\n modelSel.appendChild(op);\n }\n if (configured && !st.models.includes(configured)) {\n const external = ui.h('option', null, configured);\n external.value = configured;\n modelSel.appendChild(external);\n }\n }\n // \u9009\u4E2D\u7684\u662F\u914D\u7F6E\u91CC\u9489\u7684\u90A3\u4EFD;\u771F\u6B63\u52A0\u8F7D\u7684\u90A3\u4EFD\u5199\u5728\u4E0B\u9762\u90A3\u884C(\u81EA\u52A8\u6311\u65F6\u4E24\u8005\u4E0D\u540C)\n modelSel.value = configured;\n modelSel.disabled = st.models.length === 0 && !configured;\n modelDirNote.textContent = `\u6743\u91CD\u76EE\u5F55\uFF1A${st.modelsDir}`;\n const up = st.reachable;\n const missing = !st.installed || st.model === null;\n const tone = up ? 'on' : st.phase === 'starting' ? 'warn' : 'off';\n srvDot.className = `navdot${up ? ' on' : st.phase === 'starting' ? ' warn' : ' bad'}`;\n srvState.className = `mstate asr-${tone}`;\n srvState.textContent = up\n ? (st.pid ? '\u8FD0\u884C\u4E2D' : '\u8FD0\u884C\u4E2D(\u5916\u90E8)')\n : missing\n ? '\u672A\u5B89\u88C5'\n : ({ starting: '\u542F\u52A8\u4E2D', error: '\u5F02\u5E38', stopped: '\u672A\u542F\u52A8' } as Record<string, string>)[st.phase] ?? st.phase;\n const note = missing && !up\n ? st.detail ?? (\n `${st.profile.toUpperCase()} \u6863\u7F3A${!st.installed ? '\u8FD0\u884C\u73AF\u5883' : '\u6743\u91CD'}:\u5728\u4E0B\u9762\u300C\u8FD0\u884C\u65F6\u4E0E\u6743\u91CD\u300D\u5B89\u88C5`\n )\n : [\n st.pid ? `pid ${st.pid}` : null,\n st.url,\n st.model,\n `${st.profile.toUpperCase()} \u6863`,\n st.detail,\n ].filter(Boolean).join(' \u00B7 ');\n srvDetail.textContent = note;\n srvDetail.title = note;\n btnSrvStart.disabled = st.phase === 'starting' || up || missing;\n btnSrvStop.disabled = st.pid === null && st.phase !== 'starting';\n }\n\n function render(st: AsrListenState | null): void {\n if (ctx.signal.aborted) return;\n cur = st;\n if (!st) {\n msg.say('\u6536\u542C\u9762\u677F\u4E0D\u53EF\u7528', true);\n return;\n }\n // \u8BBE\u5907\u8868\u6309**\u540D\u5B57**\u6BD4,\u4E0D\u6309\u6761\u6570:\u63D2\u62D4\u4E24\u652F\u9EA6\u514B\u98CE\u65F6\u6761\u6570\u4E0D\u53D8,\u800C\u9009\u9879\u8BE5\u6362\n const key = st.devices.map((d) => d.name).join('\\0');\n if (key !== deviceKey) {\n deviceKey = key;\n devSel.replaceChildren();\n const auto = ui.h('option', null, '\u7CFB\u7EDF\u9ED8\u8BA4');\n auto.value = '';\n devSel.appendChild(auto);\n for (const d of st.devices) {\n const op = ui.h('option', null, `${d.name}${d.isDefault ? '(\u9ED8\u8BA4)' : ''}`);\n op.value = d.name;\n devSel.appendChild(op);\n }\n }\n // \u4E0B\u62C9\u6846\u7ED1\u7684\u662F\u914D\u7F6E\u91CC\u9489\u7684\u90A3\u4E2A,'' = \u8DDF\u968F\u7CFB\u7EDF\u9ED8\u8BA4;\u771F\u6B63\u5728\u7528\u7684\u5199\u5728\u7535\u5E73\u6761\u65C1\u8FB9\n devSel.value = st.devices.some((d) => d.name === st.deviceSetting) ? st.deviceSetting : '';\n actual.textContent = st.device ? `\u5B9E\u9645:${st.device}` : '';\n if (!st.audioAvailable) {\n msg.say('\u672C\u673A\u6CA1\u6709\u53EF\u7528\u7684\u5F55\u97F3\u8BBE\u5907(\u6216\u539F\u751F\u58F0\u5361\u6A21\u5757\u6CA1\u88C5\u4E0A)', true);\n }\n mark.style.left = `${pct(st.thresholdDb)}%`;\n setLevel(st.level, st.speaking, st.listening);\n btnListen.disabled = st.listening || !st.audioAvailable;\n btnStop.disabled = !st.listening;\n devSel.disabled = !st.audioAvailable;\n renderServer(st.server);\n stats.replaceChildren(\n ui.stat({ k: '\u8BC6\u522B\u53E5\u6570', v: st.counts.utterances }),\n ui.stat({ k: '\u5DF2\u6295\u9012', v: st.counts.delivered, accent: true }),\n ui.stat({ k: '\u4E22\u5F03', v: st.counts.dropped }),\n ui.stat({ k: '\u7AEF\u70B9', v: st.endpoint.replace(/^https?:\\/\\//, '') }),\n );\n // \u9996\u6B21\u8FDB\u9762\u677F\u8865\u4E0A\u5DF2\u7ECF\u542C\u5230\u8FC7\u7684\u90A3\u4E9B(\u5B9E\u65F6\u901A\u9053\u53EA\u63A8\u6B64\u540E\u7684)\n for (const r of [...st.recent].reverse()) {\n if (r.at <= seenAt) continue;\n seenAt = r.at;\n logView.append(`${new Date(r.at).toTimeString().slice(0, 8)} ${r.text} (${r.ms}ms)`);\n }\n if (st.detail) msg.say(st.detail, st.detail.includes('\u5931\u8D25'));\n }\n\n function setLevel(db: number, speaking: boolean, listening: boolean): void {\n fill.style.width = `${pct(db)}%`;\n fill.classList.toggle('on', speaking);\n readout.textContent = listening ? `${Math.round(db)} dB${speaking ? ' \u00B7 \u6B63\u5728\u8BF4' : ''}` : '\u672A\u6536\u542C';\n }\n\n const refresh = (): Promise<void> =>\n ctx.invoke<AsrListenState>('state').then(render, () => render(null));\n\n async function act(fn: () => Promise<AsrListenState>, working: string): Promise<void> {\n msg.say(working);\n const lock = ui.disable(btnListen, btnStop);\n let next: AsrListenState | null = null;\n try {\n next = await fn();\n } catch (err) {\n if (!ctx.signal.aborted) msg.say(`\u64CD\u4F5C\u5931\u8D25: ${errText(err)}`, true);\n } finally {\n lock.dispose();\n }\n if (next) render(next);\n }\n\n btnListen.addEventListener('click', () => {\n void act(() => ctx.invoke<AsrListenState>('start'), '\u6253\u5F00\u9EA6\u514B\u98CE\u2026');\n }, { signal: ctx.signal });\n btnStop.addEventListener('click', () => {\n void act(() => ctx.invoke<AsrListenState>('stop'), '\u505C\u6B62\u4E2D\u2026');\n }, { signal: ctx.signal });\n btnClear.addEventListener('click', () => {\n logView.clear();\n void act(() => ctx.invoke<AsrListenState>('clear'), '');\n }, { signal: ctx.signal });\n devSel.addEventListener('change', () => {\n void act(() => ctx.invoke<AsrListenState>('setDevice', [devSel.value]), '\u6362\u8BBE\u5907\u2026');\n }, { signal: ctx.signal });\n modelSel.addEventListener('change', () => {\n void act(() => ctx.invoke<AsrListenState>('setModel', [modelSel.value]), '\u6362\u6743\u91CD(\u8981\u91CD\u8D77\u540E\u7AEF)\u2026');\n }, { signal: ctx.signal });\n btnModelFile.addEventListener('click', () => {\n void (async () => {\n const lock = ui.disable(btnModelFile, modelSel);\n try {\n const path = await ctx.pickPath({\n kind: 'file',\n title: '\u9009\u62E9 FireRedASR2-AED \u7684 model.pth.tar',\n extensions: ['.tar'],\n ...(cur?.server.modelsDir ? { recommendedDir: cur.server.modelsDir } : {}),\n ...(cur?.modelFile ? { currentPath: cur.modelFile } : {}),\n });\n if (!path || ctx.signal.aborted) return;\n const next = await ctx.invoke<AsrListenState>('setModel', [path]);\n render(next);\n msg.say(`\u6743\u91CD\u8DEF\u5F84\u5DF2\u4FDD\u5B58: ${path}`);\n } catch (err) {\n if (!ctx.signal.aborted) msg.say(`\u9009\u62E9\u6743\u91CD\u5931\u8D25: ${errText(err)}`, true);\n } finally {\n lock.dispose();\n }\n })();\n }, { signal: ctx.signal });\n\n btnSrvStart.addEventListener('click', () => {\n msg.say('\u8BC6\u522B\u540E\u7AEF\u542F\u52A8\u4E2D\u2026(\u8981\u52A0\u8F7D\u6743\u91CD)');\n void ctx.invoke<AsrServerState>('server.start').then(\n (st) => { renderServer(st); waitReady(); },\n (err: unknown) => { if (!ctx.signal.aborted) msg.say(`\u542F\u52A8\u5931\u8D25: ${errText(err)}`, true); },\n );\n }, { signal: ctx.signal });\n btnSrvStop.addEventListener('click', () => {\n void ctx.invoke<AsrServerState>('server.stop').then(\n (st) => { renderServer(st); msg.say('\u8BC6\u522B\u540E\u7AEF\u5DF2\u505C\u6B62'); },\n (err: unknown) => { if (!ctx.signal.aborted) msg.say(`\u505C\u6B62\u5931\u8D25: ${errText(err)}`, true); },\n );\n }, { signal: ctx.signal });\n\n /** \u7B49\u540E\u7AEF\u8D70\u5B8C starting\u3002\u8F6E\u8BE2\u8D70 ctx.interval,\u79BB\u5F00\u9762\u677F\u5373\u505C\u3002 */\n function waitReady(): void {\n let ticks = 0;\n const timer = ctx.interval(() => {\n if (++ticks > 60) {\n timer.dispose();\n msg.say('\u8BC6\u522B\u540E\u7AEF\u542F\u52A8\u8D85\u65F6', true);\n return;\n }\n void ctx.invoke<AsrServerState>('server.state').then((st) => {\n renderServer(st);\n if (st.phase === 'starting') return;\n timer.dispose();\n msg.say(st.reachable ? '\u8BC6\u522B\u540E\u7AEF\u5C31\u7EEA' : `\u8BC6\u522B\u540E\u7AEF ${st.detail || st.phase}`, !st.reachable);\n });\n }, 2000);\n }\n\n // ---- \u5B9E\u65F6\u901A\u9053:\u7535\u5E73\u4E0E\u8BC6\u522B\u6587\u672C ----\n ctx.stream({\n message: (raw) => {\n let frame: AsrPanelFrame;\n try {\n frame = JSON.parse(raw) as AsrPanelFrame;\n } catch {\n return;\n }\n // \u53EA\u8BA4\u5F97\u8FD9\u4E09\u79CD;\u522B\u7684(\u901A\u9053\u81EA\u8EAB\u7684\u9519\u8BEF\u5E27\u4E00\u7C7B)\u4E0D\u5F80\u8BC6\u522B\u6587\u672C\u91CC\u585E\u2014\u2014\n // \u90A3\u6761\u6D41\u91CC\u6DF7\u8FDB\u4E00\u53E5\u6846\u67B6\u7684\u8BDD,\u8BFB\u8D77\u6765\u5C31\u50CF\u6709\u4EBA\u771F\u8BF4\u4E86\u8FD9\u53E5\n if (frame.type === 'level') {\n setLevel(frame.level, frame.speaking, frame.listening);\n if (cur && cur.listening !== frame.listening) void refresh();\n return;\n }\n if (frame.type !== 'text' && frame.type !== 'dropped') return;\n const at = frame.type === 'text' ? frame.at : Date.now();\n seenAt = Math.max(seenAt, at);\n const stamp = new Date(at).toTimeString().slice(0, 8);\n logView.append(frame.type === 'dropped'\n ? `${stamp} [\u4E22\u5F03] ${frame.text}`\n : `${stamp} ${frame.text} (${frame.ms}ms)`);\n },\n });\n\n void refresh();\n // \u8BA1\u6570\u4E0E\u540E\u7AEF\u72B6\u6001\u4E0D\u8D70\u5B9E\u65F6\u901A\u9053,\u6162\u4E00\u70B9\u65E0\u6240\u8C13\n ctx.interval(() => { void refresh(); }, 5000);\n\n mountRuntimeSection(ctx);\n },\n};\n", "/**\n * \u9762\u677F `overlay` \u2014\u2014 \u5B57\u5E55\u5C42:\u63A8\u6D41\u94FE\u63A5\u3001\u6837\u5F0F\u4E0E\u8BD5\u663E\u3002\n *\n * \u9884\u89C8\u7528 iframe \u5D4C**\u540C\u4E00\u4E2A** overlay \u9875(\u5E26 `?bg=dim` \u7ED9\u4E2A\u6697\u5E95),\u4E0D\u53E6\u5199\u4E00\u4EFD\n * \u6E32\u67D3:\u4E24\u4EFD\u6E32\u67D3\u610F\u5473\u7740\"\u9884\u89C8\u91CC\u597D\u770B\u3001OBS \u91CC\u4E0D\u5BF9\"\u8FD9\u7C7B\u95EE\u9898\u6C38\u8FDC\u67E5\u4E0D\u6E05\u3002\n *\n * \u6837\u5F0F\u6539\u52A8\u5373\u65F6\u751F\u6548\u2014\u2014\u670D\u52A1\u7AEF\u94B3\u5B8C\u503C\u7ECF SSE \u70ED\u63A8\u7ED9\u6240\u6709\u8BA2\u9605\u8005,OBS \u91CC\u90A3\u4EFD\u4E0D\u7528\u91CD\u5F00\u3002\n */\n\nimport type {\n ConsolePanelContext,\n ConsolePanel,\n} from 'cortico/web/shared/client-panel.ts';\nimport {\n errText,\n msgLine,\n type AsrOverlayConfig,\n type AsrOverlayState,\n} from './client.ts';\n\nconst DESC =\n 'OBS \u91CC\u52A0\u4E00\u4E2A\u6D4F\u89C8\u5668\u6E90,\u5730\u5740\u586B\u4E0B\u9762\u8FD9\u6761,\u5C3A\u5BF8\u6309\u753B\u5E03\u6765\u3002\u5B57\u5E55\u6837\u5F0F\u6539\u5B8C\u5373\u65F6\u751F\u6548,'\n + 'OBS \u90A3\u4EFD\u4E0D\u7528\u91CD\u5F00\u3002\u5B57\u4F53\u586B OBS \u90A3\u53F0\u673A\u5668\u4E0A\u88C5\u4E86\u7684\u5B57\u4F53\u540D\u3002';\n\nexport const overlayPanel: ConsolePanel = {\n mount(ctx: ConsolePanelContext) {\n const { ui, root } = ctx;\n const card = ui.sheet({ title: 'Overlay \u5B57\u5E55', en: 'subtitle overlay', desc: DESC });\n const s = card.body;\n root.appendChild(card.el);\n\n const msg = msgLine(ctx, 'grow');\n const linkBox = ui.h('div');\n s.append(ui.section('\u63A8\u6D41\u94FE\u63A5'), linkBox);\n\n // ---- \u6837\u5F0F ----\n const scale = ui.input({ type: 'number', value: '1' });\n const weight = ui.select({ options: ['300', '400', '500', '600', '700', '800'] });\n const maxLines = ui.input({ type: 'number', value: '2' });\n const holdSec = ui.input({ type: 'number', value: '6' });\n const color = ui.input({ value: '#fffef8', cls: 'mono' });\n const strokeColor = ui.input({ value: '#000000', cls: 'mono' });\n const strokeW = ui.input({ type: 'number', value: '1.5' });\n const plate = ui.input({ type: 'number', value: '0.35' });\n const font = ui.input({ placeholder: '\u7559\u7A7A = \u9875\u9762\u9ED8\u8BA4' });\n const partialIn = ui.checkbox('\u663E\u793A\"\u6B63\u5728\u8BF4\u2026\"', { checked: true });\n const form = ui.h('div', 'asr-form');\n form.append(\n ui.field('\u5B57\u53F7\u500D\u7387', scale),\n ui.field('\u5B57\u91CD', weight),\n ui.field('\u4FDD\u7559\u884C\u6570', maxLines),\n ui.field('\u505C\u7559(\u79D2)', holdSec),\n ui.field('\u5B57\u8272', color),\n ui.field('\u63CF\u8FB9\u8272', strokeColor),\n ui.field('\u63CF\u8FB9\u5BBD(px)', strokeW),\n ui.field('\u5E95\u677F\u4E0D\u900F\u660E\u5EA6', plate),\n ui.field('\u5B57\u4F53', font),\n );\n const btnApply = ui.button('\u5E94\u7528', { size: 'sm', variant: 'primary' });\n const testText = ui.input({ placeholder: '\u8BD5\u663E\u4E00\u6761\u5B57\u5E55' });\n const btnTest = ui.button('\u8BD5\u663E', { size: 'sm' });\n const acts = ui.actions();\n acts.append(partialIn.el, ui.h('span', 'grow'), testText, btnTest, btnApply);\n s.append(ui.section('\u5B57\u5E55\u6837\u5F0F'), form, acts, msg.el);\n\n // ---- \u9884\u89C8 ----\n const frame = ui.h('iframe', 'asr-preview');\n frame.setAttribute('title', '\u5B57\u5E55\u9884\u89C8');\n s.append(ui.section('\u9884\u89C8', '\u4E0E OBS \u91CC\u90A3\u4EFD\u662F\u540C\u4E00\u4E2A\u9875\u9762,\u53EA\u662F\u52A0\u4E86\u6697\u5E95'), frame);\n\n // -----------------------------------------------------------------------\n\n function render(st: AsrOverlayState | null): void {\n if (ctx.signal.aborted) return;\n if (!st) {\n msg.say('\u5B57\u5E55\u9762\u677F\u4E0D\u53EF\u7528', true);\n linkBox.replaceChildren(ui.placeholder('\u8BC6\u522B\u6D41\u6CA1\u8D77\u6765'));\n return;\n }\n linkBox.replaceChildren(ui.kv([\n { k: '\u5B57\u5E55\u9875', v: st.url ?? '(\u672A\u542F\u52A8)' },\n { k: '\u8BA2\u9605\u8005', v: `${st.subscribers} \u4E2A(OBS \u6D4F\u89C8\u5668\u6E90 / \u9884\u89C8\u5404\u7B97\u4E00\u4E2A)` },\n { k: '\u590D\u5236', v: st.url ? ui.copyButton(st.url, { label: '\u590D\u5236\u94FE\u63A5' }) : '\u2014' },\n ]));\n const c = st.config;\n scale.value = String(c.subtitle.scale);\n weight.value = String(c.subtitle.weight);\n maxLines.value = String(c.maxLines);\n holdSec.value = String(Math.round(c.holdMs / 1000));\n color.value = c.subtitle.color;\n strokeColor.value = c.subtitle.strokeColor;\n strokeW.value = String(c.subtitle.strokeW);\n plate.value = String(c.subtitle.plate);\n font.value = c.subtitle.fontFamily;\n partialIn.setChecked(c.showPartial);\n const want = st.url ? `${st.url}?bg=dim` : '';\n if (want && frame.getAttribute('src') !== want) frame.setAttribute('src', want);\n }\n\n const patch = (): Partial<AsrOverlayConfig> => ({\n maxLines: Number(maxLines.value),\n holdMs: Math.round(Number(holdSec.value) * 1000),\n showPartial: partialIn.checked,\n subtitle: {\n scale: Number(scale.value),\n weight: Number(weight.value),\n color: color.value.trim(),\n strokeColor: strokeColor.value.trim(),\n strokeW: Number(strokeW.value),\n plate: Number(plate.value),\n fontFamily: font.value.trim(),\n },\n });\n\n const refresh = (): Promise<void> =>\n ctx.invoke<AsrOverlayState>('state').then(render, () => render(null));\n\n btnApply.addEventListener('click', () => {\n msg.say('\u5E94\u7528\u4E2D\u2026');\n void ctx.invoke<AsrOverlayState>('setConfig', [patch()]).then(\n (st) => { render(st); msg.say('\u5DF2\u751F\u6548\u5E76\u843D\u76D8;\u8BA2\u9605\u4E2D\u7684\u9875\u9762\u5DF2\u70ED\u66F4\u65B0'); },\n (err: unknown) => { if (!ctx.signal.aborted) msg.say(`\u5E94\u7528\u5931\u8D25: ${errText(err)}`, true); },\n );\n }, { signal: ctx.signal });\n\n btnTest.addEventListener('click', () => {\n void ctx.invoke<AsrOverlayState>('test', [testText.value]).then(\n (st) => { render(st); msg.say('\u5DF2\u6295\u4E00\u6761\u5230\u5B57\u5E55\u6D41'); },\n (err: unknown) => { if (!ctx.signal.aborted) msg.say(`\u8BD5\u663E\u5931\u8D25: ${errText(err)}`, true); },\n );\n }, { signal: ctx.signal });\n\n void refresh();\n },\n};\n", "/**\n * \u8BED\u97F3\u8BC6\u522B World \u7684\u6D4F\u89C8\u5668\u6269\u5C55 \u2014\u2014 \u6536\u542C / Overlay \u5B57\u5E55 \u4E24\u4E2A\u9762\u677F\u3002\n *\n * \u8FD9\u4E2A\u6587\u4EF6\u53EA\u505A\u88C5\u914D\u4E0E\u5171\u4EAB\u7C7B\u578B;\u9762\u677F\u672C\u4F53\u5404\u5728\u81EA\u5DF1\u7684\u6587\u4EF6\u91CC\u3002\u4E0E\u5916\u754C\u7684\u4F9D\u8D56\u53EA\u6709\n * `client-panel.ts` \u7684\u7C7B\u578B:\u6570\u636E\u9762\u4E00\u5F8B\u8D70 `ctx.invoke`,\u5B9E\u65F6\u6570\u636E\u8D70 `ctx.stream`,\n * DOM \u4E00\u5F8B\u7528 `ctx.ui` \u539F\u8BED,\u5B9A\u65F6\u5668\u4E00\u5F8B `ctx.interval`\u3002\n *\n * `style.css` \u662F\u672C provider \u81EA\u5DF1\u7684\u6837\u5F0F,\u53EA\u653E `ctx.ui` \u6CA1\u6709\u5BF9\u5E94\u539F\u8BED\u7684\u90A3\u4E24\u5757\n * (\u7535\u5E73\u6761\u3001\u5B57\u5E55\u9884\u89C8\u6846),\u7528 `asr-` \u524D\u7F00\u907F\u5F00\u63A7\u5236\u53F0\u7684\u901A\u7528 class\u3002\n */\n\nimport type {\n ConsoleClientBundle,\n ConsolePanelContext,\n} from 'cortico/web/shared/client-panel.ts';\nimport './style.css';\nimport { listenPanel } from './listen.ts';\nimport { overlayPanel } from './overlay.ts';\n\n// ---------------------------------------------------------------------------\n// \u5171\u4EAB\u7C7B\u578B:\u670D\u52A1\u7AEF `AsrWorld.invokePanel` \u5404\u65B9\u6CD5\u7684\u8FD4\u56DE\u5F62\u72B6\n// ---------------------------------------------------------------------------\n\ninterface AsrMicDevice {\n id: number;\n name: string;\n channels: number;\n isDefault: boolean;\n}\n\nexport interface AsrServerState {\n phase: string;\n url: string;\n detail: string | null;\n pid: number | null;\n reachable: boolean;\n model: string | null;\n profile: string;\n /** \u6709\u4E00\u4E2A\u80FD\u8DD1\u7684\u8FD0\u884C\u65F6\u76EE\u5F55(\u6258\u7BA1\u88C5\u7684\u6216\u81EA\u5907\u7684) */\n installed: boolean;\n modelsDir: string;\n /** \u6743\u91CD\u6839\u4E0B\u6BCF\u4E00\u7EC4\u5B8C\u6574\u6743\u91CD */\n models: string[];\n}\n\n/** `listen.runtime` */\nexport interface AsrRuntimeState {\n revision: string;\n key: string | null;\n dir: string;\n own: boolean;\n supported: boolean;\n install: { phase: 'absent' | 'installing' | 'installed' | 'error'; step: string | null; line: string | null; detail: string | null };\n models: Array<{\n file: string; path: string; phase: 'absent' | 'downloading' | 'present' | 'error';\n bytes: number; done: number; total: number | null; detail: string | null;\n }>;\n modelsDir: string;\n modelsSource: string;\n modelsComplete: boolean;\n}\n\n/** `listen.state` */\nexport interface AsrListenState {\n listening: boolean;\n audioAvailable: boolean;\n devices: AsrMicDevice[];\n /** \u914D\u7F6E\u91CC\u9489\u7684\u90A3\u4E2A;\u7A7A = \u8DDF\u968F\u7CFB\u7EDF\u9ED8\u8BA4 */\n deviceSetting: string;\n /** \u6B64\u523B\u771F\u6B63\u5728\u7528\u7684\u8BBE\u5907\u540D */\n device: string;\n /** \u6700\u8FD1\u4E00\u5E27\u7684\u7535\u5E73(dBFS) */\n level: number;\n speaking: boolean;\n thresholdDb: number;\n /** \u914D\u7F6E\u91CC\u9489\u7684\u6743\u91CD\u6587\u4EF6\u540D;\u7A7A = \u81EA\u52A8\u6311 */\n modelFile: string;\n simplified: boolean;\n recent: Array<{ text: string; at: number; ms: number }>;\n counts: { utterances: number; delivered: number; dropped: number };\n server: AsrServerState;\n endpoint: string;\n detail: string | null;\n}\n\nexport interface AsrSubtitleStyle {\n scale: number;\n weight: number;\n color: string;\n strokeColor: string;\n strokeW: number;\n plate: number;\n fontFamily: string;\n}\n\nexport interface AsrOverlayConfig {\n maxLines: number;\n holdMs: number;\n showPartial: boolean;\n subtitle: AsrSubtitleStyle;\n}\n\n/** `overlay.state` */\nexport interface AsrOverlayState {\n url: string | null;\n streamUp: boolean;\n subscribers: number;\n config: AsrOverlayConfig;\n}\n\n/** `listen` \u7684\u5B9E\u65F6\u901A\u9053\u63A8\u7684\u5E27 */\nexport type AsrPanelFrame =\n | { type: 'level'; level: number; speaking: boolean; listening: boolean }\n | { type: 'text'; text: string; at: number; ms: number }\n | { type: 'dropped'; text: string; ms: number };\n\n// ---------------------------------------------------------------------------\n// \u5171\u4EAB helper\n// ---------------------------------------------------------------------------\n\nexport function errText(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\ninterface AsrMsgLine {\n el: HTMLDivElement;\n say(text?: string | null, bad?: boolean): void;\n}\n\nexport function msgLine(ctx: ConsolePanelContext, cls?: string): AsrMsgLine {\n const el = ctx.ui.msgline('');\n if (cls) el.classList.add(cls);\n return {\n el,\n say(text, bad) {\n el.textContent = text ?? '';\n el.classList.toggle('bad', bad === true);\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n\nconst bundle: ConsoleClientBundle = {\n panels: {\n listen: listenPanel,\n overlay: overlayPanel,\n },\n};\n\nexport default bundle;\n"],
5
+ "mappings": "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",
6
+ "names": ["SVG_NS", "SHAPES", "icon", "doc", "name", "cls", "svg", "tag", "attrs", "child", "key", "value", "DESC", "gb", "bytes", "progressText", "done", "total", "mountRuntimeSection", "ctx", "ui", "card", "msg", "chip", "btnInstall", "install", "head", "dirLine", "stepLine", "modelsMsg", "modelsChip", "btnDownload", "download", "modelsHead", "modelsDirLine", "rows", "say", "el", "text", "bad", "busy", "refresh", "st", "error", "errText", "phase", "downloading", "m", "row", "state", "poll", "DESC", "FLOOR_DB", "pct", "db", "listenPanel", "ctx", "ui", "root", "card", "s", "msg", "msgLine", "btnListen", "btnStop", "bar", "devSel", "devForm", "meter", "fill", "mark", "readout", "meterRow", "actual", "modelSel", "btnModelFile", "icon", "modelDownload", "modelForm", "modelRow", "modelMeta", "modelDirNote", "modelField", "srvLine", "srvDot", "srvState", "srvDetail", "srvActs", "btnSrvStop", "btnSrvStart", "stats", "logView", "btnClear", "logBar", "cur", "seenAt", "deviceKey", "modelKey", "renderServer", "st", "configured", "key", "auto", "m", "op", "external", "up", "missing", "tone", "note", "render", "d", "setLevel", "r", "speaking", "listening", "refresh", "act", "fn", "working", "lock", "next", "err", "errText", "path", "waitReady", "ticks", "timer", "raw", "frame", "at", "stamp", "mountRuntimeSection", "DESC", "overlayPanel", "ctx", "ui", "root", "card", "s", "msg", "msgLine", "linkBox", "scale", "weight", "maxLines", "holdSec", "color", "strokeColor", "strokeW", "plate", "font", "partialIn", "form", "btnApply", "testText", "btnTest", "acts", "frame", "render", "st", "c", "want", "patch", "refresh", "err", "errText", "errText", "err", "msgLine", "ctx", "cls", "el", "text", "bad", "bundle", "listenPanel", "overlayPanel", "client_default"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "cortico-world-asr",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Cortico World: microphone speech recognition — RtAudio capture, energy-gated segmentation, FireRedASR2-AED backend, subtitle overlay",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/Phantivia/cortico-world-asr.git"
10
+ },
11
+ "homepage": "https://github.com/Phantivia/cortico-world-asr#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/Phantivia/cortico-world-asr/issues"
14
+ },
15
+ "keywords": [
16
+ "cortico-world",
17
+ "cortico",
18
+ "asr",
19
+ "speech-recognition",
20
+ "fireredasr"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "packageManager": "pnpm@11.5.0",
26
+ "main": "./src/index.ts",
27
+ "cortico": {
28
+ "kind": "world",
29
+ "api": 4,
30
+ "consoleClient": "dist/console.js",
31
+ "consoleStyle": "dist/console.css"
32
+ },
33
+ "files": [
34
+ "src",
35
+ "dist",
36
+ "scripts",
37
+ "README.md",
38
+ "LICENSE",
39
+ "THIRD_PARTY_NOTICES.md"
40
+ ],
41
+ "scripts": {
42
+ "build": "node scripts/build-console.mjs",
43
+ "test": "vitest run",
44
+ "typecheck": "tsc --noEmit"
45
+ },
46
+ "dependencies": {
47
+ "audify": "^1.10.1",
48
+ "opencc-js": "^1.4.1"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.10.0",
52
+ "esbuild": "^0.28.2",
53
+ "jsdom": "^30.0.1",
54
+ "tsx": "^4.19.2",
55
+ "typescript": "^5.7.2",
56
+ "vitest": "^2.1.8"
57
+ }
58
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 面板 bundle:`src/console/client.ts` → `dist/console.js` + `dist/console.css`。
3
+ * 两个产物名写死在 package.json 的 `cortico.consoleClient` / `consoleStyle` 里,
4
+ * 服务端自己给它们分配 URL。
5
+ *
6
+ * 不给 `cortico/*` 配 alias 也不 external:浏览器侧只允许 `import type` 框架,
7
+ * 那些 import 编译期就被擦掉。构建若报 "Could not resolve cortico/…",说明有一处
8
+ * 运行时依赖漏网,去把它本地化,而不是在这里放行。
9
+ */
10
+ import * as esbuild from 'esbuild';
11
+ import { mkdirSync, rmSync } from 'node:fs';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const root = fileURLToPath(new URL('../', import.meta.url));
15
+ const outdir = `${root}dist`;
16
+
17
+ rmSync(outdir, { recursive: true, force: true });
18
+ mkdirSync(outdir, { recursive: true });
19
+
20
+ const result = await esbuild.build({
21
+ entryPoints: [{ in: `${root}src/console/client.ts`, out: 'console' }],
22
+ outdir,
23
+ bundle: true,
24
+ format: 'esm',
25
+ target: 'es2022',
26
+ platform: 'browser',
27
+ minify: true,
28
+ sourcemap: true,
29
+ metafile: true,
30
+ });
31
+
32
+ for (const [path, info] of Object.entries(result.metafile.outputs)) {
33
+ if (path.endsWith('.map')) continue;
34
+ console.log(`${path.replace(/\\/g, '/')} ${(info.bytes / 1024).toFixed(1)} KB`);
35
+ }