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

蓝鱼工作站 Moningxuan-bot/blue-fish-station

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

双击启动桌面版并美化界面,内置绘图出图

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

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

自动兼容检查未通过:仓库根目录无 package.json,浅层子目录有 2 份 dsh 插件清单(dsh-plugin-aurora/package.json、dsh-plugin-imagestation/package.json),无法唯一定位安装名。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Moningxuan-bot/blue-fish-station
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

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

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

验证判据与等级说明 →

可信度证据

安装兼容性检查需源码安装

仓库根目录无 package.json,浅层子目录有 2 份 dsh 插件清单(dsh-plugin-aurora/package.json、dsh-plugin-imagestation/package.json),无法唯一定位安装名

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包blue-fish-station(未发布到 npm,仅可源码安装)
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/11 22:21:37

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

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 70 处,展示前 68 条)
  • 读取本地文件
    dsh-plugin-aurora/lib/index.js:47
    try { max = Math.max(max, statSync(join(dir, f)).mtimeMs) } catch { }
  • 读取本地文件
    dsh-plugin-aurora/lib/index.js:72
    if (typeof dir !== 'string' || dir.length === 0 || !existsSync(dir)) {
  • 读取本地文件
    dsh-plugin-aurora/lib/index.js:86
    body = readFileSync(join(dir, file))
  • 读取本地文件
    dsh-plugin-imagestation/lib/credentials.js:85
    if (existsSync(filePath)) {
  • 读取本地文件
    dsh-plugin-imagestation/lib/credentials.js:87
    fileKey = parseKeyFile(readFileSync(filePath, 'utf8'), envName)
  • 写入本地文件
    dsh-plugin-imagestation/lib/credentials.js:117
    mkdirSync(dirname(filePath), { recursive: true })
  • 读取本地文件
    dsh-plugin-imagestation/lib/credentials.js:120
    if (existsSync(filePath)) {
  • 读取本地文件
    dsh-plugin-imagestation/lib/credentials.js:122
    existing = readFileSync(filePath, 'utf8')
  • 写入本地文件
    dsh-plugin-imagestation/lib/credentials.js:138
    writeFileSync(filePath, body, { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    dsh-plugin-imagestation/lib/credentials.js:156
    writeFileSync(filePath, out.join('\n'), { encoding: 'utf8', mode: 0o600 })
  • 读取本地文件
    dsh-plugin-imagestation/lib/credentials.js:169
    keyFileExists: existsSync(r.filePath),
  • 发起网络请求
    dsh-plugin-imagestation/lib/generate.js:204
    const res = await fetch(url, { method: 'POST', headers, body, signal: ac.signal })
  • 写入本地文件
    dsh-plugin-imagestation/lib/generate.js:282
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    dsh-plugin-imagestation/lib/generate.js:302
    writeFileSync(file, bytes)
  • 读取本地文件
    dsh-plugin-imagestation/lib/presets.js:58
    names = readdirSync(SPECS_DIR).filter((n) => n.toLowerCase().endsWith('.md'))
  • 读取本地文件
    dsh-plugin-imagestation/lib/presets.js:65
    const parsed = parseSpecFile(readFileSync(join(SPECS_DIR, name), 'utf8'))
  • 发起网络请求
    dsh-plugin-imagestation/lib/routes.js:180
    fetch(base + '/')
  • 读取本地文件
    dsh-plugin-imagestation/lib/routes.js:535
    body = readFileSync(file)
  • 发起网络请求
    dsh-plugin-imagestation/probe-auth.mjs:33
    const boot = await fetch(target, { redirect: 'manual' })
  • 发起网络请求
    dsh-plugin-imagestation/probe-auth.mjs:41
    const html = await (await fetch(home.toString(), { headers: cookie ? { cookie } : {} })).text()
  • 发起网络请求
    dsh-plugin-imagestation/probe-auth.mjs:54
    const good = await fetch(origin + '/image-station/presets', { headers: withCookie({ authorization: 'Bearer ' + token }) })
  • 发起网络请求
    dsh-plugin-imagestation/probe-auth.mjs:57
    const badRes = await fetch(origin + '/image-station/presets', { headers: withCookie({ authorization: 'Bearer stale-token-xxxx' }) })
  • 发起网络请求
    dsh-plugin-imagestation/probe-auth.mjs:66
    const noHeader = await fetch(origin + '/image-station/presets', { headers: withCookie() })
  • 发起网络请求
    dsh-plugin-imagestation/probe-auth.mjs:73
    const tokRes = await fetch(origin + '/image-station/token', { headers: withCookie() })
  • 执行外部命令来自配置/变量
    dsh-plugin-imagestation/probe-auth.mjs:87
    const chrome = spawn(
  • 写入本地文件
    dsh-plugin-imagestation/probe-auth.mjs:163
    writeFileSync(file, Buffer.from(shot.result.data, 'base64'))
  • 执行外部命令来自配置/变量
    dsh-plugin-imagestation/probe-boot.mjs:26
    const chrome = spawn(
  • 发起网络请求
    dsh-plugin-imagestation/probe-boot.mjs:46
    const res = await fetch('http://127.0.0.1:' + PORT + '/json/list')
  • 读取本地文件
    dsh-plugin-imagestation/probe-client.mjs:54
    const source = readFileSync(join(here, 'lib', 'client.js'), 'utf8')
  • 读取本地文件
    dsh-plugin-imagestation/probe-createelement.mjs:64
    new Function('require', 'window', 'document', readFileSync(join(here, 'lib', 'client.js'), 'utf8'))(
  • 读取本地文件
    dsh-plugin-imagestation/probe-credentials.mjs:53
    const content = readFileSync(keyFile, 'utf8')
  • 读取本地文件
    dsh-plugin-imagestation/probe-credentials.mjs:80
    const lines = readFileSync(keyFile, 'utf8').split('\n').filter((l) => l.trim().startsWith(ENV + '='))
  • 读取本地文件
    dsh-plugin-imagestation/probe-credentials.mjs:86
    writeFileSync(keyFile, readFileSync(keyFile, 'utf8') + 'OTHER_SETTING=keep-me\n')
  • 写入本地文件
    dsh-plugin-imagestation/probe-credentials.mjs:86
    writeFileSync(keyFile, readFileSync(keyFile, 'utf8') + 'OTHER_SETTING=keep-me\n')
  • 读取本地文件
    dsh-plugin-imagestation/probe-credentials.mjs:88
    const after = readFileSync(keyFile, 'utf8')
  • 发起网络请求
    dsh-plugin-imagestation/probe-endpoints.mjs:30
    const bootRes = await fetch(target, { redirect: 'manual' })
  • 发起网络请求
    dsh-plugin-imagestation/probe-endpoints.mjs:38
    const indexRes = await fetch(homeUrl.toString(), { headers: cookie ? { cookie } : {} })
  • 发起网络请求
    dsh-plugin-imagestation/probe-endpoints.mjs:54
    fetch(origin + '/image-station' + path, {
  • 发起网络请求
    dsh-plugin-imagestation/probe-endpoints.mjs:188
    const imgRes = await fetch(origin + gdone.images[0].url)
  • 发起网络请求
    dsh-plugin-imagestation/probe-endpoints.mjs:194
    check('image route rejects a wrong token', (await fetch(origin + '/image-station/image/' + gdone.images[0].id + '?token=wrong')).status === 401)
  • 读取密钥环境变量
    dsh-plugin-imagestation/probe-generate.mjs:22
    const key = process.env.DSH_IMAGE_API_KEY
  • 读取本地文件
    dsh-plugin-imagestation/probe-generate.mjs:113
    firstImage = { data: readFileSync(persisted.saved[0].file), name: 'ref.png', type: 'image/png' }
  • 发起网络请求
    dsh-plugin-imagestation/probe-keysetup.mjs:28
    const boot = await fetch(target, { redirect: 'manual' })
  • 发起网络请求
    dsh-plugin-imagestation/probe-keysetup.mjs:35
    const html = await (await fetch(home.toString(), { headers: cookie ? { cookie } : {} })).text()
  • 发起网络请求
    dsh-plugin-imagestation/probe-keysetup.mjs:39
    fetch(origin + '/image-station' + path, {
  • 读取本地文件
    dsh-plugin-imagestation/probe-main-effect.mjs:38
    new Function('require', 'window', 'document', readFileSync(join(here, 'lib', 'client.js'), 'utf8'))(
  • 执行外部命令来自配置/变量
    dsh-plugin-imagestation/probe-persist.mjs:28
    const chrome = spawn(
  • 发起网络请求
    dsh-plugin-imagestation/probe-persist.mjs:53
    const list = await (await fetch('http://127.0.0.1:' + PORT + '/json/list')).json()
  • 写入本地文件
    dsh-plugin-imagestation/probe-persist.mjs:258
    writeFileSync(file, Buffer.from(shot.result.data, 'base64'))
  • 写入本地文件
    dsh-plugin-imagestation/probe-reference.mjs:29
    writeFileSync(refPath, readFileSync(candidate))
  • 读取本地文件
    dsh-plugin-imagestation/probe-reference.mjs:29
    writeFileSync(refPath, readFileSync(candidate))
  • 写入本地文件
    dsh-plugin-imagestation/probe-reference.mjs:38
    writeFileSync(
  • 执行外部命令来自配置/变量
    dsh-plugin-imagestation/probe-reference.mjs:52
    const chrome = spawn(
  • 发起网络请求
    dsh-plugin-imagestation/probe-reference.mjs:72
    const list = await (await fetch('http://127.0.0.1:' + PORT + '/json/list')).json()
  • 写入本地文件
    dsh-plugin-imagestation/probe-reference.mjs:245
    writeFileSync(file, Buffer.from(shot.result.data, 'base64'))
  • 读取密钥环境变量
    dsh-plugin-imagestation/probe-relay-verify.mjs:20
    const key = process.env.DSH_IMAGE_API_KEY
  • 写入本地文件
    dsh-plugin-imagestation/probe-relay-verify.mjs:28
    mkdirSync('relay-samples', { recursive: true })
  • 发起网络请求
    dsh-plugin-imagestation/probe-relay-verify.mjs:40
    res = await fetch(baseUrl + path, Object.assign({ headers: auth }, options))
  • 写入本地文件
    dsh-plugin-imagestation/probe-relay-verify.mjs:86
    writeFileSync(file, bytes)
  • 读取密钥环境变量
    dsh-plugin-imagestation/probe-relay.mjs:26
    const key = process.env.DSH_IMAGE_API_KEY
  • 发起网络请求
    dsh-plugin-imagestation/probe-relay.mjs:48
    res = await fetch(baseUrl + path, Object.assign({ headers: authHeaders }, options))
  • 写入本地文件
    dsh-plugin-imagestation/probe-relay.mjs:137
    writeFileSync(file, bytes)
  • 发起网络请求
    dsh-plugin-imagestation/probe-sample.mjs:17
    const boot = await fetch(target, { redirect: 'manual' })
  • 发起网络请求
    dsh-plugin-imagestation/probe-sample.mjs:24
    const html = await (await fetch(home.toString(), { headers: cookie ? { cookie } : {} })).text()
  • 发起网络请求
    dsh-plugin-imagestation/probe-sample.mjs:29
    await fetch(origin + '/image-station/presets', {
  • 发起网络请求
    dsh-plugin-imagestation/probe-sample.mjs:39
    const res = await fetch(origin + '/image-station/expand', {
  • 执行外部命令来自配置/变量
    dsh-plugin-imagestation/probe-switch.mjs:27
    const chrome = spawn(
  • 发起网络请求
    dsh-plugin-imagestation/probe-switch.mjs:49
    const res = await fetch('http://127.0.0.1:' + PORT + '/json/list')
扫描于 2026/11/4(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库

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

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

功能亮点

  • 双击启动桌面版
  • 界面美化覆盖层
  • 内置绘图出图

蓝色大肥鱼工作站 —— DSH 单独应用 + 美化:桌面 EXE(WebView2 外壳,一键启动)+ Web UI 覆盖层(品牌标/思考文案/背景毛玻璃/推理强度滑块),不改 DSH 源码

README

由 DeepSeek 最新模型翻译生成
蓝色大肥鱼工作站 DSH 单独应用 · 界面美化 · 集成 GPT 绘图工作站 把 DeepSeek Harness 变成双击就能用的桌面程序,顺手把界面换成自己的样子,再塞进一个能直接出图的绘图工作站。 不改 DSH 一行源码:桌面部分是一个 WebView2 外壳,界面部分是一个宿主插件。 | 组成 | 是什么 | 目录 | | 桌面应用 | BlueFishStation.exe:自己拉起 DSH 服务、开一个没有浏览器界面的窗口 | desktop/ | | 界面美化 | 宿主插件:品牌标 / 思考文案 / 背景毛玻璃 / 推理强度滑块 | dsh-plugin-aurora/ · aurora.css · aurora.js | | 绘图工作站 | 独立插件:中文描述 → 扩写提示词 → 出图,带规范自检与计费 | dsh-plugin-imagestation/ | 目录 快速开始 · 功能一览 · 仓库结构 · 它是怎么工作的 ·换素材调参 · 排错 · 卸载与回滚 · 升级 DSH 之后 ·踩坑记录 · 已知限制 快速开始 前置:Windows 10/11 + 系统自带的 Windows PowerShell 5.1(不需要 PowerShell 7)、装好的 DSH(web profile 初始化过一次即可)。
展开完整文档(README 剩余 1 节)
蓝色大肥鱼工作站

DSH 单独应用 · 界面美化 · 集成 GPT 绘图工作站

把 DeepSeek Harness 变成双击就能用的桌面程序,顺手把界面换成自己的样子,再塞进一个能直接出图的绘图工作站。
不改 DSH 一行源码:桌面部分是一个 WebView2 外壳,界面部分是一个宿主插件。

| 组成 | 是什么 | 目录 |
| 桌面应用 | BlueFishStation.exe:自己拉起 DSH 服务、开一个没有浏览器界面的窗口 | desktop/ |
| 界面美化 | 宿主插件:品牌标 / 思考文案 / 背景毛玻璃 / 推理强度滑块 | dsh-plugin-aurora/ · aurora.css · aurora.js |
| 绘图工作站 | 独立插件:中文描述 → 扩写提示词 → 出图,带规范自检与计费 | dsh-plugin-imagestation/ |

目录

快速开始 · 功能一览 · 仓库结构 · 它是怎么工作的 ·换素材调参 · 排错 · 卸载与回滚 · 升级 DSH 之后 ·踩坑记录 · 已知限制

快速开始

前置:Windows 10/11 + 系统自带的 Windows PowerShell 5.1(不需要 PowerShell 7)、装好的 DSH(web profile 初始化过一次即可)。

① 桌面应用(日常用这个)

powershell -File .\desktop\install-desktop.ps1

编译 desktop\BlueFishStation.cs → 部署到 %LOCALAPPDATA%\BlueFishStation\ → 在桌面创建「蓝色大肥鱼工作站」快捷方式。
双击即用。改了代码之后,双击 desktop\redeploy.cmd 重新部署。

② 界面美化插件(随 DSH 升级存活)

powershell -File .\install-plugin.ps1

装完重启一次 dsh web(bundle 列表是启动时读的)。之后改 aurora.css / aurora.js 只要 F5 ——插件每次请求都从工作区读盘,不用同步、不用重启。

③ 绘图工作站

powershell -File .\install-imagestation.ps1

重启后侧栏出现「绘图工作站」入口。提示规范写在 dsh-plugin-imagestation/specs/.md,改规范=改文件 + F5,不用改代码。详细用法见它自己的 README。

④ 备用:文件注入(会被升级覆盖)

powershell -File .\install.ps1 # 备份 index.html + 注入 / + 同步资源到 dist powershell -File .\sync.ps1 # 只改了 CSS/JS 时

sync.ps1 会把 .png/.jpg 包进内嵌 base64 的 SVG(DSH 静态服务的 MIME 表只认 .html/.js/.css/.svg/.json/.map/.webmanifest,raster 图会被发成 application/octet-stream),再复制进 dist 并给引用打 ?v= 防缓存。每次升级 dsh-web-frontend 后都要重跑 —— 所以它只是备用路径。

功能一览

桌面应用

| | 说明 |
| 一键启动 | 双击快捷方式:拉起 dsh web --port 0 --no-open → 解析带 token 的地址 → 载入窗口 |
| 无浏览器界面 | WinForms + WebView2,没有地址栏/标签页;标题与图标都是「蓝色大肥鱼工作站」 |
| 高 DPI | 挂 app.manifest(PerMonitorV2)+ app.config,150% 缩放下按 144 DPI 原生渲染,窗口任意缩放都锐利 |
| 干净退出 | 关窗时 taskkill /pid /T /F 收掉整个服务进程树 |
| 单实例 | 命名互斥体,重复双击只提示「已经在运行」 |
| 参数 | --url= 连已有实例;--userdata= 换 WebView2 数据目录;--selftest 无头自检 |
| 日志 | %LOCALAPPDATA%\BlueFishStation\station.log(含服务 stdout/stderr) |

界面美化

| 位置 | 改动 |
| 侧边栏 / 折叠态 / 空白会话 Hero 的品牌标 | 换成自定义图片 |
| 浏览器标签图标 | 换成自定义图片 |
| 品牌字标(logo 右侧) | 换成「蓝色大肥鱼」+「工作站」双色文字 |
| 思考状态行 | 「深度求索中,用时12秒」→「大肥鱼正在吃你的 TOKEN,用时12秒」(保留计时) |
| 整站背景 | 换成自定义背景图(+ 柔光罩),替掉刺眼的纯白 |
| 侧边栏 | 毛玻璃(半透明 + 背景虚化) |
| 模型菜单 → 思考强度 | 自绘滑块:渐变胶囊轨道 + 星星圆点 + 百分比胶囊刻度 + 中文档位名 |
| 思考强度面板 | 档位立绘:四个档位各一张角色图,当前档全亮、其余压暗 |
| 拖动滑块时 | 角色跑动:12 帧跑动图骑在圆点上跟着手跑;流光从轨道左→右扫过 |
| 松手定档 | 跑动角色退场,档位立绘重新出现并停在当前档 |

绘图工作站

- 中文描述 → 扩写提示词(实时流式)→ 出图,每张 18–30 秒、固定 $0.03
- 提示规范文件化(specs/.md),出厂三个预设,支持 base 继承与 config 内联覆盖
- 规范自检(lib/lint.js):查质量词、danbooru 计数标签、Markdown、Negative Prompt 区块等,只报告不改写
- 参考图(图生图):拖入或选文件;>2MB 会在浏览器里自动压到长边 1536
- 中转站脾气适配:提交前剥离已知无效参数(background / output_format),拿到图后校验真实字节(尺寸/alpha/格式),请求与实得任何不一致都挂在面板上;size、n、/v1/images/edits、mask 实测有效
- 图片写到 $DSH_HOME/image-station/images(相对路径按 $DSH_HOME 解析,不按 cwd)

仓库结构

desktop/ 桌面应用(WebView2 外壳) BlueFishStation.cs 唯一源文件(C# 5,纯 ASCII,中文用 \u 转义) BlueFishStation.exe 编译产物(已提交,方便直接部署) app.manifest PerMonitorV2 高 DPI 清单(编译时 /win32manifest 挂上) app.config 部署为 BlueFishStation.exe.config(WinForms 的 DpiAwareness 键) install-desktop.ps1 编译 + 部署 + 建桌面快捷方式 redeploy.cmd 双击重新部署 make-icons.ps1 源图 → 6 尺寸 app-icon.ico + app-icon-256.png extract-webview2.ps1 从 nupkg 里取 WebView2 SDK 三个文件 gen-social-preview.ps1 生成 1280x640 社交预览图 Microsoft.Web.WebView2.* 运行时依赖(随 exe 部署)

dsh-plugin-aurora/ 界面美化插件(宿主半侧) lib/index.js 8 条资源路由 + tapIndex 注入 cordis.patch.yml 往 profile 插件树插入一行

aurora.css 样式:1) LOGO 2) 思考行图标 3) 品牌字标 4) 背景+毛玻璃 aurora.js 行为:思考文案替换 + 推理强度滑块 logo.jpg fish.png fish-flat.png bg.jpg moods.png run.png 源素材 tools/mkbg.cs 背景图转换:居中裁剪 + 重采样 + 锐化

dsh-plugin-imagestation/ 绘图工作站(独立插件,见其自家 README) install.ps1 / sync.ps1 备用:文件注入模式 install-plugin.ps1 / uninstall-plugin.ps1 美化插件的装/卸 docs/station-intro.png 本仓库介绍图

它是怎么工作的

桌面外壳:怎么拿到「不用登录」的窗口

0.1.7 起 DSH 的首页与 /api 都要过一道浏览器信任闸门:进程启动时生成 launch token,把 http://127.0.0.1:/?token=... 打印到终端,浏览器访问它才换到签名 cookie。所以外壳的做法是:

cmd /c dsh web --port 0 --no-open
↓ stdout: "dsh web: http://127.0.0.1:63183/?token=..."
↓ 正则抓出地址 → web.Source = new Uri(url)

--port 0 让 OS 挑一个空闲端口,所以它不会和你终端里那个实例抢端口。

高 DPI:「窗口一变尺寸就糊」的真凶

exe 默认没有 DPI 感知声明,Windows 于是按 96 DPI 渲染整个窗口、再位图拉伸到实际分辨率。
本机实测:物理 2560×1600 / 虚拟 1707×1067 = 150% 缩放,旧 build 的进程感知级别是 UNAWARE → 糊。
修法是编译时挂 app.manifest(dpiAware=true/pm + dpiAwareness=PerMonitorV2)并部署 app.config(DpiAwareness=PerMonitorV2)。修好后同一查询返回 PER_MONITOR_AWARE,页面内 window.devicePixelRatio = 1.5,内容按原生 144 DPI 渲染。

覆盖层:为什么能活过 DSH 升级

DSH 的前端由 dsh-host-frontend-static 提供,它每个请求都重新读取 dist/index.html 并跑一遍 index taps。
宿主插件正好有这两个钩子:

| 钩子 | 作用 |
| webServer.register({ kind:"exact", path:"/aurora.css", ... }) | 8 条精确路由,每次请求读工作区文件 + no-store |
| webServer.tapIndex(html => ...) | 每个 index 响应注入 /(版本号取资源 mtime) |

于是「改文件 → F5」就是最新,而 dsh-web-frontend 升级覆盖 dist 也伤不到它。

安装不用 pnpm:在 $DSH_HOME/profiles/node_modules/ 建一个目录 junction 指向本仓库的插件目录,再往 profile 的 dependencies(link: 形式,将来跑 pnpm 也不会被清掉)和 dsh.profile.bundles 各写一条。
装之前可以先干跑一次预检:dsh --profile web --dump-config,能看到 - id: aurora-overlay 就说明 bundle 解析与 patch 合成都没问题。

思考状态文案:三层兜底 + 语义匹配

新版的文案不再硬编码,而是本地化 key:chat.deepDiving =「深度求索中」/Deep diving...,
message.turnProcess.deepDivingFor =「深度求索中,用时{duration}」。而且它挂在一个回合结束后仍然存在的
里(跑完文案变「已完成工作」),所以替换必须带语义判断:

1. 容器钩子按优先级试:[data-turn-process] → [data-chat-running] → [class="_turnStatus"] → [class="_runningText"] → [class="_running"] (首次插入时 MutationObserver 给的往往是更外层节点,所以祖先找不到时会再往子树内部找一遍)
2. 只换容器内第一个「像运行文案」的文本节点,并保留「,用时12秒」尾巴,计时器继续走
3. 全局兜底:整篇文档里像运行文案的文本也换,但绝不碰 PROTECTED(我们自己的浮层、菜单、按钮、表单、role=status 无障碍节点)

三层都没命中时 selfCheck 会在控制台 warn 一次 —— 升级后一眼就能定位。

推理强度滑块:内部 RPC 的真实契约

DSH 浏览器端用的是自研 RPC。0.1.7-rc.2 换了命名与信封(以下都是打真服务端验证过的):

POST /api// content-type: application/json
body { type:'client-request', rpcId:, method:'/',
payload: { args: } }
resp { type:'server-response', rpcId, result: { ok:true, value } | { ok:false, error } }

| 调用 | 端点(斜杠) | payload |
| 读档位 | session/modelCatalog(无参数) | { args: {} } |
| 写档位 | session/selectModel | { args: { request: { sessionId, provider, model, reasoningEffort } } } |
| 会话列表 | session/list | { args: { _request: {} } } |

目录返回的结构(旧版字段叫 current,新版叫 default,两个都读):

{ "default": { "provider": "...", "model": "deepseek-flash", "reasoningEffort": "high" },
"groups": [ { "id": "deepseek-official", "models": [ { "id": "deepseek-flash",
"reasoning": { "defaultEffort": "high", "efforts": [ {"id":"off","name":"Off"} / low/high/max / ] } } ] } ] }

aurora.js 不硬编码这些名字:它 hook window.fetch,从 App 自己的请求里学端点名与信封样式,候选列表只用于「App 还没发过这个请求」时兜底。顺带也这样学到 sessionId(它藏在 payload.args.request.sessionId 里)。

滑块是怎么长进菜单里的

| 环节 | 做法 |
| 找到原生档位行 | [role="menuitemradio"] 四重校验:无 title、文字以档位名开头、数量与档位数 1:1、有实际矩形 |
| 藏掉原生行 | opacity: 0(不是 visibility: hidden)+ pointer-events: none |
| 摆浮层 | 覆盖在原生行那块矩形上,宽度取 max(行宽, 344),垂直居中 |
| 不被菜单关掉 | mousedown/click 捕获阶段屏蔽 + focusout 屏蔽(焦点落进浮层时吞掉) |
| 拖动 | 轨道连续 0..1000,生效值取最近的档位;松手没跨档位就弹回、不提交 |
| 定位驱动 | 输入事件(rAF,抢在绘制前)+ 250ms 轮询兜底;自激熔断 >60 次/秒则停用浮层 |

尺寸都在文件顶部:TRACK=24 THUMB=40 PANEL_MIN_W=344 MOOD_H=52 RUN_MS=1600 RUN_FRAMES=12。

为什么 opacity 不能换成 visibility:0.1.7 的菜单新增了 onBlur 关闭,切到档位面板时应用会 focus() 那一行「已选中」的档位按钮;visibility:hidden 的元素不可聚焦 → 焦点落回 body → 菜单判定焦点跑出去 → close(),表现就是「滑块刚出现就被关掉」。opacity:0 的元素依然可聚焦,所以两件事都能满足。

主题令牌与毛玻璃

--dsw-alias-bg-base = --dsw-static-neutral-bluish-00 (纯白) --dsw-specific-sidebar-fill = --dsw-static-neutral-bluish-50 (#f9fafb) --dsw-static-deepseek-500 = #4176e6 (主色) --dsw-alias-label-primary = #0f1115 (浅色) / #f9fafb (深色)

关键认识:--dsw-alias-bg-base 不只用在对话卡片 —— AppFrame 自己就 background: var(...),是全局调暗器; ConversationRoot 又叠了一层。算透明度时必须按真实层数叠乘,否则背景图会被叠没。
这些 alias 令牌是行内样式写在 body 上的,所以覆盖要 !important 且作用域落在 body。

绘图工作站(两半结构)

| 文件 | 作用 |
| lib/index.js | 宿主半侧接线:配置、令牌、Index 注入 |
| lib/presets.js | 预设加载:读 specs/.md,支持 base 继承与 config 覆盖 |
| lib/expand.js | 扩写核心,只依赖 ctx.llm,可脱离服务器单独测 |
| lib/lint.js | 规范自检:把「模型该守规矩」变成代码能判定的事 |
| lib/generate.js | 出图核心:调中转站、剥离无效参数、校验返回字节、算账 |
| lib/routes.js | 传输层:HTTP + NDJSON 流 + 令牌鉴权 + 图片读取 |
| lib/client.js | 浏览器半侧(手写 bundle,无构建步骤) |
| specs/.md | 提示规范(知识)。改这里就是改行为 |

换素材 / 调参

| 文件 | 用途 | 说明 |
| logo.jpg | 品牌标(侧边栏/折叠态/Hero)+ favicon | 方形效果最好,960×960 |
| fish.png | 思考状态行前的图标(已抠白底) | 127×126 |
| fish-flat.png | 同上但保留白底 | 备选 |
| bg.jpg | 整站背景图 | 当前 2560×1600(= 本机 150% 缩放下的物理分辨率,1:1 不放大)。换图用 tools\mkbg.cs:mkbg.exe bg.jpg 2560 1600 0.55 |
| moods.png | 四档角色立绘 | 4 格雪碧图,160×128/格 |
| run.png | 拖动时的跑动循环 | 12 格雪碧图,96×77/格 |

换完直接 F5(插件模式);只有文件注入模式才需要跑一次 sync.ps1。

样式调参全在 aurora.css 第 4 节:

| 想要的效果 | 改什么 |
| 背景图更清晰 | 最上层白纱 rgba(255,255,255,.10) 调小;--dsw-alias-bg-base 调小 |
| 文字发花 | 白纱调大(最安全的全局提亮),或反着调各层 alpha |
| 侧边栏玻璃更明显 | --dsw-specific-sidebar-fill 调小,blur(14px) 调大 |
| 侧边栏不想要虚化 | 删掉 [class="_sidebarCol"]::before 整段 |
| 滑块尺寸/跑动速度 | aurora.js 顶部的 TRACK/THUMB/MOOD_H/RUN_MS |

排错

| 现象 | 看哪里 |
| 桌面应用起不来 | %LOCALAPPDATA%\BlueFishStation\station.log(含服务 stdout/stderr) |
| 想不动窗口验证一遍 | & "$env:LOCALAPPDATA\BlueFishStation\BlueFishStation.exe" --selftest → 退出码 0 = WebView2 起来了且覆盖层生效 |
| 美化不生效 | 浏览器控制台 [aurora] armed: ...(脚本起来了)/ 终端里 [aurora] overlay plugin active(插件生效) |
| 思考文案没被替换 | 控制台 [aurora] 运行状态容器钩子全都没匹配上… → 上游改结构了 |
| 滑块不出来 | 控制台 [aurora] radios=… matched=… efforts=… 这行会说明是「菜单没了」「匹配失败」还是「目录读取失败」 |
| 改完 CSS/JS 没变化 | 确认走的是插件模式(终端应有 [aurora] overlay plugin active),然后 F5 |

卸载与回滚

美化插件 powershell -File .\uninstall-plugin.ps1 # 移除 junction + 清掉 bundles/dependencies,然后重启 dsh web

桌面应用:删掉桌面快捷方式 + %LOCALAPPDATA%\BlueFishStation\ 即可(不在系统里注册任何东西)

文件注入模式 $d = Join-Path $env:DSH_HOME 'profiles\node_modules\@deepseek-ai\dsh-web-frontend\dist' Copy-Item "$d\index.html.aurora-bak" "$d\index.html" -Force Remove-Item "$d\aurora.css","$d\aurora.js","$d\aurora-logo.svg","$d\aurora-fish.svg","$d\aurora-bg.svg","$d\aurora-moods.svg","$d\aurora-run.svg" -Force -ErrorAction SilentlyContinue

升级 DSH 之后

1. 插件模式:什么都不用做,最多 F5;
2. 桌面应用:不用动(每次启动都会重新解析地址与 token);
3. 文件注入模式:重跑 install.ps1。
上游变化对照表(0.1.7-rc.2 实测)

| 我们依赖的东西 | 新版状态 |
| 思考状态行 [class="_turnStatus"] | ❌ rc.2 产物里没有 data-chat-running(那是 master 源码里的,比 rc.2 新);✅ 真实结构是 + 内部 ,已作为首选钩子 |
| 文案 Deep diving... | ❌ 变成本地化 key(chat.deepDiving / message.turnProcess.deepDivingFor);✅ 已改成语义匹配 + 保留计时尾巴 |
| 模型菜单 role="menuitemradio" + effortChoices | ✅ 原样(但菜单新增了 onBlur 关闭,见上文) |
| RPC 读档位 session.models | ❌ 改名 session/modelCatalog(斜杠、无参数),current → default |
| RPC 信封 | ❌ 多了一层 payload.args(按描述符 wire 名嵌套) |
| 品牌标 _brandMark / Hero _fishHitbox / 轨迹 _thinkingToggle | ✅ 都还在 |
| frontend-static MIME 表 | ✅ 一模一样(图片仍需包进 SVG) |

踩坑记录

1. MutationObserver 自激 = 页面卡死。回调里写自己观察范围内的 DOM,微任务里再生产微任务,浏览器永远轮不到绘制。
修法不是打补丁而是换驱动:定位改由输入事件 + 定时器驱动,Observer 只做文案替换。
2. 菜单的 onBlur 会吃掉滑块:焦点离开菜单就 close()。用 focusout 捕获屏蔽 + 让浮层自己接住焦点解决。
3. opacity vs visibility:前者可聚焦、后者不可。在这套菜单里这一条决定了「滑块能不能活过一帧」。
4. backdrop-filter 会给 position: fixed 当包含块,isolation: isolate 会创建层叠上下文 ——都不能加在「别人的全屏 fixed 层挂在我 DOM 子树里」的容器上(侧边栏就是这种容器)。
5. DPI 不感知 = 整窗位图拉伸:csc 默认清单里没有 DPI 声明,150% 缩放下全窗口发糊。
6. .NET 的 Icon 类读不了 PNG 压缩条目的 ICO(ToBitmap() 抛异常),而 Explorer 能读;所以窗口图标是运行时 GetHicon() 从 PNG 加载的。
7. .ps1 不要写非 ASCII:Windows PowerShell 5.1 按 ANSI 读脚本,中文字节可能吞掉引号导致语法错误。
所有脚本保持纯 ASCII,中文名用码点拼(如桌面快捷方式名)。
8. GitHub 仓库名只允许 ASCII,而且 API 对非法字符是「静默裁剪」不报错 —— 别拿线上仓库做实验。
9. git add -A 会扫到并行会话的在制品。本仓库曾因此误提交(已撤回并 force-push)。只用显式路径 add。
10. 绘图站:相对 outputDir 按 $DSH_HOME 解析,不按 cwd —— 按 cwd 时图被写进了 DSH 安装目录(找不到、还可能被升级删掉)。

已知限制

- 只支持浅色主题(所有覆盖都在 body:not([data-ds-dark-theme]) 作用域内,深色主题保持原样)。
- 美化插件与 DSH 的具体版本强相关(钩子是 data- 属性 + _ 后缀),跨大版本升级后建议核对控制台告警。
- /api// 是 DSH 内部协议,没有稳定性承诺;端点或信封再变时,滑块需要同步调整。
- 滑块会实时改写当前会话的推理强度(与界面里选 effort 是同一个操作)。
- 绘图站:切回对话后看不到实时进度(出图不会中断,跑完自动出现在记录里);蒙版涂刷界面尚未实现。
- 桌面应用只在 Windows 上验证过(WebView2 运行时随 Edge 安装,Win10/11 一般都在)。

License

MIT —— 见 LICENSE。代码可自由使用。

素材(logo.jpg / fish.png / fish-flat.png / bg.jpg / moods.png / run.png / desktop/app-icon*.png / docs/station-intro.png)是仓库作者的个人素材(角色立绘与跑动图取自作者自己的设计稿),替换成你自己的即可。
预览分享卡片长什么样 ↓Moningxuan-bot/blue-fish-station
分享 · DeepSeek Harness Hub
Moningxuan-bot/blue-fish-station

双击启动桌面版并美化界面,内置绘图出图

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