@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.
Files changed (2) hide show
  1. package/lib/client.js +312 -235
  2. 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
- var DIAGNOSTICS_STORAGE_KEY = "dsh.bridge.diagnostics.enabled";
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 (typeof block !== "object" || block === null) return void 0;
41
- const settled = block;
42
- if (settled.kind !== "tool-result") return void 0;
43
- const meta = settled.meta;
44
- const diagnostics = meta?.diagnostics;
45
- if (!Array.isArray(diagnostics)) return void 0;
46
- return diagnostics.every(isEntry) ? diagnostics : void 0;
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 (typeof block !== "object" || block === null) return "";
50
- const settled = block;
51
- if (!Array.isArray(settled.content)) return "";
52
- return (settled.content ?? []).filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
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?.kind === "tool-result";
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
- try {
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 SELECTION_STORAGE_KEY = "dsh.bridge.selection";
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
- function ensureNodeId(e) {
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 `@\u8282\u70B9[${ensureNodeId(e)}]`;
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 toCandidate(e) {
327
- return {
328
- name: elementLabel(e),
329
- description: e.description || e.innerText || void 0,
330
- value: elementContextRef(e)
331
- };
389
+ function isEmbedded() {
390
+ try {
391
+ return window.parent !== window;
392
+ } catch {
393
+ return false;
394
+ }
332
395
  }
333
- function readSelectionCandidates() {
396
+ function postToHost(type, data = {}) {
397
+ if (!isEmbedded()) return;
334
398
  try {
335
- const raw = localStorage.getItem(SELECTION_STORAGE_KEY);
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 apply(ctx) {
345
- registerDiagnosticsView(ctx);
346
- const inputTriggers = ctx.get("inputTriggers");
347
- if (!inputTriggers) return;
348
- const source = {
349
- trigger: "@",
350
- name: "aipanel",
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 && sessions.list) {
411
+ if (sessions) {
400
412
  let lastCurrent;
401
413
  let settleTimer = null;
402
- const notifyBridge = (sessionId) => {
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
- window.dispatchEvent(new CustomEvent(SESSION_READY_EVENT, { detail: { sessionId } }));
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.getSnapshot();
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.getSnapshot()?.current === current) {
425
- notifyBridge(current);
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 dispose = sessions.list.subscribe(probe);
431
- ctx.effect(() => dispose, "aipanel: session-ready watcher");
432
- }
433
- const conversation = ctx.get("conversation");
434
- const insertElementListener = (event) => {
435
- const detail = event.detail;
436
- const element = detail?.element;
437
- if (!element) return;
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;
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
- const composer = document.querySelector(
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
- if (composer) {
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 gap = snapshot.draft.slice(end)[0] === " " ? 0 : 1;
495
- const insertedLen = reference.clipboardText.length + gap;
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
- if (insertable.insertText?.(" ", { start, end, draftRev: rev })) {
502
- const next = input.state?.getSnapshot?.();
503
- if (next) {
504
- leadingSpace = 1;
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
- input.insertReference(reference, { start, end, draftRev: rev });
514
- requestAnimationFrame(() => {
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
- } catch {
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
- window.addEventListener(INSERT_ELEMENT_EVENT, insertElementListener);
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.8",
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",