中文EN

dsh-paste-chip

ui已测通过npmMIT

长文本粘贴自动折叠成可展开的胶囊:悬停看正文与出处,发送时按原位展开;其他插件写入的长文本同样接管。

下载量 / Downloads
1,720
依赖体积 / node_modules
0.1 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

安装

dsh plugin --profile web add dsh-paste-chip

沙箱测试结果

阶段结果
1 安装✓
2 列装✓
3 挂载✓
4 启动—

四阶段在隔离沙箱里跑:安装(有网)→ 列装 → 挂载 → 启动(后三步断网)。

README

dsh-paste-chip

<p>

<a href="https://www.npmjs.com/package/dsh-paste-chip"><img alt="npm" src="https://img.shields.io/npm/v/dsh-paste-chip?color=2f81f7&label=npm"></a>

<a href="https://www.npmjs.com/package/dsh-paste-chip"><img alt="downloads" src="https://img.shields.io/npm/dw/dsh-paste-chip?color=3fb950"></a>

<a href="https://github.com/zengxiaolou/dsh-paste-chip/blob/main/LICENSE"><img alt="license" src="https://img.shields.io/npm/l/dsh-paste-chip?color=bf8700"></a>

<img alt="dsh plugin" src="https://img.shields.io/badge/DSH-plugin-7aa2ff">

<a href="https://github.com/zengxiaolou/dsh-paste-chip/stargazers"><img alt="stars" src="https://img.shields.io/github/stars/zengxiaolou/dsh-paste-chip?color=8250df"></a>

</p>

把超长粘贴折叠成一张可展开的引用胶囊,避免长草稿把输入框拖死。

  • 粘贴阈值:≥400 字符 或 ≥12 行 折叠成一张胶囊
  • 外部渠道(标注浏览器 / 划词注释 / 任何调用 API 的插件):不看长度,一律成胶囊
  • 上限:20 万字符;超过直接拒绝并提示改用附件(超长正文进模型上下文也没有意义)
  • 胶囊显示 粘贴「会议纪要:本周三…」· 1.2k 字(英文 Pasted「…」· 1.2k chars)——带正文前缀,两个同字数的胶囊也能分辨;整块删除、整块撤销
  • 发送时展开回完整正文;正文找不到就明确拦住发送,绝不把标签当正文发出去

为什么需要它

DSH 的输入框每敲一个键都在做全量工作:重算整篇投影、把整段草稿写进 localStorage。草稿一旦到几十万字,就会一卡一顿,极端情况直接把界面拖死。

这个插件把「长文本」从草稿里搬走:

没有它 · 有它

12k 字粘贴后草稿就 12k 字,每键写盘 12k+ · 草稿里只有一张 24px 高的胶囊,每键写盘回到标签大小

正文和你说的话混在一起,找不着边界 · 正文独立存放,发送时按胶囊所在位置展开

想确认粘了什么,得在几千字里翻 · 悬停或点击就能看正文前缀与上下文

为什么不改 core

DSH 的客户端是打包在 app.asar 里的,改 core 要连带改 ElectronAsarIntegrity 哈希并重签,且下一个版本会被覆盖。本插件只走三条已有公开通道:

能力 · 通道

插入胶囊 · 作用域事件 slash/input-insert-reference(内置 slash 插件插 chip 用的就是它)

拿稳定锚点 · InputActions.captureInsertion() → 带 draftRev 的 TokenSpan

发送时展开 · 注册 InputTriggerSource,实现 codec.serialize(ref)

正文不放进 ref id(core 版把 base64 塞进 ref,中文要放大 4 倍),而是按短 id 存在插件自己的 localStorage(前缀 dsh-paste-chip.v1.,最多 12 条 / 2MB,超了淘汰最旧的)。草稿里只有一个短 id,每次按键的持久化体积回到标签大小。

工作方式

1. 在 document 的捕获阶段监听 paste,只处理本插件所属 composer 的编辑器

2. 短文本直接放行给 core,不干预

3. 长文本:preventDefault() → 正文入库 → actx.bail(...) 插入胶囊

4. 插入失败:清掉刚写的正文,退回纯文本粘贴并提示——不吞内容

安装

以本地目录方式装进 profile(路径换成你自己的):

1. ~/.dsh/profiles/<profile>/package.json → dependencies 加 "dsh-paste-chip": "link:/path/to/dsh-paste-chip"

2. 同一个 package.json → dsh.profile.bundles 数组末尾加 "dsh-paste-chip" ← 真正的加载入口

3. ~/.dsh/profiles/<profile>/cordis.patch.yml → 末尾加 - id: dsh-paste-chip

然后在 profile 目录跑一次 pnpm install(需要 Node ≥22.13),再重启 DSH 客户端。

宿主启动时按 dsh.profile.bundles 的顺序叠 bundle 层,再叠 cordis.patch.yml;只加依赖和 patch 行不会加载插件。

客户端半的改动刷新页面就会重新加载;宿主半(lib/index.js)改了才需要重启。

可配置项

全部阈值都能改,两种方式二选一:

A. cordis.patch.yml(profile 里那一行加上 config)

- id: dsh-paste-chip
  config:
    collapseChars: 600     # 粘贴折叠阈值(字符)
    collapseLines: 15      # 粘贴折叠阈值(行)
    previewChars: 500      # 悬停预览多少字

B. 设置页(装了 @deepseek-ai/dsh-settings 时自动出现「dsh-paste-chip」分区)

键 · 默认 · 范围 · 作用

collapseChars · 400 · 100–10000 · 粘贴折叠阈值(字符)

collapseLines · 12 · 3–200 · 粘贴折叠阈值(行)

maxChars · 200000 · 1000–2000000 · 单段正文上限,超过拒绝

externalMinChars · 8 · 1–200 · 外部渠道插入的兜底长度,低于它视为手打

warmupMs · 1200 · 0–10000 · 挂载后预热窗口,期间不接管(防恢复草稿误折叠)

inputGraceMs · 500 · 0–3000 · 用户输入后的宽限期

previewChars · 320 · 80–2000 · 悬停浮层预览字数

contextChars · 40 · 0–200 · 悬停浮层上下文各取多少字(0 = 关闭)

hoverDelayMs · 180 · 0–1000 · 悬停多久弹出预览

leaveGraceMs · 60 · 0–1000 · 移出后多久收起

storeMaxEntries · 12 · 1–100 · 正文最多存几条,超出淘汰最旧

storeMaxBytes · 2097152 · 64KB–8MB · 正文存储总字节上限

实现方式:宿主半把这些值汇总到 GET /api/dsh-paste-chip/config,客户端启动时读一次并覆盖默认值;读不到就用同一份默认值,越界一律夹到边界(客户端与宿主各夹一次)。默认值写在 lib/client.js 的 DEFAULT_SETTINGS 与 lib/index.js 的 DEFAULTS,两边必须保持一致。

给其他插件用的主动接法(可选)

默认是被动接法:任何插件用 setDraft / insertText 写进草稿的长文本,都会被差异观察器接住,对方不用改一行代码。

需要更精确控制时(原子插入、草稿里已有胶囊、不想晚一帧),生产者调一行服务:

// 在会话槽组件里:span 必须和当前 revision 一致
const span = props.inputActions.captureInsertion()
const result = ctx.pasteChip.insert(body, { sessionId, span, label: '注释 · 3 条' })
// result: { ok: true, ref, label } | { ok: false, reason }

失败原因(都不静默):empty、too-long(超 20 万字)、span-required(没给 span)、no-session(会话 actx 解析不到)、bail-refused(插入被拒,正文已清理)。

服务版本:ctx.pasteChip.version。

预览开关的事件(v0.1.22)

常驻预览是谁打开的、谁关心它被关掉,插件自己不知道。所以给一个订阅口:

const off = ctx.get('pasteChip').watchPreview((event) => {
  // { phase: 'pin',   ref }
  // { phase: 'unpin', ref, reason: 'close' | 'dismiss' | 'switch' | 'removed' }
})

reason 是这套 API 的全部价值,别混用:

reason · 什么动作 · 联动方一般该做什么

close · 点了卡片右上角的 ✕ · 这是唯一的「用户主动关闭」,可以据此取消对应动作

dismiss · Esc / 点浮层外部 · 收起预览而已,不要当成取消

switch · 点了另一枚胶囊,预览换过去 · 同上,旧的那条只是被替换

removed · 外部插件调了 closePreview(ref) · 胶囊已经没了,卡片顺手收掉

ctx.pasteChip.closePreview(ref):只收指定 ref 的常驻预览(不是当前预览就返回 false,不误伤别人),广播 reason: 'removed'。

服务必须用 ctx.get('pasteChip') 取。cordis 上下文里直接读没在 inject 里声明的服务会抛

cannot get property ... without inject,属性访问永远是死的。

非粘贴渠道

标注浏览器、划词询问这类插件是用 setDraft / insertText 写草稿的,不触发 paste,所以另有一条通道:比对前后两次草稿,认出「这一次新塞进来的那一段」。

这条通道没有字数/行数阈值:凡是外部渠道传进来的非空白文本,一律成胶囊。 粘贴仍保留 400 字阈值(短粘贴保持原文,避免粘个链接也变胶囊)。

怎么区分「外部传进来的」和「用户自己打的」:真实键入一定先触发 beforeinput,外部 setDraft 不会。所以 beforeinput 之后 500ms 内的草稿变化算用户输入,不接管;超出这个窗口且不是用户输入的插入,一律折叠。

三道保护,缺一个都会误伤:

保护 · 为什么

挂载后 1200ms 预热窗口内不接管 · 会话切换/页面加载会"恢复"草稿,那段文本和外部插入长得一模一样

用户输入事件后 500ms 内不接管 · beforeinput / input / keydown / compositionstart / compositionend 任一命中都算。只监听 beforeinput 不够:输入法组合提交有时不发它,实测漏了 7 次,产出「粘贴文本 · 1 字」这种胶囊

少于 8 个字的插入不接管 · 误判兜底,不是产品阈值。手打/输入法提交一次就是 1~3 字;真实外部渠道塞的是整段注释。日志里 7 次误折叠全是 1 字

空白插入不接管 · 纯换行/空格没有折叠价值

草稿里已有胶囊时跳过 · 有 chip 时 clipboard 偏移与 detect 偏移不再一致,硬插会错位替换

只处理纯插入(removed === 0) · 替换、删除的量算不准,不接管

事件:insertion.detected / insertion.collapsed / insertion.skip(带原因)/ `insertion.warmup

…(truncated)

以上内容来自 npm 上的 README,我们只做格式化,不改内容。

版本

npm 上共发布:8,最后更新 2026-10-26

链接

GitHub · Homepage · npm

同类插件