dsh-select-quote 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -54
- package/lib/client.js +224 -157
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# dsh-select-quote
|
|
2
2
|
|
|
3
|
-
[DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/guide/quickstart)
|
|
3
|
+
[DeepSeek Harness](https://deepseek-harness.github.io/deepseek-harness/guide/quickstart) 的客户端插件:在对话里**划词加批注**——选中的原文连同你的评论一起交给模型,并让它在回答里明确指出说的是哪一段。
|
|
4
4
|
|
|
5
5
|
- **划词工具条**:选中对话区任意文本,浮出「复制 / 添加到任务」;拖拽结束才出现,不挡操作
|
|
6
|
-
-
|
|
7
|
-
- **正文标记**:被划中的文字在原消息里留下虚线下划线 +
|
|
8
|
-
-
|
|
9
|
-
-
|
|
6
|
+
- **批注卡片**:添加后输入框内出现摘要卡片(可多条),草稿里不会出现任何协议文本
|
|
7
|
+
- **正文标记**:被划中的文字在原消息里留下虚线下划线 + 序号角标,点卡片条目会滚回那段文字
|
|
8
|
+
- **模型回指**:模型回答里指向某条批注的位置,渲染成可点击的 chip
|
|
9
|
+
- **对话记录**:已发送消息里的批注渲染成卡片,用户气泡只剩你打的问题
|
|
10
|
+
- **界面语言**:跟随 dsh 自己的语言设置,简体中文 / English / Русский
|
|
10
11
|
|
|
11
12
|

|
|
12
13
|
|
|
@@ -14,88 +15,85 @@
|
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
17
|
-
## 兼容性
|
|
18
|
-
|
|
19
|
-
| 项 | 值 |
|
|
20
|
-
|---|---|
|
|
21
|
-
| dsh | `0.1.7-rc.1`(2026-09-24 实测:划词、批注卡片、正文标记、对话记录渲染全通) |
|
|
22
|
-
| profile | web(`dsh web`)与桌面端(desktop)都可用;桌面端在 0.2.0 及更早版本会楔死渲染进程,本仓库已修复,详见下节 |
|
|
23
|
-
| 运行时 | 只有浏览器半有业务;Node 半仅用于让包被 Loader 挂载 |
|
|
24
|
-
|
|
25
|
-
实现依赖若干产品内部约定(槽位名、composer DOM、深色主题属性等),升级 dsh 后请先跑 `npm run check`,再照 [`docs/internals.md`](docs/internals.md) 末尾的复核清单人工回归一遍。
|
|
26
|
-
|
|
27
|
-
### 桌面端(desktop profile)
|
|
28
|
-
|
|
29
|
-
桌面端是 Electron 外壳 + 同一个 Web 客户端,插件声明的 `dsh.client.platform: "web"` 在桌面端同样加载,槽位与 DOM 契约都在,功能本身可用(实测批注随消息送达模型、模型回指渲染为 chip 均正常)。
|
|
30
|
-
|
|
31
|
-
> ⚠️ **0.2.0 及更早版本在桌面端会楔死渲染进程**,见 [#1](https://github.com/latte03/dsh-select-quote/issues/1):App 能启动、界面完整渲染,但整个输入区无法点击,renderer 子进程单核 100% 持续旋转。根因在插件自己的装饰器里——协议头部逐字写出了 `:dsh-annotation{index="N"}`,当文本含有这个字面量而严格正则一条都没匹配上时,装饰器仍然替换了一次内容完全相同的 Text 节点,于是被自己的 `MutationObserver` 反复触发,微任务队列永远排不空,宏任务和定时器再也得不到执行。触发条件是文本内容,与 Electron 或 `dsh-app://` 无关,桌面端只是恰好把那段文本送进了 Text 节点。完整机制与两道防线记录在 [`docs/internals.md`](docs/internals.md) 关键机制 8。
|
|
32
|
-
|
|
33
|
-
> **现在的状态**:修复已进主干,Windows 桌面端 0.2.0-rc.2 实测重新启用插件后批注正常送达、Turn 正常结束、输入框可点击、没有新的崩溃日志。npm 上可安装的仍是 `0.2.0`,也就是带缺陷的那一版,因此桌面端用户在修复版发布之前请在 `dsh.profile.bundles` 中摘除本插件,或改用浏览器访问同一个端口。[#1](https://github.com/latte03/dsh-select-quote/issues/1) 保持 open,等报告人复验后关闭。
|
|
34
|
-
|
|
35
18
|
## 安装
|
|
36
19
|
|
|
37
|
-
|
|
20
|
+
网页版:
|
|
38
21
|
|
|
39
22
|
```bash
|
|
40
23
|
dsh plugin --profile web add dsh-select-quote
|
|
41
24
|
dsh --profile web
|
|
42
25
|
```
|
|
43
26
|
|
|
44
|
-
|
|
27
|
+
桌面版:在应用内输入`dsh-select-quote`安装,或者插件页搜索 `dsh-select-quote` 安装,重启客户端生效。
|
|
45
28
|
|
|
46
|
-
|
|
29
|
+
改了代码想立刻验证,用本地 checkout 加载:
|
|
47
30
|
|
|
48
31
|
```bash
|
|
49
|
-
git clone
|
|
32
|
+
git clone https://github.com/latte03/dsh-select-quote && cd dsh-select-quote
|
|
50
33
|
npm install && npm run build
|
|
51
34
|
dsh web --patch "$PWD/dev.patch.yml"
|
|
52
35
|
```
|
|
53
36
|
|
|
54
|
-
打开 `http://127.0.0.1:3080`,在对话消息里划词即可看到工具条。`dev.patch.yml`
|
|
37
|
+
打开 `http://127.0.0.1:3080`,在对话消息里划词即可看到工具条。`dev.patch.yml` 里的插件路径必须是绝对路径,换机器或换目录时改那一行;该文件不随 npm 包发布。
|
|
55
38
|
|
|
56
39
|
## 用法
|
|
57
40
|
|
|
58
|
-
1.
|
|
59
|
-
2.
|
|
60
|
-
3.
|
|
61
|
-
4.
|
|
62
|
-
5.
|
|
41
|
+
1. 在对话区选中一段文本,点「添加到任务」
|
|
42
|
+
2. 在弹出的评论框里写一句想说的话(可留空,只划不评),`⌘/Ctrl + Enter` 确认
|
|
43
|
+
3. 输入框上方出现批注卡片:悬停展开列表,可逐条编辑、删除或一键清空,点条目跳回正文对应文字
|
|
44
|
+
4. 正常打字发送,批注随消息一起交给模型,卡片随之消失
|
|
45
|
+
5. 模型回答里引用某条批注的位置显示为可点击 chip
|
|
63
46
|
|
|
64
|
-
## 语言
|
|
47
|
+
## 语言
|
|
65
48
|
|
|
66
|
-
|
|
49
|
+
界面语言由 dsh 决定,插件不再自己判断浏览器语言。在 **设置 → 通用 → 语言** 里切换。
|
|
67
50
|
|
|
68
|
-
| 语言 | 默认值 |
|
|
69
|
-
|---|---|
|
|
70
|
-
| `zh` | 原文案,同时作为缺失键的兜底词典 |
|
|
71
|
-
| `en` | 非中文环境 |
|
|
72
|
-
| `ru` | `ru` / `ru-RU` 等标签 |
|
|
73
51
|
|
|
74
|
-
|
|
52
|
+
## 已知限制
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
54
|
+
- **待发批注只在内存里**:刷新页面或切换会话后会丢,卡片不持久化;已发送消息里的批注不受影响。
|
|
55
|
+
- **占用了用户气泡的渲染**:本插件以最低优先级替换内置 `user` 节点。其它插件若替换同一个键(例如 `dsh-easyrewrite` 用 `-1`),会被本插件屏蔽。
|
|
56
|
+
- **卡片宽度有限**:输入框内每条摘要最宽约为输入框的 1/4,超长标题走省略号。
|
|
57
|
+
- **划词只在对话区生效**:不介入编辑器、终端等面板。
|
|
80
58
|
|
|
81
|
-
|
|
59
|
+
## 常见问题
|
|
82
60
|
|
|
83
|
-
|
|
61
|
+
**升级 dsh 之后功能不对?** 先 `npm run check`;产物没问题就对照 [`docs/internals.md`](docs/internals.md) 的「依赖的产品内部约定」逐条复核,那里列了所有产品内部名。
|
|
84
62
|
|
|
85
|
-
|
|
63
|
+
**划词没有工具条?** 确认选区在对话区而不是编辑器里;刷新页面(样式与观察者是幂等注入的);宿主 boot 日志里应出现 `dsh-select-quote`。
|
|
86
64
|
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
65
|
+
**输入框里出现了协议原文?** 发送前的注入没生效,一般是 composer 结构变了——检查 `[data-composer-card]` 是否还在。
|
|
66
|
+
|
|
67
|
+
**对话记录里没有批注卡片?** 卡片渲染在用户气泡内部,若 `user` 节点被别的接管者赢走,或那条消息里确实没有批注块,都不会出现。
|
|
68
|
+
|
|
69
|
+
**界面语言不跟着 dsh 变?** 文案取自宿主的语言服务;宿主没有该服务或语言未注册时回退到 English。此时确认 dsh 版本带 `dsh-client-locale`,以及设置里确有语言选项。
|
|
70
|
+
|
|
71
|
+
**改了 CSS 不生效?** 样式只注入一次,刷新页面。
|
|
91
72
|
|
|
92
73
|
## 开发
|
|
93
74
|
|
|
94
75
|
```bash
|
|
95
|
-
npm
|
|
76
|
+
npm install
|
|
77
|
+
npm run check # typecheck + build + verify
|
|
78
|
+
npm run watch # 增量重建
|
|
96
79
|
```
|
|
97
80
|
|
|
98
|
-
|
|
81
|
+
`npm run verify` 无头执行产物,断言 8 项贡献都注册上,并检查三份词典的键与 `{placeholder}` 在每种语言里都齐备(复数消息必须有 `.other` 兜底)。
|
|
82
|
+
|
|
83
|
+
发布:先把源码与产物 `lib/` 一起提交,再跑
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npm run release # check → 追加 CHANGELOG.md → 提升版本号并打 tag
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## 贡献
|
|
90
|
+
|
|
91
|
+
fork 后开分支,提交前跑 `npm run check`。一个 PR 只讲一件事,功能与文档分开,方便追溯。PR 里写清复现方式(环境、dsh 版本、操作步骤)。
|
|
92
|
+
|
|
93
|
+
## 致谢
|
|
94
|
+
|
|
95
|
+
- [@imallakov](https://github.com/imallakov):多语言文案、跨平台构建、装饰器楔死修复与批注卡片归属(PR #2 / #3 / #4)
|
|
96
|
+
- [@EliteOtaku](https://github.com/EliteOtaku):桌面端渲染进程楔死的定位报告与 CDP 探针方法([#1](https://github.com/latte03/dsh-select-quote/issues/1))
|
|
99
97
|
|
|
100
98
|
## License
|
|
101
99
|
|
package/lib/client.js
CHANGED
|
@@ -1765,175 +1765,204 @@ function LucideIcon({ name, size = 15 }) {
|
|
|
1765
1765
|
}
|
|
1766
1766
|
//#endregion
|
|
1767
1767
|
//#region src/client/i18n.ts
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
}
|
|
1812
|
-
en: {
|
|
1813
|
-
"common.selectedText": "Selected text",
|
|
1814
|
-
"common.commentPlaceholder": "Add an optional comment…",
|
|
1815
|
-
"common.cancel": "Cancel",
|
|
1816
|
-
"common.save": "Save",
|
|
1817
|
-
"toolbar.aria": "Selection actions",
|
|
1818
|
-
"toolbar.commentHint": "Add annotation…",
|
|
1819
|
-
"toolbar.copy": "Copy",
|
|
1820
|
-
"toolbar.copyFailed": "Copy failed",
|
|
1821
|
-
"toolbar.addToTask": "Add to task",
|
|
1822
|
-
"toolbar.commentDialogAria": "Add an optional comment",
|
|
1823
|
-
"toolbar.skipComment": "Skip comment",
|
|
1824
|
-
"toolbar.addComment": "Add comment",
|
|
1825
|
-
"card.commentSubtitle": "Comment · {comment}",
|
|
1826
|
-
"card.ariaIndexed": "Annotation {index}",
|
|
1827
|
-
"card.ariaPlain": "Selection annotation",
|
|
1828
|
-
"card.editComment": "Edit comment",
|
|
1829
|
-
"card.remove": "Remove annotation",
|
|
1830
|
-
"summary.hint": "Hover to view annotations",
|
|
1831
|
-
"summary.dialogAria": "Annotation details: {count}",
|
|
1832
|
-
"summary.edit": "Edit annotation {index}",
|
|
1833
|
-
"summary.remove": "Remove annotation {index}",
|
|
1834
|
-
"summary.userComment": "User comment",
|
|
1835
|
-
"summary.noComment": "No comment added",
|
|
1836
|
-
"summary.aria": {
|
|
1837
|
-
one: "{count} annotation. Hover to view details.",
|
|
1838
|
-
other: "{count} annotations. Hover to view details."
|
|
1839
|
-
},
|
|
1840
|
-
"summary.title": {
|
|
1841
|
-
one: "{count} annotation",
|
|
1842
|
-
other: "{count} annotations"
|
|
1843
|
-
},
|
|
1844
|
-
"summary.removeAll": "Remove all annotations",
|
|
1845
|
-
"chip.annotation": "Annotation {index}"
|
|
1846
|
-
},
|
|
1847
|
-
zh: {
|
|
1848
|
-
"common.selectedText": "选中的文本",
|
|
1849
|
-
"common.commentPlaceholder": "添加可选评论…",
|
|
1850
|
-
"common.cancel": "取消",
|
|
1851
|
-
"common.save": "保存",
|
|
1852
|
-
"toolbar.aria": "划词操作",
|
|
1853
|
-
"toolbar.commentHint": "添加批注…",
|
|
1854
|
-
"toolbar.copy": "复制",
|
|
1855
|
-
"toolbar.copyFailed": "复制失败",
|
|
1856
|
-
"toolbar.addToTask": "添加到任务",
|
|
1857
|
-
"toolbar.commentDialogAria": "添加可选评论",
|
|
1858
|
-
"toolbar.skipComment": "暂不评论",
|
|
1859
|
-
"toolbar.addComment": "添加评论",
|
|
1860
|
-
"card.commentSubtitle": "评论 · {comment}",
|
|
1861
|
-
"card.ariaIndexed": "批注 {index}",
|
|
1862
|
-
"card.ariaPlain": "划词批注",
|
|
1863
|
-
"card.editComment": "编辑评论",
|
|
1864
|
-
"card.remove": "移除批注",
|
|
1865
|
-
"summary.hint": "悬停查看批注",
|
|
1866
|
-
"summary.dialogAria": "{count} 条划词批注详情",
|
|
1867
|
-
"summary.edit": "编辑批注 {index}",
|
|
1868
|
-
"summary.remove": "移除批注 {index}",
|
|
1869
|
-
"summary.userComment": "用户评论",
|
|
1870
|
-
"summary.noComment": "未添加评论",
|
|
1871
|
-
"summary.aria": { other: "{count} 条划词批注。悬停查看详情。" },
|
|
1872
|
-
"summary.title": { other: "{count} 条批注" },
|
|
1873
|
-
"summary.removeAll": "移除全部划词批注",
|
|
1874
|
-
"chip.annotation": "批注 {index}"
|
|
1875
|
-
}
|
|
1768
|
+
/**
|
|
1769
|
+
* Copy for dsh-select-quote, served through the host locale service.
|
|
1770
|
+
*
|
|
1771
|
+
* The active language is owned by `@deepseek-ai/dsh-client-locale` (Settings →
|
|
1772
|
+
* General → Language, persisted as `locale.preference`). This module owns only
|
|
1773
|
+
* the dictionaries and the plural form selection that the host's plain-template
|
|
1774
|
+
* lookup cannot express; it never resolves a language on its own.
|
|
1775
|
+
*
|
|
1776
|
+
* NOT localized on purpose:
|
|
1777
|
+
* - wire markers (`> [选中文本]`, `<response-annotations>`,
|
|
1778
|
+
* `:dsh-annotation{index="N"}`) — they are the message format the model
|
|
1779
|
+
* and the transcript parser agree on;
|
|
1780
|
+
* - the `# Response annotations:` header — model-facing instruction prose,
|
|
1781
|
+
* not UI text.
|
|
1782
|
+
*/
|
|
1783
|
+
const LOCALE_NS = "select-quote";
|
|
1784
|
+
const zh = {
|
|
1785
|
+
"common.selectedText": "选中的文本",
|
|
1786
|
+
"common.commentPlaceholder": "添加可选评论…",
|
|
1787
|
+
"common.cancel": "取消",
|
|
1788
|
+
"common.save": "保存",
|
|
1789
|
+
"toolbar.aria": "划词操作",
|
|
1790
|
+
"toolbar.commentHint": "添加批注…",
|
|
1791
|
+
"toolbar.copy": "复制",
|
|
1792
|
+
"toolbar.copyFailed": "复制失败",
|
|
1793
|
+
"toolbar.addToTask": "添加到任务",
|
|
1794
|
+
"toolbar.commentDialogAria": "添加可选评论",
|
|
1795
|
+
"toolbar.skipComment": "暂不评论",
|
|
1796
|
+
"toolbar.addComment": "添加评论",
|
|
1797
|
+
"card.commentSubtitle": "评论 · {comment}",
|
|
1798
|
+
"card.ariaIndexed": "批注 {index}",
|
|
1799
|
+
"card.ariaPlain": "划词批注",
|
|
1800
|
+
"card.editComment": "编辑评论",
|
|
1801
|
+
"card.remove": "移除批注",
|
|
1802
|
+
"summary.hint": "悬停查看批注",
|
|
1803
|
+
"summary.dialogAria": "{count} 条划词批注详情",
|
|
1804
|
+
"summary.edit": "编辑批注 {index}",
|
|
1805
|
+
"summary.remove": "移除批注 {index}",
|
|
1806
|
+
"summary.userComment": "用户评论",
|
|
1807
|
+
"summary.noComment": "未添加评论",
|
|
1808
|
+
"summary.aria.other": "{count} 条划词批注。悬停查看详情。",
|
|
1809
|
+
"summary.title.other": "{count} 条批注",
|
|
1810
|
+
"summary.removeAll": "移除全部划词批注",
|
|
1811
|
+
"chip.annotation": "批注 {index}"
|
|
1876
1812
|
};
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1813
|
+
const en = {
|
|
1814
|
+
"common.selectedText": "Selected text",
|
|
1815
|
+
"common.commentPlaceholder": "Add an optional comment…",
|
|
1816
|
+
"common.cancel": "Cancel",
|
|
1817
|
+
"common.save": "Save",
|
|
1818
|
+
"toolbar.aria": "Selection actions",
|
|
1819
|
+
"toolbar.commentHint": "Add annotation…",
|
|
1820
|
+
"toolbar.copy": "Copy",
|
|
1821
|
+
"toolbar.copyFailed": "Copy failed",
|
|
1822
|
+
"toolbar.addToTask": "Add to task",
|
|
1823
|
+
"toolbar.commentDialogAria": "Add an optional comment",
|
|
1824
|
+
"toolbar.skipComment": "Skip comment",
|
|
1825
|
+
"toolbar.addComment": "Add comment",
|
|
1826
|
+
"card.commentSubtitle": "Comment · {comment}",
|
|
1827
|
+
"card.ariaIndexed": "Annotation {index}",
|
|
1828
|
+
"card.ariaPlain": "Selection annotation",
|
|
1829
|
+
"card.editComment": "Edit comment",
|
|
1830
|
+
"card.remove": "Remove annotation",
|
|
1831
|
+
"summary.hint": "Hover to view annotations",
|
|
1832
|
+
"summary.dialogAria": "Annotation details: {count}",
|
|
1833
|
+
"summary.edit": "Edit annotation {index}",
|
|
1834
|
+
"summary.remove": "Remove annotation {index}",
|
|
1835
|
+
"summary.userComment": "User comment",
|
|
1836
|
+
"summary.noComment": "No comment added",
|
|
1837
|
+
"summary.aria.one": "{count} annotation. Hover to view details.",
|
|
1838
|
+
"summary.aria.other": "{count} annotations. Hover to view details.",
|
|
1839
|
+
"summary.title.one": "{count} annotation",
|
|
1840
|
+
"summary.title.other": "{count} annotations",
|
|
1841
|
+
"summary.removeAll": "Remove all annotations",
|
|
1842
|
+
"chip.annotation": "Annotation {index}"
|
|
1843
|
+
};
|
|
1844
|
+
/**
|
|
1845
|
+
* Russian is not a shipped host locale: it is added through
|
|
1846
|
+
* `ctx.locale.addLanguage()` and its dictionary through the three-argument
|
|
1847
|
+
* `register()`. Its fallback chain ends at `en`, as the host requires.
|
|
1848
|
+
*/
|
|
1849
|
+
const ru = {
|
|
1850
|
+
"common.selectedText": "Выделенный текст",
|
|
1851
|
+
"common.commentPlaceholder": "Добавить необязательный комментарий…",
|
|
1852
|
+
"common.cancel": "Отмена",
|
|
1853
|
+
"common.save": "Сохранить",
|
|
1854
|
+
"toolbar.aria": "Действия с выделенным текстом",
|
|
1855
|
+
"toolbar.commentHint": "Добавить аннотацию…",
|
|
1856
|
+
"toolbar.copy": "Копировать",
|
|
1857
|
+
"toolbar.copyFailed": "Не удалось скопировать",
|
|
1858
|
+
"toolbar.addToTask": "Добавить к задаче",
|
|
1859
|
+
"toolbar.commentDialogAria": "Добавить необязательный комментарий",
|
|
1860
|
+
"toolbar.skipComment": "Без комментария",
|
|
1861
|
+
"toolbar.addComment": "Добавить комментарий",
|
|
1862
|
+
"card.commentSubtitle": "Комментарий · {comment}",
|
|
1863
|
+
"card.ariaIndexed": "Аннотация {index}",
|
|
1864
|
+
"card.ariaPlain": "Аннотация к выделенному тексту",
|
|
1865
|
+
"card.editComment": "Изменить комментарий",
|
|
1866
|
+
"card.remove": "Удалить аннотацию",
|
|
1867
|
+
"summary.hint": "Наведите, чтобы увидеть аннотации",
|
|
1868
|
+
"summary.dialogAria": "Детали аннотаций: {count}",
|
|
1869
|
+
"summary.edit": "Изменить аннотацию {index}",
|
|
1870
|
+
"summary.remove": "Удалить аннотацию {index}",
|
|
1871
|
+
"summary.userComment": "Комментарий пользователя",
|
|
1872
|
+
"summary.noComment": "Комментарий не добавлен",
|
|
1873
|
+
"summary.aria.one": "{count} аннотация. Наведите, чтобы увидеть детали.",
|
|
1874
|
+
"summary.aria.few": "{count} аннотации. Наведите, чтобы увидеть детали.",
|
|
1875
|
+
"summary.aria.many": "{count} аннотаций. Наведите, чтобы увидеть детали.",
|
|
1876
|
+
"summary.aria.other": "{count} аннотаций. Наведите, чтобы увидеть детали.",
|
|
1877
|
+
"summary.title.one": "{count} аннотация",
|
|
1878
|
+
"summary.title.few": "{count} аннотации",
|
|
1879
|
+
"summary.title.many": "{count} аннотаций",
|
|
1880
|
+
"summary.title.other": "{count} аннотаций",
|
|
1881
|
+
"summary.removeAll": "Удалить все аннотации",
|
|
1882
|
+
"chip.annotation": "Аннотация {index}"
|
|
1883
|
+
};
|
|
1884
|
+
/** Both shipped host locales, required together by the typed `register()`. */
|
|
1885
|
+
const SHIPPED_DICTS = Object.freeze({
|
|
1886
|
+
zh,
|
|
1887
|
+
en
|
|
1888
|
+
});
|
|
1889
|
+
/** Dictionary for the language registered through {@link LANGUAGE_RU}. */
|
|
1890
|
+
const DICTIONARIES = Object.freeze({
|
|
1891
|
+
zh,
|
|
1892
|
+
en,
|
|
1893
|
+
ru
|
|
1894
|
+
});
|
|
1895
|
+
const LANGUAGE_RU = Object.freeze({
|
|
1896
|
+
id: "ru",
|
|
1897
|
+
label: "Русский",
|
|
1898
|
+
fallback: "en"
|
|
1899
|
+
});
|
|
1900
|
+
let api;
|
|
1901
|
+
/**
|
|
1902
|
+
* Install the host-bound translation entry point. Called once from `apply()`
|
|
1903
|
+
* with `ctx.locale.bind(LOCALE_NS)` and the live snapshot accessors.
|
|
1904
|
+
*/
|
|
1905
|
+
function bindLocale(next) {
|
|
1906
|
+
api = next;
|
|
1904
1907
|
}
|
|
1905
|
-
/** Active
|
|
1906
|
-
function
|
|
1907
|
-
|
|
1908
|
-
return cached;
|
|
1908
|
+
/** Active language id, or `en` before the host service is bound. */
|
|
1909
|
+
function activeLocale() {
|
|
1910
|
+
return api?.getActive() ?? "en";
|
|
1909
1911
|
}
|
|
1910
|
-
function
|
|
1912
|
+
function pluralForm(count) {
|
|
1913
|
+
const locale = activeLocale();
|
|
1911
1914
|
if (locale === "ru") {
|
|
1912
1915
|
const mod10 = count % 10;
|
|
1913
1916
|
const mod100 = count % 100;
|
|
1914
|
-
if (mod10 === 1 && mod100 !== 11) return
|
|
1915
|
-
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return
|
|
1916
|
-
return
|
|
1917
|
+
if (mod10 === 1 && mod100 !== 11) return "one";
|
|
1918
|
+
if (mod10 >= 2 && mod10 <= 4 && (mod100 < 12 || mod100 > 14)) return "few";
|
|
1919
|
+
return "many";
|
|
1917
1920
|
}
|
|
1918
|
-
if (locale === "en") return
|
|
1919
|
-
return
|
|
1920
|
-
}
|
|
1921
|
-
function interpolate(template, params) {
|
|
1922
|
-
return template.replace(/\{(\w+)\}/g, (whole, name) => name in params ? String(params[name]) : whole);
|
|
1921
|
+
if (locale === "en") return count === 1 ? "one" : "other";
|
|
1922
|
+
return "other";
|
|
1923
1923
|
}
|
|
1924
1924
|
/**
|
|
1925
|
-
* Translate a key
|
|
1926
|
-
* `
|
|
1927
|
-
* selects the right plural form when the message is a `PluralForms` object.
|
|
1925
|
+
* Translate a `select-quote` key. A numeric `count` selects a plural form and
|
|
1926
|
+
* the matching `key.form` entry is used, falling back to `key.other`.
|
|
1928
1927
|
*/
|
|
1929
1928
|
function t(key, params = {}) {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1929
|
+
if (!api) return key;
|
|
1930
|
+
if (typeof params.count === "number") {
|
|
1931
|
+
const form = pluralForm(params.count);
|
|
1932
|
+
const exact = api.translate(`${key}.${form}`, params);
|
|
1933
|
+
if (exact !== `${key}.${form}`) return exact;
|
|
1934
|
+
const other = api.translate(`${key}.other`, params);
|
|
1935
|
+
if (other !== `${key}.other`) return other;
|
|
1936
|
+
}
|
|
1937
|
+
return api.translate(key, params);
|
|
1938
|
+
}
|
|
1939
|
+
/**
|
|
1940
|
+
* Subscribe to language or dictionary changes. Components go through
|
|
1941
|
+
* `useLocale()` instead; this is the non-React entry point (DOM decorations).
|
|
1942
|
+
* Subscribing before the host service is bound is a no-op, so callers subscribe
|
|
1943
|
+
* from `apply()`, after `bindLocale()`.
|
|
1944
|
+
*/
|
|
1945
|
+
function subscribeLocale(listener) {
|
|
1946
|
+
return api ? api.subscribe(listener) : () => {};
|
|
1947
|
+
}
|
|
1948
|
+
/** Monotonic host revision counter, bumped on locale and dictionary changes. */
|
|
1949
|
+
function localeRevision() {
|
|
1950
|
+
return api?.getRevision() ?? 0;
|
|
1951
|
+
}
|
|
1952
|
+
//#endregion
|
|
1953
|
+
//#region src/client/useLocale.ts
|
|
1954
|
+
/**
|
|
1955
|
+
* Re-render the calling component when the host language changes, so slot copy
|
|
1956
|
+
* switches without a page reload. Subscriptions happen after mount, by which
|
|
1957
|
+
* time `apply()` has bound the locale service.
|
|
1958
|
+
*/
|
|
1959
|
+
function useLocale() {
|
|
1960
|
+
(0, react.useSyncExternalStore)(subscribeLocale, localeRevision, localeRevision);
|
|
1933
1961
|
}
|
|
1934
1962
|
//#endregion
|
|
1935
1963
|
//#region src/client/ui/AnnotationCardView.tsx
|
|
1936
1964
|
function AnnotationCardView({ title, text, comment, variant, index, onRemove, onEditComment }) {
|
|
1965
|
+
useLocale();
|
|
1937
1966
|
const composer = variant === "composer";
|
|
1938
1967
|
const subtitle = comment ? composer ? t("card.commentSubtitle", { comment }) : comment : composer ? t("common.selectedText") : t("summary.noComment");
|
|
1939
1968
|
return (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1993,7 +2022,8 @@ const SETTLE_DELAY = 600;
|
|
|
1993
2022
|
* other cancels. Comment editing happens inline in the list — the panel stays
|
|
1994
2023
|
* put and auto-close is suppressed while an edit is open.
|
|
1995
2024
|
*/
|
|
1996
|
-
function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemove, onItemEdit
|
|
2025
|
+
function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemove, onItemEdit }) {
|
|
2026
|
+
useLocale();
|
|
1997
2027
|
const rootRef = (0, react.useRef)(null);
|
|
1998
2028
|
const popRef = (0, react.useRef)(null);
|
|
1999
2029
|
const closeTimerRef = (0, react.useRef)(null);
|
|
@@ -2285,7 +2315,7 @@ function AnnotationSummary({ count, items, onRemoveAll, onItemSelect, onItemRemo
|
|
|
2285
2315
|
children: t("summary.title", { count })
|
|
2286
2316
|
}), (0, react_jsx_runtime.jsx)("div", {
|
|
2287
2317
|
className: styles.summaryHint,
|
|
2288
|
-
children: hint
|
|
2318
|
+
children: t("summary.hint")
|
|
2289
2319
|
})]
|
|
2290
2320
|
}),
|
|
2291
2321
|
onRemoveAll ? (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -2586,6 +2616,7 @@ async function copyText(text) {
|
|
|
2586
2616
|
/** Cap for the auto-growing comment box; beyond this it scrolls internally. */
|
|
2587
2617
|
const COMMENT_MAX_HEIGHT = 220;
|
|
2588
2618
|
function SelectionToolbar({ sessionId }) {
|
|
2619
|
+
useLocale();
|
|
2589
2620
|
const toolbarRef = (0, react.useRef)(null);
|
|
2590
2621
|
const commentWrapRef = (0, react.useRef)(null);
|
|
2591
2622
|
const commentRef = (0, react.useRef)(null);
|
|
@@ -2913,6 +2944,22 @@ function decorateAnnotationDirectives(root) {
|
|
|
2913
2944
|
}
|
|
2914
2945
|
for (const node of hits) decorateTextNode(node);
|
|
2915
2946
|
}
|
|
2947
|
+
/**
|
|
2948
|
+
* Re-label chips that are already on screen after the language changed.
|
|
2949
|
+
*
|
|
2950
|
+
* Chip text is plain DOM, not React state, so a switch would otherwise leave
|
|
2951
|
+
* old-language chips next to new-language popovers. Writing `textContent` here
|
|
2952
|
+
* is safe for the observer: the new text node never contains the directive
|
|
2953
|
+
* literal, so `decorateTextNode()` returns before it can touch the DOM.
|
|
2954
|
+
*/
|
|
2955
|
+
function refreshDirectiveChips(root) {
|
|
2956
|
+
const el = root ?? (typeof document === "undefined" ? void 0 : document.body);
|
|
2957
|
+
if (!el) return;
|
|
2958
|
+
for (const chip of el.querySelectorAll(`[data-${DECO}]`)) {
|
|
2959
|
+
const index = Number(chip.dataset[DECO]);
|
|
2960
|
+
if (Number.isFinite(index)) chip.textContent = t("chip.annotation", { index });
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2916
2963
|
/** Observe chat DOM and decorate new directive text. Returns disposer. */
|
|
2917
2964
|
function watchAnnotationDirectives(root) {
|
|
2918
2965
|
const el = root ?? (typeof document === "undefined" ? void 0 : document.body);
|
|
@@ -3064,10 +3111,30 @@ function UserMessageDisplay({ node, renderMessageImages }) {
|
|
|
3064
3111
|
* kind is classified as a turn-process member and `hidden` when the completed
|
|
3065
3112
|
* turn's process disclosure is folded — mounted, but invisible. `user` is on
|
|
3066
3113
|
* that allowlist, so the bubble is the only placement that is reliably visible.
|
|
3114
|
+
*
|
|
3115
|
+
* Language is the host's decision, never this plugin's: dictionaries go to
|
|
3116
|
+
* `ctx.locale`, and `t()` resolves against whatever language the user picked in
|
|
3117
|
+
* Settings. Self-detecting from `navigator` would disagree with the host on the
|
|
3118
|
+
* desktop shell, where Electron pins the document language (`--lang`) to its own
|
|
3119
|
+
* value while the host preference comes through `__DSH_LOCALE__`.
|
|
3067
3120
|
*/
|
|
3068
|
-
const inject = [
|
|
3121
|
+
const inject = [
|
|
3122
|
+
"slots",
|
|
3123
|
+
"sessions",
|
|
3124
|
+
"locale"
|
|
3125
|
+
];
|
|
3069
3126
|
function apply(ctx) {
|
|
3070
3127
|
ensureToolbarStyles();
|
|
3128
|
+
ctx.effect(() => ctx.locale.addLanguage(LANGUAGE_RU), "dsh-select-quote: ru language");
|
|
3129
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NS, SHIPPED_DICTS), "dsh-select-quote: dictionaries (zh, en)");
|
|
3130
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NS, LANGUAGE_RU.id, DICTIONARIES.ru), "dsh-select-quote: dictionary (ru)");
|
|
3131
|
+
bindLocale({
|
|
3132
|
+
translate: ctx.locale.bind(LOCALE_NS),
|
|
3133
|
+
getActive: () => ctx.locale.getLocale().active,
|
|
3134
|
+
getRevision: () => ctx.locale.getSnapshot().revision,
|
|
3135
|
+
subscribe: (listener) => ctx.locale.subscribe(listener)
|
|
3136
|
+
});
|
|
3137
|
+
ctx.effect(() => subscribeLocale(() => refreshDirectiveChips()), "dsh-select-quote: chip language");
|
|
3071
3138
|
ctx.effect(() => watchAnnotationDirectives(), "dsh-select-quote: annotation directives");
|
|
3072
3139
|
ctx.effect(() => watchAnnotationMarks(), "dsh-select-quote: annotation marks");
|
|
3073
3140
|
ctx.effect(() => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-select-quote",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "DeepSeek Harness web plugin: annotate text selected in the conversation — the selection rides along with your next message, is marked in the source text, and the model points back at it.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"watch": "node scripts/build.mjs --watch",
|
|
40
40
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
41
41
|
"verify": "node scripts/verify-bundle.mjs",
|
|
42
|
-
"check": "npm run typecheck && npm run build && npm run verify"
|
|
42
|
+
"check": "npm run typecheck && npm run build && npm run verify",
|
|
43
|
+
"release": "npm run check && commit-and-tag-version"
|
|
43
44
|
},
|
|
44
45
|
"dsh": {
|
|
45
46
|
"client": {
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"devDependencies": {
|
|
67
68
|
"@types/react": "~18.3.1",
|
|
68
69
|
"@types/react-dom": "~18.3",
|
|
70
|
+
"commit-and-tag-version": "^13.2.2",
|
|
69
71
|
"react": "^18.2.0",
|
|
70
72
|
"react-dom": "^18.2.0",
|
|
71
73
|
"tsdown": "0.23.0",
|