@kesike/dsh-exit 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/README.zh-CN.md +5 -4
- package/lib/client.js +101 -52
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://npmjs.com/package/@deepseek-ai/dsh)
|
|
8
8
|
[](https://www.npmjs.com/package/@kesike/dsh-exit)
|
|
9
9
|
[](https://github.com/KeS1Ke/dsh-exit)
|
|
10
|
-
[](package.json)
|
|
11
11
|
[](package.json)
|
|
12
12
|
|
|
13
13
|
[简体中文](https://github.com/KeS1Ke/dsh-exit/blob/main/README.zh-CN.md)
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
|
|
20
20
|
## ✨ What it does
|
|
21
21
|
|
|
22
|
-
`dsh-exit` adds a
|
|
22
|
+
`dsh-exit` adds a dedicated **Exit** section to the dsh settings panel. It uses the highest settings order so newly installed settings plugins appear above it.
|
|
23
23
|
|
|
24
24
|
- Opens a confirmation modal before doing anything destructive.
|
|
25
|
+
- Keeps the **Exit** section at the bottom of the settings navigation.
|
|
25
26
|
- Calls the typed `dshExit/exit` Remote method only after confirmation.
|
|
26
27
|
- Gives the gateway a short acknowledgement window, then ends the host process.
|
|
27
28
|
- Releases every port held by that process, including the usual `127.0.0.1:3080`.
|
|
@@ -46,7 +47,7 @@ The animation, video, and screenshot below are repository showcase assets. They
|
|
|
46
47
|
|
|
47
48
|
## 📸 Real dsh UI
|
|
48
49
|
|
|
49
|
-
The capture below comes from a running dsh Web profile with `dsh-exit` installed. The
|
|
50
|
+
The capture below comes from a running dsh Web profile with `dsh-exit` installed. The Exit control is now provided by the final section in the settings navigation.
|
|
50
51
|
|
|
51
52
|
<p align="center">
|
|
52
53
|
<img src="docs/dsh-exit-real.png" alt="Running dsh Web UI with the dsh-exit power button in the lower-right corner" width="720">
|
|
@@ -57,7 +58,7 @@ The capture below comes from a running dsh Web profile with `dsh-exit` installed
|
|
|
57
58
|
| Layer | File | Responsibility |
|
|
58
59
|
| --- | --- | --- |
|
|
59
60
|
| Host | [`lib/index.js`](lib/index.js) | Registers the `dshExit` Cordis service and the typed `exit()` Remote method. |
|
|
60
|
-
| Client | [`lib/client.js`](lib/client.js) | Self-contained browser bundle: button, CSS, modal, keyboard handling, and Remote mounting. |
|
|
61
|
+
| Client | [`lib/client.js`](lib/client.js) | Self-contained browser bundle: settings section, exit button, CSS, modal, keyboard handling, and Remote mounting. |
|
|
61
62
|
| Bundle patch | [`cordis.patch.yml`](cordis.patch.yml) | Inserts the plugin row into the dsh profile. |
|
|
62
63
|
| Package metadata | [`package.json`](package.json) | Declares the host entry, web client, and bundle patch. |
|
|
63
64
|
|
package/README.zh-CN.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://npmjs.com/package/@deepseek-ai/dsh)
|
|
8
8
|
[](https://www.npmjs.com/package/@kesike/dsh-exit)
|
|
9
9
|
[](https://github.com/KeS1Ke/dsh-exit)
|
|
10
|
-
[](package.json)
|
|
11
11
|
[](package.json)
|
|
12
12
|
|
|
13
13
|
**简体中文** | [English](https://github.com/KeS1Ke/dsh-exit/blob/main/README.md)
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
|
|
20
20
|
## ✨ 功能概览
|
|
21
21
|
|
|
22
|
-
`dsh-exit` 在 dsh
|
|
22
|
+
`dsh-exit` 在 dsh 设置面板中增加一个独立的**退出**栏目,并使用最高排序值,确保新安装的设置类插件仍排在它前面。
|
|
23
23
|
|
|
24
24
|
- 先弹出确认弹窗,确认前不会执行退出动作。
|
|
25
|
+
- **退出**栏目始终位于设置左侧导航的最底部。
|
|
25
26
|
- 确认后调用类型化的 `dshExit/exit` Remote 方法。
|
|
26
27
|
- 先返回调用应答,再等待短暂窗口结束宿主进程。
|
|
27
28
|
- 释放该进程占用的全部端口,包括常见的 `127.0.0.1:3080`。
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
|
|
47
48
|
## 📸 真实 dsh 界面
|
|
48
49
|
|
|
49
|
-
下图来自已经安装 `dsh-exit` 的真实 dsh Web profile
|
|
50
|
+
下图来自已经安装 `dsh-exit` 的真实 dsh Web profile;退出操作现在位于设置左侧导航的最后一栏。
|
|
50
51
|
|
|
51
52
|
<p align="center">
|
|
52
53
|
<img src="docs/dsh-exit-real.png" alt="运行中的 dsh Web 界面与右下角 dsh-exit 电源按钮" width="720">
|
|
@@ -57,7 +58,7 @@
|
|
|
57
58
|
| 层次 | 文件 | 职责 |
|
|
58
59
|
| --- | --- | --- |
|
|
59
60
|
| 宿主侧 | [`lib/index.js`](lib/index.js) | 注册 `dshExit` Cordis 服务和类型化 `exit()` Remote 方法。 |
|
|
60
|
-
| 客户端 | [`lib/client.js`](lib/client.js) | 自包含浏览器 bundle
|
|
61
|
+
| 客户端 | [`lib/client.js`](lib/client.js) | 自包含浏览器 bundle:设置栏目、退出按钮、样式、弹窗、键盘交互和 Remote 挂载。 |
|
|
61
62
|
| Bundle 补丁 | [`cordis.patch.yml`](cordis.patch.yml) | 向 dsh profile 插入插件行。 |
|
|
62
63
|
| 包元数据 | [`package.json`](package.json) | 声明宿主入口、Web 客户端和 Bundle 补丁。 |
|
|
63
64
|
|
package/lib/client.js
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
//#region dsh-exit 浏览器客户端 bundle
|
|
2
2
|
/**
|
|
3
3
|
* dsh-exit 客户端:
|
|
4
|
-
* -
|
|
5
|
-
* -
|
|
4
|
+
* - 在 dsh 设置菜单中注册一个始终排在最后的“退出”栏目
|
|
5
|
+
* - 栏目中的退出按钮首次点击弹确认弹窗;确认后调用 remote.dshExit.exit(),
|
|
6
6
|
* 服务端结束宿主进程并释放端口,不操作终端窗口或浏览器标签页
|
|
7
7
|
*
|
|
8
|
-
*
|
|
8
|
+
* 页面使用 dsh 已提供的 React、slots 和 locale 运行时,不引入新的运行时依赖。
|
|
9
9
|
*/
|
|
10
10
|
window.__ModuleLoader__.load({
|
|
11
11
|
// The loader key follows the resolved npm package name, including its scope.
|
|
12
12
|
id: "@kesike/dsh-exit",
|
|
13
|
-
factory: () => {
|
|
13
|
+
factory: (require) => {
|
|
14
14
|
var module = { exports: {} };
|
|
15
15
|
var exports = module.exports;
|
|
16
16
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
const React = require("react");
|
|
18
|
+
const h = React.createElement;
|
|
17
19
|
|
|
18
20
|
/** 与 archive-manager「删除会话」相同的错误红(设计令牌)。 */
|
|
19
21
|
const ERROR_RED = "var(--dsw-alias-state-error-primary)";
|
|
@@ -22,11 +24,19 @@ window.__ModuleLoader__.load({
|
|
|
22
24
|
const ICON_SVG = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2v10"/><path d="M18.4 6.6a9 9 0 1 1-12.77.04"/></svg>';
|
|
23
25
|
|
|
24
26
|
const CSS_TEXT = [
|
|
25
|
-
".
|
|
26
|
-
".
|
|
27
|
-
".
|
|
28
|
-
".
|
|
29
|
-
".
|
|
27
|
+
".dshExitSettings{box-sizing:border-box;max-width:720px;padding:28px 32px 40px;color:var(--dsw-alias-label-primary)}",
|
|
28
|
+
".dshExitSettingsHeader{display:flex;align-items:flex-start;gap:14px;padding-bottom:22px;border-bottom:1px solid var(--dsw-alias-border-l2)}",
|
|
29
|
+
".dshExitSettingsIcon{box-sizing:border-box;width:38px;height:38px;display:grid;place-items:center;flex:none;border:1px solid " + ERROR_RED + ";border-radius:12px;color:" + ERROR_RED + ";background:var(--dsw-alias-button-elevated-fill)}",
|
|
30
|
+
".dshExitSettingsIcon svg{width:20px;height:20px}",
|
|
31
|
+
".dshExitSettingsTitle{margin:0;font-size:20px;line-height:28px;font-weight:650;color:var(--dsw-alias-label-primary)}",
|
|
32
|
+
".dshExitSettingsDescription{margin:6px 0 0;max-width:560px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
|
|
33
|
+
".dshExitSettingsCard{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:24px;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-2)}",
|
|
34
|
+
".dshExitSettingsWarning{margin:0;max-width:430px;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}",
|
|
35
|
+
".dshExitSettingsButton{display:inline-flex;align-items:center;justify-content:center;gap:8px;flex:none;min-width:112px;height:36px;padding:0 16px;cursor:pointer;border:0;border-radius:10px;background:" + ERROR_RED + ";color:#fff;font-size:13px;font-weight:650;transition:filter .12s ease,opacity .12s ease}",
|
|
36
|
+
".dshExitSettingsButton svg{width:16px;height:16px}",
|
|
37
|
+
".dshExitSettingsButton:hover:not(:disabled){filter:brightness(1.08)}",
|
|
38
|
+
".dshExitSettingsButton:focus-visible{outline:2px solid " + ERROR_RED + ";outline-offset:2px}",
|
|
39
|
+
".dshExitSettingsButton:disabled{cursor:default;opacity:.6}",
|
|
30
40
|
".dshExitOverlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;background:rgba(15,20,25,.45);animation:dshExitFade .12s ease-out}",
|
|
31
41
|
"@keyframes dshExitFade{from{opacity:0}}",
|
|
32
42
|
".dshExitModal{box-sizing:border-box;width:min(380px,calc(100vw - 48px));border-radius:16px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-button-elevated-fill);box-shadow:0 24px 64px rgba(0,0,0,.35);padding:24px;display:flex;flex-direction:column;gap:12px;animation:dshExitPop .14s ease-out}",
|
|
@@ -71,8 +81,37 @@ window.__ModuleLoader__.load({
|
|
|
71
81
|
]
|
|
72
82
|
};
|
|
73
83
|
|
|
84
|
+
const NS = "dsh-exit";
|
|
85
|
+
const DICT = {
|
|
86
|
+
zh: {
|
|
87
|
+
nav: "退出",
|
|
88
|
+
title: "退出 dsh",
|
|
89
|
+
description: "从这里安全结束 dsh 宿主进程,并释放它占用的端口。",
|
|
90
|
+
warning: "确认后将结束所有运行中的会话并停止后台服务,3080 端口会随之释放。",
|
|
91
|
+
button: "确认退出",
|
|
92
|
+
dialogTitle: "退出 DeepSeek Harness?",
|
|
93
|
+
dialogBody: "退出后将结束所有运行中的会话并停止后台服务,3080 端口会随之释放。",
|
|
94
|
+
cancel: "取消",
|
|
95
|
+
confirm: "确认退出",
|
|
96
|
+
exiting: "正在退出…",
|
|
97
|
+
errorPrefix: "退出失败:"
|
|
98
|
+
},
|
|
99
|
+
en: {
|
|
100
|
+
nav: "Exit",
|
|
101
|
+
title: "Exit dsh",
|
|
102
|
+
description: "Safely terminate the dsh host process and release the ports it owns.",
|
|
103
|
+
warning: "Confirming ends all running sessions and stops the background service, releasing port 3080.",
|
|
104
|
+
button: "Confirm exit",
|
|
105
|
+
dialogTitle: "Exit DeepSeek Harness?",
|
|
106
|
+
dialogBody: "This ends all running sessions and stops the background service, releasing port 3080.",
|
|
107
|
+
cancel: "Cancel",
|
|
108
|
+
confirm: "Confirm exit",
|
|
109
|
+
exiting: "Exiting…",
|
|
110
|
+
errorPrefix: "Exit failed: "
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
74
114
|
let exiting = false;
|
|
75
|
-
let exitBtnEl = null;
|
|
76
115
|
let overlayEl = null;
|
|
77
116
|
let escHandler = null;
|
|
78
117
|
|
|
@@ -84,32 +123,40 @@ window.__ModuleLoader__.load({
|
|
|
84
123
|
document.head.appendChild(tag);
|
|
85
124
|
}
|
|
86
125
|
|
|
87
|
-
function
|
|
88
|
-
|
|
89
|
-
exitBtn.className = "dshExitBtn";
|
|
90
|
-
exitBtn.type = "button";
|
|
91
|
-
exitBtn.disabled = false;
|
|
92
|
-
exitBtn.setAttribute("aria-label", "退出");
|
|
93
|
-
exitBtn.title = "退出 DeepSeek Harness";
|
|
94
|
-
exitBtn.dataset.dshExitButton = "true";
|
|
95
|
-
exitBtn.innerHTML = ICON_SVG;
|
|
96
|
-
return exitBtn;
|
|
126
|
+
function textOf(t, key) {
|
|
127
|
+
return typeof t === "function" ? t(key) : DICT.zh[key] ?? key;
|
|
97
128
|
}
|
|
98
129
|
|
|
99
|
-
function
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
function ExitSettingsSection({ ctx, t }) {
|
|
131
|
+
return h("section", {
|
|
132
|
+
className: "dshExitSettings",
|
|
133
|
+
"aria-labelledby": "dsh-exit-settings-title"
|
|
134
|
+
}, [
|
|
135
|
+
h("div", { className: "dshExitSettingsHeader", key: "header" }, [
|
|
136
|
+
h("span", {
|
|
137
|
+
className: "dshExitSettingsIcon",
|
|
138
|
+
dangerouslySetInnerHTML: { __html: ICON_SVG },
|
|
139
|
+
key: "icon"
|
|
140
|
+
}),
|
|
141
|
+
h("div", { key: "copy" }, [
|
|
142
|
+
h("h2", { className: "dshExitSettingsTitle", id: "dsh-exit-settings-title", key: "title" }, textOf(t, "title")),
|
|
143
|
+
h("p", { className: "dshExitSettingsDescription", key: "description" }, textOf(t, "description"))
|
|
144
|
+
])
|
|
145
|
+
]),
|
|
146
|
+
h("div", { className: "dshExitSettingsCard", key: "card" }, [
|
|
147
|
+
h("p", { className: "dshExitSettingsWarning", key: "warning" }, textOf(t, "warning")),
|
|
148
|
+
h("button", {
|
|
149
|
+
type: "button",
|
|
150
|
+
className: "dshExitSettingsButton",
|
|
151
|
+
"aria-label": textOf(t, "button"),
|
|
152
|
+
onClick: (event) => openModal(ctx, event.currentTarget, t),
|
|
153
|
+
key: "button"
|
|
154
|
+
}, [
|
|
155
|
+
h("span", { dangerouslySetInnerHTML: { __html: ICON_SVG }, key: "icon" }),
|
|
156
|
+
textOf(t, "button")
|
|
157
|
+
])
|
|
158
|
+
])
|
|
159
|
+
]);
|
|
113
160
|
}
|
|
114
161
|
|
|
115
162
|
function closeModal() {
|
|
@@ -123,7 +170,8 @@ window.__ModuleLoader__.load({
|
|
|
123
170
|
}
|
|
124
171
|
}
|
|
125
172
|
|
|
126
|
-
function openModal(ctx, exitBtn) {
|
|
173
|
+
function openModal(ctx, exitBtn, t) {
|
|
174
|
+
const text = (key) => textOf(t, key);
|
|
127
175
|
closeModal();
|
|
128
176
|
overlayEl = document.createElement("div");
|
|
129
177
|
overlayEl.className = "dshExitOverlay";
|
|
@@ -131,13 +179,13 @@ window.__ModuleLoader__.load({
|
|
|
131
179
|
panel.className = "dshExitModal";
|
|
132
180
|
panel.setAttribute("role", "dialog");
|
|
133
181
|
panel.setAttribute("aria-modal", "true");
|
|
134
|
-
panel.setAttribute("aria-label", "
|
|
182
|
+
panel.setAttribute("aria-label", text("dialogTitle"));
|
|
135
183
|
const title = document.createElement("h3");
|
|
136
184
|
title.className = "dshExitTitle";
|
|
137
|
-
title.textContent = "
|
|
185
|
+
title.textContent = text("dialogTitle");
|
|
138
186
|
const body = document.createElement("p");
|
|
139
187
|
body.className = "dshExitBody";
|
|
140
|
-
body.textContent = "
|
|
188
|
+
body.textContent = text("dialogBody");
|
|
141
189
|
const errorEl = document.createElement("p");
|
|
142
190
|
errorEl.className = "dshExitError";
|
|
143
191
|
const actions = document.createElement("div");
|
|
@@ -145,11 +193,11 @@ window.__ModuleLoader__.load({
|
|
|
145
193
|
const cancel = document.createElement("button");
|
|
146
194
|
cancel.type = "button";
|
|
147
195
|
cancel.className = "dshExitCancel";
|
|
148
|
-
cancel.textContent = "
|
|
196
|
+
cancel.textContent = text("cancel");
|
|
149
197
|
const confirm = document.createElement("button");
|
|
150
198
|
confirm.type = "button";
|
|
151
199
|
confirm.className = "dshExitConfirm";
|
|
152
|
-
confirm.textContent = "
|
|
200
|
+
confirm.textContent = text("confirm");
|
|
153
201
|
actions.append(cancel, confirm);
|
|
154
202
|
panel.append(title, body, errorEl, actions);
|
|
155
203
|
overlayEl.appendChild(panel);
|
|
@@ -170,7 +218,7 @@ window.__ModuleLoader__.load({
|
|
|
170
218
|
cancel.disabled = true;
|
|
171
219
|
errorEl.textContent = "";
|
|
172
220
|
exitBtn.disabled = true;
|
|
173
|
-
exitBtn.setAttribute("aria-label", "
|
|
221
|
+
exitBtn.setAttribute("aria-label", text("exiting"));
|
|
174
222
|
Promise.resolve()
|
|
175
223
|
.then(() => {
|
|
176
224
|
const service = ctx.get("remote.dshExit");
|
|
@@ -189,7 +237,7 @@ window.__ModuleLoader__.load({
|
|
|
189
237
|
cancel.disabled = false;
|
|
190
238
|
exitBtn.disabled = false;
|
|
191
239
|
exitBtn.setAttribute("aria-label", "退出");
|
|
192
|
-
errorEl.textContent = "
|
|
240
|
+
errorEl.textContent = text("errorPrefix") + (reason instanceof Error ? reason.message : String(reason));
|
|
193
241
|
});
|
|
194
242
|
});
|
|
195
243
|
document.body.appendChild(overlayEl);
|
|
@@ -197,25 +245,26 @@ window.__ModuleLoader__.load({
|
|
|
197
245
|
|
|
198
246
|
async function apply(ctx) {
|
|
199
247
|
injectCss();
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
248
|
+
ctx.effect(() => ctx.locale.register(NS, DICT), "dsh-exit: settings dictionaries");
|
|
249
|
+
const t = ctx.locale.bind(NS);
|
|
250
|
+
const SettingsSection = (props) => h(ExitSettingsSection, { ...props, ctx });
|
|
251
|
+
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
252
|
+
name: "settings.section",
|
|
253
|
+
id: "dsh-exit",
|
|
254
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
255
|
+
label: () => t("nav"),
|
|
256
|
+
locale: NS
|
|
257
|
+
}, SettingsSection));
|
|
205
258
|
let disposeRemote = () => {};
|
|
206
259
|
const remote = ctx.get("remote");
|
|
207
260
|
if (remote !== undefined) disposeRemote = await remote.$mount(EXIT_REMOTE);
|
|
208
261
|
return async () => {
|
|
209
|
-
observer.disconnect();
|
|
210
262
|
closeModal();
|
|
211
|
-
if (exitBtnEl) {
|
|
212
|
-
exitBtnEl.remove();
|
|
213
|
-
exitBtnEl = null;
|
|
214
|
-
}
|
|
215
263
|
disposeRemote();
|
|
216
264
|
};
|
|
217
265
|
}
|
|
218
266
|
|
|
267
|
+
exports.inject = ["slots", "locale"];
|
|
219
268
|
exports.apply = apply;
|
|
220
269
|
return module.exports;
|
|
221
270
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kesike/dsh-exit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "在 dsh web 界面提供退出按钮:确认后结束宿主进程并释放端口",
|
|
6
6
|
"keywords": [
|
|
@@ -43,6 +43,11 @@
|
|
|
43
43
|
"patch": "./cordis.patch.yml"
|
|
44
44
|
},
|
|
45
45
|
"client": {
|
|
46
|
+
"inject": [
|
|
47
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
49
|
+
"@deepseek-ai/dsh-client-locale"
|
|
50
|
+
],
|
|
46
51
|
"platform": "web"
|
|
47
52
|
}
|
|
48
53
|
}
|