Repository navigation
Releases: loyalchiiina/dsh-voice-alert
Release list
v0.4.18
v0.4.18 — 界面缺陷收口 + 效果图更新
本版全部围绕设置面板的「说了没做到」问题:四个界面缺陷 + 两张新效果图。宿主侧行为(播报、音效、音量、免费音色)无改动。
🩹 1. 音色库「每次重载都消失」
设置面板是 React 槽位,renderVoiceRows() 是命令式写 DOM。宿主随时可能把整棵 section 重渲染(切页签、其他插件注册/刷新),React 只认自己的 vdom,重渲染会把容器重建成全新的空 div,命令式塞进去的行全部蒸发;同时 loadSettings() 被 cache.loaded 挡住不再跑,界面永久停在「读取中…」+ 空音色库。盘上的 config.json 一直是好的,纯前端显示问题。
→ 重挂载时自愈:重放轻量读路径 loadVoices() + refreshAllActionGroups()(1.5 s 节流)。
🗑 2. 「我的音色库」看不到删除按钮
行布局的左栏 libLeft 挂上了,右栏 libRight(试听结果 + 删除按钮)createElement 之后从没 appendChild,一直在内存里飘着。
→ 补挂 libRight;删除改成点完即存(POST /voices/save),失败回滚并提示。
🌀 3. 音色库「有时候不显示」
上一版自愈走 setTimeout(0),是下一帧才补,React 清空到补画之间肉眼可见闪一下。
→ 给 #dva-voice-rows 挂 MutationObserver(childList),React 一清空就在同一轮微任务里同步重画,纯内存重放不发网络请求;cache.rowsRendering 闸门挡递归;容器被重建时自动重挂。
🔓 4. 删除按钮仍然「用不了」
libRight 挂回后按钮看得见了,但「正在使用的克隆音色」那行 delBtn.disabled = inUse —— 库里只有一个克隆音色时,唯一那行的删除按钮永远是灰的。
→ 删除按钮任何行都可用;删当前音色由宿主 /voices/save 的归位兜底(自动落到剩下的第一个,删空则置空串);回填 selectedVoiceId 改用 !== undefined 判(空串是合法值)。
💬 5. 试听图标的悬浮气泡「显示不完整 / 出框」
.dva-tip-bubble 是 position:absolute,CSS 无法得知祖先的 overflow 裁剪边界,也无法预知自己展开后的宽度。图标贴着面板边缘时,300px 宽的气泡必然顶出去被裁。
→ 浮出瞬间用 JS 实测真实矩形再夹回可见区:
tipClipRect(wrap)—— 向上遍历祖先,与所有overflow非visible的容器矩形求交集,得到真正的可见区(无裁剪祖先退化为视口)placeTip(wrap)—— 按可用宽度夹紧maxWidth/minWidth,水平越界回移,下方放不下而上方更宽裕时翻到锚点上方ensureTipPlacement()——document上挂一次委托监听,浮出定位、滚动/缩放重算
🖼 6. 效果图更新
两张新的脱敏实拍图替换旧图:assets/settings-panel.png(语音播报模式,含 7 个免费内置音色)+ 新增 assets/settings-panel-sfx.png(音效模式,每条事件一个下拉)。高级设置里存 Key 的输入框、克隆音色 ID 与本机路径均已打码。
✅ 自检
node test/self-check.mjs --no-sound → passed 472 / failed 0 / SELF-CHECK OK
新增 §20 系列静态断言 + 桩几何功能测试(假 DOM 没有布局引擎,气泡定位这类缺陷只能靠源码断言 + 桩几何两路锁)。
Full Changelog: v0.4.15...v0.4.18
v0.4.15
新增功能
| 功能 | 说明 |
|---|---|
| 下载 MP3 | 每条播报的「试听」旁新增「⬇ 下载」,把当前音频存成 mp3。新增 GET /audio/download?kind=&variant=loud|origin,返回 audio/mpeg + Content-Disposition: attachment;variant 可选增益版(默认)或原件 |
| 导入 MP3 | 用自己的 mp3 替换该条播报。新增 POST /audio/import?kind=,接收原始 mp3 字节(上限 10 MB),落盘前自动备份到 audio\backups\import-<ts>\,同时写 origin 与 loud 两份并清掉过期 wav 缓存 |
| 撤销导入 | 已导入的播报旁多一个「↩ 撤销导入」,从导入时的备份还原 origin + loud 并删掉标记。新增 POST /audio/import/undo?kind=;/settings 与 /status 顶层回显 imported: {complete,fail,approval} |
| 错误原因中文化 | 宿主返回的英文 reason(not an mp3 / audio file not found / body too large 等 9 类)在前端统一翻成中文人话,未知原因保留原文兜底 |
缺陷修复
| 严重度 | 缺陷 | 根因 | 修法 |
|---|---|---|---|
| P0 | 响度增益全部跳过、wav 播放内核永远降级 MCI | ffmpegPath 指向不存在的路径 |
ffmpeg 放入插件数据目录 <data>\bin\ffmpeg.exe 并改配置 |
| P0 | originalsDir 指向不存在的旧路径 → original 候选恒不存在 |
配置遗留 | 改为 <data>\audio\origin |
| P0 | 每次生成都全量重合成,白耗 TTS 额度 | generate.js 的 fingerprint 落库条件误排除 gainSkipped |
改为 if (entry && entry.ok),并删掉随之成为死代码的 else if 分支 |
| P0 | 用户导入的 mp3 会被「生成语音」静默覆盖(改任意一条文案就冲掉全部导入,且无备份) | 合成流水线只比对文案指纹,完全不知道磁盘上的音频是用户导入的 | 导入时写标记 audio\origin\voice-alert-<kind>-poetic.imported.json(含 backupDir/sha256);generate.js 在 planChanges 与 generateOne 两处按标记跳过该 kind,只有 force(强制重新生成)才覆盖 |
| P0 | 设置页「保存音色库」时,若预设音色 Key 输入框留空,会把 config.json 里已保存的 presetApiKey 覆盖成空串,且无提示、无备份 |
/voices/save 把请求里的 presetApiKey: "" 当作「用户要清空」直接写入;前端 voiceSave() 也无条件提交该字段 |
三层守卫:①前端留空时根本不带该字段;②宿主 /voices/save 只在 length > 0 时才写;③saveVoicesConfig() 落盘前同样只接受非空值,清空必须显式传 clearPresetApiKey: true |
| P1 | 播放时长按 192/160 kbps 估算,实际 mp3 为 64 kbps → 21.4s 只算成 7.15s,槽位提前释放抢声卡导致「点试听没声音」 | 固定码率常量 | 新增 lib/mp3info.js 纯 JS 解析 MP3 帧头(含 VBR Xing 头),head-slice 只读 64 KB;旧公式保留为 fallback |
| P1 | 同一 turn 内多次工具失败连续播多次 fail | agent/error 与 tools/result 各自 emit,5s 窗只限速不按轮去重 |
新增 failOncePerTurn(默认 true),同 turn 内 fail 只播一次;手动 /play 豁免 |
| P1 | 预设音色 Key 状态在前端不显示「是否已配置」,用户无法判断留空是否安全 | 缺少状态回显 | 新增 renderPresetKeyState():已配置显示「已配置(mask)· 留空表示不修改」,未配置显示引导文案 |
| P2 | 导入长音频时,排队等声卡的 24 s 固定上限会在音频还没放完时提前放弃,新进程抢声卡 → 又回到「静默无声」 | acquirePlaybackSlot 用固定次数 guard < 200 × 120 ms = 24 s,与放宽到 120 s 的时长上限脱节 |
改成按「当前占用剩余时长 + 5 s 余量」动态算截止时间,轮询节奏仍是 120 ms |
| P2 | 配置写入无原子性 | 直接覆盖写 | 改为先写临时文件再 renameSync 原子替换 |
| P2 | 导入未用 mp3info 校验可解析性 |
只查文件头 | 落盘前用 mp3info 解析一遍,解析不出时长即拒绝 |
界面
设置页做了一轮「傻瓜化」改造:分区更清晰、每条播报的操作按钮成组、关键配置配白话说明、高级设置默认折叠(平时不用动)。原有功能一个未删(145 个 dva-* 元素渲染校验通过)。
升级注意
- 本次改动不涉及默认值变更,已有
config.json不受影响 - 需重启 DSH 使宿主侧改动生效;设置页前端改动刷新页面即可
- 自检:
node test/self-check.mjs --no-sound→ 325 过 / 1 失败(唯一失败是测试自身走临时目录、读不到运行时ffmpegPath,属测试环境限制,非功能缺陷,见 README §自检)
Full Changelog: v0.4.13...v0.4.15
v0.4.13
v0.4.13:与 npm 同版本发布(GitHub 源码 + npm 包 + 本 Release 三处对齐)。