dsh-session-toolbox 0.0.0-stage → 0.1.1
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/CHANGELOG.md +21 -0
- package/LICENSE +21 -0
- package/README.md +144 -2
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/cordis.patch.yml +3 -0
- package/lib/chunk-4UQB3MPY.js +828 -0
- package/lib/chunk-4UQB3MPY.js.map +7 -0
- package/lib/cli.js +205 -0
- package/lib/cli.js.map +7 -0
- package/lib/client.js +843 -0
- package/lib/client.js.map +7 -0
- package/lib/host-v0.1.1.js +327 -0
- package/lib/host.js +327 -0
- package/lib/index.js +327 -0
- package/lib/index.js.map +7 -0
- package/package.json +73 -4
- package/session-toolbox.example.json +15 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "dsh-session-toolbox", factory: (require) => { var module = { exports: {} };
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/client.jsx
|
|
21
|
+
var client_exports = {};
|
|
22
|
+
__export(client_exports, {
|
|
23
|
+
KitPage: () => KitPage,
|
|
24
|
+
MoveDialog: () => MoveDialog,
|
|
25
|
+
MoveSessionMenuItem: () => MoveSessionMenuItem,
|
|
26
|
+
NS: () => NS,
|
|
27
|
+
TrashSessionMenuItem: () => TrashSessionMenuItem,
|
|
28
|
+
VaultBadge: () => VaultBadge,
|
|
29
|
+
apply: () => apply,
|
|
30
|
+
callHost: () => callHost,
|
|
31
|
+
inject: () => inject,
|
|
32
|
+
loadState: () => loadState,
|
|
33
|
+
name: () => name,
|
|
34
|
+
sessionKey: () => sessionKey
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(client_exports);
|
|
37
|
+
var import_react = require("react");
|
|
38
|
+
|
|
39
|
+
// package.json
|
|
40
|
+
var package_default = {
|
|
41
|
+
name: "dsh-session-toolbox",
|
|
42
|
+
version: "0.1.1",
|
|
43
|
+
repository: {
|
|
44
|
+
type: "git",
|
|
45
|
+
url: "https://github.com/AndroidSix/dsh-session-toolbox.git"
|
|
46
|
+
},
|
|
47
|
+
description: "Draft vault, session-to-project move and a trash bin for DeepSeek Harness",
|
|
48
|
+
type: "module",
|
|
49
|
+
main: "lib/index.js",
|
|
50
|
+
bin: {
|
|
51
|
+
"dsh-session-toolbox": "lib/cli.js"
|
|
52
|
+
},
|
|
53
|
+
exports: {
|
|
54
|
+
".": "./lib/index.js",
|
|
55
|
+
"./host": "./lib/host.js",
|
|
56
|
+
"./client": "./lib/client.js",
|
|
57
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
58
|
+
"./package.json": "./package.json"
|
|
59
|
+
},
|
|
60
|
+
engines: {
|
|
61
|
+
node: "^22.19.0 || >=24.0.0"
|
|
62
|
+
},
|
|
63
|
+
files: [
|
|
64
|
+
"lib",
|
|
65
|
+
"cordis.patch.yml",
|
|
66
|
+
"README.md",
|
|
67
|
+
"LICENSE",
|
|
68
|
+
"THIRD_PARTY_NOTICES.md",
|
|
69
|
+
"session-toolbox.example.json",
|
|
70
|
+
"CHANGELOG.md"
|
|
71
|
+
],
|
|
72
|
+
dsh: {
|
|
73
|
+
bundle: {
|
|
74
|
+
patch: "./cordis.patch.yml"
|
|
75
|
+
},
|
|
76
|
+
client: {
|
|
77
|
+
inject: [
|
|
78
|
+
"@deepseek-ai/dsh-client-ui-renderer"
|
|
79
|
+
],
|
|
80
|
+
platform: "web"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
scripts: {
|
|
84
|
+
build: "node scripts/build.mjs",
|
|
85
|
+
test: "node --test tests/*.test.mjs",
|
|
86
|
+
check: "npm run build && npm test && node scripts/check-package.mjs",
|
|
87
|
+
"check:tarball": "npm run build && node scripts/check-package.mjs --tarball",
|
|
88
|
+
"pack:release": "npm run build && npm pack --ignore-scripts"
|
|
89
|
+
},
|
|
90
|
+
peerDependencies: {
|
|
91
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
92
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
|
|
93
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
94
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
95
|
+
react: "^18.2.0"
|
|
96
|
+
},
|
|
97
|
+
devDependencies: {
|
|
98
|
+
"@deepseek-ai/cordis": "^4.0.4",
|
|
99
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.0-rc.2",
|
|
100
|
+
"@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
|
|
101
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
102
|
+
esbuild: "^0.25.0",
|
|
103
|
+
react: "^18.2.0",
|
|
104
|
+
"react-dom": "^18.2.0"
|
|
105
|
+
},
|
|
106
|
+
license: "MIT",
|
|
107
|
+
keywords: [
|
|
108
|
+
"dsh-plugin",
|
|
109
|
+
"deepseek-harness",
|
|
110
|
+
"draft-vault",
|
|
111
|
+
"session-move",
|
|
112
|
+
"trash-bin"
|
|
113
|
+
]
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// src/draft-vault.js
|
|
117
|
+
var DB_NAME = "dsh-session-toolbox-vault";
|
|
118
|
+
var STORE = "blobs";
|
|
119
|
+
var IMAGE_TYPES = ["image/png", "image/jpeg", "image/webp", "image/gif"];
|
|
120
|
+
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
121
|
+
var DEFAULT_LIMITS = Object.freeze({ enabled: true, maxSizeBytes: 200 * 1024 * 1024, ttlDays: 7 });
|
|
122
|
+
function isImageFile(file) {
|
|
123
|
+
return file !== void 0 && IMAGE_TYPES.includes(file.type) && file.size > 0 && file.size <= MAX_FILE_BYTES;
|
|
124
|
+
}
|
|
125
|
+
async function contentHash(file) {
|
|
126
|
+
if (globalThis.crypto?.subtle !== void 0) {
|
|
127
|
+
try {
|
|
128
|
+
const digest = await crypto.subtle.digest("SHA-256", await file.arrayBuffer());
|
|
129
|
+
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return `weak:${file.size}:${file.name}:${file.lastModified}`;
|
|
134
|
+
}
|
|
135
|
+
function openDb() {
|
|
136
|
+
if (typeof indexedDB === "undefined") throw new Error("IndexedDB unavailable");
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
139
|
+
request.onupgradeneeded = () => {
|
|
140
|
+
const database = request.result;
|
|
141
|
+
if (!database.objectStoreNames.contains(STORE)) database.createObjectStore(STORE, { keyPath: "key" });
|
|
142
|
+
};
|
|
143
|
+
request.onsuccess = () => resolve(request.result);
|
|
144
|
+
request.onerror = () => reject(request.error ?? new Error("IndexedDB open failed"));
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function done(request) {
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
request.onsuccess = () => resolve(request.result);
|
|
150
|
+
request.onerror = () => reject(request.error ?? new Error("vault storage request failed"));
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function storeOp(database, mode, run) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
let outcome;
|
|
156
|
+
const transaction = database.transaction(STORE, mode);
|
|
157
|
+
transaction.onabort = () => reject(transaction.error ?? new Error("vault transaction aborted"));
|
|
158
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("vault transaction failed"));
|
|
159
|
+
try {
|
|
160
|
+
outcome = run(transaction.objectStore(STORE));
|
|
161
|
+
} catch (error) {
|
|
162
|
+
reject(error);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
Promise.resolve(outcome).then(resolve, reject);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function emitter() {
|
|
169
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
170
|
+
return {
|
|
171
|
+
on(event, fn) {
|
|
172
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
173
|
+
listeners.get(event).add(fn);
|
|
174
|
+
},
|
|
175
|
+
off(event, fn) {
|
|
176
|
+
listeners.get(event)?.delete(fn);
|
|
177
|
+
},
|
|
178
|
+
emit(event, detail) {
|
|
179
|
+
for (const fn of [...listeners.get(event) ?? []]) {
|
|
180
|
+
try {
|
|
181
|
+
fn(detail);
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function fakeClipboardData(files) {
|
|
189
|
+
const items = files.map((f) => ({ kind: "file", type: f.type, getAsFile: () => f }));
|
|
190
|
+
return { items, files, length: files.length, types: ["Files"], getData: () => "", setData: () => false, clearData: () => {
|
|
191
|
+
} };
|
|
192
|
+
}
|
|
193
|
+
async function blobToFile(record) {
|
|
194
|
+
if (record.blob instanceof File) return record.blob;
|
|
195
|
+
const blob = record.blob instanceof Blob ? record.blob : new Blob([record.blob ?? new Uint8Array()], { type: record.type });
|
|
196
|
+
return new File([blob], record.name ?? "", { type: record.type, lastModified: record.createdAt });
|
|
197
|
+
}
|
|
198
|
+
function createVault(options = {}) {
|
|
199
|
+
const log = options.log ?? (() => {
|
|
200
|
+
});
|
|
201
|
+
const now = options.now ?? (() => Date.now());
|
|
202
|
+
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
203
|
+
const bus = emitter();
|
|
204
|
+
let dbPromise = null;
|
|
205
|
+
let limits = { ...DEFAULT_LIMITS };
|
|
206
|
+
let lastNotice = "";
|
|
207
|
+
let started = false;
|
|
208
|
+
let teardown = null;
|
|
209
|
+
let hostKey = "";
|
|
210
|
+
const db = () => dbPromise ??= openDb();
|
|
211
|
+
function notice(message) {
|
|
212
|
+
lastNotice = message;
|
|
213
|
+
bus.emit("notice", message);
|
|
214
|
+
try {
|
|
215
|
+
globalThis.window?.dispatchEvent?.(new CustomEvent("dsh-session-toolbox-notice", { detail: message }));
|
|
216
|
+
} catch {
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function changed() {
|
|
220
|
+
bus.emit("change");
|
|
221
|
+
}
|
|
222
|
+
const allRecords = async () => {
|
|
223
|
+
const database = await db();
|
|
224
|
+
return await storeOp(database, "readonly", (store2) => done(store2.getAll()));
|
|
225
|
+
};
|
|
226
|
+
const putRecord = async (record) => {
|
|
227
|
+
const database = await db();
|
|
228
|
+
await storeOp(database, "readwrite", (store2) => done(store2.put(record)));
|
|
229
|
+
};
|
|
230
|
+
const dropRecord = async (key) => {
|
|
231
|
+
const database = await db();
|
|
232
|
+
await storeOp(database, "readwrite", (store2) => done(store2.delete(key)));
|
|
233
|
+
};
|
|
234
|
+
async function syncHost() {
|
|
235
|
+
const endpoint = options.endpoint ?? "/.well-known/dsh-session-toolbox";
|
|
236
|
+
try {
|
|
237
|
+
if (hostKey === "") {
|
|
238
|
+
const probe = await fetchImpl(endpoint, { cache: "no-store" });
|
|
239
|
+
hostKey = (await probe.json()).key ?? "";
|
|
240
|
+
}
|
|
241
|
+
const records = (await allRecords()).slice(-500).map(({ key, sessionId, attachmentId, name: name2, size, hash, seeded, createdAt }) => ({ key, sessionId, attachmentId, name: name2, size, hash, seeded, createdAt }));
|
|
242
|
+
await fetchImpl(endpoint, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
cache: "no-store",
|
|
245
|
+
headers: { "content-type": "application/json", "x-session-toolbox-key": hostKey },
|
|
246
|
+
body: JSON.stringify({ type: "vault.sync", records })
|
|
247
|
+
});
|
|
248
|
+
} catch {
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async function sweep() {
|
|
252
|
+
const records = await allRecords();
|
|
253
|
+
const ttlMs = limits.ttlDays * 24 * 60 * 60 * 1e3;
|
|
254
|
+
const keep = [];
|
|
255
|
+
const remove = [];
|
|
256
|
+
for (const record of records) (now() - (record.createdAt ?? 0) > ttlMs ? remove : keep).push(record);
|
|
257
|
+
keep.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0));
|
|
258
|
+
let bytes = keep.reduce((total, record) => total + (record.size ?? 0), 0);
|
|
259
|
+
while (bytes > limits.maxSizeBytes && keep.length > 0) {
|
|
260
|
+
const oldest = keep.shift();
|
|
261
|
+
bytes -= oldest.size ?? 0;
|
|
262
|
+
remove.push(oldest);
|
|
263
|
+
}
|
|
264
|
+
for (const record of remove) await dropRecord(record.key);
|
|
265
|
+
if (remove.length > 0) notice(`\u8349\u7A3F\u4FDD\u9669\u7BB1\u5DF2\u6E05\u7406 ${remove.length} \u9879\uFF08\u8D85\u671F/\u8D85\u5BB9\u91CF\uFF09`);
|
|
266
|
+
changed();
|
|
267
|
+
return { removed: remove.length, kept: keep.length, bytes };
|
|
268
|
+
}
|
|
269
|
+
async function intake(sessionId, files) {
|
|
270
|
+
if (!limits.enabled || sessionId === void 0 || sessionId === "") return;
|
|
271
|
+
for (const item of files) {
|
|
272
|
+
if (!isImageFile(item)) continue;
|
|
273
|
+
const id = globalThis.crypto?.randomUUID?.() ?? `id-${now()}-${Math.random().toString(36).slice(2)}`;
|
|
274
|
+
const record = {
|
|
275
|
+
key: `${sessionId}__${id}`,
|
|
276
|
+
sessionId,
|
|
277
|
+
attachmentId: id,
|
|
278
|
+
name: item.name || "",
|
|
279
|
+
type: item.type,
|
|
280
|
+
size: item.size,
|
|
281
|
+
hash: await contentHash(item),
|
|
282
|
+
createdAt: now(),
|
|
283
|
+
seeded: false
|
|
284
|
+
};
|
|
285
|
+
record.blob = item;
|
|
286
|
+
await putRecord(record);
|
|
287
|
+
notice(`\u5DF2\u5907\u4EFD 1 \u5F20\u8349\u7A3F\u56FE\u7247\uFF08\u4F1A\u8BDD ${sessionId.slice(-6)}\uFF09`);
|
|
288
|
+
}
|
|
289
|
+
void syncHost();
|
|
290
|
+
}
|
|
291
|
+
const readImages = (event) => [...event.clipboardData?.files ?? event.dataTransfer?.files ?? []].filter(isImageFile);
|
|
292
|
+
const fileInputImages = (event) => {
|
|
293
|
+
const target = event.target;
|
|
294
|
+
if (!(target instanceof HTMLInputElement) || target.type !== "file" || target.files === null) return [];
|
|
295
|
+
return [...target.files].filter(isImageFile);
|
|
296
|
+
};
|
|
297
|
+
function currentSession() {
|
|
298
|
+
return document.querySelector?.("[data-conversation-session]")?.getAttribute?.("data-conversation-session") ?? "";
|
|
299
|
+
}
|
|
300
|
+
function composerRoot() {
|
|
301
|
+
return document.querySelector?.('[data-conversation-session] [contenteditable="true"]') ?? void 0;
|
|
302
|
+
}
|
|
303
|
+
function attachmentRail(editor) {
|
|
304
|
+
const bar = editor?.closest?.('[data-composer-bar], [data-conversation-region="composer"]') ?? editor?.parentElement?.parentElement?.parentElement;
|
|
305
|
+
if (bar?.querySelectorAll === void 0) return void 0;
|
|
306
|
+
return [...bar.querySelectorAll("img")].filter((img) => img.getAttribute?.("src")?.startsWith("blob:"));
|
|
307
|
+
}
|
|
308
|
+
async function seedSession(sessionId = currentSession()) {
|
|
309
|
+
if (sessionId === void 0 || sessionId === "" || !limits.enabled) return false;
|
|
310
|
+
const editor = composerRoot();
|
|
311
|
+
if (editor === void 0) return false;
|
|
312
|
+
const rail = attachmentRail(editor);
|
|
313
|
+
if (rail === void 0) return false;
|
|
314
|
+
if (rail.length > 0) return true;
|
|
315
|
+
const pending = (await allRecords()).filter((record) => record.sessionId === sessionId);
|
|
316
|
+
if (pending.length === 0) return true;
|
|
317
|
+
const files = [];
|
|
318
|
+
for (const record of pending) {
|
|
319
|
+
try {
|
|
320
|
+
files.push(await blobToFile(record));
|
|
321
|
+
} catch {
|
|
322
|
+
await dropRecord(record.key);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (files.length === 0) {
|
|
326
|
+
changed();
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
const event = new ClipboardEvent("paste", { bubbles: true, cancelable: true });
|
|
330
|
+
Object.defineProperty(event, "clipboardData", { value: fakeClipboardData(files) });
|
|
331
|
+
try {
|
|
332
|
+
editor.focus?.();
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
editor.dispatchEvent(event);
|
|
336
|
+
const accepted = event.defaultPrevented === true;
|
|
337
|
+
if (accepted) notice(`\u5DF2\u6062\u590D ${files.length} \u5F20\u8349\u7A3F\u56FE\u7247\uFF08\u4F1A\u8BDD ${sessionId.slice(-6)}\uFF09`);
|
|
338
|
+
else log("warn", "synthetic paste was not accepted; vault stays idle for this session");
|
|
339
|
+
changed();
|
|
340
|
+
return accepted;
|
|
341
|
+
}
|
|
342
|
+
function watchSession(sessionId, editor) {
|
|
343
|
+
let rail = attachmentRail(editor)?.length ?? 0;
|
|
344
|
+
const scope = editor.closest?.("[data-conversation-content]") ?? editor;
|
|
345
|
+
const check = async () => {
|
|
346
|
+
const next = attachmentRail(editor)?.length ?? rail;
|
|
347
|
+
if (next === rail) return;
|
|
348
|
+
if (rail > 0 && next === 0 && scope?.isConnected === true) {
|
|
349
|
+
for (const record of await allRecords()) if (record.sessionId === sessionId) await dropRecord(record.key);
|
|
350
|
+
notice("\u672C\u4F1A\u8BDD\u8349\u7A3F\u56FE\u7247\u5DF2\u6E05\u7A7A\uFF0C\u5907\u4EFD\u540C\u6B65\u79FB\u9664");
|
|
351
|
+
void syncHost();
|
|
352
|
+
}
|
|
353
|
+
rail = next;
|
|
354
|
+
changed();
|
|
355
|
+
};
|
|
356
|
+
if (typeof MutationObserver !== "function") return () => {
|
|
357
|
+
};
|
|
358
|
+
const observer = new MutationObserver(() => void check());
|
|
359
|
+
observer.observe(scope, { subtree: true, childList: true });
|
|
360
|
+
return () => observer.disconnect();
|
|
361
|
+
}
|
|
362
|
+
function watchMounts() {
|
|
363
|
+
let boundSession = "", dispose = () => {
|
|
364
|
+
};
|
|
365
|
+
const sync = async () => {
|
|
366
|
+
const sessionId = currentSession();
|
|
367
|
+
const editor = composerRoot();
|
|
368
|
+
if (sessionId === "" || editor === void 0) return;
|
|
369
|
+
if (sessionId === boundSession && editor.isConnected !== false) return;
|
|
370
|
+
boundSession = sessionId;
|
|
371
|
+
dispose();
|
|
372
|
+
dispose = watchSession(sessionId, editor);
|
|
373
|
+
setTimeout(() => void seedSession(sessionId).catch((error) => log("warn", `vault seed failed: ${error.message}`)), 120);
|
|
374
|
+
void sweep().catch(() => {
|
|
375
|
+
});
|
|
376
|
+
};
|
|
377
|
+
const interval = setInterval(() => void sync(), 5e3);
|
|
378
|
+
if (typeof MutationObserver !== "function") return () => clearInterval(interval);
|
|
379
|
+
const observer = new MutationObserver(() => void sync());
|
|
380
|
+
observer.observe(document.documentElement, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-conversation-session"] });
|
|
381
|
+
return () => {
|
|
382
|
+
observer.disconnect();
|
|
383
|
+
clearInterval(interval);
|
|
384
|
+
dispose();
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
return {
|
|
388
|
+
on: bus.on,
|
|
389
|
+
off: bus.off,
|
|
390
|
+
get lastNotice() {
|
|
391
|
+
return lastNotice;
|
|
392
|
+
},
|
|
393
|
+
currentSession,
|
|
394
|
+
records: allRecords,
|
|
395
|
+
countFor: async (sessionId) => {
|
|
396
|
+
const records = await allRecords();
|
|
397
|
+
return sessionId === void 0 || sessionId === "" ? records.length : records.filter((record) => record.sessionId === sessionId).length;
|
|
398
|
+
},
|
|
399
|
+
start: async ({ limits: override } = {}) => {
|
|
400
|
+
if (started || typeof document === "undefined") return false;
|
|
401
|
+
started = true;
|
|
402
|
+
if (override !== void 0) limits = { ...limits, ...override };
|
|
403
|
+
else {
|
|
404
|
+
try {
|
|
405
|
+
const res = await fetchImpl(`${options.endpoint ?? "/.well-known/dsh-session-toolbox"}`, { cache: "no-store" });
|
|
406
|
+
const next = await res.json();
|
|
407
|
+
limits = { ...limits, ...next.settings?.draftVault ?? {} };
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
document.addEventListener("paste", (event) => void intake(currentSession(), readImages(event)).catch(() => {
|
|
412
|
+
}), true);
|
|
413
|
+
document.addEventListener("drop", (event) => void intake(currentSession(), readImages(event)).catch(() => {
|
|
414
|
+
}), true);
|
|
415
|
+
document.addEventListener("change", (event) => void intake(currentSession(), fileInputImages(event)).catch(() => {
|
|
416
|
+
}), true);
|
|
417
|
+
teardown = watchMounts();
|
|
418
|
+
void sweep().catch(() => {
|
|
419
|
+
});
|
|
420
|
+
log("info", "draft vault started");
|
|
421
|
+
return true;
|
|
422
|
+
},
|
|
423
|
+
stop: () => {
|
|
424
|
+
if (started) {
|
|
425
|
+
started = false;
|
|
426
|
+
teardown?.();
|
|
427
|
+
teardown = null;
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
setLimits: (next) => {
|
|
431
|
+
limits = { ...limits, ...next };
|
|
432
|
+
if (limits.enabled === false) void (async () => {
|
|
433
|
+
for (const record of await allRecords()) await dropRecord(record.key);
|
|
434
|
+
notice("\u8349\u7A3F\u4FDD\u9669\u7BB1\u5DF2\u505C\u7528\uFF0C\u5B58\u91CF\u5DF2\u6E05\u7A7A");
|
|
435
|
+
})();
|
|
436
|
+
},
|
|
437
|
+
sweep,
|
|
438
|
+
seedSession,
|
|
439
|
+
intake,
|
|
440
|
+
watchMounts
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/client.jsx
|
|
445
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
446
|
+
var pluginVersion = package_default.version;
|
|
447
|
+
var name = "dsh-session-toolbox-client";
|
|
448
|
+
var inject = ["slots"];
|
|
449
|
+
var NS = "dsh-session-toolbox";
|
|
450
|
+
var ENDPOINT = `/.well-known/${NS}`;
|
|
451
|
+
var vault;
|
|
452
|
+
var directoryPicker;
|
|
453
|
+
var store = { state: null, error: "", listeners: /* @__PURE__ */ new Set() };
|
|
454
|
+
async function readResponse(res) {
|
|
455
|
+
let next;
|
|
456
|
+
try {
|
|
457
|
+
next = JSON.parse(await res.text());
|
|
458
|
+
} catch {
|
|
459
|
+
throw new Error(`\u63D2\u4EF6\u540E\u53F0\u672A\u5C31\u7EEA\uFF08HTTP ${res.status}\uFF09\uFF1B\u5B89\u88C5\u6216\u91CD\u542F\u671F\u95F4\u4F1A\u81EA\u52A8\u91CD\u8BD5`);
|
|
460
|
+
}
|
|
461
|
+
if (next === null || typeof next !== "object" || Array.isArray(next)) throw new Error("\u63D2\u4EF6\u540E\u53F0\u8FD4\u56DE\u4E86\u65E0\u6548\u6570\u636E");
|
|
462
|
+
if (!res.ok) throw new Error(next.error ?? `\u64CD\u4F5C\u5931\u8D25\uFF08HTTP ${res.status}\uFF09`);
|
|
463
|
+
return next;
|
|
464
|
+
}
|
|
465
|
+
var sessionKey = "";
|
|
466
|
+
async function loadState() {
|
|
467
|
+
try {
|
|
468
|
+
const next = await readResponse(await fetch(ENDPOINT, { cache: "no-store" }));
|
|
469
|
+
store.state = next;
|
|
470
|
+
store.error = "";
|
|
471
|
+
sessionKey = next.key ?? sessionKey;
|
|
472
|
+
} catch (error) {
|
|
473
|
+
store.error = error.message;
|
|
474
|
+
}
|
|
475
|
+
for (const listener of store.listeners) listener();
|
|
476
|
+
return store.state;
|
|
477
|
+
}
|
|
478
|
+
async function callHost(input) {
|
|
479
|
+
if (!sessionKey) await loadState();
|
|
480
|
+
const res = await readResponse(await fetch(ENDPOINT, {
|
|
481
|
+
method: "POST",
|
|
482
|
+
cache: "no-store",
|
|
483
|
+
headers: { "content-type": "application/json", "x-session-toolbox-key": sessionKey },
|
|
484
|
+
body: JSON.stringify(input)
|
|
485
|
+
}));
|
|
486
|
+
sessionKey = res.key ?? sessionKey;
|
|
487
|
+
for (const listener of store.listeners) listener();
|
|
488
|
+
return res;
|
|
489
|
+
}
|
|
490
|
+
function apply(ctx) {
|
|
491
|
+
directoryPicker = ctx.get?.("directoryPicker");
|
|
492
|
+
const vaultInstance = vault ??= createVault({
|
|
493
|
+
endpoint: ENDPOINT,
|
|
494
|
+
log: (level, message) => {
|
|
495
|
+
try {
|
|
496
|
+
(level === "error" ? console.warn : console.log)(`[${NS}]`, message);
|
|
497
|
+
} catch {
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
void loadState();
|
|
502
|
+
const timer = setInterval(() => void loadState(), 3e4);
|
|
503
|
+
ctx.effect?.(() => () => clearInterval(timer));
|
|
504
|
+
vaultInstance.on("change", () => void loadState());
|
|
505
|
+
void vaultInstance.start();
|
|
506
|
+
ctx.locale?.register?.(NS, {
|
|
507
|
+
zh: { "menu.move": "\u5207\u6362\u5230\u5176\u5B83\u9879\u76EE\u2026", "menu.trash": "\u79FB\u5165\u56DE\u6536\u7AD9" },
|
|
508
|
+
en: { "menu.move": "Move to another project\u2026", "menu.trash": "Move to trash" }
|
|
509
|
+
});
|
|
510
|
+
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({ name: "plugins.bundle.config", key: NS }, KitPage));
|
|
511
|
+
ctx.slots.inject("sidebar.workspaces.session.menu.item", () => ctx.slots.register({ name: "sidebar.workspaces.session.menu.item", id: "session-toolbox-move", order: 500, locale: NS }, MoveSessionMenuItem));
|
|
512
|
+
ctx.slots.inject("sidebar.workspaces.session.menu.item", () => ctx.slots.register({ name: "sidebar.workspaces.session.menu.item", id: "session-toolbox-trash", order: 600, locale: NS }, TrashSessionMenuItem));
|
|
513
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({ name: "conversation.input.dock", id: "session-toolbox-vault-badge", order: 5, locale: NS }, VaultBadge));
|
|
514
|
+
}
|
|
515
|
+
function useKit() {
|
|
516
|
+
const [, bump] = (0, import_react.useState)(0);
|
|
517
|
+
(0, import_react.useEffect)(() => {
|
|
518
|
+
const listener = () => bump((n) => n + 1);
|
|
519
|
+
store.listeners.add(listener);
|
|
520
|
+
void loadState();
|
|
521
|
+
return () => store.listeners.delete(listener);
|
|
522
|
+
}, []);
|
|
523
|
+
return store;
|
|
524
|
+
}
|
|
525
|
+
var styles = `
|
|
526
|
+
.sk{--sk-accent:var(--dsw-alias-state-active-primary,#3a71db);--sk-border:var(--dsw-alias-border-l2,#444);--sk-bg:var(--dsw-alias-bg-layer-1,#202020);--sk-card:var(--dsw-alias-bg-module-platform,#282828);--sk-text:var(--dsw-alias-label-primary,#f4f4f4);--sk-muted:var(--dsw-alias-label-secondary,#b0b0b0);color:var(--sk-text);font:inherit;font-size:14px;line-height:1.6;max-width:1120px}
|
|
527
|
+
.sk *{box-sizing:border-box}.sk h2,.sk h3,.sk p{margin:0}.sk h2{font-size:22px}.sk h3{font-size:16px}.sk .muted,.sk small{color:var(--sk-muted)}
|
|
528
|
+
.sk .box{border:1px solid var(--sk-border);border-radius:12px;padding:18px 20px;background:var(--sk-card);display:flex;flex-direction:column;gap:14px}
|
|
529
|
+
.sk .row{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.sk .stack{display:flex;flex-direction:column;gap:16px}
|
|
530
|
+
.sk button,.sk input,.sk select{font:inherit;color:inherit;border:1px solid var(--sk-border);border-radius:8px;background:var(--sk-bg);min-height:36px;padding:7px 12px}
|
|
531
|
+
.sk button{cursor:pointer}.sk button:hover{background:var(--sk-card)}.sk button:disabled{opacity:.45;cursor:default}
|
|
532
|
+
.sk button.primary{background:var(--sk-accent);color:#fff;border-color:transparent}
|
|
533
|
+
.sk table{width:100%;border-collapse:collapse;font-size:13px}.sk th,.sk td{border-bottom:1px solid var(--sk-border);padding:8px;text-align:left}.sk th{color:var(--sk-muted);font-weight:500}
|
|
534
|
+
.sk .error{color:var(--dsw-alias-state-error-primary,#ef9393)}.sk .good{color:var(--dsw-alias-state-success-primary,#77ca9d)}
|
|
535
|
+
.sk .pill{border:1px solid var(--sk-border);border-radius:100px;padding:2px 10px;font-size:12px;background:var(--sk-bg);display:inline-block}
|
|
536
|
+
.sk .mono{font:12px ui-monospace,monospace;overflow-wrap:anywhere}
|
|
537
|
+
.sk dialog{color:var(--sk-text);background:var(--sk-bg);border:1px solid var(--sk-border);border-radius:14px;width:min(560px,calc(100vw - 32px));padding:0}
|
|
538
|
+
.sk dialog::backdrop{background:#0007}.sk .modal-head{padding:16px 20px;border-bottom:1px solid var(--sk-border);display:flex;justify-content:space-between;align-items:center}
|
|
539
|
+
.sk .modal-body{padding:20px;display:flex;flex-direction:column;gap:14px}
|
|
540
|
+
.sk input.grow{flex:1;min-width:0}
|
|
541
|
+
`;
|
|
542
|
+
function KitPage() {
|
|
543
|
+
const { state, error } = useKit();
|
|
544
|
+
const [busy, setBusy2] = (0, import_react.useState)(false);
|
|
545
|
+
const [feedback, setFeedback] = (0, import_react.useState)("");
|
|
546
|
+
const say = (message) => {
|
|
547
|
+
setFeedback(message);
|
|
548
|
+
setTimeout(() => setFeedback(""), 8e3);
|
|
549
|
+
};
|
|
550
|
+
const act = async (input, ok) => {
|
|
551
|
+
setBusy2(true);
|
|
552
|
+
try {
|
|
553
|
+
const result = await callHost(input);
|
|
554
|
+
if (ok) say(ok);
|
|
555
|
+
await loadState();
|
|
556
|
+
setBusy2(false);
|
|
557
|
+
return result;
|
|
558
|
+
} catch (err) {
|
|
559
|
+
say(`\u5931\u8D25\uFF1A${err.message}`);
|
|
560
|
+
setBusy2(false);
|
|
561
|
+
throw err;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
const settings = state?.settings;
|
|
565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sk", children: [
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles }),
|
|
567
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: "\u4F1A\u8BDD\u5DE5\u5177\u7BB1" }),
|
|
568
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "muted", children: [
|
|
569
|
+
"\u8349\u7A3F\u4FDD\u9669\u7BB1 \xB7 \u4F1A\u8BDD\u5207\u6362\u9879\u76EE \xB7 \u56DE\u6536\u7AD9 \u2014 v",
|
|
570
|
+
pluginVersion,
|
|
571
|
+
"\u3002\u6570\u636E\u4EC5\u5B58\u672C\u673A\uFF08\u6D4F\u89C8\u5668 IndexedDB + ~/.dsh/session-toolbox/\uFF09\u3002"
|
|
572
|
+
] }),
|
|
573
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "error", children: [
|
|
574
|
+
"\u540E\u53F0\u8FDE\u63A5\u5931\u8D25\uFF1A",
|
|
575
|
+
error
|
|
576
|
+
] }) : null,
|
|
577
|
+
feedback ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "good", children: feedback }) : null,
|
|
578
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box", children: [
|
|
579
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u8349\u7A3F\u4FDD\u9669\u7BB1\uFF08\u56FE\u7247\u8349\u7A3F\u6301\u4E45\u5316\uFF09" }),
|
|
580
|
+
settings ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
581
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
|
|
582
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "row", children: [
|
|
583
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
584
|
+
"input",
|
|
585
|
+
{
|
|
586
|
+
type: "checkbox",
|
|
587
|
+
checked: settings.draftVault?.enabled !== false,
|
|
588
|
+
disabled: busy,
|
|
589
|
+
onChange: (e) => void act({ type: "settings.update", patch: { draftVault: { ...settings.draftVault, enabled: e.target.checked } } }, e.target.checked ? "\u5DF2\u542F\u7528\u8349\u7A3F\u4FDD\u9669\u7BB1" : "\u5DF2\u505C\u7528\u8349\u7A3F\u4FDD\u9669\u7BB1\uFF08\u6D4F\u89C8\u5668\u4FA7\u5373\u65F6\u751F\u6548\uFF09").catch(() => {
|
|
590
|
+
})
|
|
591
|
+
}
|
|
592
|
+
),
|
|
593
|
+
" \u542F\u7528"
|
|
594
|
+
] }),
|
|
595
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pill", children: [
|
|
596
|
+
"\u5BB9\u91CF ",
|
|
597
|
+
Math.round((settings.draftVault?.maxSizeBytes ?? 0) / 1048576),
|
|
598
|
+
" MB"
|
|
599
|
+
] }),
|
|
600
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pill", children: [
|
|
601
|
+
"\u4FDD\u7559 ",
|
|
602
|
+
settings.draftVault?.ttlDays ?? 7,
|
|
603
|
+
" \u5929"
|
|
604
|
+
] })
|
|
605
|
+
] }),
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "muted", children: [
|
|
607
|
+
"\u6E05\u5355\uFF1A\u5DF2\u5907\u4EFD ",
|
|
608
|
+
state?.vault?.records ?? 0,
|
|
609
|
+
" \u9879 \xB7 \u7EA6 ",
|
|
610
|
+
fmtBytes(state?.vault?.bytes ?? 0),
|
|
611
|
+
"\uFF08\u56FE\u7247\u5B57\u8282\u5728 IndexedDB\uFF0C\u8D85\u51FA\u4E0A\u9650\u6309\u6700\u65E7\u6E05\u7406\uFF0C\u4E8B\u4EF6\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\uFF09"
|
|
612
|
+
] }) }),
|
|
613
|
+
state?.vault?.notice ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: state.vault.notice }) : null,
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "row", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "vault.sweep" }, "\u5DF2\u6309 TTL/\u5BB9\u91CF\u6E05\u7406").catch(() => {
|
|
615
|
+
}), children: "\u7ACB\u5373\u6574\u7406" }) })
|
|
616
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: "\u7B49\u5F85\u540E\u53F0\u6570\u636E\u2026" })
|
|
617
|
+
] }),
|
|
618
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MoveSection, { act, busy, say, state }),
|
|
619
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(TrashSection, { act, busy, say, state })
|
|
620
|
+
] });
|
|
621
|
+
}
|
|
622
|
+
function MoveSection({ act, busy, say, state }) {
|
|
623
|
+
const [sessionId, setSessionId] = (0, import_react.useState)("");
|
|
624
|
+
const [toCwd, setToCwd] = (0, import_react.useState)("");
|
|
625
|
+
const [createTarget, setCreateTarget] = (0, import_react.useState)(false);
|
|
626
|
+
const [plan, setPlan] = (0, import_react.useState)(null);
|
|
627
|
+
const journal = state?.journal;
|
|
628
|
+
const pick = async () => {
|
|
629
|
+
try {
|
|
630
|
+
const result = await directoryPicker?.pick?.();
|
|
631
|
+
const value = result?.ok ? result.value : void 0;
|
|
632
|
+
if (typeof value === "string" && value) setToCwd(value);
|
|
633
|
+
else say("\u5F53\u524D DSH \u672A\u63D0\u4F9B\u76EE\u5F55\u9009\u62E9\u5668\uFF08\u6216\u5DF2\u53D6\u6D88\uFF09\uFF0C\u8BF7\u624B\u52A8\u8F93\u5165\u7EDD\u5BF9\u8DEF\u5F84");
|
|
634
|
+
} catch (error) {
|
|
635
|
+
say(`\u76EE\u5F55\u9009\u62E9\u5931\u8D25\uFF1A${error.message}`);
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box", children: [
|
|
639
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u4F1A\u8BDD\u5207\u6362\u9879\u76EE" }),
|
|
640
|
+
journal && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "error", children: [
|
|
641
|
+
"\u5B58\u5728\u672A\u5B8C\u6210\u7684\u8FC1\u79FB\uFF1A",
|
|
642
|
+
journal.sessionId,
|
|
643
|
+
" \u2192 ",
|
|
644
|
+
journal.toCwd,
|
|
645
|
+
"\uFF08\u5DF2\u5B8C\u6210 ",
|
|
646
|
+
journal.done?.join("/") || "\u65E0",
|
|
647
|
+
"\uFF09\u3002",
|
|
648
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "move.complete" }, "\u8FC1\u79FB\u5DF2\u524D\u6EDA\u5B8C\u6210").catch(() => {
|
|
649
|
+
}), children: "\u5B8C\u6210" }),
|
|
650
|
+
" ",
|
|
651
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "move.rollback" }, "\u8FC1\u79FB\u5DF2\u56DE\u6EDA").catch(() => {
|
|
652
|
+
}), children: "\u56DE\u6EDA" })
|
|
653
|
+
] }),
|
|
654
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
|
|
655
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "grow mono", placeholder: "session-\u2026\uFF08\u6216\u4ECE\u4F1A\u8BDD\u53F3\u952E\u83DC\u5355\u53D1\u8D77\uFF09", value: sessionId, onChange: (e) => {
|
|
656
|
+
setSessionId(e.target.value);
|
|
657
|
+
setPlan(null);
|
|
658
|
+
} }),
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "grow mono", placeholder: "\u76EE\u6807\u9879\u76EE\u7EDD\u5BF9\u8DEF\u5F84", value: toCwd, onChange: (e) => {
|
|
660
|
+
setToCwd(e.target.value);
|
|
661
|
+
setPlan(null);
|
|
662
|
+
} }),
|
|
663
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => void pick(), disabled: busy, children: "\u6D4F\u89C8\u2026" })
|
|
664
|
+
] }),
|
|
665
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "row", children: [
|
|
666
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: createTarget, onChange: (e) => setCreateTarget(e.target.checked) }),
|
|
667
|
+
" \u76EE\u6807\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA"
|
|
668
|
+
] }),
|
|
669
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
|
|
670
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy || !sessionId || !toCwd, onClick: () => {
|
|
671
|
+
setBusy(true);
|
|
672
|
+
setPlan(null);
|
|
673
|
+
void callHost({ type: "preflight", sessionId, toCwd, createTarget }).then((result) => setPlan(result)).catch((error) => say(`\u4F53\u68C0\u5931\u8D25\uFF1A${error.message}`)).finally(() => setBusy(false));
|
|
674
|
+
}, children: "\u4F53\u68C0" }),
|
|
675
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy || plan?.toProject === void 0, onClick: () => void act({ type: "move", sessionId, toCwd, createTarget }, "\u8FC1\u79FB\u5B8C\u6210\uFF1B\u5176\u5B83\u7A97\u53E3\u8BF7\u91CD\u5F00\u8BE5\u4F1A\u8BDD").catch(() => {
|
|
676
|
+
}), children: "\u8FC1\u79FB" })
|
|
677
|
+
] }),
|
|
678
|
+
plan?.toProject && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "mono muted", style: { whiteSpace: "pre-wrap" }, children: JSON.stringify({ \u4ECE: plan.fromCwd, \u5230: plan.toCwd, \u539F\u76EE\u5F55: plan.fromProject, \u65B0\u76EE\u5F55: plan.toProject, \u65E5\u5FD7\u6587\u4EF6: plan.logName }, null, 2) }),
|
|
679
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: "\u53EA\u6539 DSH \u5185\u90E8\u5F52\u5C5E\u4E0E\u4F1A\u8BDD\u5934\uFF1B\u5386\u53F2\u4E0D\u53D8\uFF0C\u65E7\u6D88\u606F\u4E2D\u7684\u7EDD\u5BF9\u8DEF\u5F84\u4ECD\u6307\u5411\u539F\u9879\u76EE\uFF08\u5DF2\u77E5\u8BED\u4E49\uFF09\u3002\u8FC1\u79FB\u524D\u8BF7\u5173\u95ED\u8BE5\u4F1A\u8BDD\u3002" })
|
|
680
|
+
] });
|
|
681
|
+
}
|
|
682
|
+
function TrashSection({ act, busy, say, state }) {
|
|
683
|
+
const items = state?.trash ?? [];
|
|
684
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "box", children: [
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { children: "\u56DE\u6536\u7AD9" }),
|
|
686
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: "\u5220\u9664\u4F1A\u8BDD = \u5B98\u65B9\u5F52\u6863 + \u5165\u7AD9\u8BB0\u5F55\uFF1B\u5220\u9664\u9879\u76EE\u4FDD\u7559\u5168\u90E8\u4F1A\u8BDD\u65E5\u5FD7\u3002\u5F7B\u5E95\u5220\u9664\u4F1A\u5148\u79FB\u5230\u6682\u5B58\u76EE\u5F55\u518D\u6E05\u7406\uFF0C\u4E2D\u65AD\u53EF\u7528 CLI \u68C0\u51FA\u3002" }),
|
|
687
|
+
items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted", children: "\uFF08\u7A7A\uFF09" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { children: [
|
|
688
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
689
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u7C7B\u578B" }),
|
|
690
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u5BF9\u8C61" }),
|
|
691
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u6807\u9898 / \u8DEF\u5F84" }),
|
|
692
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { children: "\u5165\u7AD9\u65F6\u95F4" }),
|
|
693
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", {})
|
|
694
|
+
] }) }),
|
|
695
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
|
|
696
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: item.kind === "session" ? "\u4F1A\u8BDD" : "\u9879\u76EE" }),
|
|
697
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "mono", children: shortId(item.targetId) }),
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { className: "mono", children: item.kind === "session" ? item.snapshot.title || "\uFF08\u65E0\u6807\u9898\uFF09" : item.snapshot.path }),
|
|
699
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { children: new Date(item.trashedAt).toLocaleString("zh-CN", { hour12: false }) }),
|
|
700
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { className: "row", children: [
|
|
701
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "trash.restore", itemId: item.itemId }, "\u5DF2\u8FD8\u539F\uFF08\u82E5\u5904\u4E8E\u5F52\u6863\u6001\u8BF7\u5148\u5728\u5217\u8868\u91CC\u8FD8\u539F\u5F52\u6863\uFF09").catch(() => {
|
|
702
|
+
}), children: "\u8FD8\u539F" }),
|
|
703
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "trash.purge", itemId: item.itemId }, "\u5DF2\u5F7B\u5E95\u5220\u9664").catch(() => {
|
|
704
|
+
}), children: "\u5F7B\u5E95\u5220\u9664" })
|
|
705
|
+
] })
|
|
706
|
+
] }, item.itemId)) })
|
|
707
|
+
] }),
|
|
708
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "muted", children: [
|
|
710
|
+
"\u4FDD\u7559 ",
|
|
711
|
+
state?.settings?.trash?.ttlDays ?? 30,
|
|
712
|
+
" \u5929\uFF1B\u5230\u671F\u81EA\u52A8\u6E05\u7406",
|
|
713
|
+
state?.settings?.trash?.autoPurge ? "\uFF08\u5DF2\u5F00\uFF09" : "\uFF08\u672A\u5F00\uFF0C\u4EC5\u63D0\u9192\uFF09",
|
|
714
|
+
"\u3002"
|
|
715
|
+
] }),
|
|
716
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "settings.update", patch: { trash: { ...state?.settings?.trash, autoPurge: !state?.settings?.trash?.autoPurge } } }, "\u7B56\u7565\u5DF2\u66F4\u65B0").catch(() => {
|
|
717
|
+
}), children: "\u5207\u6362\u81EA\u52A8\u6E05\u7406" }),
|
|
718
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { disabled: busy, onClick: () => void act({ type: "trash.sweep" }, "\u5DF2\u6309 TTL \u5DE1\u68C0").catch(() => {
|
|
719
|
+
}), children: "\u5DE1\u68C0\u5230\u671F\u9879" })
|
|
720
|
+
] })
|
|
721
|
+
] });
|
|
722
|
+
}
|
|
723
|
+
var shortId = (id) => String(id).length > 18 ? `${String(id).slice(0, 8)}\u2026${String(id).slice(-6)}` : String(id);
|
|
724
|
+
var fmtBytes = (n) => n >= 1048576 ? `${(n / 1048576).toFixed(1)} MB` : n >= 1024 ? `${(n / 1024).toFixed(1)} KB` : `${n} B`;
|
|
725
|
+
function MoveSessionMenuItem({ sessionId, t }) {
|
|
726
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
727
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
728
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { role: "menuitem", onClick: () => setOpen(true), style: menuItemStyle, children: t ? t("menu.move") : "\u5207\u6362\u5230\u5176\u5B83\u9879\u76EE\u2026" }),
|
|
729
|
+
open ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MoveDialog, { sessionId, close: () => setOpen(false) }) : null
|
|
730
|
+
] });
|
|
731
|
+
}
|
|
732
|
+
function TrashSessionMenuItem({ sessionId, t }) {
|
|
733
|
+
const [confirm, setConfirm] = (0, import_react.useState)(false);
|
|
734
|
+
const [busy, setBusy2] = (0, import_react.useState)(false);
|
|
735
|
+
if (confirm) {
|
|
736
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { role: "menuitem", disabled: busy, style: menuItemStyle, onClick: async () => {
|
|
737
|
+
setBusy2(true);
|
|
738
|
+
try {
|
|
739
|
+
await callHost({ type: "trash.session", sessionId });
|
|
740
|
+
setConfirm(false);
|
|
741
|
+
} catch {
|
|
742
|
+
sayRow(`\u56DE\u6536\u5931\u8D25`);
|
|
743
|
+
setConfirm(false);
|
|
744
|
+
}
|
|
745
|
+
setBusy2(false);
|
|
746
|
+
}, children: "\u786E\u8BA4\u79FB\u5165\u56DE\u6536\u7AD9\uFF1F\uFF08\u5148\u5F52\u6863\uFF09" });
|
|
747
|
+
}
|
|
748
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { role: "menuitem", style: menuItemStyle, onClick: () => setConfirm(true), children: t ? t("menu.trash") : "\u79FB\u5165\u56DE\u6536\u7AD9" });
|
|
749
|
+
}
|
|
750
|
+
function sayRow(message) {
|
|
751
|
+
store.error = message;
|
|
752
|
+
for (const listener of store.listeners) listener();
|
|
753
|
+
}
|
|
754
|
+
var menuItemStyle = { background: "transparent", border: "none", color: "inherit", font: "inherit", padding: "6px 10px", textAlign: "left", cursor: "pointer", width: "100%" };
|
|
755
|
+
function MoveDialog({ sessionId, close }) {
|
|
756
|
+
const ref = (0, import_react.useRef)(null);
|
|
757
|
+
const [toCwd, setToCwd] = (0, import_react.useState)("");
|
|
758
|
+
const [createTarget, setCreateTarget] = (0, import_react.useState)(false);
|
|
759
|
+
const [error, setError] = (0, import_react.useState)("");
|
|
760
|
+
const [busy, setBusy2] = (0, import_react.useState)(false);
|
|
761
|
+
(0, import_react.useEffect)(() => {
|
|
762
|
+
const dialog = ref.current;
|
|
763
|
+
dialog?.showModal();
|
|
764
|
+
return () => {
|
|
765
|
+
try {
|
|
766
|
+
dialog?.close();
|
|
767
|
+
} catch {
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
}, []);
|
|
771
|
+
const submit = async () => {
|
|
772
|
+
setBusy2(true);
|
|
773
|
+
setError("");
|
|
774
|
+
try {
|
|
775
|
+
await callHost({ type: "move", sessionId, toCwd, createTarget });
|
|
776
|
+
close();
|
|
777
|
+
} catch (err) {
|
|
778
|
+
setError(err.message);
|
|
779
|
+
setBusy2(false);
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
const valid = /^\/|^[A-Za-z]:[\\/]/.test(toCwd);
|
|
783
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "sk", children: [
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: styles }),
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dialog", { ref, onCancel: (e) => {
|
|
786
|
+
e.preventDefault();
|
|
787
|
+
if (!busy) close();
|
|
788
|
+
}, children: [
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-head", children: [
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "\u4F1A\u8BDD\u5207\u6362\u5230\u5176\u5B83\u9879\u76EE" }),
|
|
791
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: close, disabled: busy, children: "\u5173\u95ED" })
|
|
792
|
+
] }),
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "modal-body", children: [
|
|
794
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "muted mono", children: sessionId }),
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { autoFocus: true, className: "grow mono", placeholder: "\u76EE\u6807\u9879\u76EE\u7EDD\u5BF9\u8DEF\u5F84", value: toCwd, onChange: (e) => setToCwd(e.target.value) }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "row", children: [
|
|
797
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", checked: createTarget, onChange: (e) => setCreateTarget(e.target.checked) }),
|
|
798
|
+
" \u76EE\u5F55\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA"
|
|
799
|
+
] }),
|
|
800
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "error", children: error }) : null,
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "row", children: [
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "primary", disabled: busy || !valid, onClick: () => void submit(), children: "\u4F53\u68C0\u5E76\u8FC1\u79FB" }),
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "muted", children: "\u4F1A\u8BDD\u9700\u7A7A\u95F2\uFF1B\u8FC1\u79FB\u5728\u540E\u53F0\u79BB\u7EBF\u5B8C\u6210\u3002" })
|
|
804
|
+
] })
|
|
805
|
+
] })
|
|
806
|
+
] })
|
|
807
|
+
] });
|
|
808
|
+
}
|
|
809
|
+
function VaultBadge({ sessionId }) {
|
|
810
|
+
const [count, setCount] = (0, import_react.useState)(0);
|
|
811
|
+
const [notice, setNotice] = (0, import_react.useState)("");
|
|
812
|
+
(0, import_react.useEffect)(() => {
|
|
813
|
+
if (vault === void 0) return;
|
|
814
|
+
let live = true;
|
|
815
|
+
const sync = async () => {
|
|
816
|
+
try {
|
|
817
|
+
const value = await vault.countFor(sessionId ?? vault.currentSession());
|
|
818
|
+
if (live) setCount(value);
|
|
819
|
+
} catch {
|
|
820
|
+
if (live) setCount(0);
|
|
821
|
+
}
|
|
822
|
+
if (live) setNotice(vault.lastNotice ?? "");
|
|
823
|
+
};
|
|
824
|
+
void sync();
|
|
825
|
+
const listener = () => void sync();
|
|
826
|
+
vault.on("change", listener);
|
|
827
|
+
window.addEventListener(`${NS}-notice`, listener);
|
|
828
|
+
const timer = setInterval(listener, 2e4);
|
|
829
|
+
return () => {
|
|
830
|
+
live = false;
|
|
831
|
+
vault.off("change", listener);
|
|
832
|
+
window.removeEventListener(`${NS}-notice`, listener);
|
|
833
|
+
clearInterval(timer);
|
|
834
|
+
};
|
|
835
|
+
}, [sessionId]);
|
|
836
|
+
if (count === 0) return null;
|
|
837
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sk", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "pill", title: `\u672C\u4F1A\u8BDD\u6709 ${count} \u5F20\u56FE\u7247\u8349\u7A3F\u5DF2\u5907\u4EFD\uFF08\u5207\u6362\u4F1A\u8BDD/\u5237\u65B0/\u91CD\u542F\u540E\u53EF\u6062\u590D\uFF09${notice ? `\uFF5C\u6700\u8FD1\uFF1A${notice}` : ""}`, children: [
|
|
838
|
+
"\u{1F6DF} \u8349\u7A3F\u5907\u4EFD ",
|
|
839
|
+
count
|
|
840
|
+
] }) });
|
|
841
|
+
}
|
|
842
|
+
return module.exports; } });
|
|
843
|
+
//# sourceMappingURL=client.js.map
|