@aipanel/dsh-client 1.2.4 → 1.2.6

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 +340 -25
  2. package/package.json +8 -4
package/lib/client.js CHANGED
@@ -26,13 +26,275 @@ var index_exports = {};
26
26
  __export(index_exports, {
27
27
  INSERT_ELEMENT_EVENT: () => INSERT_ELEMENT_EVENT,
28
28
  SESSION_READY_EVENT: () => SESSION_READY_EVENT,
29
- apply: () => apply
29
+ apply: () => apply,
30
+ inject: () => inject
30
31
  });
31
32
  module.exports = __toCommonJS(index_exports);
33
+
34
+ // dsh-client/src/client/diagnostics-view.tsx
35
+ var import_react = require("react");
36
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
37
+ var import_jsx_runtime = require("react/jsx-runtime");
38
+ var DIAGNOSTICS_STORAGE_KEY = "dsh.bridge.diagnostics.enabled";
39
+ 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;
47
+ }
48
+ 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");
53
+ }
54
+ function isEntry(d) {
55
+ const e = d;
56
+ return !!e && typeof e.file === "string" && typeof e.line === "number" && typeof e.column === "number" && (e.severity === "error" || e.severity === "warning") && typeof e.message === "string";
57
+ }
58
+ function relativize(path, cwd) {
59
+ if (!cwd) return path;
60
+ const root = cwd.replace(/[/\\]+$/, "");
61
+ if (path.startsWith(`${root}/`) || path.startsWith(`${root}\\`)) {
62
+ return path.slice(root.length + 1);
63
+ }
64
+ return path;
65
+ }
66
+ var styles = {
67
+ sep: {
68
+ background: "var(--dsw-alias-label-caption)",
69
+ borderRadius: 1,
70
+ flex: "none",
71
+ width: 2,
72
+ height: 2,
73
+ margin: "0 8px"
74
+ },
75
+ summary: {
76
+ textOverflow: "ellipsis",
77
+ whiteSpace: "nowrap",
78
+ minWidth: 0,
79
+ color: "var(--dsw-alias-label-tertiary)",
80
+ flex: "auto",
81
+ fontSize: 12,
82
+ lineHeight: 24,
83
+ overflow: "hidden"
84
+ },
85
+ diagnostic: {
86
+ display: "flex",
87
+ alignItems: "flex-start",
88
+ gap: 8,
89
+ padding: "5px 0 5px 4px"
90
+ },
91
+ card: {
92
+ display: "flex",
93
+ flexDirection: "column",
94
+ gap: 2,
95
+ margin: "4px 0 4px 4px",
96
+ padding: "8px 12px",
97
+ borderRadius: 12,
98
+ background: "var(--dsw-alias-markdown-code-block)"
99
+ },
100
+ cardHead: {
101
+ display: "flex",
102
+ alignItems: "center",
103
+ justifyContent: "space-between",
104
+ gap: 8,
105
+ paddingBottom: 6,
106
+ marginBottom: 4,
107
+ borderBottom: "1px solid var(--dsw-alias-border-l1)"
108
+ },
109
+ cardTitle: {
110
+ color: "var(--dsw-alias-label-primary)",
111
+ fontWeight: 600,
112
+ fontSize: 12,
113
+ lineHeight: 1.4
114
+ },
115
+ scopeBadge: {
116
+ color: "var(--dsw-alias-label-secondary)",
117
+ fontSize: 11,
118
+ lineHeight: 1,
119
+ padding: "3px 8px",
120
+ borderRadius: 999,
121
+ border: "1px solid var(--dsw-alias-border-l2)"
122
+ },
123
+ target: {
124
+ display: "block",
125
+ width: "100%",
126
+ textAlign: "left",
127
+ border: "none",
128
+ background: "none",
129
+ padding: "0 0 4px 0",
130
+ margin: "0 0 4px 0",
131
+ color: "var(--dsw-alias-label-secondary)",
132
+ fontSize: 12,
133
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)",
134
+ cursor: "pointer",
135
+ textDecoration: "underline"
136
+ },
137
+ targetText: {
138
+ display: "block",
139
+ padding: "0 0 4px 0",
140
+ margin: "0 0 4px 0",
141
+ color: "var(--dsw-alias-label-secondary)",
142
+ fontSize: 12,
143
+ fontFamily: "var(--ds-font-family-code, ui-monospace, SFMono-Regular, Menlo, monospace)"
144
+ },
145
+ marker: { flex: "0 0 auto", width: 14, textAlign: "center", fontSize: 12, lineHeight: 1.6 },
146
+ diagBody: {
147
+ flex: 1,
148
+ minWidth: 0,
149
+ display: "flex",
150
+ flexDirection: "column",
151
+ gap: 3
152
+ },
153
+ loc: {
154
+ color: "var(--dsw-alias-label-tertiary)",
155
+ fontSize: 12,
156
+ lineHeight: 1.4
157
+ },
158
+ msg: {
159
+ color: "var(--dsw-alias-label-primary)",
160
+ fontSize: 12,
161
+ lineHeight: 1.6,
162
+ whiteSpace: "pre-wrap",
163
+ wordBreak: "break-word"
164
+ },
165
+ successMsg: {
166
+ color: "var(--dsw-alias-state-success-primary)",
167
+ fontSize: 12,
168
+ lineHeight: 1.6
169
+ }
170
+ };
171
+ function DiagnosticsRow({ block, cwd, toolName, openFile }) {
172
+ const diagnostics = readDiagnostics(block);
173
+ const [expanded, setExpanded] = (0, import_react.useState)(false);
174
+ const isSettled = block?.kind === "tool-result";
175
+ const hasDiagnostics = typeof diagnostics !== "undefined" && diagnostics.length > 0;
176
+ const errors = (diagnostics ?? []).filter((d) => d.severity === "error");
177
+ const warnings = (diagnostics ?? []).filter((d) => d.severity === "warning");
178
+ const contentText = extractBlockText(block);
179
+ const firstLine = (contentText.split("\n")[0] || "").trim();
180
+ const isFullProject = /^全量诊断结果/.test(firstLine);
181
+ const scope = isFullProject ? "project" : "file";
182
+ const scopeLabel = isFullProject ? "\u5168\u91CF" : "\u5355\u6587\u4EF6";
183
+ const cardTitle = "\u8BCA\u65AD\u7ED3\u679C";
184
+ const titleFileMatch = /^诊断结果:\s*(.+)$/.exec(firstLine);
185
+ const targetFile = scope === "file" && diagnostics && diagnostics.length > 0 ? diagnostics[0].file : null;
186
+ const targetDisplay = scope === "file" ? targetFile ? relativize(targetFile, cwd) : titleFileMatch ? titleFileMatch[1].trim() : null : null;
187
+ const errColor = "var(--dsw-alias-state-error-primary)";
188
+ const warnColor = "var(--dsw-alias-state-warn-primary)";
189
+ const state = !isSettled ? "ongoing" : errors.length > 0 ? "error" : warnings.length > 0 ? "warning" : "done";
190
+ const expandable = isSettled;
191
+ let collapsed;
192
+ if (hasDiagnostics) {
193
+ collapsed = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
194
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sep, "aria-hidden": true }),
195
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.summary, children: [
196
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-label-tertiary)" }, children: scopeLabel }),
197
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, children: " \xB7 " }),
198
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: errColor }, children: [
199
+ errors.length,
200
+ " \u9519\u8BEF"
201
+ ] }),
202
+ " \xB7",
203
+ " ",
204
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { color: warnColor }, children: [
205
+ warnings.length,
206
+ " \u8B66\u544A"
207
+ ] })
208
+ ] })
209
+ ] });
210
+ } else if (!isSettled) {
211
+ collapsed = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
212
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sep, "aria-hidden": true }),
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.summary, children: "\u8BCA\u65AD\u8FD0\u884C\u4E2D\u2026" })
214
+ ] });
215
+ } else if (diagnostics && diagnostics.length === 0) {
216
+ collapsed = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sep, "aria-hidden": true }),
218
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.summary, children: [
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-label-tertiary)" }, children: scopeLabel }),
220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, children: " \xB7 " }),
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "var(--dsw-alias-state-success-primary)" }, children: "\u672A\u53D1\u73B0\u95EE\u9898" })
222
+ ] })
223
+ ] });
224
+ } else {
225
+ collapsed = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
226
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.sep, "aria-hidden": true }),
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.summary, children: extractBlockText(block) || "\u65E0\u8F93\u51FA" })
228
+ ] });
229
+ }
230
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
231
+ import_dsh_client_ui_primitives.DisclosureRow,
232
+ {
233
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state }),
234
+ title: toolName,
235
+ open: expanded,
236
+ expandable,
237
+ expandOnRowClick: expandable,
238
+ keepContentWhenOpen: true,
239
+ onToggle: () => setExpanded((v) => !v),
240
+ collapsedContent: collapsed,
241
+ children: expandable && expanded ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.card, children: [
242
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.cardHead, children: [
243
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.cardTitle, children: cardTitle }),
244
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.scopeBadge, children: scopeLabel })
245
+ ] }),
246
+ scope === "file" && targetDisplay ? targetFile && typeof openFile === "function" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: styles.target, onClick: () => openFile(targetFile), children: targetDisplay }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.targetText, children: targetDisplay }) : null,
247
+ hasDiagnostics ? diagnostics.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.diagnostic, children: [
248
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
249
+ "span",
250
+ {
251
+ style: {
252
+ ...styles.marker,
253
+ color: d.severity === "error" ? errColor : warnColor
254
+ },
255
+ children: d.severity === "error" ? "\u2716" : "\u26A0"
256
+ }
257
+ ),
258
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.diagBody, children: [
259
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles.loc, children: [
260
+ relativize(d.file, cwd),
261
+ ":",
262
+ d.line,
263
+ ":",
264
+ d.column
265
+ ] }),
266
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.msg, children: d.message })
267
+ ] })
268
+ ] }, i)) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles.successMsg, children: "\u672A\u53D1\u73B0\u95EE\u9898" })
269
+ ] }) : null
270
+ }
271
+ );
272
+ }
273
+ function registerDiagnosticsView(ctx) {
274
+ try {
275
+ if (localStorage.getItem(DIAGNOSTICS_STORAGE_KEY) !== "1") return;
276
+ } catch {
277
+ return;
278
+ }
279
+ const slots = ctx.slots;
280
+ if (!slots) return;
281
+ slots.inject("tool.call.toolview", function* () {
282
+ yield slots.register({ name: "tool.call.toolview", key: "run_diagnostics" }, DiagnosticsRow);
283
+ });
284
+ }
285
+
286
+ // dsh-client/src/client/index.ts
32
287
  var SELECTION_STORAGE_KEY = "dsh.bridge.selection";
288
+ var inject = ["slots"];
33
289
  var SESSION_READY_EVENT = "aipanel:session-ready";
34
290
  var INSERT_ELEMENT_EVENT = "aipanel:insert-element";
35
291
  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
+ }
36
298
  function elementLabel(e) {
37
299
  if (e.description) return e.description;
38
300
  const text = e.innerText?.trim();
@@ -46,24 +308,19 @@ function serializeElement(ref) {
46
308
  try {
47
309
  const e = JSON.parse(ref);
48
310
  if (!e || typeof e !== "object") throw new Error("not an element payload");
49
- const safe = (s) => s.replace(/[\\/]/g, (m) => m === "/" ? "\uFF0F" : "\uFF3C").replace(/["\n\r]+/g, " ");
50
- const selector = safe(e.description || "element");
51
- const text = e.innerText?.trim();
52
- const textPreview = text ? text.length > 5 ? text.slice(0, 5) + "..." : text : "";
53
- const body = `\u9009\u4E2D\u5143\u7D20:${selector}${textPreview ? `(${safe(textPreview)})` : ""}`;
54
- return `@"${body}"`;
311
+ return `@\u8282\u70B9[${ensureNodeId(e)}]`;
55
312
  } catch {
56
313
  return `@${ref}`;
57
314
  }
58
315
  }
59
316
  function toReference(e) {
60
- const label = elementLabel(e);
317
+ const mark = `\u8282\u70B9[${ensureNodeId(e)}]`;
61
318
  return {
62
319
  source: "aipanel",
63
320
  ref: elementContextRef(e),
64
- label,
321
+ label: mark,
65
322
  appearance: "file",
66
- clipboardText: label
323
+ clipboardText: `@${mark}`
67
324
  };
68
325
  }
69
326
  function toCandidate(e) {
@@ -85,6 +342,7 @@ function readSelectionCandidates() {
85
342
  }
86
343
  }
87
344
  function apply(ctx) {
345
+ registerDiagnosticsView(ctx);
88
346
  const inputTriggers = ctx.get("inputTriggers");
89
347
  if (!inputTriggers) return;
90
348
  const source = {
@@ -101,16 +359,35 @@ function apply(ctx) {
101
359
  (c) => c.name.toLowerCase().includes(query) || (c.description ?? "").toLowerCase().includes(query)
102
360
  );
103
361
  },
104
- // 选定 → 铸造 file 引用:ref/label 分离(label 短,ref 携完整上下文)
105
- onPick: (pick) => ({
106
- insert: {
107
- source: "aipanel",
108
- ref: pick.candidate.value ?? pick.candidate.name,
109
- label: pick.candidate.name,
110
- appearance: "file",
111
- clipboardText: pick.candidate.name
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 {
112
380
  }
113
- }),
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
+ },
114
391
  // 模型投影
115
392
  codec: {
116
393
  clipboardText: (ref) => ref,
@@ -161,16 +438,54 @@ function apply(ctx) {
161
438
  const current = sessions?.list?.getSnapshot()?.current;
162
439
  if (!current) return;
163
440
  try {
164
- const actx = sessions.scope?.(current);
441
+ const actx = sessions?.scope?.(current);
442
+ if (!actx) return;
165
443
  const input = conversation?.input?.for?.(actx);
166
444
  if (!input || typeof input.insertReference !== "function") return;
167
445
  const snapshot = input.state?.getSnapshot?.();
168
446
  if (!snapshot) return;
169
- const at = snapshot.draft.length;
170
- input.insertReference(toReference(element), {
171
- start: at,
172
- end: at,
173
- draftRev: snapshot.draftRev
447
+ let start = snapshot.draft.length;
448
+ let end = snapshot.draft.length;
449
+ try {
450
+ const el = document.querySelector("textarea[data-phase]");
451
+ if (el && el.value === snapshot.draft) {
452
+ const s = el.selectionStart ?? 0;
453
+ const e = el.selectionEnd ?? s;
454
+ start = Math.min(s, snapshot.draft.length);
455
+ end = Math.min(e, snapshot.draft.length);
456
+ }
457
+ } catch {
458
+ }
459
+ const reference = toReference(element);
460
+ const gap = snapshot.draft.slice(end)[0] === " " ? 0 : 1;
461
+ const insertedLen = reference.clipboardText.length + gap;
462
+ let leadingSpace = 0;
463
+ let rev = snapshot.draftRev;
464
+ const insertable = input;
465
+ if (start > 0 && !/\s/.test(snapshot.draft[start - 1])) {
466
+ try {
467
+ if (insertable.insertText?.(" ", { start, end, draftRev: rev })) {
468
+ const next = input.state?.getSnapshot?.();
469
+ if (next) {
470
+ leadingSpace = 1;
471
+ start += 1;
472
+ end += 1;
473
+ rev = next.draftRev;
474
+ }
475
+ }
476
+ } catch {
477
+ }
478
+ }
479
+ input.insertReference(reference, { start, end, draftRev: rev });
480
+ requestAnimationFrame(() => {
481
+ try {
482
+ const el = document.querySelector("textarea[data-phase]");
483
+ if (!el) return;
484
+ const caret = Math.min(start + insertedLen + leadingSpace, el.value.length);
485
+ el.focus();
486
+ el.setSelectionRange(caret, caret);
487
+ } catch {
488
+ }
174
489
  });
175
490
  } catch {
176
491
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipanel/dsh-client",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "type": "module",
5
5
  "description": "AIPanel 浏览器侧插件(dsh web Web Client):注册 @aipanel 文件引用 source,选中元素以 file chip 高亮插入并可供模型解析。",
6
6
  "publishConfig": {
@@ -10,11 +10,13 @@
10
10
  "files": [
11
11
  "lib"
12
12
  ],
13
- "dependencies": {},
14
13
  "devDependencies": {
15
14
  "@deepseek-ai/cordis": "^4.0.1",
16
15
  "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.1-rc.2",
17
- "esbuild": "^0.25.0"
16
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.1",
17
+ "@types/react": "^18.3.0",
18
+ "esbuild": "^0.25.0",
19
+ "react": "^18.3.0"
18
20
  },
19
21
  "exports": {
20
22
  ".": "./lib/index.js",
@@ -24,7 +26,9 @@
24
26
  "dsh": {
25
27
  "client": {
26
28
  "platform": "web",
27
- "inject": []
29
+ "inject": [
30
+ "slots"
31
+ ]
28
32
  }
29
33
  },
30
34
  "scripts": {