@aipanel/dsh-client 1.2.4 → 1.2.5

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 +79 -23
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -33,6 +33,12 @@ var SELECTION_STORAGE_KEY = "dsh.bridge.selection";
33
33
  var SESSION_READY_EVENT = "aipanel:session-ready";
34
34
  var INSERT_ELEMENT_EVENT = "aipanel:insert-element";
35
35
  var SESSION_SETTLE_MS = 400;
36
+ function ensureNodeId(e) {
37
+ if (e.id) return e.id;
38
+ const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID().replace(/-/g, "").slice(0, 8) : Math.random().toString(36).slice(2, 10);
39
+ e.id = `n${random}`;
40
+ return e.id;
41
+ }
36
42
  function elementLabel(e) {
37
43
  if (e.description) return e.description;
38
44
  const text = e.innerText?.trim();
@@ -46,24 +52,19 @@ function serializeElement(ref) {
46
52
  try {
47
53
  const e = JSON.parse(ref);
48
54
  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}"`;
55
+ return `@\u8282\u70B9[${ensureNodeId(e)}]`;
55
56
  } catch {
56
57
  return `@${ref}`;
57
58
  }
58
59
  }
59
60
  function toReference(e) {
60
- const label = elementLabel(e);
61
+ const mark = `\u8282\u70B9[${ensureNodeId(e)}]`;
61
62
  return {
62
63
  source: "aipanel",
63
64
  ref: elementContextRef(e),
64
- label,
65
+ label: mark,
65
66
  appearance: "file",
66
- clipboardText: label
67
+ clipboardText: `@${mark}`
67
68
  };
68
69
  }
69
70
  function toCandidate(e) {
@@ -101,16 +102,35 @@ function apply(ctx) {
101
102
  (c) => c.name.toLowerCase().includes(query) || (c.description ?? "").toLowerCase().includes(query)
102
103
  );
103
104
  },
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
105
+ // 选定 → 铸造 file 引用:ref/label 分离(label 短,ref 携带完整元素 + 节点 id)
106
+ onPick: (pick) => {
107
+ try {
108
+ const parsed = JSON.parse(pick.candidate.value ?? "null");
109
+ if (parsed) {
110
+ ensureNodeId(parsed);
111
+ return {
112
+ insert: {
113
+ source: "aipanel",
114
+ ref: JSON.stringify(parsed),
115
+ label: pick.candidate.name,
116
+ appearance: "file",
117
+ // clipboardText 以 @ 开头(dsh backdrop 取首字符作 chip 触发 glyph)
118
+ clipboardText: `@${pick.candidate.name}`
119
+ }
120
+ };
121
+ }
122
+ } catch {
112
123
  }
113
- }),
124
+ return {
125
+ insert: {
126
+ source: "aipanel",
127
+ ref: pick.candidate.value ?? pick.candidate.name,
128
+ label: pick.candidate.name,
129
+ appearance: "file",
130
+ clipboardText: `@${pick.candidate.name}`
131
+ }
132
+ };
133
+ },
114
134
  // 模型投影
115
135
  codec: {
116
136
  clipboardText: (ref) => ref,
@@ -166,11 +186,47 @@ function apply(ctx) {
166
186
  if (!input || typeof input.insertReference !== "function") return;
167
187
  const snapshot = input.state?.getSnapshot?.();
168
188
  if (!snapshot) return;
169
- const at = snapshot.draft.length;
170
- input.insertReference(toReference(element), {
171
- start: at,
172
- end: at,
173
- draftRev: snapshot.draftRev
189
+ let start = snapshot.draft.length;
190
+ let end = snapshot.draft.length;
191
+ try {
192
+ const el = document.querySelector("textarea[data-phase]");
193
+ if (el && el.value === snapshot.draft) {
194
+ const s = el.selectionStart ?? 0;
195
+ const e = el.selectionEnd ?? s;
196
+ start = Math.min(s, snapshot.draft.length);
197
+ end = Math.min(e, snapshot.draft.length);
198
+ }
199
+ } catch {
200
+ }
201
+ const reference = toReference(element);
202
+ const gap = snapshot.draft.slice(end)[0] === " " ? 0 : 1;
203
+ const insertedLen = reference.clipboardText.length + gap;
204
+ let leadingSpace = 0;
205
+ let rev = snapshot.draftRev;
206
+ if (start > 0 && !/\s/.test(snapshot.draft[start - 1])) {
207
+ try {
208
+ if (input.insertText?.(" ", { start, end, draftRev: rev })) {
209
+ const next = input.state?.getSnapshot?.();
210
+ if (next) {
211
+ leadingSpace = 1;
212
+ start += 1;
213
+ end += 1;
214
+ rev = next.draftRev;
215
+ }
216
+ }
217
+ } catch {
218
+ }
219
+ }
220
+ input.insertReference(reference, { start, end, draftRev: rev });
221
+ requestAnimationFrame(() => {
222
+ try {
223
+ const el = document.querySelector("textarea[data-phase]");
224
+ if (!el) return;
225
+ const caret = Math.min(start + insertedLen + leadingSpace, el.value.length);
226
+ el.focus();
227
+ el.setSelectionRange(caret, caret);
228
+ } catch {
229
+ }
174
230
  });
175
231
  } catch {
176
232
  }
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.5",
4
4
  "type": "module",
5
5
  "description": "AIPanel 浏览器侧插件(dsh web Web Client):注册 @aipanel 文件引用 source,选中元素以 file chip 高亮插入并可供模型解析。",
6
6
  "publishConfig": {