@aipanel/dsh-client 1.2.8 → 1.2.9
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 +265 -224
- package/package.json +8 -2
package/lib/client.js
CHANGED
|
@@ -24,32 +24,70 @@ 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
|
+
|
|
34
70
|
// dsh-client/src/client/diagnostics-view.tsx
|
|
35
71
|
var import_react = require("react");
|
|
36
72
|
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
37
73
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
38
|
-
|
|
74
|
+
function isSettledToolResult(block) {
|
|
75
|
+
return typeof block === "object" && block !== null && "kind" in block && block.kind === "tool-result";
|
|
76
|
+
}
|
|
39
77
|
function readDiagnostics(block) {
|
|
40
|
-
if (
|
|
41
|
-
const
|
|
42
|
-
if (settled.kind !== "tool-result") return void 0;
|
|
43
|
-
const meta = settled.meta;
|
|
78
|
+
if (!isSettledToolResult(block)) return void 0;
|
|
79
|
+
const meta = block.meta;
|
|
44
80
|
const diagnostics = meta?.diagnostics;
|
|
45
81
|
if (!Array.isArray(diagnostics)) return void 0;
|
|
46
82
|
return diagnostics.every(isEntry) ? diagnostics : void 0;
|
|
47
83
|
}
|
|
48
84
|
function extractBlockText(block) {
|
|
49
|
-
if (
|
|
50
|
-
const
|
|
51
|
-
if (!Array.isArray(
|
|
52
|
-
return
|
|
85
|
+
if (!isSettledToolResult(block)) return "";
|
|
86
|
+
const content = block.content;
|
|
87
|
+
if (!Array.isArray(content)) return "";
|
|
88
|
+
return content.filter(
|
|
89
|
+
(b) => typeof b === "object" && b !== null && b.type === "text" && typeof b.text === "string"
|
|
90
|
+
).map((b) => b.text).join("\n");
|
|
53
91
|
}
|
|
54
92
|
function isEntry(d) {
|
|
55
93
|
const e = d;
|
|
@@ -171,7 +209,7 @@ var styles = {
|
|
|
171
209
|
function DiagnosticsRow({ block, cwd, toolName, openFile }) {
|
|
172
210
|
const diagnostics = readDiagnostics(block);
|
|
173
211
|
const [expanded, setExpanded] = (0, import_react.useState)(false);
|
|
174
|
-
const isSettled = block
|
|
212
|
+
const isSettled = isSettledToolResult(block);
|
|
175
213
|
const hasDiagnostics = typeof diagnostics !== "undefined" && diagnostics.length > 0;
|
|
176
214
|
const errors = (diagnostics ?? []).filter((d) => d.severity === "error");
|
|
177
215
|
const warnings = (diagnostics ?? []).filter((d) => d.severity === "warning");
|
|
@@ -270,12 +308,8 @@ function DiagnosticsRow({ block, cwd, toolName, openFile }) {
|
|
|
270
308
|
}
|
|
271
309
|
);
|
|
272
310
|
}
|
|
273
|
-
function registerDiagnosticsView(ctx) {
|
|
274
|
-
|
|
275
|
-
if (localStorage.getItem(DIAGNOSTICS_STORAGE_KEY) !== "1") return;
|
|
276
|
-
} catch {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
311
|
+
function registerDiagnosticsView(ctx, enabled = true) {
|
|
312
|
+
if (!enabled) return;
|
|
279
313
|
const slots = ctx.slots;
|
|
280
314
|
if (!slots) return;
|
|
281
315
|
slots.inject("tool.call.toolview", function* () {
|
|
@@ -284,23 +318,16 @@ function registerDiagnosticsView(ctx) {
|
|
|
284
318
|
}
|
|
285
319
|
|
|
286
320
|
// dsh-client/src/client/index.ts
|
|
287
|
-
var
|
|
321
|
+
var MSG = WIDGET_MSG;
|
|
288
322
|
var inject = ["slots", "sessions", "inputTriggers", "conversation"];
|
|
289
|
-
var SESSION_READY_EVENT = "aipanel:session-ready";
|
|
290
|
-
var INSERT_ELEMENT_EVENT = "aipanel:insert-element";
|
|
291
323
|
var SESSION_SETTLE_MS = 400;
|
|
324
|
+
var FOCUS_OPEN_MAX_ATTEMPTS = 3;
|
|
292
325
|
function ensureNodeId(e) {
|
|
293
326
|
if (e.id) return e.id;
|
|
294
327
|
const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 8) : Math.random().toString(36).slice(2, 10);
|
|
295
328
|
e.id = `n${random}`;
|
|
296
329
|
return e.id;
|
|
297
330
|
}
|
|
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
|
-
}
|
|
304
331
|
function elementContextRef(e) {
|
|
305
332
|
return JSON.stringify(e);
|
|
306
333
|
}
|
|
@@ -323,90 +350,91 @@ function toReference(e) {
|
|
|
323
350
|
clipboardText: `@${mark}`
|
|
324
351
|
};
|
|
325
352
|
}
|
|
326
|
-
function
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}
|
|
353
|
+
function isEmbedded() {
|
|
354
|
+
try {
|
|
355
|
+
return window.parent !== window;
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
332
359
|
}
|
|
333
|
-
function
|
|
360
|
+
function postToHost(type, data = {}) {
|
|
361
|
+
if (!isEmbedded()) return;
|
|
334
362
|
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);
|
|
363
|
+
window.parent.postMessage({ type, ...data }, "*");
|
|
340
364
|
} catch {
|
|
341
|
-
return [];
|
|
342
365
|
}
|
|
343
366
|
}
|
|
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");
|
|
367
|
+
function mapAipanelTheme(t) {
|
|
368
|
+
if (t === "light" || t === "dark") return t;
|
|
369
|
+
if (t === "system" || t === "auto") return "system";
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
function apply(ctx, config = {}) {
|
|
373
|
+
registerDiagnosticsView(ctx, config.enableDiagnostics !== false);
|
|
398
374
|
const sessions = ctx.get("sessions");
|
|
399
|
-
if (sessions
|
|
375
|
+
if (sessions) {
|
|
400
376
|
let lastCurrent;
|
|
401
377
|
let settleTimer = null;
|
|
402
|
-
|
|
378
|
+
let targetSessionId;
|
|
379
|
+
let pendingFocusId;
|
|
380
|
+
let focusAttempts = 0;
|
|
381
|
+
let refreshing = false;
|
|
382
|
+
const notifyReady = (sessionId) => {
|
|
383
|
+
postToHost(MSG.SESSION_READY, { sessionId });
|
|
384
|
+
};
|
|
385
|
+
const hasBaseline = (snap) => {
|
|
386
|
+
if (!snap) return false;
|
|
387
|
+
return !!snap.current || !!snap.ids?.length || Object.keys(snap.byId ?? {}).length > 0;
|
|
388
|
+
};
|
|
389
|
+
const listContains = (snap, id) => {
|
|
390
|
+
if (!snap) return false;
|
|
391
|
+
return !!snap.byId?.[id] || snap.ids?.includes(id) === true;
|
|
392
|
+
};
|
|
393
|
+
const drainPendingFocus = () => {
|
|
394
|
+
const id = pendingFocusId;
|
|
395
|
+
if (!id) return;
|
|
396
|
+
const snap = sessions.list.getSnapshot();
|
|
397
|
+
if (!hasBaseline(snap)) return;
|
|
398
|
+
pendingFocusId = void 0;
|
|
399
|
+
focusAttempts = 0;
|
|
400
|
+
void tryOpenTarget(id);
|
|
401
|
+
};
|
|
402
|
+
const tryOpenTarget = async (id) => {
|
|
403
|
+
if (focusAttempts >= FOCUS_OPEN_MAX_ATTEMPTS) return;
|
|
404
|
+
focusAttempts += 1;
|
|
405
|
+
const snap = sessions.list.getSnapshot();
|
|
406
|
+
if (snap.current === id) return;
|
|
407
|
+
if (!listContains(snap, id) && !refreshing) {
|
|
408
|
+
refreshing = true;
|
|
409
|
+
try {
|
|
410
|
+
await sessions.refresh();
|
|
411
|
+
} catch {
|
|
412
|
+
} finally {
|
|
413
|
+
refreshing = false;
|
|
414
|
+
}
|
|
415
|
+
const fresh = sessions.list.getSnapshot();
|
|
416
|
+
if (!listContains(fresh, id)) {
|
|
417
|
+
setTimeout(() => void tryOpenTarget(id), 500);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
403
421
|
try {
|
|
404
|
-
|
|
422
|
+
sessions.open(id);
|
|
405
423
|
} catch {
|
|
406
424
|
}
|
|
407
425
|
};
|
|
426
|
+
const handleFocus = (sessionId) => {
|
|
427
|
+
targetSessionId = sessionId;
|
|
428
|
+
const snap = sessions.list.getSnapshot();
|
|
429
|
+
if (!hasBaseline(snap)) {
|
|
430
|
+
pendingFocusId = sessionId;
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
focusAttempts = 0;
|
|
434
|
+
void tryOpenTarget(sessionId);
|
|
435
|
+
};
|
|
408
436
|
const probe = () => {
|
|
409
|
-
const snapshot = sessions.list
|
|
437
|
+
const snapshot = sessions.list?.getSnapshot?.();
|
|
410
438
|
const current = snapshot?.current;
|
|
411
439
|
if (!current) {
|
|
412
440
|
lastCurrent = void 0;
|
|
@@ -421,148 +449,161 @@ function apply(ctx) {
|
|
|
421
449
|
if (settleTimer) clearTimeout(settleTimer);
|
|
422
450
|
settleTimer = setTimeout(() => {
|
|
423
451
|
settleTimer = null;
|
|
424
|
-
if (sessions.list
|
|
425
|
-
|
|
452
|
+
if (sessions.list?.getSnapshot?.()?.current === current) {
|
|
453
|
+
notifyReady(current);
|
|
454
|
+
if (targetSessionId && targetSessionId !== current) {
|
|
455
|
+
handleFocus(targetSessionId);
|
|
456
|
+
}
|
|
426
457
|
}
|
|
427
458
|
}, SESSION_SETTLE_MS);
|
|
459
|
+
if (hasBaseline(snapshot)) drainPendingFocus();
|
|
428
460
|
};
|
|
429
461
|
probe();
|
|
430
|
-
const
|
|
431
|
-
ctx.effect(() =>
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const current = sessions?.list?.getSnapshot()?.current;
|
|
439
|
-
if (!current) return;
|
|
440
|
-
try {
|
|
441
|
-
const actx = sessions?.scope?.(current);
|
|
442
|
-
if (!actx) return;
|
|
443
|
-
const input = conversation?.input?.for?.(actx);
|
|
444
|
-
if (!input || typeof input.insertReference !== "function") return;
|
|
445
|
-
const snapshot = input.state?.getSnapshot?.();
|
|
446
|
-
if (!snapshot) return;
|
|
447
|
-
let start = snapshot.draft.length;
|
|
448
|
-
let end = snapshot.draft.length;
|
|
462
|
+
const unsubscribe = sessions.list.subscribe?.(probe);
|
|
463
|
+
ctx.effect(() => unsubscribe ?? (() => {
|
|
464
|
+
}), "aipanel: session-ready watcher");
|
|
465
|
+
const embedded = isEmbedded();
|
|
466
|
+
let selectModeActive = false;
|
|
467
|
+
const applyThemeFromHost = (theme) => {
|
|
468
|
+
const id = typeof theme === "string" ? mapAipanelTheme(theme) : null;
|
|
469
|
+
if (!id) return;
|
|
449
470
|
try {
|
|
450
|
-
const
|
|
471
|
+
const themeService = ctx.get("theme");
|
|
472
|
+
themeService?.setTheme(id);
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
const LAYOUT_STYLE_ID = "aipanel-layout-overrides";
|
|
477
|
+
const injectLayoutOverrides = () => {
|
|
478
|
+
if (!embedded) return;
|
|
479
|
+
try {
|
|
480
|
+
if (document.getElementById(LAYOUT_STYLE_ID)) return;
|
|
481
|
+
const style = document.createElement("style");
|
|
482
|
+
style.id = LAYOUT_STYLE_ID;
|
|
483
|
+
style.textContent = [
|
|
484
|
+
"[data-sidebar-collapsed] {",
|
|
485
|
+
" grid-template-columns: auto !important;",
|
|
486
|
+
"}",
|
|
487
|
+
"[data-sidebar-collapsed] > :first-child {",
|
|
488
|
+
" display: none !important;",
|
|
489
|
+
"}",
|
|
490
|
+
'[aria-label="\u9009\u62E9\u5DE5\u4F5C\u533A"] {',
|
|
491
|
+
" display: none !important;",
|
|
492
|
+
"}"
|
|
493
|
+
].join("\n");
|
|
494
|
+
document.head.appendChild(style);
|
|
495
|
+
} catch {
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const onKeydownCapture = (event) => {
|
|
499
|
+
if (event.key !== "Escape" && !(event.ctrlKey && event.key.toLowerCase() === "p")) return;
|
|
500
|
+
if (selectModeActive) {
|
|
501
|
+
event.preventDefault();
|
|
502
|
+
event.stopPropagation();
|
|
503
|
+
}
|
|
504
|
+
postToHost(MSG.KEYDOWN, {
|
|
505
|
+
key: event.key,
|
|
506
|
+
ctrlKey: event.ctrlKey,
|
|
507
|
+
metaKey: event.metaKey,
|
|
508
|
+
shiftKey: event.shiftKey,
|
|
509
|
+
altKey: event.altKey
|
|
510
|
+
});
|
|
511
|
+
};
|
|
512
|
+
const focusComposer = () => {
|
|
513
|
+
try {
|
|
514
|
+
const el = document.querySelector(
|
|
451
515
|
'[role="textbox"][contenteditable="true"], textarea[data-phase]'
|
|
452
516
|
);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
}
|
|
517
|
+
el?.focus();
|
|
518
|
+
} catch {
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
const detectSpan = (inputFor, snapshot) => {
|
|
522
|
+
if (!snapshot) return null;
|
|
523
|
+
const caret = inputFor.caretSpan?.();
|
|
524
|
+
return caret ? { start: caret.start, end: caret.end, draftRev: snapshot.draftRev } : null;
|
|
525
|
+
};
|
|
526
|
+
const INSERT_RETRY_MAX = 5;
|
|
527
|
+
const INSERT_RETRY_DELAY_MS = 80;
|
|
528
|
+
const insertElement = (element) => {
|
|
529
|
+
if (!element) return;
|
|
530
|
+
const current = sessions.list.getSnapshot().current;
|
|
531
|
+
if (!current) return;
|
|
532
|
+
let inputFor;
|
|
533
|
+
try {
|
|
534
|
+
const actx = sessions.scope(current);
|
|
535
|
+
if (!actx) return;
|
|
536
|
+
const conversation = ctx.get("conversation");
|
|
537
|
+
if (!conversation) return;
|
|
538
|
+
inputFor = conversation.input.for(actx);
|
|
491
539
|
} catch {
|
|
540
|
+
return;
|
|
492
541
|
}
|
|
542
|
+
if (!inputFor) return;
|
|
493
543
|
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])) {
|
|
544
|
+
const attempt = (left) => {
|
|
545
|
+
let applied = false;
|
|
500
546
|
try {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
start += 1;
|
|
506
|
-
end += 1;
|
|
507
|
-
rev = next.draftRev;
|
|
508
|
-
}
|
|
547
|
+
const snap = inputFor.state.getSnapshot();
|
|
548
|
+
if (snap) {
|
|
549
|
+
const span = detectSpan(inputFor, snap);
|
|
550
|
+
if (span) applied = inputFor.insertReference(reference, span);
|
|
509
551
|
}
|
|
510
552
|
} catch {
|
|
553
|
+
applied = false;
|
|
511
554
|
}
|
|
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 {
|
|
555
|
+
if (applied) {
|
|
556
|
+
focusComposer();
|
|
557
|
+
return;
|
|
557
558
|
}
|
|
558
|
-
|
|
559
|
-
|
|
559
|
+
if (left > 0) {
|
|
560
|
+
setTimeout(() => attempt(left - 1), INSERT_RETRY_DELAY_MS);
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
attempt(INSERT_RETRY_MAX);
|
|
564
|
+
};
|
|
565
|
+
const onWindowMessage = (event) => {
|
|
566
|
+
const data = event.data;
|
|
567
|
+
if (!data || typeof data.type !== "string") return;
|
|
568
|
+
if (data.type === MSG.SET_THEME && typeof data.theme === "string") {
|
|
569
|
+
applyThemeFromHost(data.theme);
|
|
570
|
+
} else if (data.type === MSG.FOCUS_SESSION && typeof data.sessionId === "string") {
|
|
571
|
+
handleFocus(data.sessionId);
|
|
572
|
+
} else if (data.type === MSG.INSERT_FILE_PART && data.element) {
|
|
573
|
+
insertElement(data.element);
|
|
574
|
+
} else if (data.type === MSG.SELECT_MODE_CHANGE) {
|
|
575
|
+
selectModeActive = data.selectMode === true;
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
window.addEventListener("message", onWindowMessage);
|
|
579
|
+
ctx.effect(
|
|
580
|
+
() => () => window.removeEventListener("message", onWindowMessage),
|
|
581
|
+
"aipanel: host message listener"
|
|
582
|
+
);
|
|
583
|
+
injectLayoutOverrides();
|
|
584
|
+
if (embedded) {
|
|
585
|
+
window.addEventListener("keydown", onKeydownCapture, true);
|
|
586
|
+
ctx.effect(
|
|
587
|
+
() => () => window.removeEventListener("keydown", onKeydownCapture, true),
|
|
588
|
+
"aipanel: keydown capture"
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (typeof config.theme === "string") {
|
|
592
|
+
applyThemeFromHost(config.theme);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
const inputTriggers = ctx.get("inputTriggers");
|
|
596
|
+
if (!inputTriggers) return;
|
|
597
|
+
const source = {
|
|
598
|
+
trigger: "@",
|
|
599
|
+
name: "aipanel",
|
|
600
|
+
candidates: async () => [],
|
|
601
|
+
onPick: () => void 0,
|
|
602
|
+
codec: {
|
|
603
|
+
clipboardText: (ref) => ref,
|
|
604
|
+
serialize: async (ref) => serializeElement(ref)
|
|
560
605
|
}
|
|
561
606
|
};
|
|
562
|
-
|
|
563
|
-
ctx.effect(
|
|
564
|
-
() => () => window.removeEventListener(INSERT_ELEMENT_EVENT, insertElementListener),
|
|
565
|
-
"aipanel: insert-element listener"
|
|
566
|
-
);
|
|
607
|
+
ctx.effect(() => inputTriggers.registerSource(source), "aipanel: @ codec source");
|
|
567
608
|
}
|
|
568
609
|
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.9",
|
|
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.9"
|
|
20
26
|
},
|
|
21
27
|
"exports": {
|
|
22
28
|
".": "./lib/index.js",
|