@dsh-plus/secret-env 0.1.1 → 0.1.3
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/lib/client.js +1180 -633
- package/lib/index.d.ts +40 -8
- package/lib/index.js +211 -57
- package/package.json +16 -14
- package/src/api.ts +24 -1
- package/src/client/api.ts +18 -0
- package/src/client/client.ts +28 -21
- package/src/client/command.ts +62 -0
- package/src/client/common.ts +14 -1
- package/src/client/i18n.ts +66 -37
- package/src/client/menu-core.ts +2 -2
- package/src/client/menu.tsx +24 -8
- package/src/client/panel-bus.ts +23 -0
- package/src/client/panel-host.tsx +63 -0
- package/src/client/panel.tsx +385 -0
- package/src/client/section.tsx +194 -38
- package/src/client/styles.ts +10 -13
- package/src/config.ts +4 -0
- package/src/contributors.ts +115 -0
- package/src/index.ts +1 -1
- package/src/inventory.ts +101 -0
- package/src/names.ts +3 -3
- package/src/service.ts +107 -94
- package/src/client/composer.tsx +0 -260
package/lib/client.js
CHANGED
|
@@ -6,6 +6,230 @@ window.__ModuleLoader__.load({
|
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
//#region src/client/panel-bus.ts
|
|
11
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
12
|
+
/** 请求打开指定会话的密钥面板(由 /secret 命令的 onSelect 调用)。 */
|
|
13
|
+
function requestOpenSessionPanel(sessionId) {
|
|
14
|
+
for (const listener of listeners) listener(sessionId);
|
|
15
|
+
}
|
|
16
|
+
/** 订阅打开信号;返回退订器(随组件卸载清理)。 */
|
|
17
|
+
function onOpenSessionPanel(listener) {
|
|
18
|
+
listeners.add(listener);
|
|
19
|
+
return () => {
|
|
20
|
+
listeners.delete(listener);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/client/command.ts
|
|
25
|
+
/**
|
|
26
|
+
* `/var` 斜杠命令(dsh-client-ui-commands 的 commandUi 服务):
|
|
27
|
+
* contribution 仅支持官方 popupSelect 壳(kind 唯一),故命令提供单个
|
|
28
|
+
* 「打开面板」动作,选中后经打开总线唤起本会话的变量面板——
|
|
29
|
+
* 完全自定义的 React 面板只能在自己占有的 overlay 槽渲染。
|
|
30
|
+
* 子代理会话不提供本命令(对齐官方 /model 的可用性过滤)。
|
|
31
|
+
* @module secret-env/client/command
|
|
32
|
+
*/
|
|
33
|
+
/** 注册 /var 命令(commandUi 服务缺席时 inject 回调不触发,自然跳过)。 */
|
|
34
|
+
function registerVarCommand(ctx, sessions, t) {
|
|
35
|
+
ctx.inject(["commandUi"], (scope) => {
|
|
36
|
+
scope.effect(() => scope.commandUi.register({
|
|
37
|
+
name: "var",
|
|
38
|
+
description: t("command.description"),
|
|
39
|
+
available: (session) => sessions.subagentAddress?.(session.sessionId) === void 0,
|
|
40
|
+
ui: {
|
|
41
|
+
kind: "popupSelect",
|
|
42
|
+
options: () => Promise.resolve([{
|
|
43
|
+
id: "open",
|
|
44
|
+
label: t("command.openPanel")
|
|
45
|
+
}]),
|
|
46
|
+
onSelect: (_option, session) => {
|
|
47
|
+
requestOpenSessionPanel(session.sessionId);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}), "secret-env: /var contribution");
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/mergeClasses.mjs
|
|
55
|
+
/**
|
|
56
|
+
* @license lucide-react v1.38.0 - ISC
|
|
57
|
+
*
|
|
58
|
+
* This source code is licensed under the ISC license.
|
|
59
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
60
|
+
*/
|
|
61
|
+
const mergeClasses = (...classes) => classes.filter((className, index, array) => {
|
|
62
|
+
return Boolean(className) && className.trim() !== "" && array.indexOf(className) === index;
|
|
63
|
+
}).join(" ").trim();
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/toKebabCase.mjs
|
|
66
|
+
/**
|
|
67
|
+
* @license lucide-react v1.38.0 - ISC
|
|
68
|
+
*
|
|
69
|
+
* This source code is licensed under the ISC license.
|
|
70
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
71
|
+
*/
|
|
72
|
+
const toKebabCase = (string) => string.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/toCamelCase.mjs
|
|
75
|
+
/**
|
|
76
|
+
* @license lucide-react v1.38.0 - ISC
|
|
77
|
+
*
|
|
78
|
+
* This source code is licensed under the ISC license.
|
|
79
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
80
|
+
*/
|
|
81
|
+
const toCamelCase = (string) => string.replace(/^([A-Z])|[\s-_]+(\w)/g, (match, p1, p2) => p2 ? p2.toUpperCase() : p1.toLowerCase());
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/toPascalCase.mjs
|
|
84
|
+
/**
|
|
85
|
+
* @license lucide-react v1.38.0 - ISC
|
|
86
|
+
*
|
|
87
|
+
* This source code is licensed under the ISC license.
|
|
88
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
89
|
+
*/
|
|
90
|
+
const toPascalCase = (string) => {
|
|
91
|
+
const camelCase = toCamelCase(string);
|
|
92
|
+
return camelCase.charAt(0).toUpperCase() + camelCase.slice(1);
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/defaultAttributes.mjs
|
|
96
|
+
/**
|
|
97
|
+
* @license lucide-react v1.38.0 - ISC
|
|
98
|
+
*
|
|
99
|
+
* This source code is licensed under the ISC license.
|
|
100
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
101
|
+
*/
|
|
102
|
+
var defaultAttributes = {
|
|
103
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
104
|
+
width: 24,
|
|
105
|
+
height: 24,
|
|
106
|
+
viewBox: "0 0 24 24",
|
|
107
|
+
fill: "none",
|
|
108
|
+
stroke: "currentColor",
|
|
109
|
+
strokeWidth: 2,
|
|
110
|
+
strokeLinecap: "round",
|
|
111
|
+
strokeLinejoin: "round"
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/shared/src/utils/hasA11yProp.mjs
|
|
115
|
+
/**
|
|
116
|
+
* @license lucide-react v1.38.0 - ISC
|
|
117
|
+
*
|
|
118
|
+
* This source code is licensed under the ISC license.
|
|
119
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
120
|
+
*/
|
|
121
|
+
const hasA11yProp = (props) => {
|
|
122
|
+
for (const prop in props) if (prop.startsWith("aria-") || prop === "role" || prop === "title") return true;
|
|
123
|
+
return false;
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/context.mjs
|
|
127
|
+
/**
|
|
128
|
+
* @license lucide-react v1.38.0 - ISC
|
|
129
|
+
*
|
|
130
|
+
* This source code is licensed under the ISC license.
|
|
131
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
132
|
+
*/
|
|
133
|
+
const LucideContext = (0, react.createContext)({});
|
|
134
|
+
const useLucideContext = () => (0, react.useContext)(LucideContext);
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/Icon.mjs
|
|
137
|
+
/**
|
|
138
|
+
* @license lucide-react v1.38.0 - ISC
|
|
139
|
+
*
|
|
140
|
+
* This source code is licensed under the ISC license.
|
|
141
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
142
|
+
*/
|
|
143
|
+
const Icon = (0, react.forwardRef)(({ color, size, strokeWidth, absoluteStrokeWidth, className = "", children, iconNode, ...rest }, ref) => {
|
|
144
|
+
const { size: contextSize = 24, strokeWidth: contextStrokeWidth = 2, absoluteStrokeWidth: contextAbsoluteStrokeWidth = false, color: contextColor = "currentColor", className: contextClass = "" } = useLucideContext() ?? {};
|
|
145
|
+
const calculatedStrokeWidth = absoluteStrokeWidth ?? contextAbsoluteStrokeWidth ? Number(strokeWidth ?? contextStrokeWidth) * 24 / Number(size ?? contextSize) : strokeWidth ?? contextStrokeWidth;
|
|
146
|
+
return (0, react.createElement)("svg", {
|
|
147
|
+
ref,
|
|
148
|
+
...defaultAttributes,
|
|
149
|
+
width: size ?? contextSize ?? defaultAttributes.width,
|
|
150
|
+
height: size ?? contextSize ?? defaultAttributes.height,
|
|
151
|
+
stroke: color ?? contextColor,
|
|
152
|
+
strokeWidth: calculatedStrokeWidth,
|
|
153
|
+
className: mergeClasses("lucide", contextClass, className),
|
|
154
|
+
...!children && !hasA11yProp(rest) && { "aria-hidden": "true" },
|
|
155
|
+
...rest
|
|
156
|
+
}, [...iconNode.map(([tag, attrs]) => (0, react.createElement)(tag, attrs)), ...Array.isArray(children) ? children : [children]]);
|
|
157
|
+
});
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region ../../node_modules/.pnpm/lucide-react@1.38.0_react@18.2.0/node_modules/lucide-react/dist/esm/createLucideIcon.mjs
|
|
160
|
+
/**
|
|
161
|
+
* @license lucide-react v1.38.0 - ISC
|
|
162
|
+
*
|
|
163
|
+
* This source code is licensed under the ISC license.
|
|
164
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
165
|
+
*/
|
|
166
|
+
const createLucideIcon = (iconName, iconNode) => {
|
|
167
|
+
const Component = (0, react.forwardRef)(({ className, ...props }, ref) => (0, react.createElement)(Icon, {
|
|
168
|
+
ref,
|
|
169
|
+
iconNode,
|
|
170
|
+
className: mergeClasses(`lucide-${toKebabCase(toPascalCase(iconName))}`, `lucide-${iconName}`, className),
|
|
171
|
+
...props
|
|
172
|
+
}));
|
|
173
|
+
Component.displayName = toPascalCase(iconName);
|
|
174
|
+
return Component;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* @license lucide-react v1.38.0 - ISC
|
|
178
|
+
*
|
|
179
|
+
* This source code is licensed under the ISC license.
|
|
180
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
181
|
+
*/
|
|
182
|
+
const Eye = createLucideIcon("eye", [["path", {
|
|
183
|
+
d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",
|
|
184
|
+
key: "1nclc0"
|
|
185
|
+
}], ["circle", {
|
|
186
|
+
cx: "12",
|
|
187
|
+
cy: "12",
|
|
188
|
+
r: "3",
|
|
189
|
+
key: "1v7zrd"
|
|
190
|
+
}]]);
|
|
191
|
+
/**
|
|
192
|
+
* @license lucide-react v1.38.0 - ISC
|
|
193
|
+
*
|
|
194
|
+
* This source code is licensed under the ISC license.
|
|
195
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
196
|
+
*/
|
|
197
|
+
const EyeOff = createLucideIcon("eye-off", [
|
|
198
|
+
["path", {
|
|
199
|
+
d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",
|
|
200
|
+
key: "ct8e1f"
|
|
201
|
+
}],
|
|
202
|
+
["path", {
|
|
203
|
+
d: "M14.084 14.158a3 3 0 0 1-4.242-4.242",
|
|
204
|
+
key: "151rxh"
|
|
205
|
+
}],
|
|
206
|
+
["path", {
|
|
207
|
+
d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",
|
|
208
|
+
key: "13bj9a"
|
|
209
|
+
}],
|
|
210
|
+
["path", {
|
|
211
|
+
d: "m2 2 20 20",
|
|
212
|
+
key: "1ooewy"
|
|
213
|
+
}]
|
|
214
|
+
]);
|
|
215
|
+
//#endregion
|
|
216
|
+
//#region ../shared/src/client/icons.tsx
|
|
217
|
+
function lucideProps(props) {
|
|
218
|
+
return {
|
|
219
|
+
size: props.size ?? 14,
|
|
220
|
+
className: props.className,
|
|
221
|
+
"aria-hidden": true
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** 可见(注入生效)语义——官方基元无 Eye 系,lucide 补齐。 */
|
|
225
|
+
function IconEye(props) {
|
|
226
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Eye, { ...lucideProps(props) });
|
|
227
|
+
}
|
|
228
|
+
/** 已屏蔽(本会话/全局不注入)语义。 */
|
|
229
|
+
function IconEyeOff(props) {
|
|
230
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EyeOff, { ...lucideProps(props) });
|
|
231
|
+
}
|
|
232
|
+
//#endregion
|
|
9
233
|
//#region ../shared/src/client/fetch.ts
|
|
10
234
|
/**
|
|
11
235
|
* 同源自定义端点的最小 fetch 封装(各插件 client/api.ts 的公共收编版)。
|
|
@@ -175,6 +399,137 @@ ${extra}
|
|
|
175
399
|
return tag;
|
|
176
400
|
}
|
|
177
401
|
//#endregion
|
|
402
|
+
//#region src/client/i18n.ts
|
|
403
|
+
/**
|
|
404
|
+
* 浏览器半文案(zh/en)。公共键来自 shared common,本文件只维护业务键。
|
|
405
|
+
* @module secret-env/client/i18n
|
|
406
|
+
*/
|
|
407
|
+
const NS = "dsh-plus-secret-env";
|
|
408
|
+
const ownZh = {
|
|
409
|
+
nav: "环境变量",
|
|
410
|
+
title: "环境变量",
|
|
411
|
+
description: "以 $DSH_VAR_* 变量名向 agent 暴露值(密钥、链接等):值在每次 shell 调用时由宿主注入,不进入对话记录与提示词,不影响上下文缓存。",
|
|
412
|
+
globalList: "全局变量",
|
|
413
|
+
configured: "已配置",
|
|
414
|
+
unconfigured: "未配置",
|
|
415
|
+
readonly: "只读来源",
|
|
416
|
+
addGlobal: "添加全局变量",
|
|
417
|
+
nameLabel: "变量名",
|
|
418
|
+
nameHint: "大写字母/数字/下划线,如 GITHUB_TOKEN;模型实际使用 $DSH_VAR_ 前缀拼接后的完整名。",
|
|
419
|
+
valueLabel: "值",
|
|
420
|
+
valueHint: "写入后不可回读;保存于宿主凭据库,不进入对话。",
|
|
421
|
+
descLabel: "描述(可选)",
|
|
422
|
+
descHint: "仅人读备注,不进提示词。",
|
|
423
|
+
save: "保存",
|
|
424
|
+
saving: "保存中…",
|
|
425
|
+
delete: "删除",
|
|
426
|
+
copy: "复制变量名",
|
|
427
|
+
copied: "已复制",
|
|
428
|
+
emptyGlobal: "暂无全局变量。添加后 agent 即可通过变量名引用。",
|
|
429
|
+
loadFailed: "加载失败,请稍后重试。",
|
|
430
|
+
retry: "重试",
|
|
431
|
+
inheritedList: "继承变量(宿主环境)",
|
|
432
|
+
inheritedNote: "来自宿主进程环境,默认纳入注入",
|
|
433
|
+
inheritedEmpty: "宿主环境中没有可继承的 DSH_VAR_* 变量。",
|
|
434
|
+
maskGlobal: "全局屏蔽",
|
|
435
|
+
unmaskGlobal: "恢复注入",
|
|
436
|
+
maskedBadge: "已屏蔽",
|
|
437
|
+
maskedGlobalBadge: "已全局屏蔽",
|
|
438
|
+
sessionManage: "会话变量",
|
|
439
|
+
sessionPick: "选择会话",
|
|
440
|
+
sessionPickEmpty: "暂无会话",
|
|
441
|
+
sessionSecrets: "会话变量",
|
|
442
|
+
sessionList: "会话变量",
|
|
443
|
+
sessionEmpty: "本会话暂无变量。",
|
|
444
|
+
sessionHint: "仅当前会话的 shell 命令可用;会话结束或一次性使用即失效。",
|
|
445
|
+
addSession: "添加会话变量",
|
|
446
|
+
onceLabel: "一次性(首次使用后销毁)",
|
|
447
|
+
globalInSession: "全局变量(可在本会话屏蔽)",
|
|
448
|
+
inheritedInSession: "继承变量(可在本会话屏蔽)",
|
|
449
|
+
maskSession: "在本会话屏蔽",
|
|
450
|
+
unmaskSession: "在本会话恢复",
|
|
451
|
+
close: "关闭",
|
|
452
|
+
refresh: "刷新",
|
|
453
|
+
scopeGlobal: "全局",
|
|
454
|
+
scopeSession: "会话",
|
|
455
|
+
scopeOnce: "一次性",
|
|
456
|
+
scopeInherited: "继承",
|
|
457
|
+
"menu.title": "变量",
|
|
458
|
+
"menu.aria": "变量候选",
|
|
459
|
+
"command.description": "管理本会话的环境变量(密钥、链接等)",
|
|
460
|
+
"command.openPanel": "打开会话变量面板",
|
|
461
|
+
"error.invalid-name": "变量名不合法:大写字母开头,仅字母/数字/下划线,≤64 字符。",
|
|
462
|
+
"error.empty-value": "值不能为空。",
|
|
463
|
+
"error.shadowed": "进程环境中已存在同名变量且优先级更高,写入无效;请先取消该环境变量。",
|
|
464
|
+
"error.conflict": "变量名与其他插件注册的受管变量冲突。",
|
|
465
|
+
"error.no-session": "缺少会话标识,请刷新后重试。",
|
|
466
|
+
"error.method": "请求方法不允许。",
|
|
467
|
+
"error.internal": "服务内部错误,请查看宿主日志。"
|
|
468
|
+
};
|
|
469
|
+
const ownEn = {
|
|
470
|
+
nav: "Env Variables",
|
|
471
|
+
title: "Env Variables",
|
|
472
|
+
description: "Expose values (keys, links, etc.) to the agent as $DSH_VAR_* variables: values are injected by the host into each shell call, never enter the transcript or prompt, and do not affect context caching.",
|
|
473
|
+
globalList: "Global variables",
|
|
474
|
+
configured: "Configured",
|
|
475
|
+
unconfigured: "Not configured",
|
|
476
|
+
readonly: "Read-only source",
|
|
477
|
+
addGlobal: "Add global variable",
|
|
478
|
+
nameLabel: "Variable name",
|
|
479
|
+
nameHint: "Uppercase letters/digits/underscore, e.g. GITHUB_TOKEN; the model uses the full name with the $DSH_VAR_ prefix.",
|
|
480
|
+
valueLabel: "Value",
|
|
481
|
+
valueHint: "Write-only after saving; stored in the host credential store, never in chat.",
|
|
482
|
+
descLabel: "Description (optional)",
|
|
483
|
+
descHint: "Human-readable note only; never enters prompts.",
|
|
484
|
+
save: "Save",
|
|
485
|
+
saving: "Saving…",
|
|
486
|
+
delete: "Delete",
|
|
487
|
+
copy: "Copy variable name",
|
|
488
|
+
copied: "Copied",
|
|
489
|
+
emptyGlobal: "No global variables yet. Once added, the agent can reference them by name.",
|
|
490
|
+
loadFailed: "Failed to load. Try again later.",
|
|
491
|
+
retry: "Retry",
|
|
492
|
+
inheritedList: "Inherited variables (host env)",
|
|
493
|
+
inheritedNote: "From the host process environment; injected by default",
|
|
494
|
+
inheritedEmpty: "No inheritable DSH_VAR_* variables in the host environment.",
|
|
495
|
+
maskGlobal: "Mask globally",
|
|
496
|
+
unmaskGlobal: "Resume injection",
|
|
497
|
+
maskedBadge: "Masked",
|
|
498
|
+
maskedGlobalBadge: "Masked globally",
|
|
499
|
+
sessionManage: "Session variables",
|
|
500
|
+
sessionPick: "Select session",
|
|
501
|
+
sessionPickEmpty: "No sessions",
|
|
502
|
+
sessionSecrets: "Session variables",
|
|
503
|
+
sessionList: "Session variables",
|
|
504
|
+
sessionEmpty: "No session variables yet.",
|
|
505
|
+
sessionHint: "Available to shell commands of this session only; expires when the session ends or after one use.",
|
|
506
|
+
addSession: "Add session variable",
|
|
507
|
+
onceLabel: "One-time (destroyed after first use)",
|
|
508
|
+
globalInSession: "Global variables (can be masked in this session)",
|
|
509
|
+
inheritedInSession: "Inherited variables (can be masked in this session)",
|
|
510
|
+
maskSession: "Mask in this session",
|
|
511
|
+
unmaskSession: "Resume in this session",
|
|
512
|
+
close: "Close",
|
|
513
|
+
refresh: "Refresh",
|
|
514
|
+
scopeGlobal: "global",
|
|
515
|
+
scopeSession: "session",
|
|
516
|
+
scopeOnce: "once",
|
|
517
|
+
scopeInherited: "inherited",
|
|
518
|
+
"menu.title": "Variables",
|
|
519
|
+
"menu.aria": "Variable suggestions",
|
|
520
|
+
"command.description": "Manage this session's env variables (keys, links, etc.)",
|
|
521
|
+
"command.openPanel": "Open the session variable panel",
|
|
522
|
+
"error.invalid-name": "Invalid name: start with an uppercase letter; letters/digits/underscore only, ≤64 chars.",
|
|
523
|
+
"error.empty-value": "Value must not be empty.",
|
|
524
|
+
"error.shadowed": "A same-named process environment variable shadows this write; unset it first.",
|
|
525
|
+
"error.conflict": "The variable conflicts with a managed variable registered by another plugin.",
|
|
526
|
+
"error.no-session": "Missing session id; refresh and retry.",
|
|
527
|
+
"error.method": "Method not allowed.",
|
|
528
|
+
"error.internal": "Internal error; check the host log."
|
|
529
|
+
};
|
|
530
|
+
const zh = mergeDict(commonZh, ownZh);
|
|
531
|
+
const en = mergeDict(commonEn, ownEn);
|
|
532
|
+
//#endregion
|
|
178
533
|
//#region src/client/api.ts
|
|
179
534
|
/**
|
|
180
535
|
* 数据端点通道:GET list / POST set/unset(同源 fetch,值不上行到任何对话通道)。
|
|
@@ -223,477 +578,93 @@ ${extra}
|
|
|
223
578
|
name
|
|
224
579
|
});
|
|
225
580
|
}
|
|
581
|
+
/** 屏蔽/恢复:sessionId 缺席为全局屏蔽(设置页),否则为会话级屏蔽。 */
|
|
582
|
+
function setMask(name, masked, sessionId) {
|
|
583
|
+
return write("mask/set", {
|
|
584
|
+
name,
|
|
585
|
+
masked,
|
|
586
|
+
sessionId
|
|
587
|
+
});
|
|
588
|
+
}
|
|
226
589
|
//#endregion
|
|
227
|
-
//#region src/client/
|
|
590
|
+
//#region src/client/menu-core.ts
|
|
591
|
+
const WORD_CHAR = /[\p{L}\p{N}_]/u;
|
|
592
|
+
const TOKEN_CHAR = /^[A-Za-z0-9_]$/;
|
|
228
593
|
/**
|
|
229
|
-
*
|
|
230
|
-
*
|
|
594
|
+
* 检测光标处的 `$` 触发令牌。词法:
|
|
595
|
+
* - 从光标向左扫描,token 字符(字母/数字/下划线)继续,空白直接判负;
|
|
596
|
+
* - 遇到 `$` 时校验词边界:起草开头 / 前字符为空白 / 前字符为非词字符(标点)
|
|
597
|
+
* 才开闸——`foo$BAR` 这类词中 $ 不触发(与官方 URL 内 / 不触发同理);
|
|
598
|
+
* - 其余字符(如 `{`、`(`)终止扫描。
|
|
231
599
|
*/
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
600
|
+
function detectSecretTrigger(draft, caret) {
|
|
601
|
+
if (caret <= 0 || caret > draft.length) return null;
|
|
602
|
+
for (let i = caret - 1; i >= 0; i--) {
|
|
603
|
+
const ch = draft.charAt(i);
|
|
604
|
+
if (/\s/u.test(ch)) return null;
|
|
605
|
+
if (ch !== "$") {
|
|
606
|
+
if (!TOKEN_CHAR.test(ch)) return null;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
if (i > 0) {
|
|
610
|
+
const prev = draft.charAt(i - 1);
|
|
611
|
+
if (!/\s/u.test(prev) && WORD_CHAR.test(prev)) return null;
|
|
612
|
+
}
|
|
613
|
+
return {
|
|
614
|
+
query: draft.slice(i + 1, caret),
|
|
615
|
+
start: i,
|
|
616
|
+
end: caret
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
return null;
|
|
237
620
|
}
|
|
238
|
-
/**
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
621
|
+
/** 两个命中的等价判定(抑制重复 setState)。 */
|
|
622
|
+
function sameHit(a, b) {
|
|
623
|
+
if (a === null || b === null) return a === b;
|
|
624
|
+
return a.query === b.query && a.start === b.start && a.end === b.end;
|
|
625
|
+
}
|
|
626
|
+
/** 按 query 过滤候选(大小写不敏感;变量名前缀命中排前,其余子串命中随后)。 */
|
|
627
|
+
function filterCandidates(entries, query) {
|
|
628
|
+
const q = query.toUpperCase();
|
|
629
|
+
const prefixed = [];
|
|
630
|
+
const partial = [];
|
|
631
|
+
for (const entry of entries) {
|
|
632
|
+
if (q === "") {
|
|
633
|
+
prefixed.push(entry);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (entry.envName.toUpperCase().includes(q) || entry.name.toUpperCase().includes(q)) {
|
|
637
|
+
if (entry.name.toUpperCase().startsWith(q) || entry.envName.toUpperCase().includes(`_${q}`)) prefixed.push(entry);
|
|
638
|
+
else partial.push(entry);
|
|
639
|
+
}
|
|
253
640
|
}
|
|
641
|
+
return [...prefixed, ...partial];
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* 渲染文本偏移 → 草稿投影偏移。contenteditable 中芯片(引用装饰器)的
|
|
645
|
+
* 渲染文本与其草稿投影长度不等,光标渲染偏移需逐芯片修正。
|
|
646
|
+
*/
|
|
647
|
+
function applyChipCorrection(renderedOffset, chipsBeforeCaret) {
|
|
648
|
+
let offset = renderedOffset;
|
|
649
|
+
for (const chip of chipsBeforeCaret) offset -= chip.rendered - chip.draft;
|
|
650
|
+
return Math.max(0, offset);
|
|
254
651
|
}
|
|
255
652
|
//#endregion
|
|
256
|
-
//#region src/client/
|
|
653
|
+
//#region src/client/menu.tsx
|
|
257
654
|
/**
|
|
258
|
-
*
|
|
259
|
-
* composer
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
655
|
+
* `$` 触发补全菜单(conversation.input.overlay 官方插槽,session 作用域):
|
|
656
|
+
* 在 composer 中输入 `$` 弹出密钥名候选(对齐官方 / 命令与 @ 引用的交互)。
|
|
657
|
+
*
|
|
658
|
+
* 官方 input-trigger 管线的检测核硬编码 '/' 与 '@'(TriggerChar 联合类型),
|
|
659
|
+
* '$' 进不了官方检测,故检测与菜单由本组件自理,插入复用官方会话作用域
|
|
660
|
+
* bail 通道 'slash/input-insert-text'(span + draftRev CAS,编辑器内应用)。
|
|
661
|
+
* @module secret-env/client/menu
|
|
263
662
|
*/
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
};
|
|
270
|
-
function SessionRow(props) {
|
|
271
|
-
const { t, entry } = props;
|
|
272
|
-
const [copied, setCopied] = (0, react.useState)(false);
|
|
273
|
-
const timer = (0, react.useRef)(null);
|
|
274
|
-
(0, react.useEffect)(() => () => {
|
|
275
|
-
if (timer.current !== null) clearTimeout(timer.current);
|
|
276
|
-
}, []);
|
|
277
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
278
|
-
className: "dse-row",
|
|
279
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
280
|
-
className: "dse-rowMain",
|
|
281
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
282
|
-
className: "dse-env",
|
|
283
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
284
|
-
className: "dse-envName",
|
|
285
|
-
children: ["$", entry.envName]
|
|
286
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
287
|
-
type: "button",
|
|
288
|
-
className: "dse-iconBtn",
|
|
289
|
-
title: t("copy"),
|
|
290
|
-
"aria-label": t("copy"),
|
|
291
|
-
onClick: () => {
|
|
292
|
-
copyText(`$${entry.envName}`).then((ok) => {
|
|
293
|
-
if (!ok) return;
|
|
294
|
-
setCopied(true);
|
|
295
|
-
if (timer.current !== null) clearTimeout(timer.current);
|
|
296
|
-
timer.current = setTimeout(() => setCopied(false), 1500);
|
|
297
|
-
});
|
|
298
|
-
},
|
|
299
|
-
children: copied ? "✓" : "⧉"
|
|
300
|
-
})]
|
|
301
|
-
}), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
302
|
-
className: "dse-note",
|
|
303
|
-
children: entry.description
|
|
304
|
-
}) : null]
|
|
305
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
306
|
-
className: "dse-badges",
|
|
307
|
-
children: [entry.once ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
308
|
-
className: "dse-badge",
|
|
309
|
-
children: t("scopeOnce")
|
|
310
|
-
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
311
|
-
type: "button",
|
|
312
|
-
className: "dse-iconBtn dse-delBtn",
|
|
313
|
-
title: t("delete"),
|
|
314
|
-
"aria-label": t("delete"),
|
|
315
|
-
onClick: () => {
|
|
316
|
-
unsetSession(props.sessionId, entry.name).then(() => props.onDeleted()).catch((error) => props.onError(errorText(t, error)));
|
|
317
|
-
},
|
|
318
|
-
children: "✕"
|
|
319
|
-
})]
|
|
320
|
-
})]
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
function ComposerSecret(props) {
|
|
324
|
-
const { sessionId, t } = props;
|
|
325
|
-
const [open, setOpen] = (0, react.useState)(false);
|
|
326
|
-
const [items, setItems] = (0, react.useState)(null);
|
|
327
|
-
const [form, setForm] = (0, react.useState)(EMPTY_FORM$1);
|
|
328
|
-
const [saving, setSaving] = (0, react.useState)(false);
|
|
329
|
-
const [status, setStatus] = (0, react.useState)(null);
|
|
330
|
-
const wrapRef = (0, react.useRef)(null);
|
|
331
|
-
const load = () => {
|
|
332
|
-
fetchSecrets(sessionId).then((list) => setItems(list.session)).catch(() => setStatus(errorText(t, /* @__PURE__ */ new Error("internal"))));
|
|
333
|
-
};
|
|
334
|
-
(0, react.useEffect)(() => {
|
|
335
|
-
if (!open) return;
|
|
336
|
-
load();
|
|
337
|
-
}, [open, sessionId]);
|
|
338
|
-
(0, react.useEffect)(() => {
|
|
339
|
-
if (!open) return;
|
|
340
|
-
const onDown = (event) => {
|
|
341
|
-
if (wrapRef.current !== null && !wrapRef.current.contains(event.target)) setOpen(false);
|
|
342
|
-
};
|
|
343
|
-
const onKey = (event) => {
|
|
344
|
-
if (event.key === "Escape") setOpen(false);
|
|
345
|
-
};
|
|
346
|
-
document.addEventListener("mousedown", onDown);
|
|
347
|
-
document.addEventListener("keydown", onKey);
|
|
348
|
-
return () => {
|
|
349
|
-
document.removeEventListener("mousedown", onDown);
|
|
350
|
-
document.removeEventListener("keydown", onKey);
|
|
351
|
-
};
|
|
352
|
-
}, [open]);
|
|
353
|
-
const onSave = () => {
|
|
354
|
-
setSaving(true);
|
|
355
|
-
setStatus(null);
|
|
356
|
-
setSession(sessionId, form.name, form.value, form.description, form.once).then(() => {
|
|
357
|
-
setForm(EMPTY_FORM$1);
|
|
358
|
-
load();
|
|
359
|
-
}).catch((error) => setStatus(errorText(t, error))).finally(() => setSaving(false));
|
|
360
|
-
};
|
|
361
|
-
const count = items?.length ?? 0;
|
|
362
|
-
const canSave = form.name.trim() !== "" && form.value !== "" && !saving;
|
|
363
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
364
|
-
className: "dse-wrap",
|
|
365
|
-
ref: wrapRef,
|
|
366
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
367
|
-
type: "button",
|
|
368
|
-
className: "dse-chip",
|
|
369
|
-
"aria-expanded": open,
|
|
370
|
-
"aria-label": t("sessionSecrets"),
|
|
371
|
-
title: t("sessionSecrets"),
|
|
372
|
-
onClick: () => setOpen(!open),
|
|
373
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
374
|
-
width: "14",
|
|
375
|
-
height: "14",
|
|
376
|
-
viewBox: "0 0 24 24",
|
|
377
|
-
fill: "none",
|
|
378
|
-
"aria-hidden": "true",
|
|
379
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
380
|
-
d: "M14.5 2a7.5 7.5 0 0 0-7.36 9.04L2 20.19V22h1.81l1.5-1.5v-1.81h1.81v-1.81h1.81l1.22-1.22A7.5 7.5 0 1 0 14.5 2Zm2 5.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3Z",
|
|
381
|
-
fill: "currentColor"
|
|
382
|
-
})
|
|
383
|
-
}), count > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
384
|
-
className: "dse-count",
|
|
385
|
-
children: count
|
|
386
|
-
}) : null]
|
|
387
|
-
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
388
|
-
className: "dse-pop",
|
|
389
|
-
role: "dialog",
|
|
390
|
-
"aria-label": t("sessionSecrets"),
|
|
391
|
-
children: [
|
|
392
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
393
|
-
className: "dse-popHead",
|
|
394
|
-
children: [
|
|
395
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
396
|
-
className: "dse-popTitle",
|
|
397
|
-
children: t("sessionSecrets")
|
|
398
|
-
}),
|
|
399
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
400
|
-
type: "button",
|
|
401
|
-
className: "dse-iconBtn",
|
|
402
|
-
title: t("refresh"),
|
|
403
|
-
"aria-label": t("refresh"),
|
|
404
|
-
onClick: load,
|
|
405
|
-
children: "⟳"
|
|
406
|
-
}),
|
|
407
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
408
|
-
type: "button",
|
|
409
|
-
className: "dse-iconBtn",
|
|
410
|
-
title: t("close"),
|
|
411
|
-
"aria-label": t("close"),
|
|
412
|
-
onClick: () => setOpen(false),
|
|
413
|
-
children: "✕"
|
|
414
|
-
})
|
|
415
|
-
]
|
|
416
|
-
}),
|
|
417
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
418
|
-
className: "dse-popHint",
|
|
419
|
-
children: t("sessionHint")
|
|
420
|
-
}),
|
|
421
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
422
|
-
className: "dse-popList",
|
|
423
|
-
children: items === null || items.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
424
|
-
className: "dse-empty",
|
|
425
|
-
children: t("sessionEmpty")
|
|
426
|
-
}) : items.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
|
|
427
|
-
t,
|
|
428
|
-
sessionId,
|
|
429
|
-
entry,
|
|
430
|
-
onDeleted: load,
|
|
431
|
-
onError: (text) => setStatus(text === "" ? null : text)
|
|
432
|
-
}, entry.name))
|
|
433
|
-
}),
|
|
434
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
435
|
-
className: "dse-popForm",
|
|
436
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
437
|
-
className: "dse-field",
|
|
438
|
-
children: [
|
|
439
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
440
|
-
className: "dse-head",
|
|
441
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
442
|
-
className: "dse-label",
|
|
443
|
-
htmlFor: "dse-s-name",
|
|
444
|
-
children: t("addSession")
|
|
445
|
-
})
|
|
446
|
-
}),
|
|
447
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
448
|
-
id: "dse-s-name",
|
|
449
|
-
className: "dse-input",
|
|
450
|
-
placeholder: "API_TOKEN",
|
|
451
|
-
value: form.name,
|
|
452
|
-
onChange: (event) => setForm({
|
|
453
|
-
...form,
|
|
454
|
-
name: event.target.value
|
|
455
|
-
})
|
|
456
|
-
}),
|
|
457
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
458
|
-
className: "dse-input",
|
|
459
|
-
type: "password",
|
|
460
|
-
autoComplete: "off",
|
|
461
|
-
placeholder: t("valueLabel"),
|
|
462
|
-
value: form.value,
|
|
463
|
-
onChange: (event) => setForm({
|
|
464
|
-
...form,
|
|
465
|
-
value: event.target.value
|
|
466
|
-
})
|
|
467
|
-
}),
|
|
468
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
469
|
-
className: "dse-input",
|
|
470
|
-
placeholder: t("descLabel"),
|
|
471
|
-
value: form.description,
|
|
472
|
-
onChange: (event) => setForm({
|
|
473
|
-
...form,
|
|
474
|
-
description: event.target.value
|
|
475
|
-
})
|
|
476
|
-
}),
|
|
477
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
478
|
-
className: "dse-checkRow",
|
|
479
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
480
|
-
id: "dse-s-once",
|
|
481
|
-
type: "checkbox",
|
|
482
|
-
role: "switch",
|
|
483
|
-
"aria-checked": form.once,
|
|
484
|
-
checked: form.once,
|
|
485
|
-
onChange: (event) => setForm({
|
|
486
|
-
...form,
|
|
487
|
-
once: event.target.checked
|
|
488
|
-
})
|
|
489
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
490
|
-
htmlFor: "dse-s-once",
|
|
491
|
-
children: t("onceLabel")
|
|
492
|
-
})]
|
|
493
|
-
})
|
|
494
|
-
]
|
|
495
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
496
|
-
className: "dse-foot",
|
|
497
|
-
children: [status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
498
|
-
className: "dse-status dse-statusError",
|
|
499
|
-
children: status
|
|
500
|
-
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
501
|
-
type: "button",
|
|
502
|
-
className: "dse-btn dse-btnPrimary",
|
|
503
|
-
disabled: !canSave,
|
|
504
|
-
onClick: onSave,
|
|
505
|
-
children: saving ? t("saving") : t("save")
|
|
506
|
-
})]
|
|
507
|
-
})]
|
|
508
|
-
})
|
|
509
|
-
]
|
|
510
|
-
}) : null]
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
//#endregion
|
|
514
|
-
//#region src/client/i18n.ts
|
|
515
|
-
/**
|
|
516
|
-
* 浏览器半文案(zh/en)。公共键来自 shared common,本文件只维护业务键。
|
|
517
|
-
* @module secret-env/client/i18n
|
|
518
|
-
*/
|
|
519
|
-
const NS = "dsh-plus-secret-env";
|
|
520
|
-
const ownZh = {
|
|
521
|
-
nav: "密钥变量",
|
|
522
|
-
title: "密钥变量",
|
|
523
|
-
description: "以 $DSH_SECRET_* 变量名向 agent 暴露密钥:值在每次 shell 调用时由宿主注入,不进入对话记录与提示词,不影响上下文缓存。",
|
|
524
|
-
globalList: "全局密钥",
|
|
525
|
-
colName: "变量名(模型可见)",
|
|
526
|
-
colDescription: "描述",
|
|
527
|
-
colState: "状态",
|
|
528
|
-
colActions: "操作",
|
|
529
|
-
configured: "已配置",
|
|
530
|
-
unconfigured: "未配置",
|
|
531
|
-
readonly: "只读来源",
|
|
532
|
-
addGlobal: "添加全局密钥",
|
|
533
|
-
nameLabel: "变量名后缀",
|
|
534
|
-
nameHint: "大写字母/数字/下划线,如 GITHUB_TOKEN;模型实际使用 $DSH_SECRET_ 前缀拼接后的完整名。",
|
|
535
|
-
valueLabel: "值",
|
|
536
|
-
valueHint: "写入后不可回读;保存于宿主凭据库,不进入对话。",
|
|
537
|
-
descLabel: "描述(可选)",
|
|
538
|
-
descHint: "仅人读备注,不进提示词。",
|
|
539
|
-
save: "保存",
|
|
540
|
-
saving: "保存中…",
|
|
541
|
-
delete: "删除",
|
|
542
|
-
deleteConfirm: "确认删除 {name}?该变量将立即对后续命令失效。",
|
|
543
|
-
copy: "复制变量名",
|
|
544
|
-
copied: "已复制",
|
|
545
|
-
emptyGlobal: "暂无全局密钥。添加后 agent 即可通过变量名引用。",
|
|
546
|
-
loadFailed: "加载失败,请稍后重试。",
|
|
547
|
-
retry: "重试",
|
|
548
|
-
sessionSecrets: "会话密钥",
|
|
549
|
-
sessionEmpty: "本会话暂无有默认值的密钥。",
|
|
550
|
-
sessionHint: "仅当前会话的 shell 命令可用;会话结束或一次性使用即失效。",
|
|
551
|
-
addSession: "添加会话密钥",
|
|
552
|
-
onceLabel: "一次性(首次使用后销毁)",
|
|
553
|
-
close: "关闭",
|
|
554
|
-
refresh: "刷新",
|
|
555
|
-
scopeGlobal: "全局",
|
|
556
|
-
scopeSession: "会话",
|
|
557
|
-
scopeOnce: "一次性",
|
|
558
|
-
"menu.title": "密钥变量",
|
|
559
|
-
"menu.aria": "密钥变量候选",
|
|
560
|
-
"error.invalid-name": "变量名不合法:大写字母开头,仅字母/数字/下划线,≤64 字符。",
|
|
561
|
-
"error.empty-value": "值不能为空。",
|
|
562
|
-
"error.shadowed": "进程环境中已存在同名变量且优先级更高,写入无效;请先取消该环境变量。",
|
|
563
|
-
"error.conflict": "变量名与其他插件注册的受管变量冲突。",
|
|
564
|
-
"error.no-session": "缺少会话标识,请刷新后重试。",
|
|
565
|
-
"error.method": "请求方法不允许。",
|
|
566
|
-
"error.internal": "服务内部错误,请查看宿主日志。"
|
|
567
|
-
};
|
|
568
|
-
const ownEn = {
|
|
569
|
-
nav: "Secret Variables",
|
|
570
|
-
title: "Secret Variables",
|
|
571
|
-
description: "Expose secrets to the agent as $DSH_SECRET_* variables: values are injected by the host into each shell call, never enter the transcript or prompt, and do not affect context caching.",
|
|
572
|
-
globalList: "Global secrets",
|
|
573
|
-
colName: "Variable (model-visible)",
|
|
574
|
-
colDescription: "Description",
|
|
575
|
-
colState: "State",
|
|
576
|
-
colActions: "Actions",
|
|
577
|
-
configured: "Configured",
|
|
578
|
-
unconfigured: "Not configured",
|
|
579
|
-
readonly: "Read-only source",
|
|
580
|
-
addGlobal: "Add global secret",
|
|
581
|
-
nameLabel: "Name suffix",
|
|
582
|
-
nameHint: "Uppercase letters/digits/underscore, e.g. GITHUB_TOKEN; the model uses the full name with the $DSH_SECRET_ prefix.",
|
|
583
|
-
valueLabel: "Value",
|
|
584
|
-
valueHint: "Write-only after saving; stored in the host credential store, never in chat.",
|
|
585
|
-
descLabel: "Description (optional)",
|
|
586
|
-
descHint: "Human-readable note only; never enters prompts.",
|
|
587
|
-
save: "Save",
|
|
588
|
-
saving: "Saving…",
|
|
589
|
-
delete: "Delete",
|
|
590
|
-
deleteConfirm: "Delete {name}? The variable stops working for subsequent commands immediately.",
|
|
591
|
-
copy: "Copy variable name",
|
|
592
|
-
copied: "Copied",
|
|
593
|
-
emptyGlobal: "No global secrets yet. Once added, the agent can reference them by name.",
|
|
594
|
-
loadFailed: "Failed to load. Try again later.",
|
|
595
|
-
retry: "Retry",
|
|
596
|
-
sessionSecrets: "Session secrets",
|
|
597
|
-
sessionEmpty: "No session secrets yet.",
|
|
598
|
-
sessionHint: "Available to shell commands of this session only; expires when the session ends or after one use.",
|
|
599
|
-
addSession: "Add session secret",
|
|
600
|
-
onceLabel: "One-time (destroyed after first use)",
|
|
601
|
-
close: "Close",
|
|
602
|
-
refresh: "Refresh",
|
|
603
|
-
scopeGlobal: "global",
|
|
604
|
-
scopeSession: "session",
|
|
605
|
-
scopeOnce: "once",
|
|
606
|
-
"menu.title": "Secret variables",
|
|
607
|
-
"menu.aria": "Secret variable suggestions",
|
|
608
|
-
"error.invalid-name": "Invalid name: start with an uppercase letter; letters/digits/underscore only, ≤64 chars.",
|
|
609
|
-
"error.empty-value": "Value must not be empty.",
|
|
610
|
-
"error.shadowed": "A same-named process environment variable shadows this write; unset it first.",
|
|
611
|
-
"error.conflict": "The variable conflicts with a managed variable registered by another plugin.",
|
|
612
|
-
"error.no-session": "Missing session id; refresh and retry.",
|
|
613
|
-
"error.method": "Method not allowed.",
|
|
614
|
-
"error.internal": "Internal error; check the host log."
|
|
615
|
-
};
|
|
616
|
-
const zh = mergeDict(commonZh, ownZh);
|
|
617
|
-
const en = mergeDict(commonEn, ownEn);
|
|
618
|
-
//#endregion
|
|
619
|
-
//#region src/client/menu-core.ts
|
|
620
|
-
const WORD_CHAR = /[\p{L}\p{N}_]/u;
|
|
621
|
-
const TOKEN_CHAR = /^[A-Za-z0-9_]$/;
|
|
622
|
-
/**
|
|
623
|
-
* 检测光标处的 `$` 触发令牌。词法:
|
|
624
|
-
* - 从光标向左扫描,token 字符(字母/数字/下划线)继续,空白直接判负;
|
|
625
|
-
* - 遇到 `$` 时校验词边界:起草开头 / 前字符为空白 / 前字符为非词字符(标点)
|
|
626
|
-
* 才开闸——`foo$BAR` 这类词中 $ 不触发(与官方 URL 内 / 不触发同理);
|
|
627
|
-
* - 其余字符(如 `{`、`(`)终止扫描。
|
|
628
|
-
*/
|
|
629
|
-
function detectSecretTrigger(draft, caret) {
|
|
630
|
-
if (caret <= 0 || caret > draft.length) return null;
|
|
631
|
-
for (let i = caret - 1; i >= 0; i--) {
|
|
632
|
-
const ch = draft.charAt(i);
|
|
633
|
-
if (/\s/u.test(ch)) return null;
|
|
634
|
-
if (ch !== "$") {
|
|
635
|
-
if (!TOKEN_CHAR.test(ch)) return null;
|
|
636
|
-
continue;
|
|
637
|
-
}
|
|
638
|
-
if (i > 0) {
|
|
639
|
-
const prev = draft.charAt(i - 1);
|
|
640
|
-
if (!/\s/u.test(prev) && WORD_CHAR.test(prev)) return null;
|
|
641
|
-
}
|
|
642
|
-
return {
|
|
643
|
-
query: draft.slice(i + 1, caret),
|
|
644
|
-
start: i,
|
|
645
|
-
end: caret
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
/** 两个命中的等价判定(抑制重复 setState)。 */
|
|
651
|
-
function sameHit(a, b) {
|
|
652
|
-
if (a === null || b === null) return a === b;
|
|
653
|
-
return a.query === b.query && a.start === b.start && a.end === b.end;
|
|
654
|
-
}
|
|
655
|
-
/** 按 query 过滤候选(大小写不敏感;变量名前缀命中排前,其余子串命中随后)。 */
|
|
656
|
-
function filterCandidates(entries, query) {
|
|
657
|
-
const q = query.toUpperCase();
|
|
658
|
-
const prefixed = [];
|
|
659
|
-
const partial = [];
|
|
660
|
-
for (const entry of entries) {
|
|
661
|
-
if (q === "") {
|
|
662
|
-
prefixed.push(entry);
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
if (entry.envName.toUpperCase().includes(q) || entry.name.toUpperCase().includes(q)) {
|
|
666
|
-
if (entry.name.toUpperCase().startsWith(q) || entry.envName.toUpperCase().includes(`_${q}`)) prefixed.push(entry);
|
|
667
|
-
else partial.push(entry);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
return [...prefixed, ...partial];
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
|
-
* 渲染文本偏移 → 草稿投影偏移。contenteditable 中芯片(引用装饰器)的
|
|
674
|
-
* 渲染文本与其草稿投影长度不等,光标渲染偏移需逐芯片修正。
|
|
675
|
-
*/
|
|
676
|
-
function applyChipCorrection(renderedOffset, chipsBeforeCaret) {
|
|
677
|
-
let offset = renderedOffset;
|
|
678
|
-
for (const chip of chipsBeforeCaret) offset -= chip.rendered - chip.draft;
|
|
679
|
-
return Math.max(0, offset);
|
|
680
|
-
}
|
|
681
|
-
//#endregion
|
|
682
|
-
//#region src/client/menu.tsx
|
|
683
|
-
/**
|
|
684
|
-
* `$` 触发补全菜单(conversation.input.overlay 官方插槽,session 作用域):
|
|
685
|
-
* 在 composer 中输入 `$` 弹出密钥名候选(对齐官方 / 命令与 @ 引用的交互)。
|
|
686
|
-
*
|
|
687
|
-
* 官方 input-trigger 管线的检测核硬编码 '/' 与 '@'(TriggerChar 联合类型),
|
|
688
|
-
* '$' 进不了官方检测,故检测与菜单由本组件自理,插入复用官方会话作用域
|
|
689
|
-
* bail 通道 'slash/input-insert-text'(span + draftRev CAS,编辑器内应用)。
|
|
690
|
-
* @module secret-env/client/menu
|
|
691
|
-
*/
|
|
692
|
-
const FALLBACK_INPUT = {
|
|
693
|
-
draft: "",
|
|
694
|
-
draftRev: 0,
|
|
695
|
-
phase: "plain",
|
|
696
|
-
occurrences: []
|
|
663
|
+
const FALLBACK_INPUT = {
|
|
664
|
+
draft: "",
|
|
665
|
+
draftRev: 0,
|
|
666
|
+
phase: "plain",
|
|
667
|
+
occurrences: []
|
|
697
668
|
};
|
|
698
669
|
/** 本组件容器向上找到 composer 卡片与 Lexical contenteditable 根。 */
|
|
699
670
|
function editorRootOf(el) {
|
|
@@ -728,18 +699,27 @@ ${extra}
|
|
|
728
699
|
return applyChipCorrection(rendered, corrections);
|
|
729
700
|
}
|
|
730
701
|
function toCandidates(list) {
|
|
731
|
-
return [
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
702
|
+
return [
|
|
703
|
+
...list.session.map((entry) => ({
|
|
704
|
+
envName: entry.envName,
|
|
705
|
+
name: entry.name,
|
|
706
|
+
description: entry.description,
|
|
707
|
+
scope: "session",
|
|
708
|
+
once: entry.once
|
|
709
|
+
})),
|
|
710
|
+
...list.global.filter((entry) => !entry.masked).map((entry) => ({
|
|
711
|
+
envName: entry.envName,
|
|
712
|
+
name: entry.name,
|
|
713
|
+
description: entry.description,
|
|
714
|
+
scope: "global"
|
|
715
|
+
})),
|
|
716
|
+
...list.inherited.filter((entry) => !entry.masked).map((entry) => ({
|
|
717
|
+
envName: entry.envName,
|
|
718
|
+
name: entry.name,
|
|
719
|
+
description: "",
|
|
720
|
+
scope: "inherited"
|
|
721
|
+
}))
|
|
722
|
+
];
|
|
743
723
|
}
|
|
744
724
|
function SecretMenu(props) {
|
|
745
725
|
const { sessionId, t, insertToken } = props;
|
|
@@ -784,137 +764,589 @@ ${extra}
|
|
|
784
764
|
const candidates = hit === null || items === null ? [] : filterCandidates(items, hit.query);
|
|
785
765
|
const open = hit !== null && candidates.length > 0;
|
|
786
766
|
(0, react.useEffect)(() => {
|
|
787
|
-
const root = editorRootOf(wrapRef.current);
|
|
788
|
-
if (root === null) return;
|
|
789
|
-
const onStart = () => {
|
|
790
|
-
composingRef.current = true;
|
|
791
|
-
};
|
|
792
|
-
const onEnd = () => {
|
|
793
|
-
composingRef.current = false;
|
|
794
|
-
};
|
|
795
|
-
root.addEventListener("compositionstart", onStart);
|
|
796
|
-
root.addEventListener("compositionend", onEnd);
|
|
797
|
-
return () => {
|
|
798
|
-
root.removeEventListener("compositionstart", onStart);
|
|
799
|
-
root.removeEventListener("compositionend", onEnd);
|
|
800
|
-
};
|
|
801
|
-
}, []);
|
|
802
|
-
const pick = (entry) => {
|
|
803
|
-
if (hit === null) return;
|
|
804
|
-
const span = {
|
|
805
|
-
start: hit.start,
|
|
806
|
-
end: hit.end,
|
|
807
|
-
draftRev: input.draftRev
|
|
808
|
-
};
|
|
809
|
-
insertToken(`$${entry.envName} `, span);
|
|
810
|
-
suppressedRef.current = null;
|
|
811
|
-
setHit(null);
|
|
767
|
+
const root = editorRootOf(wrapRef.current);
|
|
768
|
+
if (root === null) return;
|
|
769
|
+
const onStart = () => {
|
|
770
|
+
composingRef.current = true;
|
|
771
|
+
};
|
|
772
|
+
const onEnd = () => {
|
|
773
|
+
composingRef.current = false;
|
|
774
|
+
};
|
|
775
|
+
root.addEventListener("compositionstart", onStart);
|
|
776
|
+
root.addEventListener("compositionend", onEnd);
|
|
777
|
+
return () => {
|
|
778
|
+
root.removeEventListener("compositionstart", onStart);
|
|
779
|
+
root.removeEventListener("compositionend", onEnd);
|
|
780
|
+
};
|
|
781
|
+
}, []);
|
|
782
|
+
const pick = (entry) => {
|
|
783
|
+
if (hit === null) return;
|
|
784
|
+
const span = {
|
|
785
|
+
start: hit.start,
|
|
786
|
+
end: hit.end,
|
|
787
|
+
draftRev: input.draftRev
|
|
788
|
+
};
|
|
789
|
+
insertToken(`$${entry.envName} `, span);
|
|
790
|
+
suppressedRef.current = null;
|
|
791
|
+
setHit(null);
|
|
792
|
+
};
|
|
793
|
+
const pickRef = (0, react.useRef)(pick);
|
|
794
|
+
pickRef.current = pick;
|
|
795
|
+
const candidatesRef = (0, react.useRef)(candidates);
|
|
796
|
+
candidatesRef.current = candidates;
|
|
797
|
+
const highlightRef = (0, react.useRef)(highlight);
|
|
798
|
+
highlightRef.current = highlight;
|
|
799
|
+
(0, react.useEffect)(() => {
|
|
800
|
+
if (!open) return;
|
|
801
|
+
const root = editorRootOf(wrapRef.current);
|
|
802
|
+
if (root === null) return;
|
|
803
|
+
const onKey = (event) => {
|
|
804
|
+
const list = candidatesRef.current;
|
|
805
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
806
|
+
event.preventDefault();
|
|
807
|
+
event.stopPropagation();
|
|
808
|
+
const delta = event.key === "ArrowDown" ? 1 : -1;
|
|
809
|
+
setHighlight((prev) => (prev + delta + list.length) % list.length);
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
if (event.key === "Enter" || event.key === "Tab") {
|
|
813
|
+
const entry = list[highlightRef.current] ?? list[0];
|
|
814
|
+
if (entry !== void 0) {
|
|
815
|
+
event.preventDefault();
|
|
816
|
+
event.stopPropagation();
|
|
817
|
+
pickRef.current(entry);
|
|
818
|
+
}
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (event.key === "Escape") {
|
|
822
|
+
event.preventDefault();
|
|
823
|
+
event.stopPropagation();
|
|
824
|
+
suppressedRef.current = hit;
|
|
825
|
+
setHit(null);
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
root.addEventListener("keydown", onKey, true);
|
|
829
|
+
return () => root.removeEventListener("keydown", onKey, true);
|
|
830
|
+
}, [open, hit]);
|
|
831
|
+
(0, react.useEffect)(() => {
|
|
832
|
+
if (!open) return;
|
|
833
|
+
const onDown = (event) => {
|
|
834
|
+
if (!(event.target instanceof Node)) return;
|
|
835
|
+
if ((wrapRef.current?.closest("[data-composer-card]"))?.contains(event.target) === true) return;
|
|
836
|
+
setHit(null);
|
|
837
|
+
};
|
|
838
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
839
|
+
return () => document.removeEventListener("pointerdown", onDown, true);
|
|
840
|
+
}, [open]);
|
|
841
|
+
(0, react.useEffect)(() => {
|
|
842
|
+
setHighlight((prev) => candidates.length === 0 ? 0 : Math.min(prev, candidates.length - 1));
|
|
843
|
+
}, [candidates.length]);
|
|
844
|
+
if (!open) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
845
|
+
className: "dse-menuWrap",
|
|
846
|
+
ref: wrapRef
|
|
847
|
+
});
|
|
848
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
849
|
+
className: "dse-menuWrap",
|
|
850
|
+
ref: wrapRef,
|
|
851
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
852
|
+
className: "dse-menu",
|
|
853
|
+
role: "listbox",
|
|
854
|
+
"aria-label": t("menu.aria"),
|
|
855
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
856
|
+
className: "dse-menuTitle",
|
|
857
|
+
children: t("menu.title")
|
|
858
|
+
}), candidates.map((entry, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
859
|
+
type: "button",
|
|
860
|
+
role: "option",
|
|
861
|
+
"aria-selected": index === highlight,
|
|
862
|
+
title: `$${entry.envName}`,
|
|
863
|
+
className: `dse-menuItem${index === highlight ? " dse-menuItemActive" : ""}`,
|
|
864
|
+
onMouseEnter: () => setHighlight(index),
|
|
865
|
+
onMouseDown: (event) => {
|
|
866
|
+
event.preventDefault();
|
|
867
|
+
pick(entry);
|
|
868
|
+
},
|
|
869
|
+
children: [
|
|
870
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
871
|
+
className: "dse-menuName",
|
|
872
|
+
children: entry.name
|
|
873
|
+
}),
|
|
874
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
875
|
+
className: "dse-menuDesc",
|
|
876
|
+
children: entry.description
|
|
877
|
+
}),
|
|
878
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
879
|
+
className: "dse-menuBadges",
|
|
880
|
+
children: [entry.once === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
881
|
+
className: "dse-badge",
|
|
882
|
+
children: t("scopeOnce")
|
|
883
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
884
|
+
className: "dse-badge dse-badgeDim",
|
|
885
|
+
children: entry.scope === "session" ? t("scopeSession") : entry.scope === "inherited" ? t("scopeInherited") : t("scopeGlobal")
|
|
886
|
+
})]
|
|
887
|
+
})
|
|
888
|
+
]
|
|
889
|
+
}, `${entry.scope}:${entry.name}`))]
|
|
890
|
+
})
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region src/names.ts
|
|
895
|
+
/** 后缀语法:POSIX 环境变量名,大写字母开头,总长(含前缀)不超过 79。 */
|
|
896
|
+
const SUFFIX_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
897
|
+
const MAX_SUFFIX_LENGTH = 64;
|
|
898
|
+
/** 规范化用户输入:去空白并转大写。 */
|
|
899
|
+
function normalizeSuffix(raw) {
|
|
900
|
+
return raw.trim().toUpperCase();
|
|
901
|
+
}
|
|
902
|
+
/** 校验规范化后的后缀;合法返回 null,非法返回失败码。 */
|
|
903
|
+
function validateSuffix(suffix) {
|
|
904
|
+
if (suffix.length === 0) return "empty";
|
|
905
|
+
if (suffix.length > MAX_SUFFIX_LENGTH) return "too-long";
|
|
906
|
+
if (!SUFFIX_PATTERN.test(suffix)) return "bad-charset";
|
|
907
|
+
return null;
|
|
908
|
+
}
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/client/common.ts
|
|
911
|
+
/**
|
|
912
|
+
* 浏览器半共享小件:错误码文案映射、复制助手与变量名输入规则
|
|
913
|
+
* (设置页与会话控件共用;名称规则与 node 半 names.ts 同源)。
|
|
914
|
+
* @module secret-env/client/common
|
|
915
|
+
*/
|
|
916
|
+
/** 端点错误 → 本地化文案(未知码回落 internal)。 */
|
|
917
|
+
function errorText(t, error) {
|
|
918
|
+
const code = error instanceof ApiError ? error.code : "internal";
|
|
919
|
+
const text = t(`error.${code}`);
|
|
920
|
+
return text === `error.${code}` ? t("error.internal") : text;
|
|
921
|
+
}
|
|
922
|
+
/** 名称输入的实时规范化:键入即转大写(去空白交给保存时的 normalizeSuffix)。 */
|
|
923
|
+
function liveName(raw) {
|
|
924
|
+
return raw.toUpperCase();
|
|
925
|
+
}
|
|
926
|
+
/** 名称输入的实时校验:空串不标错(必填兜底),其余按 names.ts 规则判定。 */
|
|
927
|
+
function nameErrorOf(raw) {
|
|
928
|
+
if (raw.trim() === "") return null;
|
|
929
|
+
return validateSuffix(normalizeSuffix(raw));
|
|
930
|
+
}
|
|
931
|
+
/** 复制文本到剪贴板(clipboard API 缺席时退 execCommand)。 */
|
|
932
|
+
async function copyText(text) {
|
|
933
|
+
try {
|
|
934
|
+
await navigator.clipboard.writeText(text);
|
|
935
|
+
return true;
|
|
936
|
+
} catch {
|
|
937
|
+
const area = document.createElement("textarea");
|
|
938
|
+
area.value = text;
|
|
939
|
+
area.style.position = "fixed";
|
|
940
|
+
area.style.opacity = "0";
|
|
941
|
+
document.body.appendChild(area);
|
|
942
|
+
area.select();
|
|
943
|
+
const ok = document.execCommand("copy");
|
|
944
|
+
area.remove();
|
|
945
|
+
return ok;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
//#endregion
|
|
949
|
+
//#region src/client/panel.tsx
|
|
950
|
+
/**
|
|
951
|
+
* 会话密钥面板(复用件):会话级密钥的列表/增删 + 全局与继承变量的
|
|
952
|
+
* 会话内屏蔽开关。两个宿主共用——overlay 槽的面板宿主(/secret 命令唤起,
|
|
953
|
+
* 传 onClose)与设置页的会话管理区(选择会话后内嵌,无头栏)。
|
|
954
|
+
* 值只经同源端点下行,绝不回显。
|
|
955
|
+
* @module secret-env/client/panel
|
|
956
|
+
*/
|
|
957
|
+
const EMPTY_FORM$1 = {
|
|
958
|
+
name: "",
|
|
959
|
+
value: "",
|
|
960
|
+
description: "",
|
|
961
|
+
once: false
|
|
962
|
+
};
|
|
963
|
+
/** 复制按钮(复制成功短暂亮勾)。 */
|
|
964
|
+
function CopyButton$1(props) {
|
|
965
|
+
const { t } = props;
|
|
966
|
+
const [copied, setCopied] = (0, react.useState)(false);
|
|
967
|
+
const timer = (0, react.useRef)(null);
|
|
968
|
+
(0, react.useEffect)(() => () => {
|
|
969
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
970
|
+
}, []);
|
|
971
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
972
|
+
type: "button",
|
|
973
|
+
className: "dse-iconBtn",
|
|
974
|
+
title: t("copy"),
|
|
975
|
+
"aria-label": t("copy"),
|
|
976
|
+
onClick: () => {
|
|
977
|
+
copyText(props.text).then((ok) => {
|
|
978
|
+
if (!ok) return;
|
|
979
|
+
setCopied(true);
|
|
980
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
981
|
+
timer.current = setTimeout(() => setCopied(false), 1500);
|
|
982
|
+
});
|
|
983
|
+
},
|
|
984
|
+
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 })
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
/** 会话内屏蔽开关(眼睛语义:可见=注入,划掉=本会话屏蔽)。 */
|
|
988
|
+
function MaskToggle(props) {
|
|
989
|
+
const { t, masked } = props;
|
|
990
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
991
|
+
type: "button",
|
|
992
|
+
className: `dse-iconBtn dse-maskBtn${masked ? " dse-maskBtnOn" : ""}`,
|
|
993
|
+
title: masked ? t("unmaskSession") : t("maskSession"),
|
|
994
|
+
"aria-label": masked ? t("unmaskSession") : t("maskSession"),
|
|
995
|
+
"aria-pressed": masked,
|
|
996
|
+
onClick: props.onToggle,
|
|
997
|
+
children: masked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEyeOff, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEye, { size: 14 })
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
function SessionRow(props) {
|
|
1001
|
+
const { t, entry } = props;
|
|
1002
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1003
|
+
className: "dse-row",
|
|
1004
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1005
|
+
className: "dse-rowMain",
|
|
1006
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1007
|
+
className: "dse-env",
|
|
1008
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1009
|
+
className: "dse-envName",
|
|
1010
|
+
children: ["$", entry.envName]
|
|
1011
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton$1, {
|
|
1012
|
+
t,
|
|
1013
|
+
text: `$${entry.envName}`
|
|
1014
|
+
})]
|
|
1015
|
+
}), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1016
|
+
className: "dse-note",
|
|
1017
|
+
children: entry.description
|
|
1018
|
+
}) : null]
|
|
1019
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1020
|
+
className: "dse-badges",
|
|
1021
|
+
children: [entry.once ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1022
|
+
className: "dse-badge",
|
|
1023
|
+
children: t("scopeOnce")
|
|
1024
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1025
|
+
type: "button",
|
|
1026
|
+
className: "dse-iconBtn dse-delBtn",
|
|
1027
|
+
title: t("delete"),
|
|
1028
|
+
"aria-label": t("delete"),
|
|
1029
|
+
onClick: () => {
|
|
1030
|
+
unsetSession(props.sessionId, entry.name).then(() => props.onChanged()).catch((error) => props.onError(errorText(t, error)));
|
|
1031
|
+
},
|
|
1032
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
1033
|
+
})]
|
|
1034
|
+
})]
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
function GlobalRow$1(props) {
|
|
1038
|
+
const { t, entry } = props;
|
|
1039
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1040
|
+
className: "dse-row",
|
|
1041
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1042
|
+
className: "dse-rowMain",
|
|
1043
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1044
|
+
className: "dse-env",
|
|
1045
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1046
|
+
className: `dse-envName${entry.masked ? " dse-envNameDim" : ""}`,
|
|
1047
|
+
children: ["$", entry.envName]
|
|
1048
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton$1, {
|
|
1049
|
+
t,
|
|
1050
|
+
text: `$${entry.envName}`
|
|
1051
|
+
})]
|
|
1052
|
+
}), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1053
|
+
className: "dse-note",
|
|
1054
|
+
children: entry.description
|
|
1055
|
+
}) : null]
|
|
1056
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1057
|
+
className: "dse-badges",
|
|
1058
|
+
children: [entry.masked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1059
|
+
className: "dse-badge",
|
|
1060
|
+
children: t("maskedBadge")
|
|
1061
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MaskToggle, {
|
|
1062
|
+
t,
|
|
1063
|
+
masked: entry.masked,
|
|
1064
|
+
onToggle: () => {
|
|
1065
|
+
setMask(entry.name, !entry.masked, props.sessionId).then(() => props.onChanged()).catch((error) => props.onError(errorText(t, error)));
|
|
1066
|
+
}
|
|
1067
|
+
})]
|
|
1068
|
+
})]
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
function InheritedRow$1(props) {
|
|
1072
|
+
const { t, entry } = props;
|
|
1073
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1074
|
+
className: "dse-row",
|
|
1075
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1076
|
+
className: "dse-rowMain",
|
|
1077
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1078
|
+
className: "dse-env",
|
|
1079
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1080
|
+
className: `dse-envName${entry.masked ? " dse-envNameDim" : ""}`,
|
|
1081
|
+
children: ["$", entry.envName]
|
|
1082
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton$1, {
|
|
1083
|
+
t,
|
|
1084
|
+
text: `$${entry.envName}`
|
|
1085
|
+
})]
|
|
1086
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1087
|
+
className: "dse-note",
|
|
1088
|
+
children: t("inheritedNote")
|
|
1089
|
+
})]
|
|
1090
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1091
|
+
className: "dse-badges",
|
|
1092
|
+
children: entry.globallyMasked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1093
|
+
className: "dse-badge dse-badgeDim",
|
|
1094
|
+
children: t("maskedGlobalBadge")
|
|
1095
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [entry.masked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1096
|
+
className: "dse-badge",
|
|
1097
|
+
children: t("maskedBadge")
|
|
1098
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MaskToggle, {
|
|
1099
|
+
t,
|
|
1100
|
+
masked: entry.masked,
|
|
1101
|
+
onToggle: () => {
|
|
1102
|
+
setMask(entry.name, !entry.masked, props.sessionId).then(() => props.onChanged()).catch((error) => props.onError(errorText(t, error)));
|
|
1103
|
+
}
|
|
1104
|
+
})] })
|
|
1105
|
+
})]
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
function SessionSecretsPanel(props) {
|
|
1109
|
+
const { sessionId, t } = props;
|
|
1110
|
+
const [data, setData] = (0, react.useState)(null);
|
|
1111
|
+
const [form, setForm] = (0, react.useState)(EMPTY_FORM$1);
|
|
1112
|
+
const [saving, setSaving] = (0, react.useState)(false);
|
|
1113
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
1114
|
+
const load = () => {
|
|
1115
|
+
fetchSecrets(sessionId).then((list) => setData(list)).catch(() => setStatus(errorText(t, /* @__PURE__ */ new Error("internal"))));
|
|
1116
|
+
};
|
|
1117
|
+
(0, react.useEffect)(() => {
|
|
1118
|
+
load();
|
|
1119
|
+
}, [sessionId]);
|
|
1120
|
+
const onSave = () => {
|
|
1121
|
+
setSaving(true);
|
|
1122
|
+
setStatus(null);
|
|
1123
|
+
setSession(sessionId, form.name, form.value, form.description, form.once).then(() => {
|
|
1124
|
+
setForm(EMPTY_FORM$1);
|
|
1125
|
+
load();
|
|
1126
|
+
}).catch((error) => setStatus(errorText(t, error))).finally(() => setSaving(false));
|
|
812
1127
|
};
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
1128
|
+
const onError = (text) => setStatus(text === "" ? null : text);
|
|
1129
|
+
const nameError = nameErrorOf(form.name);
|
|
1130
|
+
const canSave = form.name.trim() !== "" && nameError === null && form.value !== "" && !saving;
|
|
1131
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1132
|
+
className: "dse-panel",
|
|
1133
|
+
children: [
|
|
1134
|
+
props.onClose !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1135
|
+
className: "dse-popHead",
|
|
1136
|
+
children: [
|
|
1137
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1138
|
+
className: "dse-popTitle",
|
|
1139
|
+
children: t("sessionSecrets")
|
|
1140
|
+
}),
|
|
1141
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1142
|
+
type: "button",
|
|
1143
|
+
className: "dse-iconBtn",
|
|
1144
|
+
title: t("refresh"),
|
|
1145
|
+
"aria-label": t("refresh"),
|
|
1146
|
+
onClick: load,
|
|
1147
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline16, { size: 14 })
|
|
1148
|
+
}),
|
|
1149
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1150
|
+
type: "button",
|
|
1151
|
+
className: "dse-iconBtn",
|
|
1152
|
+
title: t("close"),
|
|
1153
|
+
"aria-label": t("close"),
|
|
1154
|
+
onClick: props.onClose,
|
|
1155
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 14 })
|
|
1156
|
+
})
|
|
1157
|
+
]
|
|
1158
|
+
}) : null,
|
|
1159
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1160
|
+
className: "dse-popHint",
|
|
1161
|
+
children: t("sessionHint")
|
|
1162
|
+
}),
|
|
1163
|
+
status !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1164
|
+
className: "dse-status dse-statusError",
|
|
1165
|
+
children: status
|
|
1166
|
+
}) : null,
|
|
1167
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1168
|
+
className: "dse-groupLabel",
|
|
1169
|
+
children: t("sessionList")
|
|
1170
|
+
}),
|
|
1171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1172
|
+
className: "dse-popList",
|
|
1173
|
+
children: data === null || data.session.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1174
|
+
className: "dse-empty",
|
|
1175
|
+
children: t("sessionEmpty")
|
|
1176
|
+
}) : data.session.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionRow, {
|
|
1177
|
+
t,
|
|
1178
|
+
sessionId,
|
|
1179
|
+
entry,
|
|
1180
|
+
onChanged: load,
|
|
1181
|
+
onError
|
|
1182
|
+
}, entry.name))
|
|
1183
|
+
}),
|
|
1184
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1185
|
+
className: "dse-popForm",
|
|
1186
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1187
|
+
className: "dse-field",
|
|
1188
|
+
children: [
|
|
1189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1190
|
+
className: "dse-head",
|
|
1191
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1192
|
+
className: "dse-label",
|
|
1193
|
+
htmlFor: "dse-s-name",
|
|
1194
|
+
children: t("addSession")
|
|
1195
|
+
})
|
|
1196
|
+
}),
|
|
1197
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1198
|
+
id: "dse-s-name",
|
|
1199
|
+
className: `dse-input${nameError !== null ? " dse-inputError" : ""}`,
|
|
1200
|
+
placeholder: "API_TOKEN",
|
|
1201
|
+
value: form.name,
|
|
1202
|
+
"aria-invalid": nameError !== null,
|
|
1203
|
+
onChange: (event) => setForm({
|
|
1204
|
+
...form,
|
|
1205
|
+
name: liveName(event.target.value)
|
|
1206
|
+
})
|
|
1207
|
+
}),
|
|
1208
|
+
nameError !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1209
|
+
className: "dse-hint dse-hintError",
|
|
1210
|
+
children: t("error.invalid-name")
|
|
1211
|
+
}) : null,
|
|
1212
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1213
|
+
className: "dse-input",
|
|
1214
|
+
type: "password",
|
|
1215
|
+
autoComplete: "off",
|
|
1216
|
+
placeholder: t("valueLabel"),
|
|
1217
|
+
value: form.value,
|
|
1218
|
+
onChange: (event) => setForm({
|
|
1219
|
+
...form,
|
|
1220
|
+
value: event.target.value
|
|
1221
|
+
})
|
|
1222
|
+
}),
|
|
1223
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1224
|
+
className: "dse-input",
|
|
1225
|
+
placeholder: t("descLabel"),
|
|
1226
|
+
value: form.description,
|
|
1227
|
+
onChange: (event) => setForm({
|
|
1228
|
+
...form,
|
|
1229
|
+
description: event.target.value
|
|
1230
|
+
})
|
|
1231
|
+
}),
|
|
1232
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1233
|
+
className: "dse-checkRow",
|
|
1234
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1235
|
+
id: "dse-s-once",
|
|
1236
|
+
type: "checkbox",
|
|
1237
|
+
role: "switch",
|
|
1238
|
+
"aria-checked": form.once,
|
|
1239
|
+
checked: form.once,
|
|
1240
|
+
onChange: (event) => setForm({
|
|
1241
|
+
...form,
|
|
1242
|
+
once: event.target.checked
|
|
1243
|
+
})
|
|
1244
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1245
|
+
htmlFor: "dse-s-once",
|
|
1246
|
+
children: t("onceLabel")
|
|
1247
|
+
})]
|
|
1248
|
+
})
|
|
1249
|
+
]
|
|
1250
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1251
|
+
className: "dse-foot",
|
|
1252
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1253
|
+
type: "button",
|
|
1254
|
+
className: "dse-btn dse-btnPrimary",
|
|
1255
|
+
disabled: !canSave,
|
|
1256
|
+
onClick: onSave,
|
|
1257
|
+
children: saving ? t("saving") : t("save")
|
|
1258
|
+
})
|
|
1259
|
+
})]
|
|
1260
|
+
}),
|
|
1261
|
+
data !== null && data.global.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1262
|
+
className: "dse-groupLabel",
|
|
1263
|
+
children: t("globalInSession")
|
|
1264
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1265
|
+
className: "dse-popList",
|
|
1266
|
+
children: data.global.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GlobalRow$1, {
|
|
1267
|
+
t,
|
|
1268
|
+
sessionId,
|
|
1269
|
+
entry,
|
|
1270
|
+
onChanged: load,
|
|
1271
|
+
onError
|
|
1272
|
+
}, entry.name))
|
|
1273
|
+
})] }) : null,
|
|
1274
|
+
data !== null && data.inherited.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h4", {
|
|
1275
|
+
className: "dse-groupLabel",
|
|
1276
|
+
children: t("inheritedInSession")
|
|
1277
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1278
|
+
className: "dse-popList",
|
|
1279
|
+
children: data.inherited.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InheritedRow$1, {
|
|
1280
|
+
t,
|
|
1281
|
+
sessionId,
|
|
1282
|
+
entry,
|
|
1283
|
+
onChanged: load,
|
|
1284
|
+
onError
|
|
1285
|
+
}, entry.name))
|
|
1286
|
+
})] }) : null
|
|
1287
|
+
]
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
//#endregion
|
|
1291
|
+
//#region src/client/panel-host.tsx
|
|
1292
|
+
/**
|
|
1293
|
+
* 会话变量面板的 overlay 宿主(conversation.input.overlay 官方插槽,
|
|
1294
|
+
* session 作用域):常驻监听打开总线,/var 命令选中后弹出面板。
|
|
1295
|
+
* 弹层定位于 composer 卡片上方(与 $ 菜单同一浮层语言);
|
|
1296
|
+
* 点外部或 Esc 关闭。闭合态不渲染面板(数据在打开时现取)。
|
|
1297
|
+
* @module secret-env/client/panel-host
|
|
1298
|
+
*/
|
|
1299
|
+
function SessionPanelHost(props) {
|
|
1300
|
+
const { sessionId, t } = props;
|
|
1301
|
+
const [open, setOpen] = (0, react.useState)(false);
|
|
1302
|
+
const wrapRef = (0, react.useRef)(null);
|
|
1303
|
+
(0, react.useEffect)(() => onOpenSessionPanel((target) => {
|
|
1304
|
+
if (target === sessionId) setOpen(true);
|
|
1305
|
+
}), [sessionId]);
|
|
819
1306
|
(0, react.useEffect)(() => {
|
|
820
1307
|
if (!open) return;
|
|
821
|
-
const root = editorRootOf(wrapRef.current);
|
|
822
|
-
if (root === null) return;
|
|
823
1308
|
const onKey = (event) => {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const delta = event.key === "ArrowDown" ? 1 : -1;
|
|
829
|
-
setHighlight((prev) => (prev + delta + list.length) % list.length);
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
if (event.key === "Enter" || event.key === "Tab") {
|
|
833
|
-
const entry = list[highlightRef.current] ?? list[0];
|
|
834
|
-
if (entry !== void 0) {
|
|
835
|
-
event.preventDefault();
|
|
836
|
-
event.stopPropagation();
|
|
837
|
-
pickRef.current(entry);
|
|
838
|
-
}
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
if (event.key === "Escape") {
|
|
842
|
-
event.preventDefault();
|
|
843
|
-
event.stopPropagation();
|
|
844
|
-
suppressedRef.current = hit;
|
|
845
|
-
setHit(null);
|
|
846
|
-
}
|
|
1309
|
+
if (event.key !== "Escape") return;
|
|
1310
|
+
event.preventDefault();
|
|
1311
|
+
event.stopPropagation();
|
|
1312
|
+
setOpen(false);
|
|
847
1313
|
};
|
|
848
|
-
root.addEventListener("keydown", onKey, true);
|
|
849
|
-
return () => root.removeEventListener("keydown", onKey, true);
|
|
850
|
-
}, [open, hit]);
|
|
851
|
-
(0, react.useEffect)(() => {
|
|
852
|
-
if (!open) return;
|
|
853
1314
|
const onDown = (event) => {
|
|
854
1315
|
if (!(event.target instanceof Node)) return;
|
|
855
|
-
if (
|
|
856
|
-
|
|
1316
|
+
if (wrapRef.current?.contains(event.target) === true) return;
|
|
1317
|
+
setOpen(false);
|
|
857
1318
|
};
|
|
1319
|
+
document.addEventListener("keydown", onKey, true);
|
|
858
1320
|
document.addEventListener("pointerdown", onDown, true);
|
|
859
|
-
return () =>
|
|
1321
|
+
return () => {
|
|
1322
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1323
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1324
|
+
};
|
|
860
1325
|
}, [open]);
|
|
861
|
-
|
|
862
|
-
setHighlight((prev) => candidates.length === 0 ? 0 : Math.min(prev, candidates.length - 1));
|
|
863
|
-
}, [candidates.length]);
|
|
864
|
-
if (!open) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
865
|
-
className: "dse-menuWrap",
|
|
866
|
-
ref: wrapRef
|
|
867
|
-
});
|
|
1326
|
+
if (!open) return null;
|
|
868
1327
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
869
|
-
className: "dse-
|
|
1328
|
+
className: "dse-panelWrap",
|
|
870
1329
|
ref: wrapRef,
|
|
871
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.
|
|
872
|
-
className: "dse-
|
|
873
|
-
role: "
|
|
874
|
-
"aria-label": t("
|
|
875
|
-
children:
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
role: "option",
|
|
881
|
-
"aria-selected": index === highlight,
|
|
882
|
-
className: `dse-menuItem${index === highlight ? " dse-menuItemActive" : ""}`,
|
|
883
|
-
onMouseEnter: () => setHighlight(index),
|
|
884
|
-
onMouseDown: (event) => {
|
|
885
|
-
event.preventDefault();
|
|
886
|
-
pick(entry);
|
|
887
|
-
},
|
|
888
|
-
children: [
|
|
889
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
890
|
-
className: "dse-menuName",
|
|
891
|
-
children: ["$", entry.envName]
|
|
892
|
-
}),
|
|
893
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
894
|
-
className: "dse-menuDesc",
|
|
895
|
-
children: entry.description
|
|
896
|
-
}),
|
|
897
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
898
|
-
className: "dse-menuBadges",
|
|
899
|
-
children: [entry.once === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
900
|
-
className: "dse-badge",
|
|
901
|
-
children: t("scopeOnce")
|
|
902
|
-
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
903
|
-
className: "dse-badge dse-badgeDim",
|
|
904
|
-
children: entry.scope === "session" ? t("scopeSession") : t("scopeGlobal")
|
|
905
|
-
})]
|
|
906
|
-
})
|
|
907
|
-
]
|
|
908
|
-
}, `${entry.scope}:${entry.name}`))]
|
|
1330
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1331
|
+
className: "dse-panelCard",
|
|
1332
|
+
role: "dialog",
|
|
1333
|
+
"aria-label": t("sessionSecrets"),
|
|
1334
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionSecretsPanel, {
|
|
1335
|
+
sessionId,
|
|
1336
|
+
t,
|
|
1337
|
+
onClose: () => setOpen(false)
|
|
1338
|
+
})
|
|
909
1339
|
})
|
|
910
1340
|
});
|
|
911
1341
|
}
|
|
912
1342
|
//#endregion
|
|
913
1343
|
//#region src/client/section.tsx
|
|
914
1344
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
1345
|
+
* 环境变量设置页(settings.section 官方插槽):
|
|
1346
|
+
* - 全局变量:列表与增删(值写入后不可回读,端点不出值);
|
|
1347
|
+
* - 继承变量:识别宿主进程环境的 DSH_VAR_*(默认纳入注入),可全局屏蔽;
|
|
1348
|
+
* - 会话变量:经会话选择器内嵌会话面板(会话级/一次性变量的增删与屏蔽)。
|
|
1349
|
+
* 只展示元数据与模型可见的完整变量名。响应式:≤767px 行转堆叠卡、表单单列。
|
|
918
1350
|
* @module secret-env/client/section
|
|
919
1351
|
*/
|
|
920
1352
|
const EMPTY_FORM = {
|
|
@@ -922,23 +1354,34 @@ ${extra}
|
|
|
922
1354
|
value: "",
|
|
923
1355
|
description: ""
|
|
924
1356
|
};
|
|
925
|
-
/**
|
|
926
|
-
function
|
|
927
|
-
const { t
|
|
928
|
-
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
1357
|
+
/** 复制按钮(复制成功短暂亮勾)。 */
|
|
1358
|
+
function CopyButton(props) {
|
|
1359
|
+
const { t } = props;
|
|
929
1360
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
930
1361
|
const timer = (0, react.useRef)(null);
|
|
931
1362
|
(0, react.useEffect)(() => () => {
|
|
932
1363
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
933
1364
|
}, []);
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1365
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1366
|
+
type: "button",
|
|
1367
|
+
className: "dse-iconBtn",
|
|
1368
|
+
title: t("copy"),
|
|
1369
|
+
"aria-label": t("copy"),
|
|
1370
|
+
onClick: () => {
|
|
1371
|
+
copyText(props.text).then((ok) => {
|
|
1372
|
+
if (!ok) return;
|
|
1373
|
+
setCopied(true);
|
|
1374
|
+
if (timer.current !== null) clearTimeout(timer.current);
|
|
1375
|
+
timer.current = setTimeout(() => setCopied(false), 1500);
|
|
1376
|
+
});
|
|
1377
|
+
},
|
|
1378
|
+
children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 14 })
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
/** 全局变量行:完整变量名(复制)+ 描述 + 状态徽标 + 两步删除。 */
|
|
1382
|
+
function GlobalRow(props) {
|
|
1383
|
+
const { t, entry } = props;
|
|
1384
|
+
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
942
1385
|
const onDelete = () => {
|
|
943
1386
|
unsetGlobal(entry.name).then(() => props.onDeleted()).catch((error) => props.onError(errorText(t, error)));
|
|
944
1387
|
};
|
|
@@ -951,13 +1394,9 @@ ${extra}
|
|
|
951
1394
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
952
1395
|
className: "dse-envName",
|
|
953
1396
|
children: ["$", entry.envName]
|
|
954
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
title: t("copy"),
|
|
958
|
-
"aria-label": t("copy"),
|
|
959
|
-
onClick: onCopy,
|
|
960
|
-
children: copied ? "✓" : "⧉"
|
|
1397
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton, {
|
|
1398
|
+
t,
|
|
1399
|
+
text: `$${entry.envName}`
|
|
961
1400
|
})]
|
|
962
1401
|
}), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
963
1402
|
className: "dse-note",
|
|
@@ -991,12 +1430,94 @@ ${extra}
|
|
|
991
1430
|
title: t("delete"),
|
|
992
1431
|
"aria-label": t("delete"),
|
|
993
1432
|
onClick: () => setConfirming(true),
|
|
994
|
-
children:
|
|
1433
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
995
1434
|
})
|
|
996
1435
|
]
|
|
997
1436
|
})]
|
|
998
1437
|
});
|
|
999
1438
|
}
|
|
1439
|
+
/** 继承变量行:来源说明 + 全局屏蔽开关(眼睛语义)。 */
|
|
1440
|
+
function InheritedRow(props) {
|
|
1441
|
+
const { t, entry } = props;
|
|
1442
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1443
|
+
className: "dse-row",
|
|
1444
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1445
|
+
className: "dse-rowMain",
|
|
1446
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1447
|
+
className: "dse-env",
|
|
1448
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
1449
|
+
className: `dse-envName${entry.masked ? " dse-envNameDim" : ""}`,
|
|
1450
|
+
children: ["$", entry.envName]
|
|
1451
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton, {
|
|
1452
|
+
t,
|
|
1453
|
+
text: `$${entry.envName}`
|
|
1454
|
+
})]
|
|
1455
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1456
|
+
className: "dse-note",
|
|
1457
|
+
children: t("inheritedNote")
|
|
1458
|
+
})]
|
|
1459
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1460
|
+
className: "dse-badges",
|
|
1461
|
+
children: [entry.masked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1462
|
+
className: "dse-badge",
|
|
1463
|
+
children: t("maskedBadge")
|
|
1464
|
+
}) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1465
|
+
type: "button",
|
|
1466
|
+
className: `dse-iconBtn dse-maskBtn${entry.masked ? " dse-maskBtnOn" : ""}`,
|
|
1467
|
+
title: entry.masked ? t("unmaskGlobal") : t("maskGlobal"),
|
|
1468
|
+
"aria-label": entry.masked ? t("unmaskGlobal") : t("maskGlobal"),
|
|
1469
|
+
"aria-pressed": entry.masked,
|
|
1470
|
+
onClick: () => {
|
|
1471
|
+
setMask(entry.name, !entry.masked).then(() => props.onChanged()).catch((error) => props.onError(errorText(t, error)));
|
|
1472
|
+
},
|
|
1473
|
+
children: entry.masked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEyeOff, { size: 14 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconEye, { size: 14 })
|
|
1474
|
+
})]
|
|
1475
|
+
})]
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
/** 会话变量管理块:会话选择器 + 内嵌会话面板。 */
|
|
1479
|
+
function SessionManageBlock(props) {
|
|
1480
|
+
const { t, sessions } = props;
|
|
1481
|
+
const snapshot = (0, react.useSyncExternalStore)((listener) => sessions.list.subscribe(listener), () => sessions.list.getSnapshot());
|
|
1482
|
+
const [picked, setPicked] = (0, react.useState)("");
|
|
1483
|
+
(0, react.useEffect)(() => {
|
|
1484
|
+
sessions.refresh().catch(() => {});
|
|
1485
|
+
}, []);
|
|
1486
|
+
const rows = Object.values(snapshot.byId);
|
|
1487
|
+
const current = rows.find((row) => row.id === picked)?.id ?? snapshot.current ?? rows[0]?.id ?? "";
|
|
1488
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1489
|
+
className: "dse-groupLabel",
|
|
1490
|
+
children: t("sessionManage")
|
|
1491
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1492
|
+
className: "dse-form",
|
|
1493
|
+
children: rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1494
|
+
className: "dse-empty",
|
|
1495
|
+
children: t("sessionPickEmpty")
|
|
1496
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1497
|
+
className: "dse-field",
|
|
1498
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1499
|
+
className: "dse-head",
|
|
1500
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", {
|
|
1501
|
+
className: "dse-label",
|
|
1502
|
+
htmlFor: "dse-session-pick",
|
|
1503
|
+
children: t("sessionPick")
|
|
1504
|
+
})
|
|
1505
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
1506
|
+
id: "dse-session-pick",
|
|
1507
|
+
className: "dse-input",
|
|
1508
|
+
value: current,
|
|
1509
|
+
onChange: (event) => setPicked(event.target.value),
|
|
1510
|
+
children: rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
1511
|
+
value: row.id,
|
|
1512
|
+
children: row.displayTitle
|
|
1513
|
+
}, row.id))
|
|
1514
|
+
})]
|
|
1515
|
+
}), current !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionSecretsPanel, {
|
|
1516
|
+
sessionId: current,
|
|
1517
|
+
t
|
|
1518
|
+
}, current) : null] })
|
|
1519
|
+
})] });
|
|
1520
|
+
}
|
|
1000
1521
|
function SecretsSection(props) {
|
|
1001
1522
|
const { t } = props;
|
|
1002
1523
|
const [data, setData] = (0, react.useState)(null);
|
|
@@ -1042,7 +1563,8 @@ ${extra}
|
|
|
1042
1563
|
children: t("retry")
|
|
1043
1564
|
}) : null]
|
|
1044
1565
|
});
|
|
1045
|
-
const
|
|
1566
|
+
const nameError = nameErrorOf(form.name);
|
|
1567
|
+
const canSave = form.name.trim() !== "" && nameError === null && form.value !== "" && !saving;
|
|
1046
1568
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1047
1569
|
className: "dse-section",
|
|
1048
1570
|
children: [
|
|
@@ -1075,6 +1597,29 @@ ${extra}
|
|
|
1075
1597
|
})
|
|
1076
1598
|
}, entry.name))
|
|
1077
1599
|
}),
|
|
1600
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1601
|
+
className: "dse-groupLabel",
|
|
1602
|
+
children: t("inheritedList")
|
|
1603
|
+
}),
|
|
1604
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1605
|
+
className: "dse-rows",
|
|
1606
|
+
children: data.inherited.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1607
|
+
className: "dse-empty",
|
|
1608
|
+
children: t("inheritedEmpty")
|
|
1609
|
+
}) : data.inherited.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InheritedRow, {
|
|
1610
|
+
t,
|
|
1611
|
+
entry,
|
|
1612
|
+
onChanged: load,
|
|
1613
|
+
onError: (text) => setStatus({
|
|
1614
|
+
kind: "err",
|
|
1615
|
+
text
|
|
1616
|
+
})
|
|
1617
|
+
}, entry.name))
|
|
1618
|
+
}),
|
|
1619
|
+
props.sessions !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionManageBlock, {
|
|
1620
|
+
t,
|
|
1621
|
+
sessions: props.sessions
|
|
1622
|
+
}) : null,
|
|
1078
1623
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
1079
1624
|
className: "dse-groupLabel",
|
|
1080
1625
|
children: t("addGlobal")
|
|
@@ -1097,17 +1642,18 @@ ${extra}
|
|
|
1097
1642
|
}),
|
|
1098
1643
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
1099
1644
|
id: "dse-name",
|
|
1100
|
-
className:
|
|
1645
|
+
className: `dse-input${nameError !== null ? " dse-inputError" : ""}`,
|
|
1101
1646
|
value: form.name,
|
|
1102
1647
|
placeholder: "GITHUB_TOKEN",
|
|
1648
|
+
"aria-invalid": nameError !== null,
|
|
1103
1649
|
onChange: (event) => setForm({
|
|
1104
1650
|
...form,
|
|
1105
|
-
name: event.target.value
|
|
1651
|
+
name: liveName(event.target.value)
|
|
1106
1652
|
})
|
|
1107
1653
|
}),
|
|
1108
1654
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1109
|
-
className:
|
|
1110
|
-
children: t("nameHint")
|
|
1655
|
+
className: `dse-hint${nameError !== null ? " dse-hintError" : ""}`,
|
|
1656
|
+
children: nameError !== null ? t("error.invalid-name") : t("nameHint")
|
|
1111
1657
|
})
|
|
1112
1658
|
]
|
|
1113
1659
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1215,23 +1761,22 @@ ${extra}
|
|
|
1215
1761
|
.dse-delBtn:hover{color:var(--dsw-alias-label-error)}
|
|
1216
1762
|
.dse-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;padding:18px 14px;margin:0;background:var(--dsw-alias-bg-layer-3)}
|
|
1217
1763
|
.dse-form{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-3);padding:4px 14px 12px;margin-top:10px}
|
|
1218
|
-
.dse-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
.dse-
|
|
1222
|
-
.dse-
|
|
1223
|
-
.dse-
|
|
1224
|
-
.dse-
|
|
1225
|
-
.dse-count{background:var(--dsw-alias-brand-primary);color:var(--dsw-alias-bg-base);border-radius:999px;min-width:16px;text-align:center;font-size:11px;line-height:16px;padding:0 4px}
|
|
1226
|
-
.dse-pop{position:absolute;bottom:calc(100% + 8px);right:0;z-index:60;width:320px;max-width:calc(100vw - 24px);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.18);padding:10px 12px 12px}
|
|
1764
|
+
.dse-inputError{border-color:var(--dsw-alias-label-error)}
|
|
1765
|
+
.dse-hintError{color:var(--dsw-alias-label-error)}
|
|
1766
|
+
/* 会话变量面板(/var 命令唤起,overlay 槽浮层)与内嵌共用件 */
|
|
1767
|
+
.dse-panelWrap{position:relative}
|
|
1768
|
+
.dse-panelCard{position:absolute;bottom:calc(100% + 6px);left:0;right:0;z-index:60;max-height:min(60vh,560px);overflow:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.18);padding:10px 12px 12px}
|
|
1769
|
+
.dse-maskBtnOn{color:var(--dsw-alias-label-error)}
|
|
1770
|
+
.dse-envNameDim{opacity:.55;text-decoration:line-through}
|
|
1227
1771
|
.dse-popHead{align-items:center;gap:8px;display:flex;padding:2px 0 8px}
|
|
1228
1772
|
.dse-popTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;flex:1;margin:0}
|
|
1229
1773
|
.dse-popHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin:0 0 8px}
|
|
1230
|
-
.dse-popList{flex-direction:column;display:flex;
|
|
1774
|
+
.dse-popList{flex-direction:column;display:flex;margin:0 -4px;padding:0 4px}
|
|
1231
1775
|
.dse-popList .dse-row{background:0 0;border-radius:8px;padding:8px 8px}
|
|
1232
1776
|
.dse-popList .dse-row+.dse-row{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
1233
1777
|
.dse-popForm{border-top:1px solid var(--dsw-alias-border-l2);margin-top:8px;padding-top:4px}
|
|
1234
1778
|
.dse-foot{justify-content:flex-end;gap:8px;display:flex;padding-top:8px}
|
|
1779
|
+
.dse-formGrid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px}
|
|
1235
1780
|
/* $ 触发补全菜单(仿官方 input-trigger MenuView 的浮层语言) */
|
|
1236
1781
|
.dse-menuWrap{position:relative}
|
|
1237
1782
|
.dse-menu{position:absolute;bottom:calc(100% + 6px);left:8px;z-index:60;min-width:280px;max-width:min(420px,calc(100vw - 32px));max-height:320px;overflow:auto;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;box-shadow:0 8px 28px rgba(0,0,0,.18);padding:4px}
|
|
@@ -1252,11 +1797,9 @@ ${extra}
|
|
|
1252
1797
|
.dse-row{flex-wrap:wrap;padding:12px 12px}
|
|
1253
1798
|
.dse-rowMain{flex-basis:100%}
|
|
1254
1799
|
.dse-badges{margin-left:auto}
|
|
1255
|
-
.dse-
|
|
1256
|
-
.dse-chip{min-height:32px}
|
|
1800
|
+
.dse-panelCard{position:fixed;left:8px;right:8px;bottom:8px;top:auto;max-height:70vh}
|
|
1257
1801
|
}
|
|
1258
1802
|
@media (pointer:coarse){
|
|
1259
|
-
.dse-chip{min-height:32px}
|
|
1260
1803
|
.dse-iconBtn{min-width:44px;min-height:44px;align-items:center;justify-content:center}
|
|
1261
1804
|
}
|
|
1262
1805
|
`;
|
|
@@ -1289,15 +1832,12 @@ ${extra}
|
|
|
1289
1832
|
id: "dsh-plus-secret-env",
|
|
1290
1833
|
order: 16,
|
|
1291
1834
|
label: () => t("nav"),
|
|
1292
|
-
inject: () => ({
|
|
1835
|
+
inject: () => ({
|
|
1836
|
+
t,
|
|
1837
|
+
sessions: c.sessions
|
|
1838
|
+
})
|
|
1293
1839
|
}, SecretsSection));
|
|
1294
|
-
c.
|
|
1295
|
-
name: "conversation.input.right",
|
|
1296
|
-
id: "dsh-plus-secret-env",
|
|
1297
|
-
order: 40,
|
|
1298
|
-
locale: NS,
|
|
1299
|
-
inject: (sessionId) => ({ sessionId })
|
|
1300
|
-
}, ComposerSecret));
|
|
1840
|
+
registerVarCommand(c, c.sessions, t);
|
|
1301
1841
|
c.slots.inject("conversation.input.overlay", () => c.slots.register({
|
|
1302
1842
|
name: "conversation.input.overlay",
|
|
1303
1843
|
id: "dse-secret-menu",
|
|
@@ -1319,6 +1859,13 @@ ${extra}
|
|
|
1319
1859
|
}
|
|
1320
1860
|
})
|
|
1321
1861
|
}, SecretMenu));
|
|
1862
|
+
c.slots.inject("conversation.input.overlay", () => c.slots.register({
|
|
1863
|
+
name: "conversation.input.overlay",
|
|
1864
|
+
id: "dse-secret-panel",
|
|
1865
|
+
order: 20,
|
|
1866
|
+
locale: NS,
|
|
1867
|
+
inject: (sessionId) => ({ sessionId })
|
|
1868
|
+
}, SessionPanelHost));
|
|
1322
1869
|
}
|
|
1323
1870
|
//#endregion
|
|
1324
1871
|
exports.apply = apply;
|