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

虚拟形象舞台 Stella-Mira/muse_vtuber

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

在对话页旁展示 LIVE2D 形象,随语音口型与表情联动

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么
dsh 原生插件 · other
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 3 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

读写/删除本地文件发起外部网络请求
查看扫描证据(共 34 处,展示前 34 条)
  • 发起网络请求
    lib/client.js:47
    const response = await fetch(API + path, {
  • 发起网络请求
    lib/client.js:60
    const response = await fetch(API + path, { headers: { accept: 'application/json' } })
  • 发起网络请求
    lib/client.js:1557
    fetch(API + '/settings', { headers: { accept: 'application/json' } })
  • 发起网络请求
    lib/client.js:1584
    const response = await fetch(API + '/settings', {
  • 写入本地文件
    lib/kit.mjs:43
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    lib/kit.mjs:71
    try { chmodSync(tmp, options.mode) } catch { }
  • 写入本地文件
    lib/kit.mjs:73
    renameSync(tmp, file)
  • 删除本地文件
    lib/kit.mjs:80
    try { unlinkSync(tmp) } catch { }
  • 读取本地文件
    lib/kit.mjs:93
    if (!existsSync(file)) return fallback
  • 读取本地文件
    lib/kit.mjs:94
    const raw = JSON.parse(readFileSync(file, 'utf8'))
  • 读取本地文件
    lib/kit.mjs:103
    if (!existsSync(file)) return { ok: false, reason: 'missing' }
  • 读取本地文件
    lib/kit.mjs:106
    raw = JSON.parse(readFileSync(file, 'utf8'))
  • 读取本地文件
    lib/kit.mjs:118
    try { return statSync(file).mode & 0o777 } catch { return -1 }
  • 读取本地文件
    lib/config.js:169
    return statSync(dir).isDirectory()
  • 读取本地文件
    lib/config.js:212
    return readFileSync(this.file, 'utf8')
  • 读取本地文件
    lib/models.js:23
    return readdirSync(dir, { withFileTypes: true })
  • 读取本地文件
    lib/models.js:87
    text = readFileSync(file, 'utf8')
  • 读取本地文件
    lib/models.js:134
    if (!existsSync(full)) missing.push({ file: rel, why })
  • 读取本地文件
    lib/models.js:181
    return statSync(caps.dir).isDirectory()
  • 读取本地文件
    lib/routes.js:141
    const buf = await readFile(full)
  • 读取本地文件
    lib/legacy/node-bridge.js:130
    const readDirSafe = (dir) => { try { return readdirSync(dir, { withFileTypes: true }) } catch { return [] } }
  • 读取本地文件
    lib/legacy/node-bridge.js:172
    if (JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')).name === '@deepseek-ai/dsh') { root = dir; break }
  • 读取本地文件
    lib/legacy/node-bridge.js:181
    if (existsSync(join(mods, 'dsh-session-format-v3-to-v4', 'package.json'))) return { format: 4, how: 'host ships dsh-session-format-v3-to-v4' }
  • 读取本地文件
    lib/legacy/node-bridge.js:182
    if (existsSync(join(mods, 'dsh-session-format-v2-to-v3', 'package.json'))) return { format: 3, how: 'host ships only dsh-session-format-v2-to-v3' }
  • 发起网络请求
    lib/legacy/node-bridge.js:235
    const res = await fetch(new URL('cmd', this.cfg.node.base + '/'), {
  • 发起网络请求
    lib/legacy/node-bridge.js:246
    health() { return fetch(new URL('health', this.cfg.node.base + '/'), { headers: this.headers() }).then((r) => r.json()) }
  • 发起网络请求
    lib/legacy/node-bridge.js:262
    const res = await fetch(this.cfg.mcp.url, {
  • 发起网络请求
    lib/legacy/node-bridge.js:272
    await fetch(this.cfg.mcp.url, {
  • 发起网络请求
    lib/legacy/node-bridge.js:300
    const res = await fetch(this.cfg.mcp.url, {
  • 发起网络请求
    lib/legacy/node-bridge.js:402
    const res = await fetch(url, { headers: this.headers({ Accept: 'application/x-ndjson' }), signal })
  • 读取本地文件
    lib/legacy/node-bridge.js:485
    try { return JSON.parse(readFileSync(p, 'utf8')) || {} } catch { return {} }
  • 写入本地文件
    lib/legacy/node-bridge.js:492
    mkdirSync(this.dir, { recursive: true })
  • 写入本地文件
    lib/legacy/node-bridge.js:496
    writeFileSync(tmp, JSON.stringify(next, null, 2), 'utf8')
  • 写入本地文件
    lib/legacy/node-bridge.js:497
    renameSync(tmp, p)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH 界面交互(形象):LIVE2D 主位 + 视频/音频/表情侧边栏、虚拟摄像头与麦克风(外部采集器)、语音识别与识图事件流

README

由 DeepSeek 最新模型翻译生成
muse_vtuber — DSH 界面交互(形象) 任务书 §muse_vtuber 的实现。4.0.0 起它只负责「画」(4.1.0 起连账号模型也去掉):给 DSH GUI 加一个「形象」页签 (对话 / 轨迹之后)——主位是 LIVE2D(说话时口型跟着音量动;没模型时频谱环兜底),右侧栏自上而下是视频 / 音频 / LIVE2D 表情;设置页叫「形象」。 声音与输入已经拆成两个可复用插件(2026-11-01): muse_vtuber(本插件)──→ mira_device ──→ mira_media 画 LIVE2D、右侧栏、 摄像头/麦克风/ TTS/ASR/VAD/凭据表情/动作/模型/权限 采集器/自动推帧 (唯一一份阿里云 key) - 版本:4.1.0(4.0.0 拆分出三件套并删除账号权限;3.1.0 是拆分前的单体;更早的 2.0.1 是「外部 stella 节点」桥,见 §10) - 位置:PluginDSH/muse_vtuber - 依赖:零第三方依赖;assets/lib/ 是 LIVE2D 运行库(见 THIRD-PARTY-NOTICES) - 宿主依赖(inject):tools、webServer、miraMedia、miraDevice 1. 界面结构(对照任务书功能点) | 功能点 | 落地 |
展开完整文档(README 剩余 1 节)
muse_vtuber — DSH 界面交互(形象)

任务书 §muse_vtuber 的实现。4.0.0 起它只负责「画」(4.1.0 起连账号模型也去掉):给 DSH GUI 加一个「形象」页签 (对话 / 轨迹之后)——主位是 LIVE2D(说话时口型跟着音量动;没模型时频谱环兜底),右侧栏自上而下是视频 / 音频 / LIVE2D 表情;设置页叫「形象」。

声音与输入已经拆成两个可复用插件(2026-11-01):

muse_vtuber(本插件)──→ mira_device ──→ mira_media 画 LIVE2D、右侧栏、 摄像头/麦克风/ TTS/ASR/VAD/凭据表情/动作/模型/权限 采集器/自动推帧 (唯一一份阿里云 key)

- 版本:4.1.0(4.0.0 拆分出三件套并删除账号权限;3.1.0 是拆分前的单体;更早的 2.0.1 是「外部 stella 节点」桥,见 §10)
- 位置:PluginDSH/muse_vtuber
- 依赖:零第三方依赖;assets/lib/ 是 LIVE2D 运行库(见 THIRD-PARTY-NOTICES)
- 宿主依赖(inject):tools、webServer、miraMedia、miraDevice

1. 界面结构(对照任务书功能点)

| 功能点 | 落地 |
| 1 新界面「形象」在「对话」「轨迹」之后 | 注册 conversation.view,id:"muse-avatar"、order:20 |
| 2 形象主体 + 侧边栏 | 左侧 .mv-main(LIVE2D 舞台 + 气泡),右侧 .mv-side |
| 3 主位 LIVE2D;侧边栏自上而下 视频 / 音频 / 表情菜单 | VideoPanel → AudioPanel → ExpressionPanel(数据来自 mira_device) |
| 5 摄像头关 → 视频黑屏;开 → 显示捕获画面 | 面板读 window.__miraDevice__ 的流/来源;采集器源走 /device/api/stream.mjpg |
| 6 麦克风关 → 音频直线;开 → 显示波形 | 波形用 mira_device 的 AnalyserNode;采集器源用宿主推的 RMS |
| 7 识别结果插话 / TTS 说话 | 插话在 mira_device;说话在 mira_media(device_say),形象订阅 say 做气泡 + 口型 |
| 9 背景「频谱环」 | SpectrumRing:麦克风开着用麦克风频谱、说话时用播放器频谱、都不是就呼吸波形 |
| 8 设置界面 | 注册 settings.section(@deepseek-ai/dsh-client-ui-settings)→ 「形象」:模型/目录/画布 |

两条稳定性约定(踩过坑才写下的)

- 舞台的 DOM 节点恒定存在(隐藏只切 opacity)。早期版本用「有/没有模型就换一个节点」的方式渲染,任何一次上层重渲染都可能把挂 canvas 的那个节点换掉 → 表现就是「形象忽然不见了」。
- 表情/动作先校验再落状态:模型里没有的名字不会写进 state.json,也不会喂给 pixi-live2d-display(喂了会把表情管理器写坏,表现是「模型忽然不渲染」)。
- 布局:.mv-view 的高度按「最近可滚动祖先」实测钉死,右侧栏自己滚 —— 早期版本靠 height:100%,在宿主那个高度是 auto 的槽位里解析不出来,模型只显示下半身。

2. 落盘(约定1 / 约定3 / 约定7)

$DSH_HOME/mira_config/muse_vtuber/
config.json 配置(schemaVersion 1)。唯一会自动创建的文件 state.json 形象实况(可见性/模型/表情/开关/动作,schemaVersion 1) model/ 可选:自己的模型目录(留空 modelsDir 时优先用它)

不再在这里:creds/dashscope.json(→ mira_config/mira_media/creds/)、 capture.json(→ mira_config/mira_device/capture.json)。

热生效:config.json 内容变了,下一次读取就重新加载(约定9);换代码仍需重启宿主(§11 的坑)。

3. 配置表

| 键 | 默认 | 说明 |
| enabled | true | false = 不注册工具与路由 |
| live2d.modelsDir | "" | 留空 = 依次找 mira_config/muse_vtuber/model → mira_config/mira_live2d/model。手填绝对路径优先 |
| live2d.model | "" | 要显示的模型(目录名或 .model3.json)。留空 = 不显示模型(只剩频谱环);设置页选完保存会立即切换 |
| live2d.width / height | 420 / 560 | 初始画布尺寸(CSS 像素),实际随容器缩放 |
| legacyNode.enabled | false | 旧的外部节点桥(见 §10),默认不加载 |
| ui.showHint | true | 首次进入给操作提示 |

旧版(≤3.1.0)的 camera / mic / tts / asr / vision / capture / events 键不再被读取,但会原样留在 config.json 里(前向兼容);对应能力分别搬到了 mira_device / mira_media(见 §9)。

4. 工具(5 个)

没有权限模型(4.1.0 起):形象是纯展示面,任何会话都能调这 5 个工具,不再需要 mira_account、不再有 taker/observer/force。旧版那套「形象是一个账号」的绑定模型已整个删除(真要防多会话打架,等需要时再加一个内存认领,不必把账号池拉进来)。

| 工具 | 干什么 |
| vtuber_status | 只读:形象状态(可见性/模型/表情/开关/动作)、模型清单与缺失资源、连着的页面数,并转述 audio(TTS/ASR/key)与 input(开关/采集器/识图)状态 |
| vtuber_expression | 表情三件套:name(互斥)/ toggles(整体替换叠加开关)/ switch+on。未知名字 → expression/unknown + 清单,状态原样不动 |
| vtuber_motion | 播放动作组;不传 = 列清单 |
| vtuber_visible | 显示/隐藏形象主体(频谱环仍在) |
| vtuber_model | 切模型;不传 = 列清单;clear:true = 只留频谱环 |

5. 宿主 HTTP 面(/vtuber/api/,全部同源校验)

| 方法与路径 | 作用 |
| GET /vtuber/api/bootstrap | 一次拿全:配置(脱敏)、状态、模型清单、deps(声音/输入是否就位)、endpoints |
| GET /vtuber/api/capabilities?model= | 表情/动作清单 + 缺失资源 |
| GET /vtuber/api/events | SSE:hello / state(模型/表情/动作/可见性) |
| GET/POST /vtuber/api/state | 形象状态 → state.json → 广播(clientId 记成 by: client:<id>) |
| GET/POST /vtuber/api/settings | 设置页读写(只有 live2d.);GET 回 keyConfigured(转述声音插件)与 deps |
| GET /vtuber/api/health | 存活探针(客户端数、依赖是否就位、features.live2d) |
| GET /vtuber/assets/、/vtuber/models/ | 运行库与模型静态文件 |

已经不在这里(拆分后的入口):

| 旧 | 新 |
| POST /vtuber/api/say、GET /vtuber/api/audio/<id> | POST /media/api/say、GET /media/api/audio/<id> |
| POST /vtuber/api/asr、POST /vtuber/api/vision | POST /device/api/asr、POST /device/api/vision |
| POST /vtuber/api/capture/、GET /vtuber/api/stream.mjpg | /device/api/capture/、/device/api/stream.mjpg |

6. 页面全局契约(形象读别的插件)

window.__miraDevice__ = { state(), subscribe(fn), level(), analyser(), play(url), ready(), version }
window.__miraDevice__ = { state(), subscribe(fn), videoStream(), analyser(), level(),
setCamera(on), setMic(on), setSource(kind, source), heard(), seen(), captureUrl() }

- 形象不自己播音频:声音由 mira_media 的全局播放器播,这里只读 state().level 驱动口型(两边都播会变成双重声音)。
- 形象不碰硬件:视频/音频面板只消费 mira_device 的流、电平与日志。
- 两个全局对象都可能比本模块晚出现(模块加载顺序不定),所以都做了短轮询 + 订阅。

7. 模型与资源缺失

models.js 解析 model3.json 的 FileReferences,逐个检查 Moc / Textures / Physics / Pose / Expressions[].File / Motions[].File,缺什么就说清是哪个文件、属于什么类型:

vtuber_status → models.missing: [{ file: "SylviaMuse.4096/texture_02.png", why: "贴图" }] 侧边栏表情菜单 → 缺资源:…(放进 <模型目录>/<模型名>/)

表情分两类(与 mira_live2d 同一套约定):开关- / 动作- 可同时打开(叠加层),其余(表情-)互斥。叠加是把"当前互斥表情 + 所有打开的开关"合成一个 ExpressionMotion,经 expressionManager 的官方队列应用。

8. 迁移:3.1.0(单体)→ 4.0.0(三件套)→ 4.1.0(去掉账号)

| 旧位置 | 新位置 |
| muse_vtuber/config.json 的 tts / asr / mic.vad | mira_media/config.json(首次创建时自动迁移) |
| muse_vtuber/creds/dashscope.json | mira_media/creds/dashscope.json(首次加载时自动复制,600;旧文件保留) |
| muse_vtuber/config.json 的 camera / mic / capture / vision / events | mira_device/config.json(首次创建时自动迁移) |
| muse_vtuber/capture.json | mira_device/capture.json(mira_device 重新生成;muse_galaxy 已改读这里) |
| muse_vtuber/state.json 的 cameraOn/micOn/cameraSource/micSource | mira_device/state.json(不迁移,重新点一次开关即可) |
| 工具 vtuber_say | → device_say(工具名变了,提示词/预设里如果写了要改) |
| 工具 vtuber_camera / vtuber_mic / vtuber_look / vtuber_listen | → device_camera / device_mic / device_look / device_listen |
| 设置页「形象」里的 TTS/ASR/识图/设备 | → 「声音」/「输入」两个设置页;「形象」只剩 LIVE2D |

迁移只读旧文件,不删不改;回滚就停用三个新插件,重新启用旧 muse_vtuber 3.1.0。

9. 已知取舍

1. 说话工具改了名字:vtuber_say → device_say。用户口径是「各自的能力归各自的插件」:发声属于声音插件,形象只管口型。预设/技能里提到旧名字的要一起改。
2. 插话不再由形象负责:events. 配置、限流、模板都搬到了 mira_device;形象不再需要 sessionController。
3. 自动识图默认关(mira_device 的 vision.autoIntervalMs: 0):定时抓帧会持续消耗额度。
4. 模型目录默认复用 mira_live2d 的那套(存在才用)。
5. LIVE2D 运行库是复制进来的,不是跨插件 import(link: 安装下跨包相对 import 不可靠)。
6. 表情叠加用 expressionManager._setExpression()(下划线接口):mira_live2d 已在生产用,上游改名就会失效 —— 表现是「开关点了没反应」,不会崩。

10. 旧的外部节点桥(2.0.1,默认关)

旧版是「DSH ↔ stella muse_vtuber 节点」的桥:MCP 拿工具、/dsh 出口拿事件,工具名 muse_。
3.0.0 按任务书重写成纯 GUI 插件,桥不是任务书范围。代码原样留在 lib/legacy/node-bridge.js,默认不加载;要恢复就把旧配置整体搬进 config.legacyNode.config 并把 legacyNode.enabled 设为 true(失败只 warn,不影响形象页签)。

11. 验收

node --check lib/client.js # 浏览器模块只做语法自检
node test/smoke.mjs # → ALL PASS(通过 34 项断言)
node test/http-e2e.mjs # → ALL PASS(10 项端到端断言,真起 http + 真 SSE)
node ..\tool_shared\tools\check-zero-dep.mjs muse_vtuber
node ..\tool_shared\tools\check-kit.mjs

手工(三件套一起装好之后):

| # | 步骤 | 期望 |
| 1 | 打开会议话界面 | 顶部页签:对话、轨迹、形象 |
| 2 | 点「形象」 | 主位出现 LIVE2D(或频谱环);右侧 视频 / 音频 / LIVE2D 表情 |
| 3 | 点输入框里的「摄像头」 | 侧边栏视频出现画面;再点一次黑屏(按钮属于 mira_device) |
| 4 | 点「麦克风」说一句话 | 波形动;停顿后「听到:…(已插话)」;会话里多一条 [感知·语音](插话由 mira_device 投递) |
| 5 | 让模型说话(device_say / 「设置 · 声音」的试听) | 形象出声、口型跟音量动、气泡显示文字 |
| 6 | 设置 · 形象 → 选模型 → 保存 | 立即切换;空白 = 只显示频谱环 |
| 7 | 把 config.json 的 schemaVersion 改成 99 再重载 | 工具报 config/schema,不静默清空 |

两个「改代码不生效」的坑(都实测过)

1. 宿主半:Loader 把模块缓存在「解析后的真实路径」上,plugin_manager set_plugin 关开只会重放缓存里的旧模块(实测:改代码后关开,/vtuber/api/health 仍是旧响应)。换宿主代码必须重启 dsh web。新插件(首次 import)不受影响,可以热加载。
2. 前端半:dsh-client-modules 解析 dsh.client 时,从模块真实路径往上找 package.json,且要求 name 严格等于 loader 条目的 specifier。所以安装名必须与包名一致(muse_vtuber),别用别名装。

lib/client.js 末尾仍同时注册 muse_vtuber / muse_vtuber_ui 两个 id:只有宿主清单里那个会被真正 import,另一个只是躺着的工厂(历史原因:曾用别名装过)。

12. 开发约定

- 只依赖 node: 内置与浏览器 API;lib/kit.mjs 是 tool_shared/kit/kit.mjs 的逐字节副本。
- 所有工具返回 { ok:true, … } 或 { ok:false, error:{ code, message, hint } }。
- 单实例保护:apply 里 claimInstance('muse_vtuber'),第二个实例报 plugin/instance。
- 退场顺序:摘工具 → 卸路由 → 停 SSE → 落状态 → 放认领(ctx.effect 与装配失败走同一条路径)。
- 依赖方向不允许反向:形象可以读 miraMedia / miraDevice,它们不反过来依赖形象。
预览分享卡片长什么样 ↓Stella-Mira/muse_vtuber
分享 · DeepSeek Harness Hub
Stella-Mira/muse_vtuber

在对话页旁展示 LIVE2D 形象,随语音口型与表情联动

这是你的仓库?认领条目

回到顶部