@dsh-plus/secret-env 0.1.0 → 0.1.2
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 +1222 -338
- package/lib/index.d.ts +40 -8
- package/lib/index.js +211 -57
- package/package.json +8 -4
- package/src/api.ts +24 -1
- package/src/client/api.ts +18 -0
- package/src/client/client.ts +59 -14
- package/src/client/command.ts +62 -0
- package/src/client/common.ts +14 -1
- package/src/client/i18n.ts +67 -33
- package/src/client/menu-core.ts +103 -0
- package/src/client/menu.tsx +301 -0
- 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 +25 -14
- 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,10 +578,339 @@ ${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
|
+
}
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/client/menu-core.ts
|
|
591
|
+
const WORD_CHAR = /[\p{L}\p{N}_]/u;
|
|
592
|
+
const TOKEN_CHAR = /^[A-Za-z0-9_]$/;
|
|
593
|
+
/**
|
|
594
|
+
* 检测光标处的 `$` 触发令牌。词法:
|
|
595
|
+
* - 从光标向左扫描,token 字符(字母/数字/下划线)继续,空白直接判负;
|
|
596
|
+
* - 遇到 `$` 时校验词边界:起草开头 / 前字符为空白 / 前字符为非词字符(标点)
|
|
597
|
+
* 才开闸——`foo$BAR` 这类词中 $ 不触发(与官方 URL 内 / 不触发同理);
|
|
598
|
+
* - 其余字符(如 `{`、`(`)终止扫描。
|
|
599
|
+
*/
|
|
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;
|
|
620
|
+
}
|
|
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
|
+
}
|
|
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);
|
|
651
|
+
}
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/client/menu.tsx
|
|
654
|
+
/**
|
|
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
|
|
662
|
+
*/
|
|
663
|
+
const FALLBACK_INPUT = {
|
|
664
|
+
draft: "",
|
|
665
|
+
draftRev: 0,
|
|
666
|
+
phase: "plain",
|
|
667
|
+
occurrences: []
|
|
668
|
+
};
|
|
669
|
+
/** 本组件容器向上找到 composer 卡片与 Lexical contenteditable 根。 */
|
|
670
|
+
function editorRootOf(el) {
|
|
671
|
+
const root = (el?.closest("[data-composer-card]"))?.querySelector("[contenteditable=\"true\"]");
|
|
672
|
+
return root instanceof HTMLElement ? root : null;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* 光标的草稿投影偏移:Range 求光标前渲染文本长,再按光标前的引用芯片
|
|
676
|
+
* (data-composer-chip,DOM 序与 occurrences 的 offset 序一致)修正长度差。
|
|
677
|
+
*/
|
|
678
|
+
function caretDraftOffset(root, occurrences) {
|
|
679
|
+
const sel = window.getSelection();
|
|
680
|
+
if (sel === null || sel.rangeCount === 0 || !sel.isCollapsed) return null;
|
|
681
|
+
const range = sel.getRangeAt(0);
|
|
682
|
+
if (!root.contains(range.startContainer)) return null;
|
|
683
|
+
const pre = document.createRange();
|
|
684
|
+
pre.selectNodeContents(root);
|
|
685
|
+
pre.setEnd(range.startContainer, range.startOffset);
|
|
686
|
+
const rendered = pre.toString().length;
|
|
687
|
+
const corrections = [];
|
|
688
|
+
const chips = root.querySelectorAll("[data-composer-chip]");
|
|
689
|
+
for (let i = 0; i < chips.length; i++) {
|
|
690
|
+
const el = chips[i];
|
|
691
|
+
if (range.comparePoint(el, el.childNodes.length) > 0) continue;
|
|
692
|
+
const occurrence = occurrences[i];
|
|
693
|
+
if (occurrence === void 0) continue;
|
|
694
|
+
corrections.push({
|
|
695
|
+
rendered: el.textContent?.length ?? 0,
|
|
696
|
+
draft: occurrence.length
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
return applyChipCorrection(rendered, corrections);
|
|
700
|
+
}
|
|
701
|
+
function toCandidates(list) {
|
|
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
|
+
];
|
|
723
|
+
}
|
|
724
|
+
function SecretMenu(props) {
|
|
725
|
+
const { sessionId, t, insertToken } = props;
|
|
726
|
+
const input = (props.useInput ?? ((_selector) => FALLBACK_INPUT))((state) => state);
|
|
727
|
+
const [hit, setHit] = (0, react.useState)(null);
|
|
728
|
+
const [items, setItems] = (0, react.useState)(null);
|
|
729
|
+
const [highlight, setHighlight] = (0, react.useState)(0);
|
|
730
|
+
const wrapRef = (0, react.useRef)(null);
|
|
731
|
+
const composingRef = (0, react.useRef)(false);
|
|
732
|
+
/** Esc 抑制:记录被关闭的令牌,token 变化前不重复弹出。 */
|
|
733
|
+
const suppressedRef = (0, react.useRef)(null);
|
|
734
|
+
/** 候选拉取(ref 稳定;挂载、会话切换与菜单展开沿调用)。 */
|
|
735
|
+
const reloadRef = (0, react.useRef)((_sid) => {});
|
|
736
|
+
reloadRef.current = (sid) => {
|
|
737
|
+
fetchSecrets(sid).then((list) => setItems(toCandidates(list))).catch(() => setItems([]));
|
|
738
|
+
};
|
|
739
|
+
/** 上一次检测的展开态(展开沿判定用)。 */
|
|
740
|
+
const wasOpenRef = (0, react.useRef)(false);
|
|
741
|
+
(0, react.useEffect)(() => {
|
|
742
|
+
reloadRef.current(sessionId);
|
|
743
|
+
}, [sessionId]);
|
|
744
|
+
(0, react.useEffect)(() => {
|
|
745
|
+
const update = () => {
|
|
746
|
+
const root = editorRootOf(wrapRef.current);
|
|
747
|
+
if (root === null || composingRef.current || input.phase !== "plain") {
|
|
748
|
+
setHit((prev) => prev === null ? prev : null);
|
|
749
|
+
wasOpenRef.current = false;
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const caret = caretDraftOffset(root, input.occurrences);
|
|
753
|
+
const next = caret === null ? null : detectSecretTrigger(input.draft, caret);
|
|
754
|
+
const suppressed = suppressedRef.current;
|
|
755
|
+
const live = next !== null && suppressed !== null && next.start === suppressed.start && next.query === suppressed.query ? null : next;
|
|
756
|
+
if (live !== null && !wasOpenRef.current) reloadRef.current(sessionId);
|
|
757
|
+
wasOpenRef.current = live !== null;
|
|
758
|
+
setHit((prev) => sameHit(prev, live) ? prev : live);
|
|
759
|
+
};
|
|
760
|
+
update();
|
|
761
|
+
document.addEventListener("selectionchange", update);
|
|
762
|
+
return () => document.removeEventListener("selectionchange", update);
|
|
763
|
+
}, [input, sessionId]);
|
|
764
|
+
const candidates = hit === null || items === null ? [] : filterCandidates(items, hit.query);
|
|
765
|
+
const open = hit !== null && candidates.length > 0;
|
|
766
|
+
(0, react.useEffect)(() => {
|
|
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
|
+
}
|
|
226
909
|
//#endregion
|
|
227
910
|
//#region src/client/common.ts
|
|
228
911
|
/**
|
|
229
|
-
*
|
|
912
|
+
* 浏览器半共享小件:错误码文案映射、复制助手与变量名输入规则
|
|
913
|
+
* (设置页与会话控件共用;名称规则与 node 半 names.ts 同源)。
|
|
230
914
|
* @module secret-env/client/common
|
|
231
915
|
*/
|
|
232
916
|
/** 端点错误 → 本地化文案(未知码回落 internal)。 */
|
|
@@ -235,6 +919,15 @@ ${extra}
|
|
|
235
919
|
const text = t(`error.${code}`);
|
|
236
920
|
return text === `error.${code}` ? t("error.internal") : text;
|
|
237
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
|
+
}
|
|
238
931
|
/** 复制文本到剪贴板(clipboard API 缺席时退 execCommand)。 */
|
|
239
932
|
async function copyText(text) {
|
|
240
933
|
try {
|
|
@@ -253,13 +946,13 @@ ${extra}
|
|
|
253
946
|
}
|
|
254
947
|
}
|
|
255
948
|
//#endregion
|
|
256
|
-
//#region src/client/
|
|
949
|
+
//#region src/client/panel.tsx
|
|
257
950
|
/**
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* @module secret-env/client/
|
|
951
|
+
* 会话密钥面板(复用件):会话级密钥的列表/增删 + 全局与继承变量的
|
|
952
|
+
* 会话内屏蔽开关。两个宿主共用——overlay 槽的面板宿主(/secret 命令唤起,
|
|
953
|
+
* 传 onClose)与设置页的会话管理区(选择会话后内嵌,无头栏)。
|
|
954
|
+
* 值只经同源端点下行,绝不回显。
|
|
955
|
+
* @module secret-env/client/panel
|
|
263
956
|
*/
|
|
264
957
|
const EMPTY_FORM$1 = {
|
|
265
958
|
name: "",
|
|
@@ -267,13 +960,45 @@ ${extra}
|
|
|
267
960
|
description: "",
|
|
268
961
|
once: false
|
|
269
962
|
};
|
|
270
|
-
|
|
271
|
-
|
|
963
|
+
/** 复制按钮(复制成功短暂亮勾)。 */
|
|
964
|
+
function CopyButton$1(props) {
|
|
965
|
+
const { t } = props;
|
|
272
966
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
273
967
|
const timer = (0, react.useRef)(null);
|
|
274
968
|
(0, react.useEffect)(() => () => {
|
|
275
969
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
276
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;
|
|
277
1002
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
278
1003
|
className: "dse-row",
|
|
279
1004
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -283,20 +1008,9 @@ ${extra}
|
|
|
283
1008
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
284
1009
|
className: "dse-envName",
|
|
285
1010
|
children: ["$", entry.envName]
|
|
286
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
287
|
-
|
|
288
|
-
|
|
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 ? "✓" : "⧉"
|
|
1011
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton$1, {
|
|
1012
|
+
t,
|
|
1013
|
+
text: `$${entry.envName}`
|
|
300
1014
|
})]
|
|
301
1015
|
}), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
302
1016
|
className: "dse-note",
|
|
@@ -313,43 +1027,96 @@ ${extra}
|
|
|
313
1027
|
title: t("delete"),
|
|
314
1028
|
"aria-label": t("delete"),
|
|
315
1029
|
onClick: () => {
|
|
316
|
-
unsetSession(props.sessionId, entry.name).then(() => props.
|
|
1030
|
+
unsetSession(props.sessionId, entry.name).then(() => props.onChanged()).catch((error) => props.onError(errorText(t, error)));
|
|
317
1031
|
},
|
|
318
|
-
children:
|
|
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
|
+
}
|
|
319
1067
|
})]
|
|
320
1068
|
})]
|
|
321
1069
|
});
|
|
322
1070
|
}
|
|
323
|
-
function
|
|
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) {
|
|
324
1109
|
const { sessionId, t } = props;
|
|
325
|
-
const [
|
|
326
|
-
const [items, setItems] = (0, react.useState)(null);
|
|
1110
|
+
const [data, setData] = (0, react.useState)(null);
|
|
327
1111
|
const [form, setForm] = (0, react.useState)(EMPTY_FORM$1);
|
|
328
1112
|
const [saving, setSaving] = (0, react.useState)(false);
|
|
329
1113
|
const [status, setStatus] = (0, react.useState)(null);
|
|
330
|
-
const wrapRef = (0, react.useRef)(null);
|
|
331
1114
|
const load = () => {
|
|
332
|
-
fetchSecrets(sessionId).then((list) =>
|
|
1115
|
+
fetchSecrets(sessionId).then((list) => setData(list)).catch(() => setStatus(errorText(t, /* @__PURE__ */ new Error("internal"))));
|
|
333
1116
|
};
|
|
334
1117
|
(0, react.useEffect)(() => {
|
|
335
|
-
if (!open) return;
|
|
336
1118
|
load();
|
|
337
|
-
}, [
|
|
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]);
|
|
1119
|
+
}, [sessionId]);
|
|
353
1120
|
const onSave = () => {
|
|
354
1121
|
setSaving(true);
|
|
355
1122
|
setStatus(null);
|
|
@@ -358,265 +1125,228 @@ ${extra}
|
|
|
358
1125
|
load();
|
|
359
1126
|
}).catch((error) => setStatus(errorText(t, error))).finally(() => setSaving(false));
|
|
360
1127
|
};
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
children: [
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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",
|
|
394
1188
|
children: [
|
|
395
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
396
|
-
className: "dse-
|
|
397
|
-
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
|
+
})
|
|
398
1196
|
}),
|
|
399
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
400
|
-
|
|
401
|
-
className: "dse-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
+
})
|
|
406
1207
|
}),
|
|
407
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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,
|
|
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,
|
|
472
1240
|
onChange: (event) => setForm({
|
|
473
1241
|
...form,
|
|
474
|
-
|
|
1242
|
+
once: event.target.checked
|
|
475
1243
|
})
|
|
476
|
-
}),
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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
|
+
]
|
|
511
1288
|
});
|
|
512
1289
|
}
|
|
513
1290
|
//#endregion
|
|
514
|
-
//#region src/client/
|
|
1291
|
+
//#region src/client/panel-host.tsx
|
|
515
1292
|
/**
|
|
516
|
-
*
|
|
517
|
-
*
|
|
1293
|
+
* 会话变量面板的 overlay 宿主(conversation.input.overlay 官方插槽,
|
|
1294
|
+
* session 作用域):常驻监听打开总线,/var 命令选中后弹出面板。
|
|
1295
|
+
* 弹层定位于 composer 卡片上方(与 $ 菜单同一浮层语言);
|
|
1296
|
+
* 点外部或 Esc 关闭。闭合态不渲染面板(数据在打开时现取)。
|
|
1297
|
+
* @module secret-env/client/panel-host
|
|
518
1298
|
*/
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
"error.no-session": "缺少会话标识,请刷新后重试。",
|
|
563
|
-
"error.method": "请求方法不允许。",
|
|
564
|
-
"error.internal": "服务内部错误,请查看宿主日志。"
|
|
565
|
-
};
|
|
566
|
-
const ownEn = {
|
|
567
|
-
nav: "Secret Variables",
|
|
568
|
-
title: "Secret Variables",
|
|
569
|
-
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.",
|
|
570
|
-
globalList: "Global secrets",
|
|
571
|
-
colName: "Variable (model-visible)",
|
|
572
|
-
colDescription: "Description",
|
|
573
|
-
colState: "State",
|
|
574
|
-
colActions: "Actions",
|
|
575
|
-
configured: "Configured",
|
|
576
|
-
unconfigured: "Not configured",
|
|
577
|
-
readonly: "Read-only source",
|
|
578
|
-
addGlobal: "Add global secret",
|
|
579
|
-
nameLabel: "Name suffix",
|
|
580
|
-
nameHint: "Uppercase letters/digits/underscore, e.g. GITHUB_TOKEN; the model uses the full name with the $DSH_SECRET_ prefix.",
|
|
581
|
-
valueLabel: "Value",
|
|
582
|
-
valueHint: "Write-only after saving; stored in the host credential store, never in chat.",
|
|
583
|
-
descLabel: "Description (optional)",
|
|
584
|
-
descHint: "Human-readable note only; never enters prompts.",
|
|
585
|
-
save: "Save",
|
|
586
|
-
saving: "Saving…",
|
|
587
|
-
delete: "Delete",
|
|
588
|
-
deleteConfirm: "Delete {name}? The variable stops working for subsequent commands immediately.",
|
|
589
|
-
copy: "Copy variable name",
|
|
590
|
-
copied: "Copied",
|
|
591
|
-
emptyGlobal: "No global secrets yet. Once added, the agent can reference them by name.",
|
|
592
|
-
loadFailed: "Failed to load. Try again later.",
|
|
593
|
-
retry: "Retry",
|
|
594
|
-
sessionSecrets: "Session secrets",
|
|
595
|
-
sessionEmpty: "No session secrets yet.",
|
|
596
|
-
sessionHint: "Available to shell commands of this session only; expires when the session ends or after one use.",
|
|
597
|
-
addSession: "Add session secret",
|
|
598
|
-
onceLabel: "One-time (destroyed after first use)",
|
|
599
|
-
close: "Close",
|
|
600
|
-
refresh: "Refresh",
|
|
601
|
-
scopeGlobal: "global",
|
|
602
|
-
scopeSession: "session",
|
|
603
|
-
scopeOnce: "once",
|
|
604
|
-
"error.invalid-name": "Invalid name: start with an uppercase letter; letters/digits/underscore only, ≤64 chars.",
|
|
605
|
-
"error.empty-value": "Value must not be empty.",
|
|
606
|
-
"error.shadowed": "A same-named process environment variable shadows this write; unset it first.",
|
|
607
|
-
"error.conflict": "The variable conflicts with a managed variable registered by another plugin.",
|
|
608
|
-
"error.no-session": "Missing session id; refresh and retry.",
|
|
609
|
-
"error.method": "Method not allowed.",
|
|
610
|
-
"error.internal": "Internal error; check the host log."
|
|
611
|
-
};
|
|
612
|
-
const zh = mergeDict(commonZh, ownZh);
|
|
613
|
-
const en = mergeDict(commonEn, ownEn);
|
|
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]);
|
|
1306
|
+
(0, react.useEffect)(() => {
|
|
1307
|
+
if (!open) return;
|
|
1308
|
+
const onKey = (event) => {
|
|
1309
|
+
if (event.key !== "Escape") return;
|
|
1310
|
+
event.preventDefault();
|
|
1311
|
+
event.stopPropagation();
|
|
1312
|
+
setOpen(false);
|
|
1313
|
+
};
|
|
1314
|
+
const onDown = (event) => {
|
|
1315
|
+
if (!(event.target instanceof Node)) return;
|
|
1316
|
+
if (wrapRef.current?.contains(event.target) === true) return;
|
|
1317
|
+
setOpen(false);
|
|
1318
|
+
};
|
|
1319
|
+
document.addEventListener("keydown", onKey, true);
|
|
1320
|
+
document.addEventListener("pointerdown", onDown, true);
|
|
1321
|
+
return () => {
|
|
1322
|
+
document.removeEventListener("keydown", onKey, true);
|
|
1323
|
+
document.removeEventListener("pointerdown", onDown, true);
|
|
1324
|
+
};
|
|
1325
|
+
}, [open]);
|
|
1326
|
+
if (!open) return null;
|
|
1327
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
1328
|
+
className: "dse-panelWrap",
|
|
1329
|
+
ref: wrapRef,
|
|
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
|
+
})
|
|
1339
|
+
})
|
|
1340
|
+
});
|
|
1341
|
+
}
|
|
614
1342
|
//#endregion
|
|
615
1343
|
//#region src/client/section.tsx
|
|
616
1344
|
/**
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
1345
|
+
* 环境变量设置页(settings.section 官方插槽):
|
|
1346
|
+
* - 全局变量:列表与增删(值写入后不可回读,端点不出值);
|
|
1347
|
+
* - 继承变量:识别宿主进程环境的 DSH_VAR_*(默认纳入注入),可全局屏蔽;
|
|
1348
|
+
* - 会话变量:经会话选择器内嵌会话面板(会话级/一次性变量的增删与屏蔽)。
|
|
1349
|
+
* 只展示元数据与模型可见的完整变量名。响应式:≤767px 行转堆叠卡、表单单列。
|
|
620
1350
|
* @module secret-env/client/section
|
|
621
1351
|
*/
|
|
622
1352
|
const EMPTY_FORM = {
|
|
@@ -624,23 +1354,34 @@ ${extra}
|
|
|
624
1354
|
value: "",
|
|
625
1355
|
description: ""
|
|
626
1356
|
};
|
|
627
|
-
/**
|
|
628
|
-
function
|
|
629
|
-
const { t
|
|
630
|
-
const [confirming, setConfirming] = (0, react.useState)(false);
|
|
1357
|
+
/** 复制按钮(复制成功短暂亮勾)。 */
|
|
1358
|
+
function CopyButton(props) {
|
|
1359
|
+
const { t } = props;
|
|
631
1360
|
const [copied, setCopied] = (0, react.useState)(false);
|
|
632
1361
|
const timer = (0, react.useRef)(null);
|
|
633
1362
|
(0, react.useEffect)(() => () => {
|
|
634
1363
|
if (timer.current !== null) clearTimeout(timer.current);
|
|
635
1364
|
}, []);
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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);
|
|
644
1385
|
const onDelete = () => {
|
|
645
1386
|
unsetGlobal(entry.name).then(() => props.onDeleted()).catch((error) => props.onError(errorText(t, error)));
|
|
646
1387
|
};
|
|
@@ -653,13 +1394,9 @@ ${extra}
|
|
|
653
1394
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
654
1395
|
className: "dse-envName",
|
|
655
1396
|
children: ["$", entry.envName]
|
|
656
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
title: t("copy"),
|
|
660
|
-
"aria-label": t("copy"),
|
|
661
|
-
onClick: onCopy,
|
|
662
|
-
children: copied ? "✓" : "⧉"
|
|
1397
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopyButton, {
|
|
1398
|
+
t,
|
|
1399
|
+
text: `$${entry.envName}`
|
|
663
1400
|
})]
|
|
664
1401
|
}), entry.description !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
665
1402
|
className: "dse-note",
|
|
@@ -693,12 +1430,94 @@ ${extra}
|
|
|
693
1430
|
title: t("delete"),
|
|
694
1431
|
"aria-label": t("delete"),
|
|
695
1432
|
onClick: () => setConfirming(true),
|
|
696
|
-
children:
|
|
1433
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconTrashOutline16, { size: 14 })
|
|
697
1434
|
})
|
|
698
1435
|
]
|
|
699
1436
|
})]
|
|
700
1437
|
});
|
|
701
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
|
+
}
|
|
702
1521
|
function SecretsSection(props) {
|
|
703
1522
|
const { t } = props;
|
|
704
1523
|
const [data, setData] = (0, react.useState)(null);
|
|
@@ -744,7 +1563,8 @@ ${extra}
|
|
|
744
1563
|
children: t("retry")
|
|
745
1564
|
}) : null]
|
|
746
1565
|
});
|
|
747
|
-
const
|
|
1566
|
+
const nameError = nameErrorOf(form.name);
|
|
1567
|
+
const canSave = form.name.trim() !== "" && nameError === null && form.value !== "" && !saving;
|
|
748
1568
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
749
1569
|
className: "dse-section",
|
|
750
1570
|
children: [
|
|
@@ -777,6 +1597,29 @@ ${extra}
|
|
|
777
1597
|
})
|
|
778
1598
|
}, entry.name))
|
|
779
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,
|
|
780
1623
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
781
1624
|
className: "dse-groupLabel",
|
|
782
1625
|
children: t("addGlobal")
|
|
@@ -799,17 +1642,18 @@ ${extra}
|
|
|
799
1642
|
}),
|
|
800
1643
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
801
1644
|
id: "dse-name",
|
|
802
|
-
className:
|
|
1645
|
+
className: `dse-input${nameError !== null ? " dse-inputError" : ""}`,
|
|
803
1646
|
value: form.name,
|
|
804
1647
|
placeholder: "GITHUB_TOKEN",
|
|
1648
|
+
"aria-invalid": nameError !== null,
|
|
805
1649
|
onChange: (event) => setForm({
|
|
806
1650
|
...form,
|
|
807
|
-
name: event.target.value
|
|
1651
|
+
name: liveName(event.target.value)
|
|
808
1652
|
})
|
|
809
1653
|
}),
|
|
810
1654
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
811
|
-
className:
|
|
812
|
-
children: t("nameHint")
|
|
1655
|
+
className: `dse-hint${nameError !== null ? " dse-hintError" : ""}`,
|
|
1656
|
+
children: nameError !== null ? t("error.invalid-name") : t("nameHint")
|
|
813
1657
|
})
|
|
814
1658
|
]
|
|
815
1659
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -917,34 +1761,45 @@ ${extra}
|
|
|
917
1761
|
.dse-delBtn:hover{color:var(--dsw-alias-label-error)}
|
|
918
1762
|
.dse-empty{color:var(--dsw-alias-label-tertiary);font-size:13px;padding:18px 14px;margin:0;background:var(--dsw-alias-bg-layer-3)}
|
|
919
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}
|
|
920
|
-
.dse-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
.dse-
|
|
924
|
-
.dse-
|
|
925
|
-
.dse-
|
|
926
|
-
.dse-
|
|
927
|
-
.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}
|
|
928
|
-
.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}
|
|
929
1771
|
.dse-popHead{align-items:center;gap:8px;display:flex;padding:2px 0 8px}
|
|
930
1772
|
.dse-popTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:600;flex:1;margin:0}
|
|
931
1773
|
.dse-popHint{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5;margin:0 0 8px}
|
|
932
|
-
.dse-popList{flex-direction:column;display:flex;
|
|
1774
|
+
.dse-popList{flex-direction:column;display:flex;margin:0 -4px;padding:0 4px}
|
|
933
1775
|
.dse-popList .dse-row{background:0 0;border-radius:8px;padding:8px 8px}
|
|
934
1776
|
.dse-popList .dse-row+.dse-row{border-top:1px solid var(--dsw-alias-border-l2)}
|
|
935
1777
|
.dse-popForm{border-top:1px solid var(--dsw-alias-border-l2);margin-top:8px;padding-top:4px}
|
|
936
1778
|
.dse-foot{justify-content:flex-end;gap:8px;display:flex;padding-top:8px}
|
|
937
|
-
|
|
1779
|
+
.dse-formGrid{display:grid;grid-template-columns:1fr 1fr;gap:0 16px}
|
|
1780
|
+
/* $ 触发补全菜单(仿官方 input-trigger MenuView 的浮层语言) */
|
|
1781
|
+
.dse-menuWrap{position:relative}
|
|
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}
|
|
1783
|
+
.dse-menuTitle{color:var(--dsw-alias-label-tertiary);font-size:11px;font-weight:600;padding:6px 10px 4px}
|
|
1784
|
+
.dse-menuItem{appearance:none;border:none;background:0 0;cursor:pointer;align-items:center;gap:8px;width:100%;padding:7px 10px;border-radius:8px;display:flex;text-align:left}
|
|
1785
|
+
.dse-menuItem:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1786
|
+
.dse-menuItemActive{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1787
|
+
.dse-menuName{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--dsw-alias-label-primary);font-size:13px;flex:none}
|
|
1788
|
+
.dse-menuDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
1789
|
+
.dse-menuBadges{align-items:center;gap:4px;flex:none;display:flex}
|
|
1790
|
+
@media (max-width:767px){
|
|
1791
|
+
.dse-menu{left:0;right:0;min-width:0;max-width:none;max-height:45vh}
|
|
1792
|
+
.dse-menuItem{min-height:44px}
|
|
1793
|
+
}
|
|
1794
|
+
|
|
938
1795
|
@media (max-width:767px){
|
|
939
1796
|
.dse-formGrid{grid-template-columns:1fr}
|
|
940
1797
|
.dse-row{flex-wrap:wrap;padding:12px 12px}
|
|
941
1798
|
.dse-rowMain{flex-basis:100%}
|
|
942
1799
|
.dse-badges{margin-left:auto}
|
|
943
|
-
.dse-
|
|
944
|
-
.dse-chip{min-height:32px}
|
|
1800
|
+
.dse-panelCard{position:fixed;left:8px;right:8px;bottom:8px;top:auto;max-height:70vh}
|
|
945
1801
|
}
|
|
946
1802
|
@media (pointer:coarse){
|
|
947
|
-
.dse-chip{min-height:32px}
|
|
948
1803
|
.dse-iconBtn{min-width:44px;min-height:44px;align-items:center;justify-content:center}
|
|
949
1804
|
}
|
|
950
1805
|
`;
|
|
@@ -956,7 +1811,11 @@ ${extra}
|
|
|
956
1811
|
//#region src/client/client.ts
|
|
957
1812
|
const name = "dsh-plus-secret-env";
|
|
958
1813
|
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
959
|
-
const inject = [
|
|
1814
|
+
const inject = [
|
|
1815
|
+
"slots",
|
|
1816
|
+
"locale",
|
|
1817
|
+
"sessions"
|
|
1818
|
+
];
|
|
960
1819
|
function apply(ctx) {
|
|
961
1820
|
const c = ctx;
|
|
962
1821
|
const styleTag = injectSecretEnvStyle("@dsh-plus/secret-env");
|
|
@@ -973,15 +1832,40 @@ ${extra}
|
|
|
973
1832
|
id: "dsh-plus-secret-env",
|
|
974
1833
|
order: 16,
|
|
975
1834
|
label: () => t("nav"),
|
|
976
|
-
inject: () => ({
|
|
1835
|
+
inject: () => ({
|
|
1836
|
+
t,
|
|
1837
|
+
sessions: c.sessions
|
|
1838
|
+
})
|
|
977
1839
|
}, SecretsSection));
|
|
978
|
-
c.
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1840
|
+
registerVarCommand(c, c.sessions, t);
|
|
1841
|
+
c.slots.inject("conversation.input.overlay", () => c.slots.register({
|
|
1842
|
+
name: "conversation.input.overlay",
|
|
1843
|
+
id: "dse-secret-menu",
|
|
1844
|
+
order: 10,
|
|
1845
|
+
locale: NS,
|
|
1846
|
+
inject: (sessionId) => ({
|
|
1847
|
+
sessionId,
|
|
1848
|
+
insertToken: (text, span) => {
|
|
1849
|
+
const actx = c.sessions.scope(sessionId);
|
|
1850
|
+
if (actx === void 0) return false;
|
|
1851
|
+
return actx.bail(actx, "slash/input-insert-text", {
|
|
1852
|
+
text,
|
|
1853
|
+
span: {
|
|
1854
|
+
start: span.start,
|
|
1855
|
+
end: span.end,
|
|
1856
|
+
draftRev: span.draftRev
|
|
1857
|
+
}
|
|
1858
|
+
}) === true;
|
|
1859
|
+
}
|
|
1860
|
+
})
|
|
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,
|
|
982
1866
|
locale: NS,
|
|
983
1867
|
inject: (sessionId) => ({ sessionId })
|
|
984
|
-
},
|
|
1868
|
+
}, SessionPanelHost));
|
|
985
1869
|
}
|
|
986
1870
|
//#endregion
|
|
987
1871
|
exports.apply = apply;
|