@sidleo3/dsh-chat 0.0.4
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/client/bot-list.js +243 -0
- package/client/bot-settings.js +175 -0
- package/client/bot-shared-settings.js +561 -0
- package/client/chat-ui.js +134 -0
- package/client/context-enhancement.js +435 -0
- package/client/delivery-targets.js +334 -0
- package/client/diagnostics.js +160 -0
- package/client/i18n.js +371 -0
- package/client/index.js +77 -0
- package/client/list-order.js +144 -0
- package/client/rpc.js +52 -0
- package/client/scoped-mode-editor.js +111 -0
- package/client/section.js +250 -0
- package/client/session-badges.js +263 -0
- package/client/styles.js +960 -0
- package/client/version-panel.js +97 -0
- package/cordis.patch.yml +5 -0
- package/host/bot-model.mjs +53 -0
- package/host/bot-settings.mjs +247 -0
- package/host/channel-registry.mjs +237 -0
- package/host/commands.mjs +857 -0
- package/host/deferred.mjs +291 -0
- package/host/delivery.mjs +377 -0
- package/host/file-log.mjs +169 -0
- package/host/guidance.mjs +73 -0
- package/host/index.mjs +7 -0
- package/host/interactions.mjs +330 -0
- package/host/json-store.mjs +144 -0
- package/host/log-tail.mjs +63 -0
- package/host/panel.mjs +1012 -0
- package/host/paths.mjs +50 -0
- package/host/plugin.mjs +873 -0
- package/host/prompt-context.mjs +70 -0
- package/host/rpc.mjs +147 -0
- package/host/session-keys.mjs +25 -0
- package/host/session-store.mjs +187 -0
- package/host/sessions.mjs +1348 -0
- package/host/tools.mjs +283 -0
- package/lib/client.js +4431 -0
- package/lib/index.js +5676 -0
- package/package.json +63 -0
- package/shared/access-policy.mjs +263 -0
- package/shared/channel-rail.mjs +156 -0
- package/shared/context-enhancement.mjs +415 -0
- package/shared/contract.mjs +120 -0
- package/shared/panel-sections.mjs +76 -0
- package/shared/reply-reference.mjs +115 -0
package/client/i18n.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-chat 的文案。每个包用自己的命名空间注册,互不覆盖。
|
|
3
|
+
*
|
|
4
|
+
* @module dsh-chat/client/i18n
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export const LOCALE_NAMESPACE = 'dsh-chat';
|
|
8
|
+
|
|
9
|
+
export const zh = {
|
|
10
|
+
'Chat机器人': 'Chat机器人',
|
|
11
|
+
'模型': '模型',
|
|
12
|
+
'默认模型': '默认模型',
|
|
13
|
+
'推理等级': '推理等级',
|
|
14
|
+
'这个模型没有可选的推理等级。': '这个模型没有可选的推理等级。',
|
|
15
|
+
'先选一个模型。': '先选一个模型。',
|
|
16
|
+
'模型默认': '模型默认',
|
|
17
|
+
'部分 provider 读取失败:': '部分 provider 读取失败:',
|
|
18
|
+
'当前 Host 读不到模型目录。': '当前 Host 读不到模型目录。',
|
|
19
|
+
'还没有会话时用哪个模型:选完对下一条消息新建的会话生效。会话内还能单独改(面板的模型下拉)。':
|
|
20
|
+
'还没有会话时用哪个模型:选完对下一条消息新建的会话生效。会话内还能单独改(面板的模型下拉)。',
|
|
21
|
+
|
|
22
|
+
'Chat机器人设置': 'Chat机器人设置',
|
|
23
|
+
'渠道导航': '渠道导航',
|
|
24
|
+
'未安装任何聊天软件插件': '未安装任何聊天软件插件',
|
|
25
|
+
'安装渠道插件后,这里会出现对应的聊天软件。': '安装渠道插件后,这里会出现对应的聊天软件。',
|
|
26
|
+
'已知渠道插件': '已知渠道插件',
|
|
27
|
+
'渠道正在启动': '正在启动',
|
|
28
|
+
'渠道已就绪': '已就绪',
|
|
29
|
+
'渠道启动失败': '启动失败',
|
|
30
|
+
'渠道已停止': '已停止',
|
|
31
|
+
'重新读取': '重新读取',
|
|
32
|
+
'读取中…': '读取中…',
|
|
33
|
+
'上下文增强': '上下文增强',
|
|
34
|
+
'任务过程展示': '任务过程展示',
|
|
35
|
+
'保存': '保存',
|
|
36
|
+
'保存中…': '保存中…',
|
|
37
|
+
'取消': '取消',
|
|
38
|
+
'保存失败,请重试。': '保存失败,请重试。',
|
|
39
|
+
// 主动投递(共享组件 delivery-targets.js)
|
|
40
|
+
'主动投递': '主动投递',
|
|
41
|
+
'让定时任务或 agent 把结果直接发到指定会话。': '让定时任务或 agent 把结果直接发到指定会话。',
|
|
42
|
+
'显示项': '显示项',
|
|
43
|
+
'控制面板显示项': '控制面板显示项',
|
|
44
|
+
'只影响 /menu 发出来的那张卡片:关掉的项不显示,功能照旧(私聊与群聊分别设置)。': '只影响 /menu 发出来的那张卡片:关掉的项不显示,功能照旧(私聊与群聊分别设置)。',
|
|
45
|
+
'模型与推理等级': '模型与推理等级',
|
|
46
|
+
'会话': '会话',
|
|
47
|
+
'Agent 预设与工作区': 'Agent 预设与工作区',
|
|
48
|
+
'上下文增强(本会话)': '上下文增强(本会话)',
|
|
49
|
+
'访问策略(本会话)': '访问策略(本会话)',
|
|
50
|
+
'渠道设置(任务过程展示等)': '渠道设置(任务过程展示等)',
|
|
51
|
+
'渠道动作按钮(重连等)': '渠道动作按钮(重连等)',
|
|
52
|
+
'命令按钮(新会话/状态/诊断…)': '命令按钮(新会话/状态/诊断…)',
|
|
53
|
+
'拖动可调整顺序': '拖动可调整顺序',
|
|
54
|
+
'在渠道自己的配置里完成接入后,机器人会出现在这里。': '在渠道自己的配置里完成接入后,机器人会出现在这里。',
|
|
55
|
+
'私聊': '私聊',
|
|
56
|
+
'群聊': '群聊',
|
|
57
|
+
'候选': '候选',
|
|
58
|
+
'保存为投递目标': '保存为投递目标',
|
|
59
|
+
'重命名': '重命名',
|
|
60
|
+
'自定义名称': '自定义名称',
|
|
61
|
+
'留空则用自动识别的名字': '留空则用自动识别的名字',
|
|
62
|
+
'已改名。': '已改名。',
|
|
63
|
+
'已恢复自动名字。': '已恢复自动名字。',
|
|
64
|
+
'删除': '删除',
|
|
65
|
+
'确认删除': '确认删除',
|
|
66
|
+
'当前渠道不支持主动投递。': '当前渠道不支持主动投递。',
|
|
67
|
+
|
|
68
|
+
'已保存,现在可以主动发消息了。': '已保存,现在可以主动发消息了。',
|
|
69
|
+
'已删除。': '已删除。',
|
|
70
|
+
'还没有可添加的会话:在群里 @ 一次机器人,或与它私聊一次,会话就会出现在这里,保存后即可主动投递。': '还没有可添加的会话:在群里 @ 一次机器人,或与它私聊一次,会话就会出现在这里,保存后即可主动投递。',
|
|
71
|
+
'没有可添加的会话:在群里 @ 一次机器人,或与它私聊一次,该会话就会出现在这里。': '没有可添加的会话:在群里 @ 一次机器人,或与它私聊一次,该会话就会出现在这里。',
|
|
72
|
+
'上面标「候选」的会话还不能主动投递,点「保存为投递目标」后才行。': '上面标「候选」的会话还不能主动投递,点「保存为投递目标」后才行。',
|
|
73
|
+
'已保存': '已保存',
|
|
74
|
+
'可添加的候选': '可添加的候选',
|
|
75
|
+
'展开全部': '展开全部',
|
|
76
|
+
'收起': '收起',
|
|
77
|
+
'按名字或 id 过滤': '按名字或 id 过滤',
|
|
78
|
+
'没有匹配的目标。': '没有匹配的目标。',
|
|
79
|
+
'填入示例': '填入示例',
|
|
80
|
+
'清空': '清空',
|
|
81
|
+
'来源字段': '来源字段',
|
|
82
|
+
'启用': '启用',
|
|
83
|
+
'备注名(可选)': '备注名(可选)',
|
|
84
|
+
'张三': '张三',
|
|
85
|
+
'新增': '新增',
|
|
86
|
+
'还没有指定设置。': '还没有指定设置。',
|
|
87
|
+
'关闭': '关闭',
|
|
88
|
+
'增强提示词': '增强提示词',
|
|
89
|
+
'告诉模型如何使用来源字段。只填正文,插件会自动包成来源增强块。': '告诉模型如何使用来源字段。只填正文,插件会自动包成来源增强块。',
|
|
90
|
+
'叠加全局提示词(不勾选则只使用上面的专属提示词)': '叠加全局提示词(不勾选则只使用上面的专属提示词)',
|
|
91
|
+
'上下文增强': '上下文增强',
|
|
92
|
+
'未开启': '未开启',
|
|
93
|
+
'来源字段只在当前消息已提供时才会发送,不会额外查询平台接口。': '来源字段只在当前消息已提供时才会发送,不会额外查询平台接口。',
|
|
94
|
+
'上下文增强范围': '上下文增强范围',
|
|
95
|
+
'已开启': '已开启',
|
|
96
|
+
'从会话里选…': '从会话里选…',
|
|
97
|
+
'属主': '属主',
|
|
98
|
+
'属主不需要进白名单:消息与命令都直接放行。这里改完会重连一次,立刻生效。': '属主不需要进白名单:消息与命令都直接放行。这里改完会重连一次,立刻生效。',
|
|
99
|
+
'当前没有属主:没有人绕过访问策略,谁能用完全由下面的「访问策略」决定。': '当前没有属主:没有人绕过访问策略,谁能用完全由下面的「访问策略」决定。',
|
|
100
|
+
'从会话里选一个人设为属主': '从会话里选一个人设为属主',
|
|
101
|
+
'设为属主': '设为属主',
|
|
102
|
+
'没有可选的会话(先和机器人聊一次)': '没有可选的会话(先和机器人聊一次)',
|
|
103
|
+
'清空(无属主)': '清空(无属主)',
|
|
104
|
+
'清空后没有人绕过访问策略': '清空后没有人绕过访问策略',
|
|
105
|
+
// 机器人设置页的共享编辑块(bot-shared-settings.js)
|
|
106
|
+
'工作区': '工作区',
|
|
107
|
+
'机器人跑在哪个目录:能读写哪些文件、用哪份 AGENTS.md。只对新建会话生效。': '机器人跑在哪个目录:能读写哪些文件、用哪份 AGENTS.md。只对新建会话生效。',
|
|
108
|
+
'目录': '目录',
|
|
109
|
+
'下拉里是这台机器人用过的目录。': '下拉里是这台机器人用过的目录。',
|
|
110
|
+
'Agent 预设': 'Agent 预设',
|
|
111
|
+
'这个机器人用哪套 Agent 预设(人设与工具集)。只对新建会话生效。': '这个机器人用哪套 Agent 预设(人设与工具集)。只对新建会话生效。',
|
|
112
|
+
'当前 Host 读不到 Agent Preset 列表。': '当前 Host 读不到 Agent Preset 列表。',
|
|
113
|
+
'跟随 Host 默认': '跟随 Host 默认',
|
|
114
|
+
'访问策略': '访问策略',
|
|
115
|
+
'谁能跟机器人说话、谁能执行命令。改动立即生效;属主始终可用。': '谁能跟机器人说话、谁能执行命令。改动立即生效;属主始终可用。',
|
|
116
|
+
'访问模式': '访问模式',
|
|
117
|
+
'仅名单内可用': '仅名单内可用',
|
|
118
|
+
'任何人可用': '任何人可用',
|
|
119
|
+
'允许执行命令': '允许执行命令',
|
|
120
|
+
'可执行命令': '可执行命令',
|
|
121
|
+
'移除': '移除',
|
|
122
|
+
'添加': '添加',
|
|
123
|
+
'名单为空时只有属主可用。': '名单为空时只有属主可用。',
|
|
124
|
+
'对方的平台 id,回车添加': '对方的平台 id,回车添加',
|
|
125
|
+
// 版本与更新(version-panel.js)
|
|
126
|
+
'当前页面不支持渠道子槽。': '当前页面不支持渠道子槽。',
|
|
127
|
+
'设置': '设置',
|
|
128
|
+
'机器人': '机器人',
|
|
129
|
+
'每个机器人一行;点「设置」进入它的设置页(上下文增强、主动投递、工作区…)。':
|
|
130
|
+
'每个机器人一行;点「设置」进入它的设置页(上下文增强、主动投递、工作区…)。',
|
|
131
|
+
'这个渠道还没有机器人': '这个渠道还没有机器人',
|
|
132
|
+
'在渠道设置页完成接入(飞书填应用凭据、微信扫码)后,机器人会出现在这里。':
|
|
133
|
+
'在渠道设置页完成接入(飞书填应用凭据、微信扫码)后,机器人会出现在这里。',
|
|
134
|
+
'未命名机器人': '未命名机器人',
|
|
135
|
+
'这台机器人没有可用的身份标识,无法单独配置': '这台机器人没有可用的身份标识,无法单独配置',
|
|
136
|
+
'渠道设置': '渠道设置',
|
|
137
|
+
'打开渠道设置页': '打开渠道设置页',
|
|
138
|
+
'← 机器人列表': '← 机器人列表',
|
|
139
|
+
'已处理': '已处理',
|
|
140
|
+
'最近': '最近',
|
|
141
|
+
'运行正常': '运行正常',
|
|
142
|
+
'正在启动': '正在启动',
|
|
143
|
+
'重连中': '重连中',
|
|
144
|
+
'已停止': '已停止',
|
|
145
|
+
// 诊断面板(client/diagnostics.js):出故障时的自助现场。
|
|
146
|
+
'诊断': '诊断',
|
|
147
|
+
'收起诊断': '收起诊断',
|
|
148
|
+
'出故障时先看这里:连接状态、最近错误、日志尾部。': '出故障时先看这里:连接状态、最近错误、日志尾部。',
|
|
149
|
+
'运行中': '运行中',
|
|
150
|
+
'启动中': '启动中',
|
|
151
|
+
'已连接': '已连接',
|
|
152
|
+
'未连接': '未连接',
|
|
153
|
+
'已处理': '已处理',
|
|
154
|
+
'去开通权限': '去开通权限',
|
|
155
|
+
'最近一次错误': '最近一次错误',
|
|
156
|
+
'这台渠道下还没有机器人。': '这台渠道下还没有机器人。',
|
|
157
|
+
'还没有日志': '还没有日志',
|
|
158
|
+
'看最后 40 行': '看最后 40 行',
|
|
159
|
+
'收起': '收起',
|
|
160
|
+
'版本与更新': '版本与更新',
|
|
161
|
+
'收起版本与更新': '收起版本与更新',
|
|
162
|
+
'升级插件后需要重启 dsh;只改设置页代码则刷新页面即可。':
|
|
163
|
+
'升级插件后需要重启 dsh;只改设置页代码则刷新页面即可。',
|
|
164
|
+
'Chat机器人内核': 'Chat机器人内核',
|
|
165
|
+
'渠道契约版本': '渠道契约版本',
|
|
166
|
+
'数据目录': '数据目录',
|
|
167
|
+
'日志目录': '日志目录',
|
|
168
|
+
'读取失败': '读取失败',
|
|
169
|
+
'更新方式:在仓库里拉取新代码后重新打包,再让 DSH 重新加载插件。':
|
|
170
|
+
'更新方式:在仓库里拉取新代码后重新打包,再让 DSH 重新加载插件。',
|
|
171
|
+
'发一条测试消息': '发一条测试消息',
|
|
172
|
+
'选择目标': '选择目标',
|
|
173
|
+
'发送': '发送',
|
|
174
|
+
'发送中…': '发送中…',
|
|
175
|
+
'测试消息内容': '测试消息内容',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export const en = {
|
|
179
|
+
'显示项': 'Section',
|
|
180
|
+
'控制面板显示项': 'Control panel sections',
|
|
181
|
+
'只影响 /menu 发出来的那张卡片:关掉的项不显示,功能照旧(私聊与群聊分别设置)。': 'Only affects the card sent by /menu: hidden items are not drawn, everything keeps working (direct and group are configured separately).',
|
|
182
|
+
'模型与推理等级': 'Model & reasoning',
|
|
183
|
+
'会话': 'Session',
|
|
184
|
+
'Agent 预设与工作区': 'Agent preset & workspace',
|
|
185
|
+
'上下文增强(本会话)': 'Context enhancement (this chat)',
|
|
186
|
+
'访问策略(本会话)': 'Access policy (this chat)',
|
|
187
|
+
'渠道设置(任务过程展示等)': 'Channel settings (step display etc.)',
|
|
188
|
+
'渠道动作按钮(重连等)': 'Channel actions (reconnect etc.)',
|
|
189
|
+
'命令按钮(新会话/状态/诊断…)': 'Command buttons (new session/status/diagnostics…)',
|
|
190
|
+
'Chat机器人': 'Chat bot',
|
|
191
|
+
'模型': 'Model',
|
|
192
|
+
'默认模型': 'Default model',
|
|
193
|
+
'推理等级': 'Reasoning effort',
|
|
194
|
+
'这个模型没有可选的推理等级。': 'This model has no reasoning efforts to choose from.',
|
|
195
|
+
'先选一个模型。': 'Pick a model first.',
|
|
196
|
+
'模型默认': 'Model default',
|
|
197
|
+
'部分 provider 读取失败:': 'Some providers failed to load: ',
|
|
198
|
+
'当前 Host 读不到模型目录。': 'The model catalog is unavailable right now.',
|
|
199
|
+
'还没有会话时用哪个模型:选完对下一条消息新建的会话生效。会话内还能单独改(面板的模型下拉)。':
|
|
200
|
+
'Model used before a conversation exists; it applies to the session created by your next message. You can still change it per conversation from the panel.',
|
|
201
|
+
|
|
202
|
+
'Chat机器人设置': 'Chat bot settings',
|
|
203
|
+
'渠道导航': 'Channel navigation',
|
|
204
|
+
'未安装任何聊天软件插件': 'No chat channel plugin installed',
|
|
205
|
+
'安装渠道插件后,这里会出现对应的聊天软件。':
|
|
206
|
+
'Install a channel plugin and its chat service appears here.',
|
|
207
|
+
'已知渠道插件': 'Known channel plugins',
|
|
208
|
+
'渠道正在启动': 'Starting',
|
|
209
|
+
'渠道已就绪': 'Ready',
|
|
210
|
+
'渠道启动失败': 'Failed to start',
|
|
211
|
+
'渠道已停止': 'Stopped',
|
|
212
|
+
'重新读取': 'Reload',
|
|
213
|
+
'读取中…': 'Loading…',
|
|
214
|
+
'上下文增强': 'Context enhancement',
|
|
215
|
+
'任务过程展示': 'Task progress display',
|
|
216
|
+
'保存': 'Save',
|
|
217
|
+
'保存中…': 'Saving…',
|
|
218
|
+
'取消': 'Cancel',
|
|
219
|
+
'保存失败,请重试。': 'Could not save. Try again.',
|
|
220
|
+
// 主动投递
|
|
221
|
+
'主动投递': 'Proactive delivery',
|
|
222
|
+
'让定时任务或 agent 把结果直接发到指定会话。':
|
|
223
|
+
'Let a scheduled job or an agent push results straight into a conversation.',
|
|
224
|
+
'拖动可调整顺序': 'Drag to reorder',
|
|
225
|
+
'在渠道自己的配置里完成接入后,机器人会出现在这里。': 'Once the channel is configured, its bots show up here.',
|
|
226
|
+
'私聊': 'Direct',
|
|
227
|
+
'群聊': 'Group',
|
|
228
|
+
'候选': 'Candidate',
|
|
229
|
+
'保存为投递目标': 'Save as target',
|
|
230
|
+
'重命名': 'Rename',
|
|
231
|
+
'自定义名称': 'Custom name',
|
|
232
|
+
'留空则用自动识别的名字': 'Leave empty to use the detected name',
|
|
233
|
+
'已改名。': 'Renamed.',
|
|
234
|
+
'已恢复自动名字。': 'Restored the detected name.',
|
|
235
|
+
'删除': 'Delete',
|
|
236
|
+
'确认删除': 'Confirm delete',
|
|
237
|
+
'当前渠道不支持主动投递。': 'This channel does not support proactive delivery.',
|
|
238
|
+
'已保存,现在可以主动发消息了。': 'Saved. You can now send proactively.',
|
|
239
|
+
'已删除。': 'Deleted.',
|
|
240
|
+
'还没有可添加的会话:在群里 @ 一次机器人,或与它私聊一次,会话就会出现在这里,保存后即可主动投递。':
|
|
241
|
+
'No conversation to add yet: mention the bot once in a group, or send it a direct '
|
|
242
|
+
+ 'message — the conversation then shows up here and can be saved for proactive delivery.',
|
|
243
|
+
'没有可添加的会话:在群里 @ 一次机器人,或与它私聊一次,该会话就会出现在这里。':
|
|
244
|
+
'No conversation to add: mention the bot once in a group, or send it a direct message, and that conversation shows up here.',
|
|
245
|
+
'上面标「候选」的会话还不能主动投递,点「保存为投递目标」后才行。':
|
|
246
|
+
'A conversation marked "Candidate" cannot receive proactive messages yet — press "Save as target" first.',
|
|
247
|
+
'已保存': 'Saved',
|
|
248
|
+
'可添加的候选': 'Candidates',
|
|
249
|
+
'展开全部': 'Show all',
|
|
250
|
+
'收起': 'Collapse',
|
|
251
|
+
'按名字或 id 过滤': 'Filter by name or id',
|
|
252
|
+
'没有匹配的目标。': 'No target matches the filter.',
|
|
253
|
+
'填入示例': 'Fill with example',
|
|
254
|
+
'清空': 'Clear',
|
|
255
|
+
'来源字段': 'Source fields',
|
|
256
|
+
'启用': 'Enabled',
|
|
257
|
+
'备注名(可选)': 'Display name (optional)',
|
|
258
|
+
'张三': 'Alice',
|
|
259
|
+
'新增': 'Add',
|
|
260
|
+
'还没有指定设置。': 'Nothing configured yet.',
|
|
261
|
+
'关闭': 'Close',
|
|
262
|
+
'增强提示词': 'Prepended prompt',
|
|
263
|
+
'告诉模型如何使用来源字段。只填正文,插件会自动包成来源增强块。': 'Tell the model how to use the source fields. Write the body only — the plugin wraps it into a source block.',
|
|
264
|
+
'叠加全局提示词(不勾选则只使用上面的专属提示词)': 'Also stack the global prompt (unchecked: use only the prompt above)',
|
|
265
|
+
'上下文增强': 'Context enhancement',
|
|
266
|
+
'未开启': 'Off',
|
|
267
|
+
'来源字段只在当前消息已提供时才会发送,不会额外查询平台接口。': 'Source fields are sent only when the incoming message already carries them; no extra platform calls are made.',
|
|
268
|
+
'上下文增强范围': 'Context enhancement scope',
|
|
269
|
+
'已开启': 'On',
|
|
270
|
+
'从会话里选…': 'Pick a conversation…',
|
|
271
|
+
'属主': 'Owner',
|
|
272
|
+
'属主不需要进白名单:消息与命令都直接放行。这里改完会重连一次,立刻生效。': 'An owner does not need to be on the allowlist: their messages and commands always pass. Saving reconnects this bot once so the change takes effect immediately.',
|
|
273
|
+
'当前没有属主:没有人绕过访问策略,谁能用完全由下面的「访问策略」决定。': 'No owner right now: nobody bypasses the access policy, so who may use this bot is decided entirely by the access policy below.',
|
|
274
|
+
'从会话里选一个人设为属主': 'Pick a person from a conversation to make them the owner',
|
|
275
|
+
'设为属主': 'Make owner',
|
|
276
|
+
'没有可选的会话(先和机器人聊一次)': 'No conversation to pick yet (talk to the bot once first)',
|
|
277
|
+
'清空(无属主)': 'Clear (no owner)',
|
|
278
|
+
'清空后没有人绕过访问策略': 'After clearing, nobody bypasses the access policy',
|
|
279
|
+
// 机器人设置页的共享编辑块(bot-shared-settings.js)
|
|
280
|
+
'工作区': 'Workspace',
|
|
281
|
+
'机器人跑在哪个目录:能读写哪些文件、用哪份 AGENTS.md。只对新建会话生效。':
|
|
282
|
+
'Which directory the bot runs in: which files it may read and write, and which AGENTS.md'
|
|
283
|
+
+ 'applies. Applies to new conversations only.',
|
|
284
|
+
'目录': 'Directory',
|
|
285
|
+
'下拉里是这台机器人用过的目录。': 'The suggestions are directories this bot has used before.',
|
|
286
|
+
'Agent 预设': 'Agent preset',
|
|
287
|
+
'这个机器人用哪套 Agent 预设(人设与工具集)。只对新建会话生效。':
|
|
288
|
+
'Which agent preset this bot uses (persona and tool set). Applies to new conversations only.',
|
|
289
|
+
'当前 Host 读不到 Agent Preset 列表。': 'This Host does not expose an agent preset list.',
|
|
290
|
+
'跟随 Host 默认': 'Follow the Host default',
|
|
291
|
+
'访问策略': 'Access policy',
|
|
292
|
+
'谁能跟机器人说话、谁能执行命令。改动立即生效;属主始终可用。':
|
|
293
|
+
'Who may talk to the bot and who may run commands. Changes apply immediately;'
|
|
294
|
+
+ 'the owner always has access.',
|
|
295
|
+
'访问模式': 'Access mode',
|
|
296
|
+
'仅名单内可用': 'Allowlist only',
|
|
297
|
+
'任何人可用': 'Anyone',
|
|
298
|
+
'允许执行命令': 'Allow commands',
|
|
299
|
+
'可执行命令': 'Allow commands',
|
|
300
|
+
'移除': 'Remove',
|
|
301
|
+
'添加': 'Add',
|
|
302
|
+
'名单为空时只有属主可用。': 'An empty allowlist means only the owner can use it.',
|
|
303
|
+
'对方的平台 id,回车添加': 'Their platform id — press Enter to add',
|
|
304
|
+
'当前页面不支持渠道子槽。': 'This page does not support channel sub-slots.',
|
|
305
|
+
'设置': 'Settings',
|
|
306
|
+
'机器人': 'Bots',
|
|
307
|
+
'每个机器人一行;点「设置」进入它的设置页(上下文增强、主动投递、工作区…)。':
|
|
308
|
+
'One row per bot; click Settings to open its page (context enhancement, proactive delivery, workspace…).',
|
|
309
|
+
'这个渠道还没有机器人': 'No bot on this channel yet',
|
|
310
|
+
'在渠道设置页完成接入(飞书填应用凭据、微信扫码)后,机器人会出现在这里。':
|
|
311
|
+
'Finish onboarding on the channel page (Feishu app credentials, WeChat QR scan) and the bot shows up here.',
|
|
312
|
+
'未命名机器人': 'Unnamed bot',
|
|
313
|
+
'这台机器人没有可用的身份标识,无法单独配置':
|
|
314
|
+
'This bot has no usable identity, so it cannot be configured separately',
|
|
315
|
+
'渠道设置': 'Channel settings',
|
|
316
|
+
'打开渠道设置页': 'Open channel settings',
|
|
317
|
+
'← 机器人列表': '← Bot list',
|
|
318
|
+
'已处理': 'Handled',
|
|
319
|
+
'最近': 'Last',
|
|
320
|
+
'运行正常': 'Running',
|
|
321
|
+
'正在启动': 'Starting',
|
|
322
|
+
'重连中': 'Reconnecting',
|
|
323
|
+
'已停止': 'Stopped',
|
|
324
|
+
// 诊断面板(client/diagnostics.js)
|
|
325
|
+
'诊断': 'Diagnostics',
|
|
326
|
+
'收起诊断': 'Hide diagnostics',
|
|
327
|
+
'出故障时先看这里:连接状态、最近错误、日志尾部。':
|
|
328
|
+
'Start here when something breaks: connection state, latest errors, log tail.',
|
|
329
|
+
'运行中': 'Running',
|
|
330
|
+
'启动中': 'Starting',
|
|
331
|
+
'已连接': 'Connected',
|
|
332
|
+
'未连接': 'Disconnected',
|
|
333
|
+
'已处理': 'Handled',
|
|
334
|
+
'去开通权限': 'Grant the scope',
|
|
335
|
+
'最近一次错误': 'Last error',
|
|
336
|
+
'这台渠道下还没有机器人。': 'No bots on this channel yet.',
|
|
337
|
+
'还没有日志': 'No log yet',
|
|
338
|
+
'看最后 40 行': 'Show last 40 lines',
|
|
339
|
+
'收起': 'Hide',
|
|
340
|
+
'版本与更新': 'Version & updates',
|
|
341
|
+
'收起版本与更新': 'Hide version & updates',
|
|
342
|
+
'升级插件后需要重启 dsh;只改设置页代码则刷新页面即可。':
|
|
343
|
+
'Upgrading the plugin needs a dsh restart; settings-only changes just need a page refresh.',
|
|
344
|
+
'Chat机器人内核': 'Chat bot core',
|
|
345
|
+
'渠道契约版本': 'Channel contract version',
|
|
346
|
+
'数据目录': 'Data directory',
|
|
347
|
+
'日志目录': 'Log directory',
|
|
348
|
+
'读取失败': 'Failed to read',
|
|
349
|
+
'更新方式:在仓库里拉取新代码后重新打包,再让 DSH 重新加载插件。':
|
|
350
|
+
'To update: pull the repo, rebuild, then let DSH reload the plugin.',
|
|
351
|
+
'发一条测试消息': 'Send a test message',
|
|
352
|
+
'选择目标': 'Choose a target',
|
|
353
|
+
'发送': 'Send',
|
|
354
|
+
'发送中…': 'Sending…',
|
|
355
|
+
'测试消息内容': 'Test message',
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* 绑定一个命名空间的翻译函数。
|
|
360
|
+
*
|
|
361
|
+
* @param locale - DSH client 的 locale 服务。
|
|
362
|
+
* @returns (key) => string。
|
|
363
|
+
*/
|
|
364
|
+
export function bindTranslator(locale) {
|
|
365
|
+
const dictionary = { zh, en };
|
|
366
|
+
return (key) => {
|
|
367
|
+
const language = locale?.getSnapshot?.()?.locale ?? 'zh';
|
|
368
|
+
const table = dictionary[language] ?? zh;
|
|
369
|
+
return table[key] ?? zh[key] ?? key;
|
|
370
|
+
};
|
|
371
|
+
}
|
package/client/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-chat Hub:client 侧插件入口。
|
|
3
|
+
*
|
|
4
|
+
* 在设置页注册唯一的「Chat机器人」入口,向渠道插件发布两个服务:
|
|
5
|
+
* - `chatChannels`:渠道元数据注册表(驱动左栏);
|
|
6
|
+
* - `chatUi`:共享组件、样式、RPC 助手。
|
|
7
|
+
* 并在 section 内声明 `chat.channel.page` 子槽,由渠道包注册自己的页面。
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-chat/client/index
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
CHANNEL_PAGE_SLOT,
|
|
14
|
+
CLIENT_CHANNEL_SERVICE,
|
|
15
|
+
CLIENT_UI_SERVICE,
|
|
16
|
+
CONTRACT_VERSION,
|
|
17
|
+
SETTINGS_LABEL_KEY,
|
|
18
|
+
SETTINGS_SECTION_ID,
|
|
19
|
+
SETTINGS_SECTION_ORDER,
|
|
20
|
+
SETTINGS_SECTION_SLOT,
|
|
21
|
+
} from '../shared/contract.mjs';
|
|
22
|
+
import { createChannelRail } from '../shared/channel-rail.mjs';
|
|
23
|
+
import { createChatUi } from './chat-ui.js';
|
|
24
|
+
import { LOCALE_NAMESPACE, bindTranslator, en, zh } from './i18n.js';
|
|
25
|
+
import { installSessionBadges } from './session-badges.js';
|
|
26
|
+
import { ChatSettingsSection } from './section.js';
|
|
27
|
+
import { installChatStyles } from './styles.js';
|
|
28
|
+
|
|
29
|
+
export const name = 'dsh-chat-client';
|
|
30
|
+
|
|
31
|
+
/** client 侧需要的浏览器服务。 */
|
|
32
|
+
export const inject = ['slots', 'connection', 'locale'];
|
|
33
|
+
|
|
34
|
+
function provideService(ctx, serviceName, value) {
|
|
35
|
+
if (typeof ctx?.reflect?.provide === 'function') return ctx.reflect.provide(serviceName, value);
|
|
36
|
+
if (typeof ctx?.provide === 'function') return ctx.provide(serviceName, value);
|
|
37
|
+
throw new TypeError(`dsh-chat 需要 Cordis 的 provide 能力来发布 ${serviceName} 服务。`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cordis client 插件入口。
|
|
42
|
+
*
|
|
43
|
+
* @param ctx - client 上下文。
|
|
44
|
+
*/
|
|
45
|
+
export function apply(ctx) {
|
|
46
|
+
ctx.effect(() => ctx.locale.register(LOCALE_NAMESPACE, { zh, en }),
|
|
47
|
+
'dsh-chat: 双语文案');
|
|
48
|
+
const t = typeof ctx.locale?.bind === 'function'
|
|
49
|
+
? ctx.locale.bind(LOCALE_NAMESPACE)
|
|
50
|
+
: bindTranslator(ctx.locale);
|
|
51
|
+
|
|
52
|
+
const channels = createChannelRail();
|
|
53
|
+
const chatUi = createChatUi({ ctx, translate: t });
|
|
54
|
+
|
|
55
|
+
ctx.effect(() => provideService(ctx, CLIENT_CHANNEL_SERVICE, channels),
|
|
56
|
+
'dsh-chat: 渠道 rail 服务');
|
|
57
|
+
ctx.effect(() => provideService(ctx, CLIENT_UI_SERVICE, chatUi),
|
|
58
|
+
'dsh-chat: 共享 UI 套件');
|
|
59
|
+
ctx.effect(() => installChatStyles(), 'dsh-chat: 共享样式');
|
|
60
|
+
// 侧边栏会话行的渠道徽标:会话列表没有插槽,只能做纯装饰的 DOM 增强
|
|
61
|
+
// (保留「飞书 · 」文字前缀作为匹配依据与降级形态,卸载时全部还原)。
|
|
62
|
+
ctx.effect(() => installSessionBadges({ channels }), 'dsh-chat: 会话渠道徽标');
|
|
63
|
+
|
|
64
|
+
ctx.slots.inject(SETTINGS_SECTION_SLOT, () => ctx.slots.register({
|
|
65
|
+
name: SETTINGS_SECTION_SLOT,
|
|
66
|
+
id: SETTINGS_SECTION_ID,
|
|
67
|
+
order: SETTINGS_SECTION_ORDER,
|
|
68
|
+
label: () => t(SETTINGS_LABEL_KEY),
|
|
69
|
+
locale: LOCALE_NAMESPACE,
|
|
70
|
+
inject: () => ({
|
|
71
|
+
channels, chatUi, translate: t, contractVersion: CONTRACT_VERSION, connection: ctx.connection,
|
|
72
|
+
}),
|
|
73
|
+
children: {
|
|
74
|
+
[CHANNEL_PAGE_SLOT]: { kind: 'keyed', scope: 'root' },
|
|
75
|
+
},
|
|
76
|
+
}, ChatSettingsSection));
|
|
77
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 界面列表顺序(左栏聊天软件、机器人列表):纯显示偏好,存**浏览器** localStorage。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不放 hub 的数据目录:它只影响"这台浏览器上怎么排",不影响机器人的任何行为;
|
|
5
|
+
* 放浏览器里改完刷新即生效(不必重启 dsh),也不必为它加一套 RPC 与磁盘文档。
|
|
6
|
+
* 代价写在明处:换浏览器/换设备要重排一次;清掉站点数据会回到默认顺序。
|
|
7
|
+
*
|
|
8
|
+
* 未知键(新装的渠道、新加的机器人)排在**已排序的那些之后**,并保持它们之间的原有顺序
|
|
9
|
+
* ——默认顺序仍然由渠道注册的 `order` 与机器人的自然顺序决定。
|
|
10
|
+
*
|
|
11
|
+
* @module dsh-chat/client/list-order
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import * as React from 'react';
|
|
15
|
+
|
|
16
|
+
const PREFIX = 'dsh-chat:order:';
|
|
17
|
+
|
|
18
|
+
/** 渠道左栏的顺序键。 */
|
|
19
|
+
export const CHANNEL_ORDER_KEY = `${PREFIX}channels`;
|
|
20
|
+
|
|
21
|
+
/** 某个渠道下机器人列表的顺序键(每渠道一份)。 */
|
|
22
|
+
export function botOrderKey(channelId) {
|
|
23
|
+
return `${PREFIX}bots:${channelId}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 安全拿一次 localStorage:无(Node/SSR)或抛异常(隐私模式)都当没有。 */
|
|
27
|
+
function storageOf(storage) {
|
|
28
|
+
if (storage) return storage;
|
|
29
|
+
try {
|
|
30
|
+
return globalThis.localStorage ?? null;
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 读一份顺序。
|
|
38
|
+
*
|
|
39
|
+
* @param key - localStorage 键。
|
|
40
|
+
* @param storage - 可注入的 storage(测试用)。
|
|
41
|
+
* @returns 键数组;读不到/坏数据一律空数组(= 用默认顺序)。
|
|
42
|
+
*/
|
|
43
|
+
export function readOrder(key, storage = null) {
|
|
44
|
+
const store = storageOf(storage);
|
|
45
|
+
if (!store || typeof store.getItem !== 'function') return [];
|
|
46
|
+
try {
|
|
47
|
+
const raw = store.getItem(key);
|
|
48
|
+
if (!raw) return [];
|
|
49
|
+
const parsed = JSON.parse(raw);
|
|
50
|
+
return Array.isArray(parsed) ? parsed.filter((item) => typeof item === 'string' && item) : [];
|
|
51
|
+
} catch {
|
|
52
|
+
// 坏数据不能让列表渲染不出来:当作"没排过"。
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 写一份顺序(写失败就算了:这只影响显示顺序,不该弹错误)。
|
|
59
|
+
*
|
|
60
|
+
* @param key - localStorage 键。
|
|
61
|
+
* @param keys - 完整顺序。
|
|
62
|
+
* @param storage - 可注入的 storage(测试用)。
|
|
63
|
+
* @returns 是否写成功。
|
|
64
|
+
*/
|
|
65
|
+
export function writeOrder(key, keys, storage = null) {
|
|
66
|
+
const store = storageOf(storage);
|
|
67
|
+
if (!store || typeof store.setItem !== 'function') return false;
|
|
68
|
+
try {
|
|
69
|
+
store.setItem(key, JSON.stringify([...keys]));
|
|
70
|
+
return true;
|
|
71
|
+
} catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 按已保存的顺序排列一批条目。
|
|
78
|
+
*
|
|
79
|
+
* 稳定排序:没记录过的条目保持各自原有相对顺序,排在已记录的那些之后。
|
|
80
|
+
*
|
|
81
|
+
* @param items - 待排列的条目。
|
|
82
|
+
* @param order - 已保存的键顺序。
|
|
83
|
+
* @param keyOf - 取键的函数。
|
|
84
|
+
* @returns 新数组(不修改入参)。
|
|
85
|
+
*/
|
|
86
|
+
export function orderedItems(items, order, keyOf) {
|
|
87
|
+
const list = Array.isArray(items) ? items : [];
|
|
88
|
+
if (!Array.isArray(order) || order.length === 0) return [...list];
|
|
89
|
+
const rank = new Map();
|
|
90
|
+
order.forEach((key, index) => rank.set(key, index));
|
|
91
|
+
return [...list].sort((left, right) => {
|
|
92
|
+
const a = rank.has(keyOf(left)) ? rank.get(keyOf(left)) : Number.MAX_SAFE_INTEGER;
|
|
93
|
+
const b = rank.has(keyOf(right)) ? rank.get(keyOf(right)) : Number.MAX_SAFE_INTEGER;
|
|
94
|
+
return a - b;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 把 `fromKey` 挪到 `toKey` 原来的位置。
|
|
100
|
+
*
|
|
101
|
+
* @param keys - 当前顺序(应包含这两个键)。
|
|
102
|
+
* @param fromKey - 被拖动的键。
|
|
103
|
+
* @param toKey - 落点键。
|
|
104
|
+
* @returns 新顺序;参数不合法时原样返回(不产生"半截顺序")。
|
|
105
|
+
*/
|
|
106
|
+
export function moveKey(keys, fromKey, toKey) {
|
|
107
|
+
const list = Array.isArray(keys) ? [...keys] : [];
|
|
108
|
+
const from = list.indexOf(fromKey);
|
|
109
|
+
const to = list.indexOf(toKey);
|
|
110
|
+
if (from < 0 || to < 0 || from === to) return list;
|
|
111
|
+
list.splice(from, 1);
|
|
112
|
+
list.splice(to, 0, fromKey);
|
|
113
|
+
return list;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* 某个列表的排序状态(读一次、拖动时写回)。
|
|
118
|
+
*
|
|
119
|
+
* @param key - localStorage 键(用 `CHANNEL_ORDER_KEY` / `botOrderKey(id)`)。
|
|
120
|
+
* @param keyOf - 取键的函数(用于把当前渲染顺序换算成"完整键序列")。
|
|
121
|
+
* @returns `{ order, ordered, move, reset }`。
|
|
122
|
+
*/
|
|
123
|
+
export function useListOrder(key, keyOf) {
|
|
124
|
+
const [order, setOrder] = React.useState(() => readOrder(key));
|
|
125
|
+
// 换渠道(机器人列表的键变了)要重新读一份,否则会把上一个渠道的顺序套上来。
|
|
126
|
+
React.useEffect(() => {
|
|
127
|
+
setOrder(readOrder(key));
|
|
128
|
+
}, [key]);
|
|
129
|
+
|
|
130
|
+
const move = React.useCallback((items, fromKey, toKey) => {
|
|
131
|
+
const current = (Array.isArray(items) ? items : []).map(keyOf);
|
|
132
|
+
const next = moveKey(current, fromKey, toKey);
|
|
133
|
+
setOrder(next);
|
|
134
|
+
writeOrder(key, next);
|
|
135
|
+
return next;
|
|
136
|
+
}, [key, keyOf]);
|
|
137
|
+
|
|
138
|
+
const reset = React.useCallback(() => {
|
|
139
|
+
setOrder([]);
|
|
140
|
+
writeOrder(key, []);
|
|
141
|
+
}, [key]);
|
|
142
|
+
|
|
143
|
+
return { order, move, reset };
|
|
144
|
+
}
|
package/client/rpc.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 浏览器侧 RPC 调用:所有渠道共用同一条 `/api/dsh-chat/<channelId>` 通道,
|
|
3
|
+
* 控制端点固定为 `control`。与 host 的 rpc.mjs 共享同一份线路格式。
|
|
4
|
+
*
|
|
5
|
+
* @module dsh-chat/client/rpc
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { CONTROL_CHANNEL_ID, RPC_PREFIX } from '../shared/contract.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param channelId - 渠道 id(控制端点用 CONTROL_CHANNEL_ID)。
|
|
12
|
+
* @returns 端点名,如 `dsh-chat/feishu`。
|
|
13
|
+
*/
|
|
14
|
+
export function chatEndpoint(channelId) {
|
|
15
|
+
return `${RPC_PREFIX}/${channelId}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 调用一次聊天插件 RPC。
|
|
20
|
+
*
|
|
21
|
+
* @param connection - DSH client 的 connection 服务。
|
|
22
|
+
* @param channelId - 渠道 id 或 'control'。
|
|
23
|
+
* @param method - 方法名。
|
|
24
|
+
* @param payload - 载荷。
|
|
25
|
+
* @param signal - AbortSignal。
|
|
26
|
+
* @returns RPC 结果对象 `{ ok, value }` 或 `{ ok:false, error }`。
|
|
27
|
+
*/
|
|
28
|
+
export function callChatRpc(connection, channelId, method, payload = {}, signal) {
|
|
29
|
+
if (typeof connection?.rpc?.call !== 'function') {
|
|
30
|
+
throw new TypeError('当前页面缺少 DSH Connection RPC 能力。');
|
|
31
|
+
}
|
|
32
|
+
return connection.rpc.call('/api', chatEndpoint(channelId), { method, payload }, signal);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 便捷封装:只调用 hub 控制端点。 */
|
|
36
|
+
export function callControlRpc(connection, method, payload, signal) {
|
|
37
|
+
return callChatRpc(connection, CONTROL_CHANNEL_ID, method, payload, signal);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 把 `{ ok:false, error }` 结果转成异常,便于在 UI 的 try/catch 里统一处理。
|
|
42
|
+
*
|
|
43
|
+
* @param result - RPC 结果。
|
|
44
|
+
* @returns 成功时的 value。
|
|
45
|
+
*/
|
|
46
|
+
export function unwrapRpc(result) {
|
|
47
|
+
if (result?.ok === true) return result.value;
|
|
48
|
+
const error = new Error(result?.error?.message ?? '聊天插件调用失败。');
|
|
49
|
+
error.code = result?.error?.code ?? 'chat/rpc-failed';
|
|
50
|
+
error.details = result?.error?.details ?? {};
|
|
51
|
+
throw error;
|
|
52
|
+
}
|