Skip to content

dsh-ui-turn-rail

Verified

@huanghanheng/dsh-ui-turn-rail · v0.1.3 · MIT · Web UI

Turn progress rail plugin for the DeepSeek Harness Web GUI: a sticky dot strip over the conversation scrollport with per-turn question summaries, one-click deep history loading, and AI-option summaries

Install

dsh plugin add @huanghanheng/dsh-ui-turn-rail

Confirm the layer applied with dsh --profile default --dump-config — see the install guide.

Source

Readme

dsh-ui-turn-rail

🌐 语言 / Language: English · 简体中文

DeepSeek Harness Web GUI 的回合进度条插件:钉在会话滚动视口左侧的圆点竖条,已加载回合各一个圆点,支持每轮提问摘要、一次点击深度加载历史、以及 AI 选项的摘要显示。

GitHub 话题:dsh-plugin · deepseek-harness


功能

  • 常驻可见的进度条。圆点条 sticky 在会话滚动视口上,转录区滚动时始终可见——即使超长对话也不会消失。
  • 每个「提问已加载」的轮次一个圆点,提问尚未进入窗口的轮次用虚线占位点表示。AI 回复与思考过程不视为已加载的提问:提问还没翻页进来的轮次始终是占位点。
  • 提问摘要。悬停圆点可看到该轮第一个提问的单行预览。人工输入(user 节点)和从 AI 提供的选项中选择(steering 节点)都会生成摘要。
  • 一次点击深度加载。点击占位点后,插件会持续翻页,直到目标回合的提问真正进入窗口(页面边界可能先加载到该轮首个节点、而提问在更早的页里),然后直接跳到该提问所在行。
  • 点击已加载圆点:把转录区滚到该轮提问处。
  • 可滚动的圆点窗口。进度条一次显示约十个圆点,可内部滚动;读者移动时当前轮圆点自动跟入视野。

安全

  • 纯展示。进度条只渲染会话快照里已有的数据。它不会产生任何模型可见输入、不会写会话日志、不发网络请求、不接触任何凭据。
  • 模型体验:不进入任何模型请求;Token 影响无;KV 缓存影响无。
  • 不新增权限。宿主插桩(见下)只给聊天视图加一个只读座位;不引入新 RPC、权限或密钥处理。
  • 数据都在宿主。所有派生(轮次顺序、节点→轮次映射、摘要)都基于客户端 ChatSnapshot 计算;进度条自身不持有任何会话状态。

前置要求

一个 DeepSeek Harness 检出(或已发布的 @deepseek-ai/dsh-* 包)且已应用宿主插桩——进度条注册进 ui-conversation 聊天视图必须声明的 conversation.chat.rail 座位(见下)。没有该座位时插件可以加载但不渲染(座位渲染 fallback: null)。

安装

插件支持两种安装方式。两种都不会自动化宿主插桩——那是针对 harness 检出的源码级补丁(见宿主插桩),无论包怎么装都必须应用并重建。

方式一——通过 dsh plugin 的 bundle 安装(推荐)

本仓库是一个 DSH bundle:package.json 声明了 dsh.bundle.patch → ./cordis.patch.yml,因此 profile 插件管理器会把它作为补丁层安装,补丁会把插件插入 web profile 的浏览器花名册。

# 直接从 git 仓库安装(无需发版 npm):
dsh plugin --profile web add @huanghanheng/dsh-ui-turn-rail

# 或发布到 npm 后(更短的 spec):
dsh plugin --profile web add @huanghanheng/dsh-ui-turn-rail

dsh plugin 在 profile 目录里执行 pnpm add,并自动对账 dsh.profile.bundles:manifest 声明了 dsh.bundle.patch 的依赖会自动加入层栈。

然后应用宿主插桩——profile 运行的检出必须有该座位,进度条才会渲染。本仓库附带现成补丁,一条命令应用(然后重建 web 客户端):

scripts/apply-instrumentation.sh /path/to/harness-checkout
(cd /path/to/harness-checkout && pnpm run build)

方式二——手动 npm 安装

包发布到 npm,且构建产物(lib/)已提交,因此可以像普通包一样安装:

# 在应用运行的 profile 目录(或应用本身)里:
npm install @huanghanheng/dsh-ui-turn-rail
# 或:pnpm add @huanghanheng/dsh-ui-turn-rail

手动 npm install 不会自动加 bundle 层——需要在 profile manifest 里声明,补丁才会插入插件行:

// $DSH_HOME/profiles/web/package.json
{
  "dependencies": { "@huanghanheng/dsh-ui-turn-rail": "^0.1.0" },
  "dsh": { "profile": { "bundles": ["@huanghanheng/dsh-ui-turn-rail"] } }
}

或者直接运行 dsh plugin --profile web add @huanghanheng/dsh-ui-turn-rail,它会替你完成安装与 bundle 层对账(等价于方式一)。

然后应用宿主插桩并重建,与方式一完全相同。

宿主插桩(无法打包的部分)

座位声明在 ui-conversation 的槽位契约里,跳转/翻页手势必须操作 ChatView 的私有滚动锚定——这两者都无法作为插件交付,因此 dsh plugin add 无法自动化它。本仓库把确切改动存为 patches/chat-rail-seat.patch(基于 harness master b150a551b8);用 scripts/apply-instrumentation.sh 或 git apply --3way 应用后重建即可。如果你的检出已有漂移,请按下述改动手工应用。以下是确切改动:

packages/client/ui-conversation/src/client/contract/slots.ts

  1. SlotMap 条目:
    'conversation.chat.rail': { kind: 'single'; scope: 'session'; owner: ChatTurnRailOwnerProps }
    
  2. Owner 数据契约:
    export interface ChatTurnRailOwnerProps {
      currentTurn: number | undefined
      loadingOlder: boolean
      hasMore: boolean
      onJump: (key: string) => void
      onLoadOlder: () => Promise<void>
    }
    
  3. 在 ChatViewSlotProps 的 PropsRenderSlots 联合里加 'conversation.chat.rail'。
  4. 把注入的分页契约放宽为返回 promise:
    loadOlder: () => Promise<void>
    

packages/client/ui-conversation/src/client/apply.ts

在聊天视图 entry 的 children 里声明该座位:

children: {
  'conversation.chat.node': { kind: 'keyed', scope: 'session', inject: CHAT_NODE_INJECT },
  'conversation.message.images': { kind: 'single', scope: 'session' },
  'conversation.chat.rail': { kind: 'single', scope: 'session' },
},

packages/client/ui-conversation/src/client/chat/ChatView.tsx

  1. 用于滚动位置归因的节点键→轮次映射:
    const chat = useSession(s => s.chat)
    const turnByKey = useMemo(() => {
      const map = new Map<string, number>()
      for (const turn of chat.timeline.turnOrder) {
        for (const key of chat.locations.getTurn(turn)) map.set(key, turn)
      }
      return map
    }, [chat])
    
  2. 在滚动处理器里跟踪 currentTurn(从 turnOf(anchorKey) 或钉在底部时的最新轮次设置)。
  3. loadOlderAnchored 返回分页 promise:
    const loadOlderAnchored = (): Promise<void> => {
      // ...原有分页锚定逻辑...
      return loadOlder()
    }
    
  4. 用 owner 数据渲染座位:
    {renderSlot('conversation.chat.rail', {
      currentTurn,
      loadingOlder,
      hasMore,
      onJump: jumpTo,
      onLoadOlder: loadOlderAnchored,
    }, { fallback: null })}
    

开发

pnpm install
pnpm vitest run tests/        # 单元测试(模型 + 组件,jsdom)
pnpm bundle                   # tsdown 客户端打包(需要 harness 的 clientBundle preset)

测试覆盖回合模型(tick 派生、人工/AI 选项摘要、占位点)和组件(跳转、跨页边界的一次点击翻页、当前轮跟随、窗口滚动)。

许可证

MIT