@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.
- package/lib/client.js +79 -23
- 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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
}
|