@aipanel/dsh-client 1.2.8 → 1.2.10
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 +312 -235
- package/package.json +8 -2
package/lib/client.js
CHANGED
|
@@ -24,32 +24,111 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
24
24
|
// dsh-client/src/client/index.ts
|
|
25
25
|
var index_exports = {};
|
|
26
26
|
__export(index_exports, {
|
|
27
|
-
INSERT_ELEMENT_EVENT: () => INSERT_ELEMENT_EVENT,
|
|
28
|
-
SESSION_READY_EVENT: () => SESSION_READY_EVENT,
|
|
29
27
|
apply: () => apply,
|
|
30
28
|
inject: () => inject
|
|
31
29
|
});
|
|
32
30
|
module.exports = __toCommonJS(index_exports);
|
|
33
31
|
|
|
32
|
+
// ../../core/es/common/constants.mjs
|
|
33
|
+
var EXT_BROADCAST = {
|
|
34
|
+
PAGE_CONTEXT: "PAGE_CONTEXT",
|
|
35
|
+
THEME_CHANGE: "THEME_CHANGE",
|
|
36
|
+
SERVICE_APPEARED: "SERVICE_APPEARED",
|
|
37
|
+
SERVICE_GONE: "SERVICE_GONE"
|
|
38
|
+
};
|
|
39
|
+
var EXT_MSG = {
|
|
40
|
+
...EXT_BROADCAST,
|
|
41
|
+
GET_PORT_INFO: "GET_PORT_INFO",
|
|
42
|
+
TAB_SWITCHED: "TAB_SWITCHED",
|
|
43
|
+
REQUEST_PAGE_CONTEXT: "REQUEST_PAGE_CONTEXT",
|
|
44
|
+
SELECTION_START: "SELECTION_START",
|
|
45
|
+
SELECTION_STOP: "SELECTION_STOP",
|
|
46
|
+
CS_QUERY_WINDOW: "__CS_QUERY_WINDOW__",
|
|
47
|
+
/** Side Panel → Background:立即轮询一次并回传当前服务信息 */
|
|
48
|
+
FORCE_POLL: "FORCE_POLL"
|
|
49
|
+
};
|
|
50
|
+
var WIDGET_MSG = {
|
|
51
|
+
READY: "AIPANEL_READY",
|
|
52
|
+
KEYDOWN: "AIPANEL_KEYDOWN",
|
|
53
|
+
SET_THEME: "AIPANEL_SET_THEME",
|
|
54
|
+
INSERT_FILE_PART: "AIPANEL_INSERT_FILE_PART",
|
|
55
|
+
SELECT_MODE_CHANGE: "AIPANEL_SELECT_MODE_CHANGE",
|
|
56
|
+
ELEMENT_SELECTED: "AIPANEL_ELEMENT_SELECTED",
|
|
57
|
+
SELECTION_CANCELLED: "AIPANEL_SELECTION_CANCELLED",
|
|
58
|
+
SELECTOR_START: "AIPANEL_SELECTOR_START",
|
|
59
|
+
SELECTOR_STOP: "AIPANEL_SELECTOR_STOP",
|
|
60
|
+
SERVICE_INFO: "AIPANEL_SERVICE_INFO",
|
|
61
|
+
MINIMIZE_STATE: "MINIMIZE_STATE_CHANGE",
|
|
62
|
+
PROMPT_DOCK_VISIBILITY: "PROMPT_DOCK_VISIBILITY_CHANGE",
|
|
63
|
+
REVIEW_PANEL_TOGGLE: "REVIEW_PANEL_TOGGLE",
|
|
64
|
+
/** 无 deepLink 能力的 Provider:通知 iframe 聚焦指定会话 */
|
|
65
|
+
FOCUS_SESSION: "AIPANEL_FOCUS_SESSION",
|
|
66
|
+
/** 无 deepLink 能力的 Provider:iframe 确认目标会话已激活且渲染稳定(携带 sessionId) */
|
|
67
|
+
SESSION_READY: "AIPANEL_SESSION_READY"
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ../../core/es/common/utils.mjs
|
|
71
|
+
function ensureNodeId(element) {
|
|
72
|
+
if (element.id) return element.id;
|
|
73
|
+
const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 8) : Math.random().toString(36).slice(2, 10);
|
|
74
|
+
element.id = `n${random}`;
|
|
75
|
+
return element.id;
|
|
76
|
+
}
|
|
77
|
+
function toNodeMention(id) {
|
|
78
|
+
return `@\u8282\u70B9[${id}]`;
|
|
79
|
+
}
|
|
80
|
+
function widgetEnvelope(type, data) {
|
|
81
|
+
return { type, ...data };
|
|
82
|
+
}
|
|
83
|
+
|
|
34
84
|
// dsh-client/src/client/diagnostics-view.tsx
|
|
35
85
|
var import_react = require("react");
|
|
36
86
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
37
87
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
38
|
-
|
|
88
|
+
function isSettledToolResult(block) {
|
|
89
|
+
return typeof block === "object" && block !== null && "kind" in block && block.kind === "tool-result";
|
|
90
|
+
}
|
|
91
|
+
var TEXT_ESLINT_RE = /^[ \t]*(ERROR|WARN) \[([^\]\r\n]+?):(\d+):(\d+)\] (.*)$/gm;
|
|
92
|
+
var TEXT_TSC_RE = /^[ \t]*([^():\r\n]+)\((\d+),(\d+)\):\s*(error|warning)\s+(.*)$/gm;
|
|
93
|
+
function parseTextDiagnostics(text) {
|
|
94
|
+
const out = [];
|
|
95
|
+
if (!text) return out;
|
|
96
|
+
for (const m of text.matchAll(TEXT_ESLINT_RE)) {
|
|
97
|
+
out.push({
|
|
98
|
+
file: m[2],
|
|
99
|
+
line: Number(m[3]),
|
|
100
|
+
column: Number(m[4]),
|
|
101
|
+
severity: m[1].toLowerCase(),
|
|
102
|
+
message: m[5].trim()
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const m of text.matchAll(TEXT_TSC_RE)) {
|
|
106
|
+
out.push({
|
|
107
|
+
file: m[1].trim(),
|
|
108
|
+
line: Number(m[2]),
|
|
109
|
+
column: Number(m[3]),
|
|
110
|
+
severity: m[4],
|
|
111
|
+
message: m[5].trim()
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
39
116
|
function readDiagnostics(block) {
|
|
40
|
-
if (
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return
|
|
117
|
+
if (!isSettledToolResult(block)) return void 0;
|
|
118
|
+
const meta = block.meta;
|
|
119
|
+
if (Array.isArray(meta?.diagnostics) && meta.diagnostics.every(isEntry)) {
|
|
120
|
+
return meta.diagnostics;
|
|
121
|
+
}
|
|
122
|
+
const parsed = parseTextDiagnostics(extractBlockText(block));
|
|
123
|
+
return parsed.length > 0 ? parsed : void 0;
|
|
47
124
|
}
|
|
48
125
|
function extractBlockText(block) {
|
|
49
|
-
if (
|
|
50
|
-
const
|
|
51
|
-
if (!Array.isArray(
|
|
52
|
-
return
|
|
126
|
+
if (!isSettledToolResult(block)) return "";
|
|
127
|
+
const content = block.content;
|
|
128
|
+
if (!Array.isArray(content)) return "";
|
|
129
|
+
return content.filter(
|
|
130
|
+
(b) => typeof b === "object" && b !== null && b.type === "text" && typeof b.text === "string"
|
|
131
|
+
).map((b) => b.text).join("\n");
|
|
53
132
|
}
|
|
54
133
|
function isEntry(d) {
|
|
55
134
|
const e = d;
|
|
@@ -171,11 +250,12 @@ var styles = {
|
|
|
171
250
|
function DiagnosticsRow({ block, cwd, toolName, openFile }) {
|
|
172
251
|
const diagnostics = readDiagnostics(block);
|
|
173
252
|
const [expanded, setExpanded] = (0, import_react.useState)(false);
|
|
174
|
-
const isSettled = block
|
|
253
|
+
const isSettled = isSettledToolResult(block);
|
|
175
254
|
const hasDiagnostics = typeof diagnostics !== "undefined" && diagnostics.length > 0;
|
|
176
255
|
const errors = (diagnostics ?? []).filter((d) => d.severity === "error");
|
|
177
256
|
const warnings = (diagnostics ?? []).filter((d) => d.severity === "warning");
|
|
178
257
|
const contentText = extractBlockText(block);
|
|
258
|
+
const textHasIssues = /^[ \t]*(?:ERROR|WARN)\s+\[/m.test(contentText);
|
|
179
259
|
const firstLine = (contentText.split("\n")[0] || "").trim();
|
|
180
260
|
const isFullProject = /^全量诊断结果/.test(firstLine);
|
|
181
261
|
const scope = isFullProject ? "project" : "file";
|
|
@@ -212,7 +292,7 @@ function DiagnosticsRow({ block, cwd, toolName, openFile }) {
|
|
|
212
292
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sep, "aria-hidden": true }),
|
|
213
293
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.summary, children: "\u8BCA\u65AD\u8FD0\u884C\u4E2D\u2026" })
|
|
214
294
|
] });
|
|
215
|
-
} else if (diagnostics && diagnostics.length === 0) {
|
|
295
|
+
} else if (diagnostics && diagnostics.length === 0 && !textHasIssues) {
|
|
216
296
|
collapsed = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
217
297
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sep, "aria-hidden": true }),
|
|
218
298
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.summary, children: [
|
|
@@ -270,12 +350,8 @@ function DiagnosticsRow({ block, cwd, toolName, openFile }) {
|
|
|
270
350
|
}
|
|
271
351
|
);
|
|
272
352
|
}
|
|
273
|
-
function registerDiagnosticsView(ctx) {
|
|
274
|
-
|
|
275
|
-
if (localStorage.getItem(DIAGNOSTICS_STORAGE_KEY) !== "1") return;
|
|
276
|
-
} catch {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
353
|
+
function registerDiagnosticsView(ctx, enabled = true) {
|
|
354
|
+
if (!enabled) return;
|
|
279
355
|
const slots = ctx.slots;
|
|
280
356
|
if (!slots) return;
|
|
281
357
|
slots.inject("tool.call.toolview", function* () {
|
|
@@ -284,23 +360,10 @@ function registerDiagnosticsView(ctx) {
|
|
|
284
360
|
}
|
|
285
361
|
|
|
286
362
|
// dsh-client/src/client/index.ts
|
|
287
|
-
var
|
|
363
|
+
var MSG = WIDGET_MSG;
|
|
288
364
|
var inject = ["slots", "sessions", "inputTriggers", "conversation"];
|
|
289
|
-
var SESSION_READY_EVENT = "aipanel:session-ready";
|
|
290
|
-
var INSERT_ELEMENT_EVENT = "aipanel:insert-element";
|
|
291
365
|
var SESSION_SETTLE_MS = 400;
|
|
292
|
-
|
|
293
|
-
if (e.id) return e.id;
|
|
294
|
-
const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 8) : Math.random().toString(36).slice(2, 10);
|
|
295
|
-
e.id = `n${random}`;
|
|
296
|
-
return e.id;
|
|
297
|
-
}
|
|
298
|
-
function elementLabel(e) {
|
|
299
|
-
if (e.description) return e.description;
|
|
300
|
-
const text = e.innerText?.trim();
|
|
301
|
-
if (text) return text.slice(0, 40);
|
|
302
|
-
return "\u5143\u7D20";
|
|
303
|
-
}
|
|
366
|
+
var FOCUS_OPEN_MAX_ATTEMPTS = 3;
|
|
304
367
|
function elementContextRef(e) {
|
|
305
368
|
return JSON.stringify(e);
|
|
306
369
|
}
|
|
@@ -308,7 +371,7 @@ function serializeElement(ref) {
|
|
|
308
371
|
try {
|
|
309
372
|
const e = JSON.parse(ref);
|
|
310
373
|
if (!e || typeof e !== "object") throw new Error("not an element payload");
|
|
311
|
-
return
|
|
374
|
+
return toNodeMention(ensureNodeId(e));
|
|
312
375
|
} catch {
|
|
313
376
|
return `@${ref}`;
|
|
314
377
|
}
|
|
@@ -323,90 +386,91 @@ function toReference(e) {
|
|
|
323
386
|
clipboardText: `@${mark}`
|
|
324
387
|
};
|
|
325
388
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
389
|
+
function isEmbedded() {
|
|
390
|
+
try {
|
|
391
|
+
return window.parent !== window;
|
|
392
|
+
} catch {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
332
395
|
}
|
|
333
|
-
function
|
|
396
|
+
function postToHost(type, data = {}) {
|
|
397
|
+
if (!isEmbedded()) return;
|
|
334
398
|
try {
|
|
335
|
-
|
|
336
|
-
if (!raw) return [];
|
|
337
|
-
const elements = JSON.parse(raw);
|
|
338
|
-
if (!Array.isArray(elements)) return [];
|
|
339
|
-
return elements.filter((e) => !!e && typeof e === "object").slice(0, 20).map(toCandidate);
|
|
399
|
+
window.parent.postMessage(widgetEnvelope(type, data), "*");
|
|
340
400
|
} catch {
|
|
341
|
-
return [];
|
|
342
401
|
}
|
|
343
402
|
}
|
|
344
|
-
function
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
order: 300,
|
|
352
|
-
showGroupTitle: false,
|
|
353
|
-
// 候选 = 最近选中元素(bridge 写入),按输入查询过滤
|
|
354
|
-
candidates: async (_session, req) => {
|
|
355
|
-
const all = readSelectionCandidates();
|
|
356
|
-
const query = req.query.trim().toLowerCase();
|
|
357
|
-
if (!query) return all;
|
|
358
|
-
return all.filter(
|
|
359
|
-
(c) => c.name.toLowerCase().includes(query) || (c.description ?? "").toLowerCase().includes(query)
|
|
360
|
-
);
|
|
361
|
-
},
|
|
362
|
-
// 选定 → 铸造 file 引用:ref/label 分离(label 短,ref 携带完整元素 + 节点 id)
|
|
363
|
-
onPick: (pick) => {
|
|
364
|
-
try {
|
|
365
|
-
const parsed = JSON.parse(pick.candidate.value ?? "null");
|
|
366
|
-
if (parsed) {
|
|
367
|
-
ensureNodeId(parsed);
|
|
368
|
-
return {
|
|
369
|
-
insert: {
|
|
370
|
-
source: "aipanel",
|
|
371
|
-
ref: JSON.stringify(parsed),
|
|
372
|
-
label: pick.candidate.name,
|
|
373
|
-
appearance: "file",
|
|
374
|
-
// clipboardText 以 @ 开头(dsh backdrop 取首字符作 chip 触发 glyph)
|
|
375
|
-
clipboardText: `@${pick.candidate.name}`
|
|
376
|
-
}
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
} catch {
|
|
380
|
-
}
|
|
381
|
-
return {
|
|
382
|
-
insert: {
|
|
383
|
-
source: "aipanel",
|
|
384
|
-
ref: pick.candidate.value ?? pick.candidate.name,
|
|
385
|
-
label: pick.candidate.name,
|
|
386
|
-
appearance: "file",
|
|
387
|
-
clipboardText: `@${pick.candidate.name}`
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
},
|
|
391
|
-
// 模型投影
|
|
392
|
-
codec: {
|
|
393
|
-
clipboardText: (ref) => ref,
|
|
394
|
-
serialize: async (ref) => serializeElement(ref)
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
ctx.effect(() => inputTriggers.registerSource(source), "aipanel: @ source");
|
|
403
|
+
function mapAipanelTheme(t) {
|
|
404
|
+
if (t === "light" || t === "dark") return t;
|
|
405
|
+
if (t === "system" || t === "auto") return "system";
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
function apply(ctx, config = {}) {
|
|
409
|
+
registerDiagnosticsView(ctx, config.enableDiagnostics !== false);
|
|
398
410
|
const sessions = ctx.get("sessions");
|
|
399
|
-
if (sessions
|
|
411
|
+
if (sessions) {
|
|
400
412
|
let lastCurrent;
|
|
401
413
|
let settleTimer = null;
|
|
402
|
-
|
|
414
|
+
let targetSessionId;
|
|
415
|
+
let pendingFocusId;
|
|
416
|
+
let focusAttempts = 0;
|
|
417
|
+
let refreshing = false;
|
|
418
|
+
const notifyReady = (sessionId) => {
|
|
419
|
+
postToHost(MSG.SESSION_READY, { sessionId });
|
|
420
|
+
};
|
|
421
|
+
const hasBaseline = (snap) => {
|
|
422
|
+
if (!snap) return false;
|
|
423
|
+
return !!snap.current || !!snap.ids?.length || Object.keys(snap.byId ?? {}).length > 0;
|
|
424
|
+
};
|
|
425
|
+
const listContains = (snap, id) => {
|
|
426
|
+
if (!snap) return false;
|
|
427
|
+
return !!snap.byId?.[id] || snap.ids?.includes(id) === true;
|
|
428
|
+
};
|
|
429
|
+
const drainPendingFocus = () => {
|
|
430
|
+
const id = pendingFocusId;
|
|
431
|
+
if (!id) return;
|
|
432
|
+
const snap = sessions.list.getSnapshot();
|
|
433
|
+
if (!hasBaseline(snap)) return;
|
|
434
|
+
pendingFocusId = void 0;
|
|
435
|
+
focusAttempts = 0;
|
|
436
|
+
void tryOpenTarget(id);
|
|
437
|
+
};
|
|
438
|
+
const tryOpenTarget = async (id) => {
|
|
439
|
+
if (focusAttempts >= FOCUS_OPEN_MAX_ATTEMPTS) return;
|
|
440
|
+
focusAttempts += 1;
|
|
441
|
+
const snap = sessions.list.getSnapshot();
|
|
442
|
+
if (snap.current === id) return;
|
|
443
|
+
if (!listContains(snap, id) && !refreshing) {
|
|
444
|
+
refreshing = true;
|
|
445
|
+
try {
|
|
446
|
+
await sessions.refresh();
|
|
447
|
+
} catch {
|
|
448
|
+
} finally {
|
|
449
|
+
refreshing = false;
|
|
450
|
+
}
|
|
451
|
+
const fresh = sessions.list.getSnapshot();
|
|
452
|
+
if (!listContains(fresh, id)) {
|
|
453
|
+
setTimeout(() => void tryOpenTarget(id), 500);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
403
457
|
try {
|
|
404
|
-
|
|
458
|
+
sessions.open(id);
|
|
405
459
|
} catch {
|
|
406
460
|
}
|
|
407
461
|
};
|
|
462
|
+
const handleFocus = (sessionId) => {
|
|
463
|
+
targetSessionId = sessionId;
|
|
464
|
+
const snap = sessions.list.getSnapshot();
|
|
465
|
+
if (!hasBaseline(snap)) {
|
|
466
|
+
pendingFocusId = sessionId;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
focusAttempts = 0;
|
|
470
|
+
void tryOpenTarget(sessionId);
|
|
471
|
+
};
|
|
408
472
|
const probe = () => {
|
|
409
|
-
const snapshot = sessions.list
|
|
473
|
+
const snapshot = sessions.list?.getSnapshot?.();
|
|
410
474
|
const current = snapshot?.current;
|
|
411
475
|
if (!current) {
|
|
412
476
|
lastCurrent = void 0;
|
|
@@ -421,148 +485,161 @@ function apply(ctx) {
|
|
|
421
485
|
if (settleTimer) clearTimeout(settleTimer);
|
|
422
486
|
settleTimer = setTimeout(() => {
|
|
423
487
|
settleTimer = null;
|
|
424
|
-
if (sessions.list
|
|
425
|
-
|
|
488
|
+
if (sessions.list?.getSnapshot?.()?.current === current) {
|
|
489
|
+
notifyReady(current);
|
|
490
|
+
if (targetSessionId && targetSessionId !== current) {
|
|
491
|
+
handleFocus(targetSessionId);
|
|
492
|
+
}
|
|
426
493
|
}
|
|
427
494
|
}, SESSION_SETTLE_MS);
|
|
495
|
+
if (hasBaseline(snapshot)) drainPendingFocus();
|
|
428
496
|
};
|
|
429
497
|
probe();
|
|
430
|
-
const
|
|
431
|
-
ctx.effect(() =>
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
if (!
|
|
447
|
-
let start = snapshot.draft.length;
|
|
448
|
-
let end = snapshot.draft.length;
|
|
498
|
+
const unsubscribe = sessions.list.subscribe?.(probe);
|
|
499
|
+
ctx.effect(() => unsubscribe ?? (() => {
|
|
500
|
+
}), "aipanel: session-ready watcher");
|
|
501
|
+
const embedded = isEmbedded();
|
|
502
|
+
let selectModeActive = false;
|
|
503
|
+
const applyThemeFromHost = (theme) => {
|
|
504
|
+
const id = typeof theme === "string" ? mapAipanelTheme(theme) : null;
|
|
505
|
+
if (!id) return;
|
|
506
|
+
try {
|
|
507
|
+
const themeService = ctx.get("theme");
|
|
508
|
+
themeService?.setTheme(id);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const LAYOUT_STYLE_ID = "aipanel-layout-overrides";
|
|
513
|
+
const injectLayoutOverrides = () => {
|
|
514
|
+
if (!embedded) return;
|
|
449
515
|
try {
|
|
450
|
-
|
|
516
|
+
if (document.getElementById(LAYOUT_STYLE_ID)) return;
|
|
517
|
+
const style = document.createElement("style");
|
|
518
|
+
style.id = LAYOUT_STYLE_ID;
|
|
519
|
+
style.textContent = [
|
|
520
|
+
"[data-sidebar-collapsed] {",
|
|
521
|
+
" grid-template-columns: auto !important;",
|
|
522
|
+
"}",
|
|
523
|
+
"[data-sidebar-collapsed] > :first-child {",
|
|
524
|
+
" display: none !important;",
|
|
525
|
+
"}",
|
|
526
|
+
'[aria-label="\u9009\u62E9\u5DE5\u4F5C\u533A"] {',
|
|
527
|
+
" display: none !important;",
|
|
528
|
+
"}"
|
|
529
|
+
].join("\n");
|
|
530
|
+
document.head.appendChild(style);
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
const onKeydownCapture = (event) => {
|
|
535
|
+
if (event.key !== "Escape" && !(event.ctrlKey && event.key.toLowerCase() === "p")) return;
|
|
536
|
+
if (selectModeActive) {
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
event.stopPropagation();
|
|
539
|
+
}
|
|
540
|
+
postToHost(MSG.KEYDOWN, {
|
|
541
|
+
key: event.key,
|
|
542
|
+
ctrlKey: event.ctrlKey,
|
|
543
|
+
metaKey: event.metaKey,
|
|
544
|
+
shiftKey: event.shiftKey,
|
|
545
|
+
altKey: event.altKey
|
|
546
|
+
});
|
|
547
|
+
};
|
|
548
|
+
const focusComposer = () => {
|
|
549
|
+
try {
|
|
550
|
+
const el = document.querySelector(
|
|
451
551
|
'[role="textbox"][contenteditable="true"], textarea[data-phase]'
|
|
452
552
|
);
|
|
453
|
-
|
|
454
|
-
if (composer.isContentEditable) {
|
|
455
|
-
const draftInComposer = (composer.innerText ?? "").replace(/\n$/, "");
|
|
456
|
-
if (draftInComposer === snapshot.draft) {
|
|
457
|
-
const sel = window.getSelection();
|
|
458
|
-
if (sel && sel.rangeCount > 0 && sel.anchorNode && sel.focusNode && composer.contains(sel.anchorNode) && composer.contains(sel.focusNode)) {
|
|
459
|
-
const textNodes = [];
|
|
460
|
-
const walker = document.createTreeWalker(composer, NodeFilter.SHOW_TEXT);
|
|
461
|
-
let n = walker.nextNode();
|
|
462
|
-
while (n) {
|
|
463
|
-
textNodes.push(n);
|
|
464
|
-
n = walker.nextNode();
|
|
465
|
-
}
|
|
466
|
-
const posOf = (node, off) => {
|
|
467
|
-
const idx = textNodes.indexOf(node);
|
|
468
|
-
if (idx < 0) return null;
|
|
469
|
-
let acc = 0;
|
|
470
|
-
for (let i = 0; i < idx; i++) acc += textNodes[i].data.length;
|
|
471
|
-
return acc + Math.min(off, textNodes[idx].data.length);
|
|
472
|
-
};
|
|
473
|
-
const s = posOf(sel.anchorNode, sel.anchorOffset);
|
|
474
|
-
const e = posOf(sel.focusNode, sel.focusOffset);
|
|
475
|
-
if (s !== null && e !== null) {
|
|
476
|
-
start = Math.min(Math.max(s, 0), snapshot.draft.length);
|
|
477
|
-
end = Math.min(Math.max(e, start), snapshot.draft.length);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
} else {
|
|
482
|
-
const ta = composer;
|
|
483
|
-
if (ta.value === snapshot.draft) {
|
|
484
|
-
const s = ta.selectionStart ?? snapshot.draft.length;
|
|
485
|
-
const e = ta.selectionEnd ?? s;
|
|
486
|
-
start = Math.min(s, snapshot.draft.length);
|
|
487
|
-
end = Math.min(e, snapshot.draft.length);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
553
|
+
el?.focus();
|
|
491
554
|
} catch {
|
|
492
555
|
}
|
|
556
|
+
};
|
|
557
|
+
const detectSpan = (inputFor, snapshot) => {
|
|
558
|
+
if (!snapshot) return null;
|
|
559
|
+
const caret = inputFor.caretSpan?.();
|
|
560
|
+
return caret ? { start: caret.start, end: caret.end, draftRev: snapshot.draftRev } : null;
|
|
561
|
+
};
|
|
562
|
+
const INSERT_RETRY_MAX = 5;
|
|
563
|
+
const INSERT_RETRY_DELAY_MS = 80;
|
|
564
|
+
const insertElement = (element) => {
|
|
565
|
+
if (!element) return;
|
|
566
|
+
const current = sessions.list.getSnapshot().current;
|
|
567
|
+
if (!current) return;
|
|
568
|
+
let inputFor;
|
|
569
|
+
try {
|
|
570
|
+
const actx = sessions.scope(current);
|
|
571
|
+
if (!actx) return;
|
|
572
|
+
const conversation = ctx.get("conversation");
|
|
573
|
+
if (!conversation) return;
|
|
574
|
+
inputFor = conversation.input.for(actx);
|
|
575
|
+
} catch {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (!inputFor) return;
|
|
493
579
|
const reference = toReference(element);
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
let leadingSpace = 0;
|
|
497
|
-
let rev = snapshot.draftRev;
|
|
498
|
-
const insertable = input;
|
|
499
|
-
if (start > 0 && !/\s/.test(snapshot.draft[start - 1])) {
|
|
580
|
+
const attempt = (left) => {
|
|
581
|
+
let applied = false;
|
|
500
582
|
try {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
start += 1;
|
|
506
|
-
end += 1;
|
|
507
|
-
rev = next.draftRev;
|
|
508
|
-
}
|
|
583
|
+
const snap = inputFor.state.getSnapshot();
|
|
584
|
+
if (snap) {
|
|
585
|
+
const span = detectSpan(inputFor, snap);
|
|
586
|
+
if (span) applied = inputFor.insertReference(reference, span);
|
|
509
587
|
}
|
|
510
588
|
} catch {
|
|
589
|
+
applied = false;
|
|
511
590
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
try {
|
|
516
|
-
const el = document.querySelector(
|
|
517
|
-
'[role="textbox"][contenteditable="true"], textarea[data-phase]'
|
|
518
|
-
);
|
|
519
|
-
if (!el) return;
|
|
520
|
-
const caret = start + insertedLen + leadingSpace;
|
|
521
|
-
if (el.isContentEditable) {
|
|
522
|
-
const textNodes = [];
|
|
523
|
-
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
524
|
-
let n = walker.nextNode();
|
|
525
|
-
while (n) {
|
|
526
|
-
textNodes.push(n);
|
|
527
|
-
n = walker.nextNode();
|
|
528
|
-
}
|
|
529
|
-
let target = textNodes[textNodes.length - 1] ?? null;
|
|
530
|
-
let off = target ? target.data.length : 0;
|
|
531
|
-
let remaining = Math.max(0, caret);
|
|
532
|
-
if (target) {
|
|
533
|
-
for (const t of textNodes) {
|
|
534
|
-
if (remaining <= t.data.length) {
|
|
535
|
-
target = t;
|
|
536
|
-
off = remaining;
|
|
537
|
-
break;
|
|
538
|
-
}
|
|
539
|
-
remaining -= t.data.length;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
if (target) {
|
|
543
|
-
const range = document.createRange();
|
|
544
|
-
range.setStart(target, Math.min(off, target.data.length));
|
|
545
|
-
range.collapse(true);
|
|
546
|
-
const sel = window.getSelection();
|
|
547
|
-
sel?.removeAllRanges();
|
|
548
|
-
sel?.addRange(range);
|
|
549
|
-
}
|
|
550
|
-
el.focus();
|
|
551
|
-
} else {
|
|
552
|
-
const ta = el;
|
|
553
|
-
ta.focus();
|
|
554
|
-
ta.setSelectionRange(Math.min(caret, ta.value.length), Math.min(caret, ta.value.length));
|
|
555
|
-
}
|
|
556
|
-
} catch {
|
|
591
|
+
if (applied) {
|
|
592
|
+
focusComposer();
|
|
593
|
+
return;
|
|
557
594
|
}
|
|
558
|
-
|
|
559
|
-
|
|
595
|
+
if (left > 0) {
|
|
596
|
+
setTimeout(() => attempt(left - 1), INSERT_RETRY_DELAY_MS);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
attempt(INSERT_RETRY_MAX);
|
|
600
|
+
};
|
|
601
|
+
const onWindowMessage = (event) => {
|
|
602
|
+
const data = event.data;
|
|
603
|
+
if (!data || typeof data.type !== "string") return;
|
|
604
|
+
if (data.type === MSG.SET_THEME && typeof data.theme === "string") {
|
|
605
|
+
applyThemeFromHost(data.theme);
|
|
606
|
+
} else if (data.type === MSG.FOCUS_SESSION && typeof data.sessionId === "string") {
|
|
607
|
+
handleFocus(data.sessionId);
|
|
608
|
+
} else if (data.type === MSG.INSERT_FILE_PART && data.element) {
|
|
609
|
+
insertElement(data.element);
|
|
610
|
+
} else if (data.type === MSG.SELECT_MODE_CHANGE) {
|
|
611
|
+
selectModeActive = data.selectMode === true;
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
window.addEventListener("message", onWindowMessage);
|
|
615
|
+
ctx.effect(
|
|
616
|
+
() => () => window.removeEventListener("message", onWindowMessage),
|
|
617
|
+
"aipanel: host message listener"
|
|
618
|
+
);
|
|
619
|
+
injectLayoutOverrides();
|
|
620
|
+
if (embedded) {
|
|
621
|
+
window.addEventListener("keydown", onKeydownCapture, true);
|
|
622
|
+
ctx.effect(
|
|
623
|
+
() => () => window.removeEventListener("keydown", onKeydownCapture, true),
|
|
624
|
+
"aipanel: keydown capture"
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
if (typeof config.theme === "string") {
|
|
628
|
+
applyThemeFromHost(config.theme);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const inputTriggers = ctx.get("inputTriggers");
|
|
632
|
+
if (!inputTriggers) return;
|
|
633
|
+
const source = {
|
|
634
|
+
trigger: "@",
|
|
635
|
+
name: "aipanel",
|
|
636
|
+
candidates: async () => [],
|
|
637
|
+
onPick: () => void 0,
|
|
638
|
+
codec: {
|
|
639
|
+
clipboardText: (ref) => ref,
|
|
640
|
+
serialize: async (ref) => serializeElement(ref)
|
|
560
641
|
}
|
|
561
642
|
};
|
|
562
|
-
|
|
563
|
-
ctx.effect(
|
|
564
|
-
() => () => window.removeEventListener(INSERT_ELEMENT_EVENT, insertElementListener),
|
|
565
|
-
"aipanel: insert-element listener"
|
|
566
|
-
);
|
|
643
|
+
ctx.effect(() => inputTriggers.registerSource(source), "aipanel: @ codec source");
|
|
567
644
|
}
|
|
568
645
|
return module.exports; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aipanel/dsh-client",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AIPanel 浏览器侧插件(dsh web Web Client):注册 @aipanel 文件引用 source,选中元素以 file chip 高亮插入并可供模型解析。",
|
|
6
6
|
"publishConfig": {
|
|
@@ -12,11 +12,17 @@
|
|
|
12
12
|
],
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
15
|
+
"@deepseek-ai/dsh-api-session-controller": "^0.1.2-rc.1",
|
|
16
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.1.2-rc.1",
|
|
15
17
|
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.2-rc.1",
|
|
16
18
|
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.2-rc.1",
|
|
19
|
+
"@deepseek-ai/dsh-client-ui-theme": "^0.1.2-rc.1",
|
|
20
|
+
"@deepseek-ai/dsh-client-ui-tool": "^0.1.2-rc.1",
|
|
21
|
+
"@deepseek-ai/dsh-session": "^0.1.2-rc.1",
|
|
17
22
|
"@types/react": "^18.3.0",
|
|
18
23
|
"esbuild": "^0.25.0",
|
|
19
|
-
"react": "^18.3.0"
|
|
24
|
+
"react": "^18.3.0",
|
|
25
|
+
"@aipanel/core": "1.2.10"
|
|
20
26
|
},
|
|
21
27
|
"exports": {
|
|
22
28
|
".": "./lib/index.js",
|