🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

说话音效 nsswkj/dsh-typewriter-blip

DeepSeek Harness
nsswkj26 天前有提交已提供中文文档扫描:含敏感能力

模型逐字回答时发出打字音效,可换音色

29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/10 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add nsswkj/dsh-typewriter-blip
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么[目录]—
dsh 原生插件 · ui
暂无时间戳·判据 →
装得上吗[实装验证]✓
本站已在 dsh 0.2.1-rc.5 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
数据截至 2026/11/05·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 26 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

读写/删除本地文件
查看扫描证据(共 7 处,展示前 7 条)
  • 读取本地文件
    build.mjs:46
    return readFileSync(join(HERE, relativePath), "utf8").replace(/ /g, "\n").trimEnd();
  • 读取本地文件
    build.mjs:142
    const current = existsSync(target) ? readFileSync(target, "utf8").replace(/ /g, "\n") : "";
  • 读取本地文件
    build.mjs:142
    const current = existsSync(target) ? readFileSync(target, "utf8").replace(/ /g, "\n") : "";
  • 写入本地文件
    build.mjs:150
    mkdirSync(HERE, { recursive: true });
  • 写入本地文件
    build.mjs:151
    writeFileSync(target, expected, "utf8");
  • 读取本地文件
    test.mjs:54
    const shipped = readFileSync(join(HERE, "client.js"), "utf8").replace(/ /g, "\n");
  • 读取本地文件
    test.mjs:334
    const bundle = readFileSync(join(HERE, "client.js"), "utf8");
扫描于 2026/11/4(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 逐字回答打字音效
  • 后台窗口可听见
  • 音色可更换可调

助手回答流式输出时会有语音提示音,窗口在后台也听得见;音色可换,也能自己调。

README

由 DeepSeek 最新模型翻译生成
说话音效 / Speech Blip 模型回答时逐字发出音效,音色可换,也可以自己调。 进入对话时不发声,模型说完立刻停;窗口最小化、被挡住、或者切到别的标签页时照常发声。 安装 插件市场(dshmarket)里搜说话音效,点一下安装;或者直接装 npm 包: dsh plugin --profile desktop add dsh-typewriter-blip 也可以从仓库装源码(内容和 npm 包一致): dsh plugin --profile desktop add github:nsswkj/dsh-typewriter-blip 装完刷新一次 Harness 页面即可,无需重启。 用法 1. 插件安装后(profile:desktop),刷新一次 Harness 页面。 2. 输入框上方会出现一个 ♪ 说话音效 按钮。 3. 点它打开面板: | 控件 | 作用 | | 启用 | 总开关(关闭后不再发声,也不排队) | | 音色 | 柔和(三角波,默认)/ 打字机(锯齿 + 噪声瞬态)/ 自定义 | | 自定义音色 | 两种来源:合成音色(波形 方波/三角/锯齿/正弦 + 基频 + 音长 + 亮度)或音频文件(本地 audio/,可调播放速度,每个字最多播 0.6 秒)。
展开完整文档(README 剩余 1 节)
说话音效 / Speech Blip

模型回答时逐字发出音效,音色可换,也可以自己调。
进入对话时不发声,模型说完立刻停;窗口最小化、被挡住、或者切到别的标签页时照常发声。

安装

插件市场(dshmarket)里搜说话音效,点一下安装;或者直接装 npm 包:

dsh plugin --profile desktop add dsh-typewriter-blip

也可以从仓库装源码(内容和 npm 包一致):

dsh plugin --profile desktop add github:nsswkj/dsh-typewriter-blip

装完刷新一次 Harness 页面即可,无需重启。

用法

1. 插件安装后(profile:desktop),刷新一次 Harness 页面。
2. 输入框上方会出现一个 ♪ 说话音效 按钮。
3. 点它打开面板:

| 控件 | 作用 |
| 启用 | 总开关(关闭后不再发声,也不排队) |
| 音色 | 柔和(三角波,默认)/ 打字机(锯齿 + 噪声瞬态)/ 自定义 |
| 自定义音色 | 两种来源:合成音色(波形 方波/三角/锯齿/正弦 + 基频 + 音长 + 亮度)或音频文件(本地 audio/,可调播放速度,每个字最多播 0.6 秒)。动这里的任何一项会自动切到「自定义」 |
| 音高 / 音量 | ±12 半音升降调;0–100% 音量 |
| 最小间隔 | 两个音之间的最小毫秒数;流式输出很快时用它控制密度 |
| 说完即停 | 最后一个字之后多久掐掉还响着的音(默认 140 ms,且不会短于一次唤醒的间隔)。还没播的队列不会整段丢掉:前台最多 6 个字、不到三分之一秒就播完;后台按下面「后台也要发声」的规则只留最后 0.3 秒 |
| 跳过空格 | 空格、换行不发声 |
| 只对回答发声 | 只对助手回答的文字发声;工具调用、思考过程(reasoning)和你的消息都不响。默认开 |
| 随机音高 | 每个音随机偏移的半音数,让声音更自然 |
| 试听 | 连播几个字试听当前音色。一次只能听一遍:按下后按钮变灰显示「试听中…」,最后一个字播完才解锁,中途再点无效 |
| 恢复默认 | 回到出厂设置 |

设置即时生效并自动保存(localStorage,键 dsh:typewriter-blip:v1)。
按钮上只有 ♪ 说话音效 两个元素,不再显示发声计数。

面板开在按钮上方(输入框贴着窗口底边,下方没有地方放),并且高度只取按钮上方到窗口顶边之间的空间,内容多就在面板里滚动;只有按钮靠近窗口顶部、上方确实放不下时才会翻到下方。出现时用 ease-out 缓动淡入(180 ms,从按钮那一侧滑进来),关闭时先播完 140 ms 的淡出再从 DOM 里移除,prefers-reduced-motion 下两个动画都会关掉。

什么时候响,什么时候不响

用户要的是"说话"的感觉,而浏览器里只有一个越来越长的 DOM,所以下面三件事必须分开:

- 进入对话 / 历史消息整段挂载:一律不响。第一次看到的容器只建立基线。
- 宿主复用了行元素(换会话、虚拟滚动、Markdown 重新渲染):新文本不再以旧文本开头、或者变短了,就当成换了一行内容,重新建立基线,不发声。
- 一次刷新里整块出现:那是挂载,不是打字(首次增长 > 24 字、之后单帧 > 160 字都按挂载处理)。

反向的两个规则让声音跟得上说话:

- 回答停止:最后一个字之后超过「说完即停」的窗口没有任何新字符,就 cut() 掉所有已排程的音——否则队列里剩下的字会继续说,用户听到的就是"说完了还在响"。队列本身不丢,只裁到还剩最后 0.3 秒(PLAYOUT_MS):前台队列最多 6 个字(MAX_PENDING),按「最小间隔」算不到三分之一秒就播完,所以一个字都不会少;以前一次性丢掉整个队列,才是"换完音效第一句断断续续、只响了一半"的根因。
- 跟得上说话:音效速率上限就是「最小间隔」(默认 45 ms ≈ 每秒 22 个音),而模型一次 chunk 能吐几十个字,所以队列只存得下最新几个字(溢出丢最旧的,前台 6 个、后台按 lead 放宽)。队列长了不会让声音更丰富,只会让声音落后文本一两秒——而这份落后迟早会被整段丢掉。
- 只对回答发声:data-chat-flow-kind 必须是 assistant-step,且 data-chat-group-part 不是 reasoning;代码块、JSON 块、按钮、时间戳等 UI 部分一律不计入。

后台也要发声

最小化、被别的窗口盖住、或者切到别的标签页时,浏览器会停止产生动画帧(requestAnimationFrame 一次都不再触发),并把定时器压到大约一秒一次。排程原来完全挂在动画帧上,所以窗口一进后台就彻底没声了。现在:

- 两个时钟:动画帧照旧是主时钟(页面可见时它总是先醒,定时器不参与);另一个 250 ms 的定时器盯着帧,超过 400 ms 没有帧就接管排程。它不看 Page Visibility API,因为被遮挡的窗口可能既不产生帧、也不肯说自己隐藏了。
- 按唤醒间隔缓冲:driver 用两次唤醒之间的间隔算出 lead(提前排到音频时钟上的秒数),队列容量也由它决定——前台的 lead 是 0.02 秒(⇒ 6 个字),后台是 1.25 秒(⇒ 30 个字,上限 MAX_QUEUE 48)。
音频时钟不受页面可见性影响,所以只要声音已经排在它上面,就一定响得出来。
- 收到文字本身就是一次唤醒:后台定时器可能被压到一分钟一次,那时答案早就结束了,所以每个字符批次进来都会直接 tick 一次;页面一隐藏也会立刻把 lead 抬上去,而不是等第一次粗唤醒才发现。
- 说完即停仍然成立:文字停了以后队列会被裁到只剩最后 0.3 秒(PLAYOUT_MS)再播完。前台队列本来就短于 0.3 秒,所以「换完音效第一句断断续续」那个 bug 不会因为这次改动回来;掐音的窗口也不会短于一次唤醒的间隔——一秒宽的时钟判断不了 140 毫秒的安静,否则 chunk 之间的每个停顿都会被当成"说完了",把刚填好的缓冲整段掐掉。
- 回到前台时 lead 立刻降回帧级并重新开始测量——否则前台会拖着后台那一整秒的积压说话。

音色怎么来的

内置音色由 src/voices.js 的 PRESETS 决定波形、基频、时长、滤波与按字符类别的移调: 柔和 是三角波、打字机 是锯齿加一段高通噪声瞬态。
「自定义」音色由设置里的 custom 字段决定(CUSTOM_DEFAULTS 是出厂值): source: "synth" 时用 wave/freq/duration/filter 合成,source: "sample" 时播放用户载入的音频。
要加内置音色:在 PRESETS 里加一条,再把 id 加进 PRESET_ORDER(PRESET_ORDER[0] 就是新装默认音色);删掉一个音色不用管旧设置,normalizeSettings 会把认不出来的 id 收回默认值。

结构

package.json 清单:dsh.bundle.patch + dsh.client(web) cordis.patch.yml 插入 id 为 typewriter-blip 的 Loader 行 index.js Host 半:空实现(功能全在页面里) src/settings.js 设置与 localStorage 持久化(含自定义音色) src/voices.js 音色预设表 + 自定义音色的出厂值与波形表 src/audio.js Web Audio 合成、采样播放、cut() src/observer.js 回答文字 → 字符增量 → 排程发声 → 说完即停(音频时钟 + 自适应唤醒间隔) src/client.js 输入框上方的按钮与面板(React) build.mjs 把 src/ 拼成单文件 client.js client.js 生成产物(浏览器模块图每个包只加载一个 ./client) test.mjs 离线测试台 LICENSE MIT

开发

页面里的模块图每个包只加载一个 ./client 产物,所以 client.js 是生成文件:改 src/,然后

node build.mjs # 重新生成 client.js
node test.mjs # 离线检查(语法、设置、音色、音频、观察器、节奏、端到端)
node build.mjs --check # 只检查产物是否过期

本机没有独立的 Node 时,可用 Electron 当 Node:

$env:ELECTRON_RUN_AS_NODE=1
& "C:\Program Files\DSH Desktop\DSH Desktop.exe" build.mjs

改完 client.js 需要刷新页面(或重启 DSH Desktop)才会加载新产物。

实现要点

- 插件通过 conversation.composer.dock 这个 slot 把按钮挂到输入框上方,样式只用 Harness 的 --dsw-alias- / --dsw-specific- 主题变量,跟随明暗主题。
- 面板底色用的是 DSH 自己那套浮层表面,而不是随手挑的灰:--dsw-specific-menu (浅色 #f8f9fa94、深色 #43454a73)+ 同一支 --dsw-menu-backdrop-filter
(blur(40px) saturate(150%))毛玻璃 + --dsw-elevation-prominent 投影,和 DSH 的
stats 面板、各种菜单完全一致。原先用的 --dsw-alias-bg-overlay 是徽标底色(浅色 #e9ecf2、深色 #61666b),深色主题下面板会是一块偏亮的灰板,跟界面里其它表面都对不上——「背景色不随主题」就是这么来的。
- 面板是 .twb-root 里的 position:absolute,所以 top/left 必须是相对按钮 的偏移。一旦把窗口坐标当成局部偏移写进去,等于又叠了一次按钮自身的偏移——面板就会掉到屏幕外面去。这段换算抽成了纯函数 placePanel(),离线测试直接钉住它的算术。
- 量面板尺寸只能用 offsetWidth/offsetHeight(布局盒),不能用
getBoundingClientRect()*:入场动画带 scale(.97),rect 返回的是动画中的视觉盒,
拿它算位置会让面板低约 3%(底部压住按钮),直到下一次重新摆放才弹回去——就是「点一下面板才回到该在的位置」那个 bug。measurePanel() 固定了这条规则。
- 逐字音效需要一个「新出现了几个字符」的信号。conversation.chat.node 的 props 里 hookContext 只有 { turnData, disclosureReset },拿不到流式文本,所以插件观察 Conversation 层已经打好的 data-chat-flow-kind 容器。
- 发声在 Web Audio 的时钟上排程:每个音之间至少隔 最小间隔,一次唤醒最多补满 lead 那么长的一段(可见时是一帧的量,后台是一整秒),所以既不会在长时间隐藏后炸出一串音,也不会在两次唤醒之间静音。driver 不假设帧率,它只按自己观测到的唤醒间隔调整。
- 排程是一次同步跑完的,所以后台那一下会把一整秒的音全部记进 voices——它们都还没轮到结束。因此「超额就掐掉最旧的一个」是错的(最旧的那个正是此刻在响的,会被拉成一秒长的音):#reap 只回收音频时钟已经越过的音,onended 照旧负责正常回收。
- 试听不走 driver 的字符队列,而是直接把音排到音频时钟上,所以在它看来「一直没有任何
新字符」=「模型说完了」,说完即停会在第一个音之后就把它掐掉。BlipDriver.hold(秒)
在试听期间压住这个掐音;试听时长由 BlipAudio.blipSeconds()(单个音最长多久)加上排程跨度算出,同一段时间也用来锁住试听按钮。
- 生成文件里的 loader 契约:window.__ModuleLoader__.load({ id, factory(require) }), factory 的第一个参数才是加载器的 require(react 只能从这里拿)。factory 里的任何异常都会落在启动批次里,会白屏整页,所以 build.mjs 给整段加载套了 try/catch。
预览分享卡片长什么样 ↓nsswkj/dsh-typewriter-blip
分享 · DeepSeek Harness Hub
nsswkj/dsh-typewriter-blip

模型逐字回答时发出打字音效,可换音色

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部