@xmanrui/dsh-im 3.1.0 → 3.2.0
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.en.md +15 -0
- package/README.md +15 -0
- package/lib/client.js +1653 -333
- package/lib/index.js +235 -223
- package/package.json +5 -1
- package/plugin-src/client/channels/dingtalk/api.js +3 -0
- package/plugin-src/client/channels/dingtalk/index.js +18 -5
- package/plugin-src/client/channels/feishu/api.js +3 -0
- package/plugin-src/client/channels/feishu/index.js +18 -5
- package/plugin-src/client/channels/qq/api.js +3 -0
- package/plugin-src/client/channels/qq/index.js +13 -0
- package/plugin-src/client/channels/shared/token-api.js +3 -0
- package/plugin-src/client/channels/shared/token-channel.js +13 -1
- package/plugin-src/client/channels/wecom/api.js +3 -0
- package/plugin-src/client/channels/wecom/index.js +13 -0
- package/plugin-src/client/channels/weixin/api.js +3 -0
- package/plugin-src/client/channels/weixin/index.js +19 -5
- package/plugin-src/client/channels/whatsapp/api.js +3 -0
- package/plugin-src/client/channels/whatsapp/index.js +13 -0
- package/plugin-src/client/context-enhancement.js +275 -0
- package/plugin-src/client/i18n.js +54 -3
- package/plugin-src/client/styles.js +78 -0
- package/plugin-src/client/update-panel.js +108 -7
- package/plugin-src/host/channels/dingtalk/production.mjs +1 -0
- package/plugin-src/host/channels/dingtalk/rpc.mjs +11 -0
- package/plugin-src/host/channels/feishu/production.mjs +3 -0
- package/plugin-src/host/channels/feishu/rpc.mjs +13 -0
- package/plugin-src/host/channels/qq/production.mjs +1 -0
- package/plugin-src/host/channels/qq/rpc.mjs +11 -0
- package/plugin-src/host/channels/shared/context-enhancement-rpc.mjs +17 -0
- package/plugin-src/host/channels/shared/production.mjs +1 -0
- package/plugin-src/host/channels/shared/rpc.mjs +9 -0
- package/plugin-src/host/channels/shared/workspace-rpc.mjs +1 -0
- package/plugin-src/host/channels/slack/production.mjs +1 -0
- package/plugin-src/host/channels/slack/rpc.mjs +10 -0
- package/plugin-src/host/channels/wecom/production.mjs +1 -0
- package/plugin-src/host/channels/wecom/rpc.mjs +11 -0
- package/plugin-src/host/channels/weixin/production.mjs +1 -0
- package/plugin-src/host/channels/weixin/rpc.mjs +11 -0
- package/plugin-src/host/channels/whatsapp/production.mjs +1 -0
- package/plugin-src/host/channels/whatsapp/rpc.mjs +11 -0
- package/plugin-src/host/update-runtime.mjs +3 -2
- package/plugin-src/host/update-service.mjs +2 -0
- package/scripts/verify-package.mjs +15 -2
- package/src/channels/dingtalk/dingtalk-api.mjs +39 -16
- package/src/channels/dingtalk/dingtalk-bridge.mjs +52 -23
- package/src/channels/dingtalk/dingtalk-runtime.mjs +20 -1
- package/src/channels/discord/discord-runtime.mjs +18 -4
- package/src/channels/feishu/bridge.mjs +20 -4
- package/src/channels/feishu/feishu-channel.mjs +100 -44
- package/src/channels/feishu/feishu-runtime.mjs +4 -0
- package/src/channels/qq/qq-bridge.mjs +20 -4
- package/src/channels/qq/qq-runtime.mjs +4 -0
- package/src/channels/shared/bot-workspace-store.mjs +103 -6
- package/src/channels/shared/context-enhancement.mjs +135 -0
- package/src/channels/shared/i18n-en/feishu.mjs +2 -0
- package/src/channels/shared/text-harness-bridge.mjs +19 -4
- package/src/channels/slack/slack-runtime.mjs +4 -0
- package/src/channels/telegram/telegram-runtime.mjs +24 -2
- package/src/channels/wecom/wecom-bridge.mjs +18 -3
- package/src/channels/wecom/wecom-runtime.mjs +4 -0
- package/src/channels/weixin/weixin-bridge.mjs +19 -4
- package/src/channels/weixin/weixin-runtime.mjs +4 -0
- package/src/channels/whatsapp/whatsapp-runtime.mjs +5 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CONTEXT_ENHANCEMENT_FIELDS,
|
|
6
|
+
CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH,
|
|
7
|
+
CONTEXT_GUIDANCE_EXAMPLE,
|
|
8
|
+
normalizeContextEnhancementConfig,
|
|
9
|
+
validateContextEnhancementConfig,
|
|
10
|
+
} from '../../src/channels/shared/context-enhancement.mjs';
|
|
11
|
+
import { h, localizeText } from './i18n.js';
|
|
12
|
+
|
|
13
|
+
const FIELD_LABELS = Object.freeze({
|
|
14
|
+
channel: '渠道',
|
|
15
|
+
conversationType: '会话类型',
|
|
16
|
+
senderId: '发送者标识',
|
|
17
|
+
senderName: '发送者昵称',
|
|
18
|
+
botId: '机器人标识',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export function contextEnhancementLabel(config) {
|
|
22
|
+
const { groupEnabled, directEnabled } = normalizeContextEnhancementConfig(config);
|
|
23
|
+
if (groupEnabled && directEnabled) return '群聊和私聊';
|
|
24
|
+
if (groupEnabled) return '仅群聊';
|
|
25
|
+
if (directEnabled) return '仅私聊';
|
|
26
|
+
return '未开启';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ContextIcon({ kind = 'sliders' }) {
|
|
30
|
+
const path = kind === 'close' ? 'M6 6l12 12M6 18 18 6'
|
|
31
|
+
: kind === 'chevron' ? 'm9 5 7 7-7 7'
|
|
32
|
+
: 'M21 4h-7M10 4H3M21 12h-9M8 12H3M21 20h-3M14 20H3M14 2v4M8 10v4M18 18v4';
|
|
33
|
+
return h('svg', {
|
|
34
|
+
width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none',
|
|
35
|
+
stroke: 'currentColor', strokeWidth: 1.8, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
36
|
+
'aria-hidden': 'true', focusable: 'false',
|
|
37
|
+
}, h('path', { d: path }));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ContextEnhancementDialog({ config, groupSupported, disabled, onSave, onClose, returnFocusRef, id }) {
|
|
41
|
+
// A mounted dialog owns its draft; status refreshes must not replace unsaved edits.
|
|
42
|
+
const [draft, setDraft] = React.useState(() => ({
|
|
43
|
+
...normalizeContextEnhancementConfig(config),
|
|
44
|
+
...(!groupSupported ? { groupEnabled: false } : {}),
|
|
45
|
+
}));
|
|
46
|
+
const [saving, setSaving] = React.useState(false);
|
|
47
|
+
const [error, setError] = React.useState(null);
|
|
48
|
+
const savingRef = React.useRef(false);
|
|
49
|
+
const dialogRef = React.useRef(null);
|
|
50
|
+
const mountedRef = React.useRef(true);
|
|
51
|
+
const titleId = React.useId();
|
|
52
|
+
const descriptionId = React.useId();
|
|
53
|
+
const groupNoticeId = React.useId();
|
|
54
|
+
const guidanceId = React.useId();
|
|
55
|
+
const guidanceHelpId = React.useId();
|
|
56
|
+
const fieldIdPrefix = React.useId();
|
|
57
|
+
const fieldsHelpId = React.useId();
|
|
58
|
+
const senderNameHelpId = React.useId();
|
|
59
|
+
const guidanceExample = localizeText(CONTEXT_GUIDANCE_EXAMPLE);
|
|
60
|
+
const busy = disabled || saving;
|
|
61
|
+
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
mountedRef.current = true;
|
|
64
|
+
dialogRef.current?.focus?.();
|
|
65
|
+
const keepFocus = (event) => {
|
|
66
|
+
if (dialogRef.current && !dialogRef.current.contains(event.target)) dialogRef.current.focus();
|
|
67
|
+
};
|
|
68
|
+
globalThis.document?.addEventListener?.('focusin', keepFocus);
|
|
69
|
+
return () => {
|
|
70
|
+
mountedRef.current = false;
|
|
71
|
+
globalThis.document?.removeEventListener?.('focusin', keepFocus);
|
|
72
|
+
queueMicrotask(() => returnFocusRef.current?.focus?.());
|
|
73
|
+
};
|
|
74
|
+
}, [returnFocusRef]);
|
|
75
|
+
|
|
76
|
+
const change = (key, value) => {
|
|
77
|
+
if (busy || savingRef.current) return;
|
|
78
|
+
setDraft((current) => ({ ...current, [key]: value }));
|
|
79
|
+
setError(null);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const cancel = () => {
|
|
83
|
+
if (!savingRef.current) onClose();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const save = async () => {
|
|
87
|
+
if (busy || savingRef.current) return;
|
|
88
|
+
savingRef.current = true;
|
|
89
|
+
setSaving(true);
|
|
90
|
+
setError(null);
|
|
91
|
+
dialogRef.current?.focus?.();
|
|
92
|
+
try {
|
|
93
|
+
const next = validateContextEnhancementConfig(draft);
|
|
94
|
+
await onSave(next);
|
|
95
|
+
if (mountedRef.current) onClose();
|
|
96
|
+
} catch (cause) {
|
|
97
|
+
if (mountedRef.current) setError(cause?.message ?? '上下文增强保存失败,请重试。');
|
|
98
|
+
} finally {
|
|
99
|
+
savingRef.current = false;
|
|
100
|
+
if (mountedRef.current) setSaving(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const content = h('div', {
|
|
105
|
+
className: 'dim-contextBackdrop',
|
|
106
|
+
onMouseDown: (event) => { if (event.target === event.currentTarget) cancel(); },
|
|
107
|
+
}, h('section', {
|
|
108
|
+
id,
|
|
109
|
+
ref: dialogRef,
|
|
110
|
+
className: 'dim-contextDialog',
|
|
111
|
+
role: 'dialog',
|
|
112
|
+
'aria-modal': 'true',
|
|
113
|
+
'aria-labelledby': titleId,
|
|
114
|
+
'aria-describedby': descriptionId,
|
|
115
|
+
'aria-busy': saving,
|
|
116
|
+
tabIndex: -1,
|
|
117
|
+
onKeyDown: (event) => {
|
|
118
|
+
if (event.key === 'Escape') {
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
event.stopPropagation();
|
|
121
|
+
cancel();
|
|
122
|
+
}
|
|
123
|
+
if (event.key !== 'Tab') return;
|
|
124
|
+
const controls = dialogRef.current?.querySelectorAll?.(
|
|
125
|
+
'button:not(:disabled), input:not(:disabled), textarea:not(:disabled)',
|
|
126
|
+
);
|
|
127
|
+
if (!controls?.length) {
|
|
128
|
+
event.preventDefault();
|
|
129
|
+
dialogRef.current?.focus?.();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const first = controls[0];
|
|
133
|
+
const last = controls[controls.length - 1];
|
|
134
|
+
const active = globalThis.document?.activeElement;
|
|
135
|
+
if (event.shiftKey && (active === first || active === dialogRef.current)) {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
last.focus();
|
|
138
|
+
} else if (!event.shiftKey && (active === last || active === dialogRef.current)) {
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
first.focus();
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
h('header', { className: 'dim-contextHeader' },
|
|
145
|
+
h('div', { className: 'dim-contextHeaderTitle' },
|
|
146
|
+
h('h3', { id: titleId }, '上下文增强'),
|
|
147
|
+
h('span', { className: 'dim-contextHelp dim-contextHeaderHelp' },
|
|
148
|
+
h('button', {
|
|
149
|
+
type: 'button', className: 'dim-contextHelpButton', disabled: busy,
|
|
150
|
+
'aria-label': '查看上下文增强说明', 'aria-describedby': descriptionId,
|
|
151
|
+
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
|
152
|
+
h('span', { id: descriptionId, className: 'dim-contextTooltip dim-contextHeaderTooltip', role: 'tooltip' },
|
|
153
|
+
'选择在哪些会话中启用、提供哪些来源字段,以及如何使用这些信息。仅使用已有消息元数据,不查询平台 API。'))),
|
|
154
|
+
h('button', {
|
|
155
|
+
type: 'button', className: 'dim-contextClose', 'aria-label': '关闭弹窗',
|
|
156
|
+
disabled: saving, onClick: cancel,
|
|
157
|
+
}, h(ContextIcon, { kind: 'close' }))),
|
|
158
|
+
h('fieldset', { className: 'dim-contextSection', disabled: busy },
|
|
159
|
+
h('legend', null, '启用范围'),
|
|
160
|
+
h('div', { className: 'dim-contextSwitches' },
|
|
161
|
+
h('label', { className: 'dim-contextSwitchRow' },
|
|
162
|
+
h('span', { className: 'dim-contextSwitchLabel' },
|
|
163
|
+
h('span', null, '群聊中启用'),
|
|
164
|
+
!groupSupported ? h('span', {
|
|
165
|
+
id: groupNoticeId, className: 'dim-contextUnavailable',
|
|
166
|
+
}, '(当前渠道不支持群聊)') : null),
|
|
167
|
+
h('input', {
|
|
168
|
+
type: 'checkbox', role: 'switch', className: 'dim-contextSwitch',
|
|
169
|
+
checked: groupSupported && draft.groupEnabled,
|
|
170
|
+
disabled: busy || !groupSupported,
|
|
171
|
+
'aria-describedby': !groupSupported ? groupNoticeId : undefined,
|
|
172
|
+
onChange: (event) => { if (groupSupported) change('groupEnabled', event.target.checked); },
|
|
173
|
+
})),
|
|
174
|
+
h('label', { className: 'dim-contextSwitchRow' },
|
|
175
|
+
h('span', null, '私聊中启用'),
|
|
176
|
+
h('input', {
|
|
177
|
+
type: 'checkbox', role: 'switch', className: 'dim-contextSwitch',
|
|
178
|
+
checked: draft.directEnabled, disabled: busy,
|
|
179
|
+
onChange: (event) => change('directEnabled', event.target.checked),
|
|
180
|
+
}))),
|
|
181
|
+
),
|
|
182
|
+
h('fieldset', { className: 'dim-contextSection', disabled: busy },
|
|
183
|
+
h('legend', { className: 'dim-contextLegend' },
|
|
184
|
+
h('span', null, '来源字段'),
|
|
185
|
+
h('span', { className: 'dim-contextHelp dim-contextLegendHelp' },
|
|
186
|
+
h('button', {
|
|
187
|
+
type: 'button', className: 'dim-contextHelpButton', disabled: busy,
|
|
188
|
+
'aria-label': '查看来源字段说明', 'aria-describedby': fieldsHelpId,
|
|
189
|
+
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
|
190
|
+
h('span', { id: fieldsHelpId, className: 'dim-contextTooltip dim-contextLegendTooltip', role: 'tooltip' },
|
|
191
|
+
'增强提示词中请使用字段名(如 senderId、conversationType)引用这些信息。只发送勾选且当前消息中可用的字段,不会额外查询或补全。'))),
|
|
192
|
+
h('div', { className: 'dim-contextFields' }, CONTEXT_ENHANCEMENT_FIELDS.map((field) => {
|
|
193
|
+
const fieldId = `${fieldIdPrefix}-${field}`;
|
|
194
|
+
return h('div', { key: field, className: 'dim-contextField' },
|
|
195
|
+
h('input', {
|
|
196
|
+
id: fieldId, type: 'checkbox', name: field,
|
|
197
|
+
checked: draft.fields.includes(field), disabled: busy,
|
|
198
|
+
onChange: (event) => change('fields', event.target.checked
|
|
199
|
+
? [...draft.fields, field] : draft.fields.filter((value) => value !== field)),
|
|
200
|
+
}),
|
|
201
|
+
h('span', { className: 'dim-contextFieldText' },
|
|
202
|
+
h('label', { className: 'dim-contextFieldName', htmlFor: fieldId }, FIELD_LABELS[field]),
|
|
203
|
+
field === 'senderName' ? h('span', { className: 'dim-contextHelp dim-contextFieldHelp' },
|
|
204
|
+
h('button', {
|
|
205
|
+
type: 'button', className: 'dim-contextHelpButton dim-contextFieldHelpButton', disabled: busy,
|
|
206
|
+
'aria-label': '查看发送者昵称字段说明', 'aria-describedby': senderNameHelpId,
|
|
207
|
+
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
|
208
|
+
h('span', {
|
|
209
|
+
id: senderNameHelpId,
|
|
210
|
+
className: 'dim-contextTooltip dim-contextFieldTooltip',
|
|
211
|
+
role: 'tooltip',
|
|
212
|
+
}, '该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。')) : null,
|
|
213
|
+
h('label', { className: 'dim-contextFieldKey', htmlFor: fieldId }, field)));
|
|
214
|
+
}))),
|
|
215
|
+
h('div', { className: 'dim-contextGuidance' },
|
|
216
|
+
h('div', { className: 'dim-contextEditorHeader' },
|
|
217
|
+
h('span', { className: 'dim-contextEditorTitle' },
|
|
218
|
+
h('label', { htmlFor: guidanceId }, '增强提示词'),
|
|
219
|
+
h('span', { className: 'dim-contextHelp' },
|
|
220
|
+
h('button', {
|
|
221
|
+
type: 'button', className: 'dim-contextHelpButton', disabled: busy,
|
|
222
|
+
'aria-label': '查看增强提示词使用说明', 'aria-describedby': guidanceHelpId,
|
|
223
|
+
}, h('span', { 'aria-hidden': 'true' }, '?')),
|
|
224
|
+
h('span', { id: guidanceHelpId, className: 'dim-contextTooltip dim-contextGuidanceTooltip', role: 'tooltip' },
|
|
225
|
+
h('strong', null, '使用说明'),
|
|
226
|
+
h('span', null, '用于告诉模型如何使用 <dsh_im_source> 中已选择的来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。'),
|
|
227
|
+
h('strong', null, '生效规则'),
|
|
228
|
+
h('span', null, '只需填写正文,插件自动添加 <dsh_im_source_guidance> 成对标签。清空并保存后,不再附加增强提示词,已选来源字段仍按开关设置发送。'),
|
|
229
|
+
h('strong', null, '隐私提示'),
|
|
230
|
+
h('span', null, '发送者标识可能包含平台用户 ID 或电话号码形式的标识。关闭开关不会删除已经写入会话历史的信息。'),
|
|
231
|
+
h('strong', null, '使用示例'),
|
|
232
|
+
h('span', { className: 'dim-contextTooltipExample' }, guidanceExample)))),
|
|
233
|
+
h('div', { className: 'dim-contextTextActions' },
|
|
234
|
+
h('button', { type: 'button', disabled: busy, onClick: () => change('guidance', guidanceExample) }, '填入示例'),
|
|
235
|
+
h('button', { type: 'button', disabled: busy, onClick: () => change('guidance', '') }, '清空'))),
|
|
236
|
+
h('textarea', {
|
|
237
|
+
id: guidanceId, value: draft.guidance, placeholder: guidanceExample, rows: 4, disabled: busy,
|
|
238
|
+
maxLength: CONTEXT_ENHANCEMENT_GUIDANCE_MAX_LENGTH,
|
|
239
|
+
'aria-describedby': guidanceHelpId,
|
|
240
|
+
onChange: (event) => change('guidance', event.target.value),
|
|
241
|
+
})),
|
|
242
|
+
error ? h('p', { className: 'dim-contextError', role: 'alert' }, error) : null,
|
|
243
|
+
h('footer', { className: 'dim-contextFooter' },
|
|
244
|
+
h('button', { type: 'button', disabled: saving, onClick: cancel }, '取消'),
|
|
245
|
+
h('button', {
|
|
246
|
+
type: 'button', className: 'dim-contextSave', disabled: busy,
|
|
247
|
+
onClick: () => { void save(); },
|
|
248
|
+
}, saving ? '保存中…' : '保存'))));
|
|
249
|
+
|
|
250
|
+
return globalThis.document?.body ? createPortal(content, document.body) : content;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function ContextEnhancementEditor({ config, groupSupported = true, disabled = false, onSave }) {
|
|
254
|
+
const [open, setOpen] = React.useState(false);
|
|
255
|
+
const entryRef = React.useRef(null);
|
|
256
|
+
const dialogId = React.useId();
|
|
257
|
+
const statusId = React.useId();
|
|
258
|
+
const saved = normalizeContextEnhancementConfig(config);
|
|
259
|
+
const label = contextEnhancementLabel(groupSupported ? saved : { ...saved, groupEnabled: false });
|
|
260
|
+
|
|
261
|
+
return h(React.Fragment, null,
|
|
262
|
+
h('button', {
|
|
263
|
+
type: 'button', ref: entryRef, className: 'dim-contextEntry', disabled,
|
|
264
|
+
'aria-label': '上下文增强', 'aria-describedby': statusId,
|
|
265
|
+
'aria-haspopup': 'dialog', 'aria-expanded': open, 'aria-controls': open ? dialogId : undefined,
|
|
266
|
+
onClick: () => setOpen(true),
|
|
267
|
+
}, h(ContextIcon),
|
|
268
|
+
h('span', { className: 'dim-contextLabel' }, '上下文增强'),
|
|
269
|
+
h('span', { id: statusId, className: 'dim-contextStatus', 'data-active': label !== '未开启', 'aria-live': 'polite' }, label),
|
|
270
|
+
h(ContextIcon, { kind: 'chevron' })),
|
|
271
|
+
open ? h(ContextEnhancementDialog, {
|
|
272
|
+
id: dialogId, config, groupSupported, disabled, onSave,
|
|
273
|
+
onClose: () => setOpen(false), returnFocusRef: entryRef,
|
|
274
|
+
}) : null);
|
|
275
|
+
}
|
|
@@ -13,17 +13,28 @@ const EN = Object.freeze({
|
|
|
13
13
|
'检查更新': 'Check for updates',
|
|
14
14
|
'重新检查': 'Check again',
|
|
15
15
|
'刷新状态': 'Refresh status',
|
|
16
|
+
'手工更新': 'Manual update',
|
|
17
|
+
'手工更新命令': 'Manual update command',
|
|
18
|
+
'复制命令': 'Copy command',
|
|
19
|
+
'复制中…': 'Copying…',
|
|
20
|
+
'已复制': 'Copied',
|
|
21
|
+
'命令已复制。': 'Command copied.',
|
|
22
|
+
'复制失败,请选中命令后按 Ctrl+C 或 ⌘C 复制。': 'Copy failed. Select the command and press Ctrl+C or ⌘C to copy it.',
|
|
23
|
+
'自动更新失败可以使用命令更新:': 'If the automatic update fails, update with this command:',
|
|
24
|
+
'尚未确认目标版本,此命令安装执行时 npm 的 latest 版本。': 'No target version has been confirmed. This command installs the npm latest version available when you run it.',
|
|
25
|
+
'请在当前 Desktop 的内置终端执行,完成后手动重启。': 'Run this in the current Desktop’s built-in terminal, then restart manually.',
|
|
26
|
+
'请在运行当前 Harness 的同一环境执行,保持 DSH_HOME 一致,完成后手动重启。': 'Run this in the same environment as the current Harness, with the same DSH_HOME, then restart manually.',
|
|
27
|
+
'请等待当前操作结束,确认没有安装进程运行后再执行命令。': 'Wait for the current operation to finish and confirm no installer is running before using this command.',
|
|
28
|
+
'源码或链接安装请按原安装方式更新,不提供覆盖源码的 npm 命令。': 'Update source or linked installations using their original method. No npm command is provided to overwrite the source.',
|
|
29
|
+
'无法安全生成当前 profile 的命令,请在终端中手动确认 profile 后更新。': 'A safe command cannot be generated for this profile. Confirm the profile in your terminal before updating manually.',
|
|
16
30
|
'更新至': 'Update to',
|
|
17
31
|
'安装更新': 'Install update',
|
|
18
32
|
'正在更新…': 'Updating…',
|
|
19
33
|
'待手动重启': 'Restart needed',
|
|
20
34
|
'运行版本': 'Running version',
|
|
21
35
|
'已安装版本': 'Installed version',
|
|
22
|
-
'npm 最新版本': 'Latest on npm',
|
|
23
36
|
'目标 profile': 'Target profile',
|
|
24
37
|
'目标版本': 'Target version',
|
|
25
|
-
'更新来源': 'Update source',
|
|
26
|
-
'页面版本': 'Page version',
|
|
27
38
|
'无法确认': 'Unknown',
|
|
28
39
|
'发现新版本': 'Update available',
|
|
29
40
|
'已是最新版本': 'Up to date',
|
|
@@ -197,6 +208,44 @@ const EN = Object.freeze({
|
|
|
197
208
|
'/绝对路径/到/工作区': '/absolute/path/to/workspace',
|
|
198
209
|
'修改': 'Change',
|
|
199
210
|
'保存': 'Save',
|
|
211
|
+
'上下文增强': 'Context enhancement',
|
|
212
|
+
'未开启': 'Not enabled',
|
|
213
|
+
'仅群聊': 'Groups only',
|
|
214
|
+
'仅私聊': 'Direct only',
|
|
215
|
+
'群聊和私聊': 'Groups and direct',
|
|
216
|
+
'关闭弹窗': 'Close dialog',
|
|
217
|
+
'查看上下文增强说明': 'View context enhancement details',
|
|
218
|
+
'启用范围': 'Enable in',
|
|
219
|
+
'群聊中启用': 'Enable in group chats',
|
|
220
|
+
'私聊中启用': 'Enable in direct chats',
|
|
221
|
+
'(当前渠道不支持群聊)': '(This channel does not support group chats)',
|
|
222
|
+
'来源字段': 'Source fields',
|
|
223
|
+
'查看来源字段说明': 'View source field details',
|
|
224
|
+
'渠道': 'Channel',
|
|
225
|
+
'会话类型': 'Conversation type',
|
|
226
|
+
'发送者标识': 'Sender ID',
|
|
227
|
+
'发送者昵称': 'Sender name',
|
|
228
|
+
'查看发送者昵称字段说明': 'View sender name field details',
|
|
229
|
+
'该字段不是每个渠道都能提供。当前消息没有发送者昵称时,即使已选择该字段,<dsh_im_source> 中也会省略 senderName。': 'This field is not available on every channel. If the current message has no sender name, <dsh_im_source> omits senderName even when the field is selected.',
|
|
230
|
+
'机器人标识': 'Bot ID',
|
|
231
|
+
'增强提示词': 'Guidance',
|
|
232
|
+
'查看增强提示词使用说明': 'View guidance instructions',
|
|
233
|
+
'使用说明': 'How to use',
|
|
234
|
+
'用于告诉模型如何使用 <dsh_im_source> 中已选择的来源字段。只填写正文,插件会自动添加 <dsh_im_source_guidance> 成对标签。': 'Tell the model how to use the selected fields in <dsh_im_source>. Enter only the body; the plugin adds paired <dsh_im_source_guidance> tags.',
|
|
235
|
+
'生效规则': 'Behavior',
|
|
236
|
+
'隐私提示': 'Privacy',
|
|
237
|
+
'使用示例': 'Example',
|
|
238
|
+
'仅依据当前消息的 <dsh_im_source> 中实际提供的字段理解来源;没有提供的字段不要猜测或补全。\nconversationType是群聊时回复严肃一点,conversationType是私聊时回复一定要幽默搞笑,像周星驰的电影一样搞笑': 'Understand the source only from fields actually present in <dsh_im_source> for the current message; do not guess or fill in missing fields.\nWhen conversationType is group, reply more seriously. When conversationType is direct, make the reply humorous and funny.',
|
|
239
|
+
'填入示例': 'Use example',
|
|
240
|
+
'清空': 'Clear',
|
|
241
|
+
'选择在哪些会话中启用、提供哪些来源字段,以及如何使用这些信息。仅使用已有消息元数据,不查询平台 API。': 'Choose which conversations to enhance, which source fields to include, and how to use them. Only existing message metadata is used; no platform APIs are queried.',
|
|
242
|
+
'增强提示词中请使用字段名(如 senderId、conversationType)引用这些信息。只发送勾选且当前消息中可用的字段,不会额外查询或补全。': 'Reference these values by field name (such as senderId or conversationType) in the guidance. Only selected fields available in the current message are sent; no extra lookups are made.',
|
|
243
|
+
'只需填写正文,插件自动添加 <dsh_im_source_guidance> 成对标签。清空并保存后,不再附加增强提示词,已选来源字段仍按开关设置发送。': 'Enter only the body; the plugin adds paired <dsh_im_source_guidance> tags. Clear and save to omit guidance. Selected source fields still follow the conversation switches.',
|
|
244
|
+
'发送者标识可能包含平台用户 ID 或电话号码形式的标识。关闭开关不会删除已经写入会话历史的信息。': 'Sender IDs may contain platform user IDs or phone-number-like identifiers. Turning this off does not remove information already saved in conversation history.',
|
|
245
|
+
'上下文增强保存失败,请重试。': 'Could not save context enhancement. Try again.',
|
|
246
|
+
'请提交完整的上下文增强设置。': 'Submit the complete context enhancement settings.',
|
|
247
|
+
'群聊和私聊开关必须是布尔值。': 'Group and direct switches must be booleans.',
|
|
248
|
+
'来源字段只能选择已定义的五个字段。': 'Source fields must be chosen from the five defined fields.',
|
|
200
249
|
'保存中…': 'Saving…',
|
|
201
250
|
'未设置': 'Not set',
|
|
202
251
|
'工作区修改失败,请重试。': 'Could not update the workspace. Try again.',
|
|
@@ -646,6 +695,8 @@ function channelName(value) {
|
|
|
646
695
|
}
|
|
647
696
|
|
|
648
697
|
function translateDynamic(text) {
|
|
698
|
+
const guidanceLimit = /^增强提示词不得超过 (\d+) 个字符。$/.exec(text);
|
|
699
|
+
if (guidanceLimit) return `Guidance must not exceed ${guidanceLimit[1]} characters.`;
|
|
649
700
|
let match = /^(\d+) \/ (\d+) 在线$/.exec(text);
|
|
650
701
|
if (match) return `${match[1]}/${match[2]} online`;
|
|
651
702
|
match = /^已接入 (\d+) 个机器人,其中 (\d+) 个在线$/.exec(text);
|
|
@@ -40,6 +40,17 @@ const CSS = String.raw`
|
|
|
40
40
|
.dim-updateHint, .dim-updateError { margin: 12px 0 0; font-size: 12px; line-height: 19px; overflow-wrap: anywhere; }
|
|
41
41
|
.dim-updateHint { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
42
42
|
.dim-updateError { color: var(--dsw-alias-state-danger-primary, #d92d20); }
|
|
43
|
+
.dim-updateManual { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
44
|
+
.dim-updateManualHeading { margin: 0; font-size: 13px; line-height: 20px; font-weight: 600; }
|
|
45
|
+
.dim-updateManualHint { margin: 8px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 12px; line-height: 19px; }
|
|
46
|
+
.dim-updateCommandRow { display: flex; align-items: center; gap: 8px; margin-top: 10px; padding: 10px 12px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; background: var(--dsw-alias-bg-layer-1, #f7f8fa); }
|
|
47
|
+
.dim-updateCommand { display: block; flex: 1; width: 100%; min-width: 0; padding: 0; resize: none; border: 0; color: var(--dsw-alias-label-primary, #1f2329); background: transparent; font: 12px/19px ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; }
|
|
48
|
+
.dim-updateCommand:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
49
|
+
.dim-updateCopy { display: inline-flex; flex: 0 0 28px; align-items: center; justify-content: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 6px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; cursor: pointer; }
|
|
50
|
+
.dim-updateCopy:hover:not(:disabled) { color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
51
|
+
.dim-updateCopy:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
52
|
+
.dim-updateCopy:disabled { opacity: .55; cursor: default; }
|
|
53
|
+
.dim-updateCopyCopied { color: var(--dsw-alias-state-success-primary, #20a162); }
|
|
43
54
|
.dim-updateFooter { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 8px; padding: 14px 24px; border-top: 1px solid var(--dsw-alias-border-l1, #eef0f3); }
|
|
44
55
|
.dim-updateFooter .dim-updateButton:first-child { margin-right: auto; }
|
|
45
56
|
.dim-updatePrimary, .dim-updatePrimary:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); color: #fff; background: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
@@ -200,6 +211,73 @@ const CSS = String.raw`
|
|
|
200
211
|
.dim-panel .dim-presetSelect { min-width: 0; max-width: 100%; grid-column: 1 / -1; grid-row: 2; height: 30px; padding: 0 8px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 12px; }
|
|
201
212
|
.dim-panel .dim-presetSelect:disabled { cursor: not-allowed; opacity: .55; }
|
|
202
213
|
.dim-panel .dim-presetError { grid-column: 1 / -1; grid-row: 3; margin: 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.4; }
|
|
214
|
+
.dim-contextEntry { width: 100%; min-height: 40px; display: grid; grid-template-columns: 16px minmax(0, 1fr) max-content 16px; align-items: center; gap: 9px; margin: 10px 0; padding: 8px 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 13px; line-height: 20px; text-align: left; cursor: pointer; }
|
|
215
|
+
.dim-contextEntry:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
216
|
+
.dim-contextEntry > svg { color: var(--dsw-alias-label-secondary, #646a73); }
|
|
217
|
+
.dim-contextLabel { min-width: 0; font-weight: 500; overflow-wrap: anywhere; }
|
|
218
|
+
.dim-contextStatus { padding: 2px 7px; border-radius: 5px; color: var(--dsw-alias-label-secondary, #646a73); background: var(--dsw-alias-bg-layer-1, #fff); font-size: 11px; line-height: 16px; font-weight: 400; white-space: nowrap; }
|
|
219
|
+
.dim-contextStatus[data-active="true"] { color: var(--dsw-alias-state-business-primary, #3370ff); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 10%, var(--dsw-alias-bg-layer-1, #fff)); }
|
|
220
|
+
.dim-contextBackdrop { position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 12px; background: rgb(15 17 21 / 42%); }
|
|
221
|
+
.dim-contextBackdrop, .dim-contextBackdrop *, .dim-contextBackdrop *::before, .dim-contextBackdrop *::after { box-sizing: border-box; }
|
|
222
|
+
.dim-contextDialog { width: min(450px, 100%); min-width: 0; max-height: calc(100vh - 24px); max-height: calc(100dvh - 24px); overflow-y: auto; padding: 16px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 20px 70px rgb(0 0 0 / 20%); font-size: 13px; line-height: 1.5; text-align: left; }
|
|
223
|
+
.dim-contextDialog:focus { outline: none; }
|
|
224
|
+
.dim-contextHeader, .dim-contextEditorHeader { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
225
|
+
.dim-contextHeader { position: relative; }
|
|
226
|
+
.dim-contextHeaderTitle { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
|
|
227
|
+
.dim-contextHeader h3 { margin: 0; font-size: 15px; line-height: 22px; font-weight: 500; }
|
|
228
|
+
.dim-contextHeaderTooltip { width: min(340px, calc(100vw - 72px)); }
|
|
229
|
+
.dim-contextClose { width: 30px; height: 30px; flex: none; display: grid; place-items: center; padding: 0; border: 0; border-radius: 6px; color: var(--dsw-alias-label-secondary, #646a73); background: transparent; cursor: pointer; }
|
|
230
|
+
.dim-contextClose:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
231
|
+
.dim-contextSection { min-width: 0; margin: 14px 0; padding: 0; border: 0; }
|
|
232
|
+
.dim-contextSection legend { margin-bottom: 8px; padding: 0; font-weight: 500; }
|
|
233
|
+
.dim-contextLegend { position: relative; display: inline-flex; align-items: center; gap: 6px; }
|
|
234
|
+
.dim-contextSwitches { display: grid; grid-template-columns: 1fr; gap: 2px; }
|
|
235
|
+
.dim-contextSwitchRow { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 34px; cursor: pointer; }
|
|
236
|
+
.dim-contextSwitchLabel { min-width: 0; display: inline-flex; align-items: baseline; gap: 5px; flex-wrap: wrap; }
|
|
237
|
+
.dim-contextUnavailable { color: var(--dsw-alias-label-tertiary, #8f959e); font-size: 11px; line-height: 16px; font-weight: 400; }
|
|
238
|
+
.dim-contextSwitch { appearance: none; flex: none; width: 32px; height: 19px; margin: 0; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 12px; background: var(--dsw-alias-interactive-bg-hover, #eef0f3); cursor: pointer; }
|
|
239
|
+
.dim-contextSwitch::before { content: ""; display: block; width: 13px; height: 13px; margin: 2px; border-radius: 50%; background: var(--dsw-alias-label-secondary, #646a73); }
|
|
240
|
+
.dim-contextSwitch:checked { border-color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
241
|
+
.dim-contextSwitch:checked::before { transform: translateX(13px); background: #fff; }
|
|
242
|
+
.dim-contextFields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 3px 12px; }
|
|
243
|
+
.dim-contextField { min-width: 0; min-height: 30px; display: flex; align-items: center; gap: 6px; }
|
|
244
|
+
.dim-contextField input { flex: none; width: 14px; height: 14px; margin: 0; accent-color: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
245
|
+
.dim-contextFieldText { min-width: 0; display: grid; grid-template-columns: max-content max-content; align-items: center; column-gap: 5px; overflow-wrap: anywhere; }
|
|
246
|
+
.dim-contextFieldName { min-width: 0; line-height: 17px; cursor: pointer; }
|
|
247
|
+
.dim-contextFieldKey { min-width: 0; grid-column: 1 / -1; color: var(--dsw-alias-label-tertiary, #8f959e); font: 10px/14px ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; cursor: pointer; }
|
|
248
|
+
.dim-contextFieldHelp { position: relative; }
|
|
249
|
+
.dim-contextFieldHelpButton { width: 16px; height: 16px; font-size: 10px; }
|
|
250
|
+
.dim-contextTooltip.dim-contextFieldTooltip { top: calc(100% + 6px); right: 0; left: auto; width: min(280px, calc(100vw - 72px)); }
|
|
251
|
+
.dim-contextEditorHeader { position: relative; flex-wrap: wrap; }
|
|
252
|
+
.dim-contextEditorTitle { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
|
|
253
|
+
.dim-contextEditorTitle > label { font-weight: 500; }
|
|
254
|
+
.dim-contextHelp { display: inline-flex; align-items: center; flex: none; }
|
|
255
|
+
.dim-contextHelpButton { width: 18px; height: 18px; display: grid; place-items: center; padding: 0; border: 1px solid color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 30%, var(--dsw-alias-border-l2, #dfe1e5)); border-radius: 50%; color: var(--dsw-alias-state-business-primary, #3370ff); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; font-size: 11px; line-height: 1; font-weight: 700; cursor: help; transition: border-color .15s ease, color .15s ease, background .15s ease, box-shadow .15s ease; }
|
|
256
|
+
.dim-contextHelpButton:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3370ff) 8%, var(--dsw-alias-bg-layer-1, #fff)); }
|
|
257
|
+
.dim-contextTooltip { position: absolute; top: calc(100% + 7px); left: 0; z-index: 30; width: min(330px, calc(100vw - 72px)); display: grid; gap: 5px; padding: 10px 11px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 8px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-3, #fff); box-shadow: 0 10px 28px rgb(31 35 41 / 16%); font-size: 11px; line-height: 16px; font-weight: 400; overflow-wrap: anywhere; white-space: normal; opacity: 0; visibility: hidden; transform: translateY(-3px); pointer-events: none; transition: opacity .15s ease, transform .15s ease, visibility .15s ease; }
|
|
258
|
+
.dim-contextTooltip strong { font-weight: 600; }
|
|
259
|
+
.dim-contextTooltipExample { padding: 7px 8px; border-radius: 6px; background: var(--dsw-alias-bg-module-platform, #f7f8fa); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }
|
|
260
|
+
.dim-contextTooltip.dim-contextLegendTooltip { width: min(350px, calc(100vw - 72px)); }
|
|
261
|
+
.dim-contextTooltip.dim-contextGuidanceTooltip { top: auto; bottom: calc(100% + 7px); width: min(380px, calc(100vw - 72px)); max-height: calc(100dvh - 48px); overflow-y: auto; }
|
|
262
|
+
.dim-contextHelp:hover .dim-contextTooltip, .dim-contextHelp:focus-within .dim-contextTooltip { opacity: 1; visibility: visible; transform: translateY(0); pointer-events: auto; }
|
|
263
|
+
.dim-contextTextActions { display: flex; gap: 10px; margin-left: auto; }
|
|
264
|
+
.dim-contextTextActions button { min-height: 30px; padding: 4px 0; border: 0; border-radius: 4px; color: var(--dsw-alias-state-business-primary, #3370ff); background: transparent; font: inherit; font-size: 12px; cursor: pointer; }
|
|
265
|
+
.dim-contextTextActions button:hover:not(:disabled) { text-decoration: underline; }
|
|
266
|
+
.dim-contextGuidance textarea { display: block; width: 100%; min-height: 88px; margin-top: 7px; padding: 9px 10px; resize: vertical; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-module-platform, #f7f8fa); font: inherit; font-size: 12px; line-height: 1.6; }
|
|
267
|
+
.dim-contextGuidance textarea::placeholder { color: var(--dsw-alias-label-tertiary, #8f959e); opacity: 1; }
|
|
268
|
+
.dim-contextHint { margin: 5px 0 0; color: var(--dsw-alias-label-secondary, #646a73); font-size: 11px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
269
|
+
.dim-contextError { margin: 12px 0 0; color: var(--dsw-alias-state-error-primary, #d54941); font-size: 12px; line-height: 1.5; overflow-wrap: anywhere; }
|
|
270
|
+
.dim-contextFooter { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
|
|
271
|
+
.dim-contextFooter button { min-height: 34px; padding: 6px 15px; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 7px; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); font: inherit; cursor: pointer; }
|
|
272
|
+
.dim-contextFooter button:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, #eef0f3); }
|
|
273
|
+
.dim-contextFooter .dim-contextSave, .dim-contextFooter .dim-contextSave:hover:not(:disabled) { border-color: var(--dsw-alias-state-business-primary, #3370ff); color: #fff; background: var(--dsw-alias-state-business-primary, #3370ff); }
|
|
274
|
+
.dim-contextEntry:focus-visible, .dim-contextDialog button:focus-visible, .dim-contextDialog input:focus-visible, .dim-contextDialog textarea:focus-visible { outline: 2px solid var(--dsw-alias-state-business-primary, #3370ff); outline-offset: 2px; }
|
|
275
|
+
.dim-contextEntry:disabled, .dim-contextDialog button:disabled, .dim-contextDialog input:disabled, .dim-contextDialog textarea:disabled { opacity: .55; cursor: not-allowed; }
|
|
276
|
+
@media (pointer: coarse) {
|
|
277
|
+
.dim-contextEntry, .dim-contextClose, .dim-contextFooter button, .dim-contextTextActions button, .dim-contextField, .dim-contextSwitchRow { min-height: 44px; }
|
|
278
|
+
.dim-contextClose, .dim-contextTextActions button { min-width: 44px; }
|
|
279
|
+
.dim-contextGuidance textarea { font-size: 16px; }
|
|
280
|
+
}
|
|
203
281
|
.dim-directoryPickerBackdrop { --dim-blue: var(--dsw-alias-state-business-primary, #3370ff); --dim-blue-soft: color-mix(in srgb, var(--dim-blue) 9%, transparent); position: fixed; inset: 0; z-index: 1000; display: grid; place-items: center; padding: 24px; background: rgb(15 17 21 / 42%); backdrop-filter: blur(3px); }
|
|
204
282
|
.dim-directoryPickerBackdrop, .dim-directoryPickerBackdrop *, .dim-directoryPickerBackdrop *::before, .dim-directoryPickerBackdrop *::after { box-sizing: border-box; }
|
|
205
283
|
.dim-directoryPicker { width: min(720px, 100%); height: min(620px, calc(100vh - 48px)); min-height: 420px; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; overflow: hidden; border: 1px solid var(--dsw-alias-border-l2, #dfe1e5); border-radius: 18px; outline: none; color: var(--dsw-alias-label-primary, #1f2329); background: var(--dsw-alias-bg-layer-1, #fff); box-shadow: 0 24px 72px rgb(15 17 21 / 24%); }
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { createPortal } from 'react-dom';
|
|
3
|
+
import validSemver from 'semver/functions/valid.js';
|
|
4
|
+
import compareVersionsDescending from 'semver/functions/rcompare.js';
|
|
3
5
|
|
|
4
6
|
import { h } from './i18n.js';
|
|
5
7
|
import { createPollScheduler } from './lifecycle.js';
|
|
@@ -85,6 +87,102 @@ function retainDialogFocus(event) {
|
|
|
85
87
|
event?.currentTarget?.closest?.('.dim-updateDialog')?.focus?.({ preventScroll: true });
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
function manualUpdateCommand(snapshot) {
|
|
91
|
+
const profile = snapshot?.profileName;
|
|
92
|
+
if (snapshot?.sourceInstall || ['source-install', 'unknown-profile'].includes(snapshot?.blockedReason)
|
|
93
|
+
|| typeof profile !== 'string' || !profile.trim() || profile.length > 255
|
|
94
|
+
|| profile.startsWith('-') || ['.', '..', 'node_modules'].includes(profile)
|
|
95
|
+
|| !/^[\p{L}\p{N}_. -]+$/u.test(profile)) return null;
|
|
96
|
+
|
|
97
|
+
// Only allow shell-neutral profile characters; spaces and Unicode stay one argument.
|
|
98
|
+
const profileArgument = /^[A-Za-z0-9_.-]+$/.test(profile) ? profile : `"${profile}"`;
|
|
99
|
+
const validVersion = (version) => typeof version === 'string' && validSemver(version) === version;
|
|
100
|
+
const pendingRestart = snapshot.blockedReason === 'pending-restart'
|
|
101
|
+
|| snapshot.job?.state === 'restart-required';
|
|
102
|
+
const targets = [
|
|
103
|
+
snapshot.latestVersion,
|
|
104
|
+
pendingRestart ? snapshot.installedVersion : null,
|
|
105
|
+
snapshot.job?.state !== 'completed' ? snapshot.job?.targetVersion : null,
|
|
106
|
+
].filter(validVersion);
|
|
107
|
+
// Keep a known target pinned, and never suggest downgrading an installed/running version.
|
|
108
|
+
const version = targets.length
|
|
109
|
+
? [...targets, snapshot.runningVersion, snapshot.installedVersion]
|
|
110
|
+
.filter(validVersion).sort(compareVersionsDescending)[0]
|
|
111
|
+
: 'latest';
|
|
112
|
+
return `dsh plugin --profile ${profileArgument} add -w @xmanrui/dsh-im@${version}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function ManualUpdateCommand({ command, disabled, sourceInstall, desktop }) {
|
|
116
|
+
const [copyState, setCopyState] = React.useState('idle');
|
|
117
|
+
const commandRef = React.useRef(null);
|
|
118
|
+
const mounted = React.useRef(false);
|
|
119
|
+
const copying = React.useRef(false);
|
|
120
|
+
React.useEffect(() => {
|
|
121
|
+
mounted.current = true;
|
|
122
|
+
return () => { mounted.current = false; };
|
|
123
|
+
}, []);
|
|
124
|
+
|
|
125
|
+
const copy = async (event) => {
|
|
126
|
+
if (!command || disabled || copying.current) return;
|
|
127
|
+
retainDialogFocus(event);
|
|
128
|
+
copying.current = true;
|
|
129
|
+
setCopyState('copying');
|
|
130
|
+
try {
|
|
131
|
+
const clipboard = globalThis.navigator?.clipboard;
|
|
132
|
+
if (typeof clipboard?.writeText !== 'function') throw new Error('Clipboard unavailable');
|
|
133
|
+
await clipboard.writeText(command);
|
|
134
|
+
if (mounted.current) setCopyState('copied');
|
|
135
|
+
} catch {
|
|
136
|
+
if (mounted.current) {
|
|
137
|
+
setCopyState('failed');
|
|
138
|
+
commandRef.current?.focus?.({ preventScroll: true });
|
|
139
|
+
commandRef.current?.select?.();
|
|
140
|
+
}
|
|
141
|
+
} finally {
|
|
142
|
+
copying.current = false;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const copyLabel = copyState === 'copying' ? '复制中…' : copyState === 'copied' ? '已复制' : '复制命令';
|
|
147
|
+
|
|
148
|
+
return h('section', { className: 'dim-updateManual', 'aria-label': '手工更新' },
|
|
149
|
+
h('h4', { className: 'dim-updateManualHeading' }, '手工更新'),
|
|
150
|
+
command ? h(React.Fragment, null,
|
|
151
|
+
h('p', { className: 'dim-updateManualHint' }, '自动更新失败可以使用命令更新:'),
|
|
152
|
+
h('div', { className: 'dim-updateCommandRow' },
|
|
153
|
+
h('textarea', {
|
|
154
|
+
ref: commandRef, className: 'dim-updateCommand', 'aria-label': '手工更新命令',
|
|
155
|
+
value: command, readOnly: true, spellCheck: false, rows: 2, dir: 'ltr',
|
|
156
|
+
}),
|
|
157
|
+
h('button', {
|
|
158
|
+
type: 'button', className: `dim-updateCopy${copyState === 'copied' ? ' dim-updateCopyCopied' : ''}`,
|
|
159
|
+
'aria-label': copyLabel, title: copyLabel, 'aria-busy': copyState === 'copying',
|
|
160
|
+
disabled: disabled || copyState === 'copying', onClick: (event) => void copy(event),
|
|
161
|
+
}, h('svg', {
|
|
162
|
+
width: 16, height: 16, viewBox: '0 0 20 20', fill: 'none',
|
|
163
|
+
stroke: 'currentColor', strokeWidth: 1.6, strokeLinecap: 'round', strokeLinejoin: 'round',
|
|
164
|
+
'aria-hidden': 'true', focusable: 'false',
|
|
165
|
+
}, copyState === 'copied'
|
|
166
|
+
? h('path', { d: 'm4 10 4 4 8-8' })
|
|
167
|
+
: h(React.Fragment, null,
|
|
168
|
+
h('rect', { x: 7, y: 7, width: 10, height: 11, rx: 1.5 }),
|
|
169
|
+
h('path', { d: 'M5 13H3.5A1.5 1.5 0 0 1 2 11.5v-8A1.5 1.5 0 0 1 3.5 2h8A1.5 1.5 0 0 1 13 3.5V5' }))))),
|
|
170
|
+
command.endsWith('@xmanrui/dsh-im@latest') ? h('p', { className: 'dim-updateManualHint' },
|
|
171
|
+
'尚未确认目标版本,此命令安装执行时 npm 的 latest 版本。') : null,
|
|
172
|
+
h('p', { className: 'dim-updateManualHint' }, desktop
|
|
173
|
+
? '请在当前 Desktop 的内置终端执行,完成后手动重启。'
|
|
174
|
+
: '请在运行当前 Harness 的同一环境执行,保持 DSH_HOME 一致,完成后手动重启。'),
|
|
175
|
+
disabled ? h('p', { className: 'dim-updateManualHint' },
|
|
176
|
+
'请等待当前操作结束,确认没有安装进程运行后再执行命令。') : null,
|
|
177
|
+
copyState === 'failed' || copyState === 'copied' ? h('p', {
|
|
178
|
+
className: copyState === 'failed' ? 'dim-updateError' : 'dim-updateManualHint',
|
|
179
|
+
role: copyState === 'failed' ? 'alert' : 'status',
|
|
180
|
+
}, copyState === 'failed' ? '复制失败,请选中命令后按 Ctrl+C 或 ⌘C 复制。' : '命令已复制。') : null)
|
|
181
|
+
: h('p', { className: 'dim-updateManualHint' }, sourceInstall
|
|
182
|
+
? '源码或链接安装请按原安装方式更新,不提供覆盖源码的 npm 命令。'
|
|
183
|
+
: '无法安全生成当前 profile 的命令,请在终端中手动确认 profile 后更新。'));
|
|
184
|
+
}
|
|
185
|
+
|
|
88
186
|
function UpdateDialog({ children, onClose }) {
|
|
89
187
|
const dialogRef = React.useRef(null);
|
|
90
188
|
const titleId = React.useId();
|
|
@@ -115,7 +213,7 @@ function UpdateDialog({ children, onClose }) {
|
|
|
115
213
|
onClose();
|
|
116
214
|
}
|
|
117
215
|
if (event.key !== 'Tab') return;
|
|
118
|
-
const buttons = dialogRef.current?.querySelectorAll?.('button:not(:disabled)');
|
|
216
|
+
const buttons = dialogRef.current?.querySelectorAll?.('button:not(:disabled), textarea:not(:disabled)');
|
|
119
217
|
if (!buttons?.length) return;
|
|
120
218
|
const first = buttons[0];
|
|
121
219
|
const last = buttons[buttons.length - 1];
|
|
@@ -299,6 +397,7 @@ export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
|
299
397
|
const failedJob = ['failed', 'interrupted'].includes(snapshot?.job?.state);
|
|
300
398
|
const jobMessage = snapshot?.job?.message;
|
|
301
399
|
const targetVersion = snapshot?.job?.targetVersion;
|
|
400
|
+
const manualCommand = manualUpdateCommand(snapshot);
|
|
302
401
|
const buttonLabel = action === 'checking' ? '检查中…'
|
|
303
402
|
: action === 'starting' || activeJob ? '正在更新…'
|
|
304
403
|
: restartRequired ? '待手动重启'
|
|
@@ -324,13 +423,9 @@ export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
|
324
423
|
snapshot?.installedVersion && snapshot.installedVersion !== snapshot.runningVersion
|
|
325
424
|
? h(React.Fragment, null,
|
|
326
425
|
h('dt', null, '已安装版本'), h('dd', null, `v${snapshot.installedVersion}`)) : null,
|
|
327
|
-
h('dt', null, 'npm 最新版本'), h('dd', null, snapshot?.latestVersion ? `v${snapshot.latestVersion}` : '尚未检查'),
|
|
328
426
|
targetVersion ? h(React.Fragment, null,
|
|
329
427
|
h('dt', null, '目标版本'), h('dd', null, `v${targetVersion}`)) : null,
|
|
330
|
-
h('dt', null, '目标 profile'), h('dd', null, snapshot?.profileName ?? '无法确认'),
|
|
331
|
-
h('dt', null, '更新来源'), h('dd', null, 'registry.npmjs.org'),
|
|
332
|
-
versionsDiffer ? h(React.Fragment, null,
|
|
333
|
-
h('dt', null, '页面版本'), h('dd', null, `v${clientVersion}`)) : null),
|
|
428
|
+
h('dt', null, '目标 profile'), h('dd', null, snapshot?.profileName ?? '无法确认')),
|
|
334
429
|
h('div', {
|
|
335
430
|
className: `dim-updateStatus${error || failedJob ? ' dim-updateStatusError' : ''}`,
|
|
336
431
|
role: 'status',
|
|
@@ -347,7 +442,13 @@ export function UpdatePanel({ rpcCall, clientVersion, onStatus }) {
|
|
|
347
442
|
blocked && !restartRequired ? h('p', { className: 'dim-updateHint' }, blocked) : null,
|
|
348
443
|
versionsDiffer && !restartRequired ? h('p', { className: 'dim-updateHint' }, '页面版本与运行版本不同,请手动刷新页面;若仍不一致,请手动重启 Harness 或 Desktop。') : null,
|
|
349
444
|
canConfirm ? h('p', { className: 'dim-updateHint' },
|
|
350
|
-
'请在机器人空闲时安装;安装会修改当前 profile 的依赖,完成后需手动重启。') : null
|
|
445
|
+
'请在机器人空闲时安装;安装会修改当前 profile 的依赖,完成后需手动重启。') : null,
|
|
446
|
+
h(ManualUpdateCommand, {
|
|
447
|
+
key: manualCommand ?? 'unavailable', command: manualCommand,
|
|
448
|
+
disabled: busyAction || activeJob || uncertainInstall,
|
|
449
|
+
sourceInstall: snapshot?.sourceInstall || snapshot?.blockedReason === 'source-install',
|
|
450
|
+
desktop: snapshot?.environmentKind === 'desktop',
|
|
451
|
+
})),
|
|
351
452
|
h('footer', { className: 'dim-updateFooter' },
|
|
352
453
|
h('button', { type: 'button', className: 'dim-updateButton', onClick: () => setOpen(false) }, '关闭'),
|
|
353
454
|
restartRequired || !activeJob ? h('button', {
|
|
@@ -109,6 +109,7 @@ export async function createProductionController(ctx, config = {}, internals = {
|
|
|
109
109
|
clientSecret,
|
|
110
110
|
harness: workspaceScope.harness,
|
|
111
111
|
state: workspaceScope.state,
|
|
112
|
+
contextEnhancement: { botId, getSettings: () => workspaces.contextEnhancementFor(botId) },
|
|
112
113
|
replyTimeoutMs: config.replyTimeoutMs ?? 600_000,
|
|
113
114
|
maxMessageChars: config.maxMessageChars ?? 4_000,
|
|
114
115
|
connectTimeoutMs: config.connectTimeoutMs ?? 15_000,
|