@tangle-network/agent-app 0.46.2 → 0.46.4
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/README.md +1 -1
- package/dist/assistant/index.js +3 -1
- package/dist/assistant/index.js.map +1 -1
- package/dist/chunk-KKBTPZIE.js +805 -0
- package/dist/chunk-KKBTPZIE.js.map +1 -0
- package/dist/{chunk-2RTYQU4W.js → chunk-LEGZX5MQ.js} +150 -4
- package/dist/chunk-LEGZX5MQ.js.map +1 -0
- package/dist/chunk-RLOHIX5Y.js +36 -0
- package/dist/chunk-RLOHIX5Y.js.map +1 -0
- package/dist/{chunk-MBKNKAN2.js → chunk-YTEUZXX2.js} +584 -1252
- package/dist/chunk-YTEUZXX2.js.map +1 -0
- package/dist/mention-editor-GVLG3W7C.js +521 -0
- package/dist/mention-editor-GVLG3W7C.js.map +1 -0
- package/dist/stories/studio/StudioProviders.d.ts +10 -0
- package/dist/stories/studio/fixtures.d.ts +10 -2
- package/dist/studio/audio-preview.d.ts +12 -0
- package/dist/studio/generation.d.ts +22 -1
- package/dist/studio/index.d.ts +2 -0
- package/dist/studio/index.js +29 -1
- package/dist/studio/ports.d.ts +54 -0
- package/dist/studio-react/composer-option-controls.d.ts +19 -8
- package/dist/studio-react/download-generations.d.ts +4 -0
- package/dist/studio-react/index.d.ts +38 -22
- package/dist/studio-react/index.js +2198 -545
- package/dist/studio-react/index.js.map +1 -1
- package/dist/studio-react/media-tile.d.ts +21 -0
- package/dist/studio-react/media-viewer.d.ts +12 -0
- package/dist/studio-react/studio-confirm.d.ts +8 -0
- package/dist/studio-react/studio-generation-screen.d.ts +42 -0
- package/dist/studio-react/studio-history-screen.d.ts +28 -0
- package/dist/studio-react/studio-home-screen.d.ts +20 -0
- package/dist/studio-react/studio-playback.d.ts +31 -0
- package/dist/studio-react/studio-toasts.d.ts +21 -0
- package/dist/studio-react/studio.css +142 -72
- package/dist/studio-react/use-batch-navigation.d.ts +20 -0
- package/dist/studio-react/use-deferred-delete.d.ts +19 -0
- package/dist/studio-react/use-generation-history.d.ts +22 -0
- package/dist/studio-react/vault-path-popover.d.ts +12 -0
- package/dist/teams/drizzle/invitations-schema.d.ts +1 -1
- package/dist/teams/drizzle/schema.d.ts +1 -1
- package/dist/web-react/chat-composer.d.ts +24 -4
- package/dist/web-react/index.d.ts +1 -0
- package/dist/web-react/index.js +12 -6
- package/dist/web-react/mention-boundaries.d.ts +33 -0
- package/dist/web-react/mention-editor.d.ts +84 -0
- package/dist/web-react/mention-list.d.ts +35 -0
- package/dist/web-react/mention-pill.d.ts +10 -0
- package/dist/web-react/mention-serialize.d.ts +44 -0
- package/dist/web-react/use-file-mentions.d.ts +28 -14
- package/package.json +33 -7
- package/dist/chunk-2RTYQU4W.js.map +0 -1
- package/dist/chunk-MBKNKAN2.js.map +0 -1
- package/dist/studio-react/generation-card.d.ts +0 -9
- package/dist/studio-react/generation-detail-modal.d.ts +0 -11
- package/dist/studio-react/generation-detail.d.ts +0 -6
- package/dist/studio-react/generation-grid.d.ts +0 -15
- package/dist/studio-react/library-drawer.d.ts +0 -12
- package/dist/studio-react/library-panel.d.ts +0 -15
- package/dist/studio-react/result-canvas.d.ts +0 -6
- package/dist/studio-react/studio-header.d.ts +0 -5
- package/dist/studio-react/studio-sheet.d.ts +0 -12
- package/dist/studio-react/studio-workspace.d.ts +0 -21
- package/dist/studio-react/type-config.d.ts +0 -11
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MENTION_PILL_CLASS,
|
|
3
|
+
PATH_CONTINUATION_CHAR,
|
|
4
|
+
WORD_CHAR,
|
|
5
|
+
charAt,
|
|
6
|
+
charBefore,
|
|
7
|
+
joinClasses
|
|
8
|
+
} from "./chunk-RLOHIX5Y.js";
|
|
9
|
+
import {
|
|
10
|
+
PopoverSurface
|
|
11
|
+
} from "./chunk-YDOQE47G.js";
|
|
12
|
+
|
|
13
|
+
// src/web-react/mention-editor.tsx
|
|
14
|
+
import { useEffect as useEffect2, useId, useRef as useRef2, useState as useState2 } from "react";
|
|
15
|
+
|
|
16
|
+
// src/web-react/mention-list.tsx
|
|
17
|
+
import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
18
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
+
var MentionList = forwardRef(
|
|
20
|
+
function MentionList2({ items, loading, error, emptyText = "No matches", renderItem, onSelect, id, onActiveChange, className }, ref) {
|
|
21
|
+
const [selected, setSelected] = useState(0);
|
|
22
|
+
const selectedRef = useRef(0);
|
|
23
|
+
const onActiveChangeRef = useRef(onActiveChange);
|
|
24
|
+
onActiveChangeRef.current = onActiveChange;
|
|
25
|
+
const optionRefs = useRef([]);
|
|
26
|
+
const move = (next) => {
|
|
27
|
+
selectedRef.current = next;
|
|
28
|
+
setSelected(next);
|
|
29
|
+
onActiveChangeRef.current?.(next);
|
|
30
|
+
};
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
optionRefs.current[selected]?.scrollIntoView({ block: "nearest" });
|
|
33
|
+
}, [selected, items]);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
move(0);
|
|
36
|
+
}, [items]);
|
|
37
|
+
useImperativeHandle(
|
|
38
|
+
ref,
|
|
39
|
+
() => ({
|
|
40
|
+
onKeyDown(event) {
|
|
41
|
+
const count = loading || error ? 0 : items.length;
|
|
42
|
+
switch (event.key) {
|
|
43
|
+
case "ArrowDown":
|
|
44
|
+
if (count > 0) move((selectedRef.current + 1) % count);
|
|
45
|
+
return true;
|
|
46
|
+
case "ArrowUp":
|
|
47
|
+
if (count > 0) move((selectedRef.current - 1 + count) % count);
|
|
48
|
+
return true;
|
|
49
|
+
case "Enter":
|
|
50
|
+
if (count > 0) onSelect(items[Math.min(selectedRef.current, count - 1)]);
|
|
51
|
+
return true;
|
|
52
|
+
case "Tab":
|
|
53
|
+
if (count === 0) return false;
|
|
54
|
+
onSelect(items[Math.min(selectedRef.current, count - 1)]);
|
|
55
|
+
return true;
|
|
56
|
+
default:
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}),
|
|
61
|
+
[items, loading, error, onSelect]
|
|
62
|
+
);
|
|
63
|
+
return /* @__PURE__ */ jsxs(
|
|
64
|
+
"div",
|
|
65
|
+
{
|
|
66
|
+
role: "listbox",
|
|
67
|
+
id,
|
|
68
|
+
"aria-label": "File mentions",
|
|
69
|
+
className: joinClasses(
|
|
70
|
+
"max-h-64 min-w-[16rem] max-w-sm overflow-y-auto rounded-xl border border-border",
|
|
71
|
+
"bg-popover p-1 text-popover-foreground shadow-lg",
|
|
72
|
+
className
|
|
73
|
+
),
|
|
74
|
+
children: [
|
|
75
|
+
loading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2.5 py-2 text-sm text-muted-foreground", children: [
|
|
76
|
+
/* @__PURE__ */ jsx("span", { className: "h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary border-t-transparent" }),
|
|
77
|
+
"Searching\u2026"
|
|
78
|
+
] }),
|
|
79
|
+
!loading && error && /* @__PURE__ */ jsx("div", { className: "px-2.5 py-2 text-sm text-destructive", children: "Couldn\u2019t load matches" }),
|
|
80
|
+
!loading && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-2.5 py-2 text-sm text-muted-foreground", children: emptyText }),
|
|
81
|
+
!loading && !error && items.map((item, index) => {
|
|
82
|
+
const active = index === selected;
|
|
83
|
+
return /* @__PURE__ */ jsx(
|
|
84
|
+
"button",
|
|
85
|
+
{
|
|
86
|
+
type: "button",
|
|
87
|
+
id: id ? `${id}-opt-${index}` : void 0,
|
|
88
|
+
role: "option",
|
|
89
|
+
"aria-selected": active,
|
|
90
|
+
ref: (el) => {
|
|
91
|
+
optionRefs.current[index] = el;
|
|
92
|
+
},
|
|
93
|
+
onMouseDown: (event) => {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
},
|
|
96
|
+
onClick: () => onSelect(item),
|
|
97
|
+
onMouseEnter: () => move(index),
|
|
98
|
+
className: joinClasses(
|
|
99
|
+
"flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-sm",
|
|
100
|
+
active ? "bg-primary/10 text-foreground" : "text-muted-foreground"
|
|
101
|
+
),
|
|
102
|
+
children: renderItem ? renderItem(item) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
103
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: item.label }),
|
|
104
|
+
item.detail && /* @__PURE__ */ jsx("span", { className: "ml-auto truncate text-xs text-muted-foreground", children: item.detail })
|
|
105
|
+
] })
|
|
106
|
+
},
|
|
107
|
+
item.id
|
|
108
|
+
);
|
|
109
|
+
})
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
// src/web-react/mention-serialize.ts
|
|
117
|
+
var MENTION_TYPE = "mention";
|
|
118
|
+
function serializeMentionDoc(doc) {
|
|
119
|
+
return (doc.content ?? []).map(serializeBlock).join("\n");
|
|
120
|
+
}
|
|
121
|
+
function serializeBlock(node) {
|
|
122
|
+
if (node.type === "paragraph") {
|
|
123
|
+
return (node.content ?? []).map(serializeInline).join("");
|
|
124
|
+
}
|
|
125
|
+
return "";
|
|
126
|
+
}
|
|
127
|
+
function serializeInline(node) {
|
|
128
|
+
if (node.type === "text") return node.text ?? "";
|
|
129
|
+
if (node.type === "hardBreak") return "\n";
|
|
130
|
+
if (node.type === MENTION_TYPE) return `@${node.attrs?.id ?? ""}`;
|
|
131
|
+
return "";
|
|
132
|
+
}
|
|
133
|
+
function collectMentions(doc) {
|
|
134
|
+
const out = [];
|
|
135
|
+
const walk = (node) => {
|
|
136
|
+
if (node.type === MENTION_TYPE && node.attrs?.id) {
|
|
137
|
+
out.push({
|
|
138
|
+
id: node.attrs.id,
|
|
139
|
+
label: node.attrs.label ?? node.attrs.id,
|
|
140
|
+
kind: node.attrs.kind ?? void 0
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
for (const child of node.content ?? []) walk(child);
|
|
144
|
+
};
|
|
145
|
+
walk(doc);
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
function parseMentionValue(value, known) {
|
|
149
|
+
const lines = value.split("\n");
|
|
150
|
+
return {
|
|
151
|
+
type: "doc",
|
|
152
|
+
content: lines.map((line) => ({
|
|
153
|
+
type: "paragraph",
|
|
154
|
+
content: parseLine(line, known)
|
|
155
|
+
}))
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function parseLine(line, known) {
|
|
159
|
+
const content = [];
|
|
160
|
+
let text = "";
|
|
161
|
+
const flush = () => {
|
|
162
|
+
if (text) {
|
|
163
|
+
content.push({ type: "text", text });
|
|
164
|
+
text = "";
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
let i = 0;
|
|
168
|
+
while (i < line.length) {
|
|
169
|
+
const before = charBefore(line, i);
|
|
170
|
+
const partOfLongerToken = before !== void 0 && WORD_CHAR.test(before);
|
|
171
|
+
if (line[i] === "@" && !partOfLongerToken) {
|
|
172
|
+
const id = matchKnownId(line, i + 1, known);
|
|
173
|
+
if (id) {
|
|
174
|
+
const item = known.get(id);
|
|
175
|
+
flush();
|
|
176
|
+
content.push({
|
|
177
|
+
type: MENTION_TYPE,
|
|
178
|
+
attrs: {
|
|
179
|
+
id: item.id,
|
|
180
|
+
label: item.label,
|
|
181
|
+
kind: item.kind ?? null
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
i += 1 + id.length;
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
text += line[i];
|
|
189
|
+
i += 1;
|
|
190
|
+
}
|
|
191
|
+
flush();
|
|
192
|
+
return content;
|
|
193
|
+
}
|
|
194
|
+
function matchKnownId(line, pos, known) {
|
|
195
|
+
let best = null;
|
|
196
|
+
for (const id of known.keys()) {
|
|
197
|
+
if (id.length === 0) continue;
|
|
198
|
+
if (best !== null && id.length <= best.length) continue;
|
|
199
|
+
if (!line.startsWith(id, pos)) continue;
|
|
200
|
+
const next = charAt(line, pos + id.length);
|
|
201
|
+
if (next !== void 0 && PATH_CONTINUATION_CHAR.test(next)) continue;
|
|
202
|
+
best = id;
|
|
203
|
+
}
|
|
204
|
+
return best;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/web-react/mention-editor.tsx
|
|
208
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
209
|
+
var PEERS_MISSING_MESSAGE = "ChatComposer `mention` needs the @tiptap/* optional peers \u2014 install @tiptap/core, @tiptap/extension-mention, @tiptap/pm, @tiptap/react, @tiptap/starter-kit and @tiptap/suggestion (>=3.28.0 <4.0.0; @tiptap/suggestion also peers on @floating-ui/dom)";
|
|
210
|
+
var RESOLUTION_FAILURE = /could not resolve|cannot find module|failed to resolve/i;
|
|
211
|
+
async function loadTiptapModules() {
|
|
212
|
+
let modules;
|
|
213
|
+
try {
|
|
214
|
+
const [core, extensionMention, react, starterKit, suggestion] = await Promise.all([
|
|
215
|
+
import("@tiptap/core"),
|
|
216
|
+
import("@tiptap/extension-mention"),
|
|
217
|
+
import("@tiptap/react"),
|
|
218
|
+
import("@tiptap/starter-kit"),
|
|
219
|
+
import("@tiptap/suggestion")
|
|
220
|
+
]);
|
|
221
|
+
modules = { core, extensionMention, react, starterKit, suggestion };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (error instanceof Error && RESOLUTION_FAILURE.test(error.message)) {
|
|
224
|
+
throw new Error(PEERS_MISSING_MESSAGE, { cause: error });
|
|
225
|
+
}
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
if (typeof modules.core.mergeAttributes !== "function" || typeof modules.react.useEditor !== "function" || modules.react.EditorContent === void 0 || modules.extensionMention.default === void 0 || modules.starterKit.default === void 0 || typeof modules.suggestion.exitSuggestion !== "function") {
|
|
229
|
+
throw new Error(PEERS_MISSING_MESSAGE);
|
|
230
|
+
}
|
|
231
|
+
return modules;
|
|
232
|
+
}
|
|
233
|
+
function buildComposerStarterKit(tiptap) {
|
|
234
|
+
return tiptap.starterKit.default.configure({
|
|
235
|
+
blockquote: false,
|
|
236
|
+
bold: false,
|
|
237
|
+
bulletList: false,
|
|
238
|
+
code: false,
|
|
239
|
+
codeBlock: false,
|
|
240
|
+
dropcursor: false,
|
|
241
|
+
heading: false,
|
|
242
|
+
horizontalRule: false,
|
|
243
|
+
italic: false,
|
|
244
|
+
link: false,
|
|
245
|
+
listItem: false,
|
|
246
|
+
listKeymap: false,
|
|
247
|
+
orderedList: false,
|
|
248
|
+
strike: false,
|
|
249
|
+
trailingNode: false,
|
|
250
|
+
underline: false
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function buildMentionExtension(tiptap, trigger, suggestion) {
|
|
254
|
+
const { mergeAttributes } = tiptap.core;
|
|
255
|
+
return tiptap.extensionMention.default.extend({
|
|
256
|
+
addAttributes() {
|
|
257
|
+
return {
|
|
258
|
+
id: {
|
|
259
|
+
default: null,
|
|
260
|
+
parseHTML: (element) => element.getAttribute("data-id"),
|
|
261
|
+
renderHTML: (attrs) => attrs.id ? { "data-id": attrs.id } : {}
|
|
262
|
+
},
|
|
263
|
+
label: {
|
|
264
|
+
default: null,
|
|
265
|
+
parseHTML: (element) => element.getAttribute("data-label"),
|
|
266
|
+
renderHTML: (attrs) => attrs.label ? { "data-label": attrs.label } : {}
|
|
267
|
+
},
|
|
268
|
+
kind: {
|
|
269
|
+
default: null,
|
|
270
|
+
parseHTML: (element) => element.getAttribute("data-kind"),
|
|
271
|
+
renderHTML: (attrs) => attrs.kind ? { "data-kind": attrs.kind } : {}
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}).configure({
|
|
276
|
+
HTMLAttributes: { class: MENTION_PILL_CLASS },
|
|
277
|
+
renderText: ({ node }) => `${trigger}${node.attrs.id}`,
|
|
278
|
+
renderHTML: ({ options, node }) => [
|
|
279
|
+
"span",
|
|
280
|
+
mergeAttributes(options.HTMLAttributes, {
|
|
281
|
+
title: node.attrs.id ?? void 0
|
|
282
|
+
}),
|
|
283
|
+
`${trigger}${node.attrs.label ?? node.attrs.id}`
|
|
284
|
+
],
|
|
285
|
+
suggestion
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
var EDITOR_CLASS = "w-full whitespace-pre-wrap break-words bg-transparent px-1.5 py-1 text-base leading-6 text-foreground outline-none";
|
|
289
|
+
var FETCH_DEBOUNCE_MS = 100;
|
|
290
|
+
function mentionsKey(mentions) {
|
|
291
|
+
return JSON.stringify(mentions.map((item) => item.id));
|
|
292
|
+
}
|
|
293
|
+
async function loadMentionEditor() {
|
|
294
|
+
return { default: createMentionEditor(await loadTiptapModules()) };
|
|
295
|
+
}
|
|
296
|
+
function createMentionEditor(tiptap) {
|
|
297
|
+
const { EditorContent, useEditor } = tiptap.react;
|
|
298
|
+
const { exitSuggestion } = tiptap.suggestion;
|
|
299
|
+
return function MentionEditor({
|
|
300
|
+
value,
|
|
301
|
+
onChange,
|
|
302
|
+
onSubmit,
|
|
303
|
+
placeholder,
|
|
304
|
+
disabled,
|
|
305
|
+
autoFocus,
|
|
306
|
+
minHeight,
|
|
307
|
+
maxHeight,
|
|
308
|
+
mention,
|
|
309
|
+
registerFocus,
|
|
310
|
+
onPasteFiles
|
|
311
|
+
}) {
|
|
312
|
+
const trigger = mention.trigger ?? "@";
|
|
313
|
+
const [open, setOpen] = useState2(false);
|
|
314
|
+
const [query, setQuery] = useState2("");
|
|
315
|
+
const [items, setItems] = useState2([]);
|
|
316
|
+
const [loading, setLoading] = useState2(false);
|
|
317
|
+
const [errored, setErrored] = useState2(false);
|
|
318
|
+
const [activeIndex, setActiveIndex] = useState2(0);
|
|
319
|
+
const listboxId = useId();
|
|
320
|
+
const anchorRef = useRef2(null);
|
|
321
|
+
const panelRef = useRef2(null);
|
|
322
|
+
const openRef = useRef2(false);
|
|
323
|
+
const commandRef = useRef2(null);
|
|
324
|
+
const listRef = useRef2(null);
|
|
325
|
+
const knownRef = useRef2(/* @__PURE__ */ new Map());
|
|
326
|
+
const requestIdRef = useRef2(0);
|
|
327
|
+
const lastMentionsKeyRef = useRef2(mentionsKey([]));
|
|
328
|
+
const onSubmitRef = useRef2(onSubmit);
|
|
329
|
+
onSubmitRef.current = onSubmit;
|
|
330
|
+
const onChangeRef = useRef2(onChange);
|
|
331
|
+
onChangeRef.current = onChange;
|
|
332
|
+
const onMentionsChangeRef = useRef2(mention.onMentionsChange);
|
|
333
|
+
onMentionsChangeRef.current = mention.onMentionsChange;
|
|
334
|
+
const onPasteFilesRef = useRef2(onPasteFiles);
|
|
335
|
+
onPasteFilesRef.current = onPasteFiles;
|
|
336
|
+
const fetchItemsRef = useRef2(mention.fetchItems);
|
|
337
|
+
fetchItemsRef.current = mention.fetchItems;
|
|
338
|
+
const editor = useEditor(
|
|
339
|
+
{
|
|
340
|
+
immediatelyRender: false,
|
|
341
|
+
editable: !disabled,
|
|
342
|
+
autofocus: autoFocus ? "end" : false,
|
|
343
|
+
content: parseMentionValue(value, knownRef.current),
|
|
344
|
+
editorProps: {
|
|
345
|
+
attributes: {
|
|
346
|
+
class: EDITOR_CLASS,
|
|
347
|
+
role: "textbox",
|
|
348
|
+
"aria-multiline": "true",
|
|
349
|
+
"aria-label": "Message input",
|
|
350
|
+
"aria-haspopup": "listbox"
|
|
351
|
+
},
|
|
352
|
+
handleKeyDown: (_view, event) => {
|
|
353
|
+
if (event.key !== "Enter" || event.shiftKey) return false;
|
|
354
|
+
if (event.isComposing || event.keyCode === 229) return false;
|
|
355
|
+
if (openRef.current) return false;
|
|
356
|
+
event.preventDefault();
|
|
357
|
+
onSubmitRef.current();
|
|
358
|
+
return true;
|
|
359
|
+
},
|
|
360
|
+
handlePaste: (_view, event) => {
|
|
361
|
+
const files = event.clipboardData?.files;
|
|
362
|
+
if (files && files.length > 0 && onPasteFilesRef.current) {
|
|
363
|
+
return onPasteFilesRef.current(files);
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
onUpdate: ({ editor: editor2 }) => {
|
|
369
|
+
const json = editor2.getJSON();
|
|
370
|
+
onChangeRef.current(serializeMentionDoc(json));
|
|
371
|
+
const mentions = collectMentions(json);
|
|
372
|
+
for (const item of mentions) knownRef.current.set(item.id, item);
|
|
373
|
+
const key = mentionsKey(mentions);
|
|
374
|
+
if (key !== lastMentionsKeyRef.current) {
|
|
375
|
+
lastMentionsKeyRef.current = key;
|
|
376
|
+
onMentionsChangeRef.current?.(mentions);
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
extensions: [
|
|
380
|
+
buildComposerStarterKit(tiptap),
|
|
381
|
+
buildMentionExtension(tiptap, trigger, {
|
|
382
|
+
char: trigger,
|
|
383
|
+
allowSpaces: false,
|
|
384
|
+
// Items are fetched by this component (so it can model loading and
|
|
385
|
+
// error states), not by the suggestion plugin.
|
|
386
|
+
items: () => [],
|
|
387
|
+
command: ({ editor: editor2, range, props }) => {
|
|
388
|
+
const item = props;
|
|
389
|
+
editor2.chain().focus().insertContentAt(range, [
|
|
390
|
+
{
|
|
391
|
+
type: "mention",
|
|
392
|
+
attrs: {
|
|
393
|
+
id: item.id,
|
|
394
|
+
label: item.label,
|
|
395
|
+
kind: item.kind ?? null
|
|
396
|
+
}
|
|
397
|
+
},
|
|
398
|
+
{ type: "text", text: " " }
|
|
399
|
+
]).run();
|
|
400
|
+
knownRef.current.set(item.id, item);
|
|
401
|
+
},
|
|
402
|
+
render: () => ({
|
|
403
|
+
onStart: (props) => {
|
|
404
|
+
openRef.current = true;
|
|
405
|
+
commandRef.current = props.command;
|
|
406
|
+
setOpen(true);
|
|
407
|
+
setQuery(props.query);
|
|
408
|
+
},
|
|
409
|
+
onUpdate: (props) => {
|
|
410
|
+
commandRef.current = props.command;
|
|
411
|
+
setQuery(props.query);
|
|
412
|
+
},
|
|
413
|
+
onKeyDown: (props) => {
|
|
414
|
+
if (props.event.key === "Escape") {
|
|
415
|
+
exitSuggestion(props.view);
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
return listRef.current?.onKeyDown(props.event) ?? false;
|
|
419
|
+
},
|
|
420
|
+
onExit: () => {
|
|
421
|
+
openRef.current = false;
|
|
422
|
+
commandRef.current = null;
|
|
423
|
+
setOpen(false);
|
|
424
|
+
setItems([]);
|
|
425
|
+
setLoading(false);
|
|
426
|
+
setErrored(false);
|
|
427
|
+
}
|
|
428
|
+
})
|
|
429
|
+
})
|
|
430
|
+
]
|
|
431
|
+
},
|
|
432
|
+
[]
|
|
433
|
+
);
|
|
434
|
+
useEffect2(() => {
|
|
435
|
+
if (!open) return;
|
|
436
|
+
const requestId = requestIdRef.current += 1;
|
|
437
|
+
setLoading(true);
|
|
438
|
+
setErrored(false);
|
|
439
|
+
const timer = setTimeout(() => {
|
|
440
|
+
Promise.resolve().then(() => fetchItemsRef.current(query)).then((results) => {
|
|
441
|
+
if (requestId !== requestIdRef.current) return;
|
|
442
|
+
setItems(results);
|
|
443
|
+
setLoading(false);
|
|
444
|
+
}).catch(() => {
|
|
445
|
+
if (requestId !== requestIdRef.current) return;
|
|
446
|
+
setErrored(true);
|
|
447
|
+
setLoading(false);
|
|
448
|
+
});
|
|
449
|
+
}, FETCH_DEBOUNCE_MS);
|
|
450
|
+
return () => clearTimeout(timer);
|
|
451
|
+
}, [open, query]);
|
|
452
|
+
useEffect2(() => {
|
|
453
|
+
if (!editor) return;
|
|
454
|
+
if (serializeMentionDoc(editor.getJSON()) === value) return;
|
|
455
|
+
const doc = parseMentionValue(value, knownRef.current);
|
|
456
|
+
editor.commands.setContent(doc, { emitUpdate: false });
|
|
457
|
+
const mentions = collectMentions(doc);
|
|
458
|
+
const key = mentionsKey(mentions);
|
|
459
|
+
if (key !== lastMentionsKeyRef.current) {
|
|
460
|
+
lastMentionsKeyRef.current = key;
|
|
461
|
+
onMentionsChangeRef.current?.(mentions);
|
|
462
|
+
}
|
|
463
|
+
}, [editor, value]);
|
|
464
|
+
useEffect2(() => {
|
|
465
|
+
editor?.setEditable(!disabled);
|
|
466
|
+
}, [editor, disabled]);
|
|
467
|
+
useEffect2(() => {
|
|
468
|
+
if (!editor) return;
|
|
469
|
+
const dom = editor.view.dom;
|
|
470
|
+
dom.setAttribute("aria-expanded", open ? "true" : "false");
|
|
471
|
+
if (open) dom.setAttribute("aria-controls", listboxId);
|
|
472
|
+
else dom.removeAttribute("aria-controls");
|
|
473
|
+
const hasRows = open && !loading && !errored && items.length > 0;
|
|
474
|
+
if (hasRows) dom.setAttribute("aria-activedescendant", `${listboxId}-opt-${activeIndex}`);
|
|
475
|
+
else dom.removeAttribute("aria-activedescendant");
|
|
476
|
+
}, [editor, open, loading, errored, items.length, activeIndex, listboxId]);
|
|
477
|
+
useEffect2(() => {
|
|
478
|
+
if (!editor || !registerFocus) return;
|
|
479
|
+
registerFocus(() => editor.commands.focus());
|
|
480
|
+
return () => registerFocus(null);
|
|
481
|
+
}, [editor, registerFocus]);
|
|
482
|
+
return /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
483
|
+
/* @__PURE__ */ jsxs2("div", { ref: anchorRef, className: "overflow-y-auto", style: { maxHeight, minHeight }, children: [
|
|
484
|
+
/* @__PURE__ */ jsx2(EditorContent, { editor }),
|
|
485
|
+
value.length === 0 && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute left-1.5 top-1 text-base leading-6 text-muted-foreground", children: placeholder })
|
|
486
|
+
] }),
|
|
487
|
+
/* @__PURE__ */ jsx2(
|
|
488
|
+
PopoverSurface,
|
|
489
|
+
{
|
|
490
|
+
open,
|
|
491
|
+
triggerRef: anchorRef,
|
|
492
|
+
panelRef,
|
|
493
|
+
className: "overflow-y-auto",
|
|
494
|
+
children: /* @__PURE__ */ jsx2(
|
|
495
|
+
MentionList,
|
|
496
|
+
{
|
|
497
|
+
ref: listRef,
|
|
498
|
+
id: listboxId,
|
|
499
|
+
items,
|
|
500
|
+
loading,
|
|
501
|
+
error: errored,
|
|
502
|
+
emptyText: mention.emptyText,
|
|
503
|
+
renderItem: mention.renderItem,
|
|
504
|
+
onSelect: (item) => commandRef.current?.(item),
|
|
505
|
+
onActiveChange: setActiveIndex,
|
|
506
|
+
className: mention.popoverClassName
|
|
507
|
+
}
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
] });
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
export {
|
|
515
|
+
buildComposerStarterKit,
|
|
516
|
+
buildMentionExtension,
|
|
517
|
+
createMentionEditor,
|
|
518
|
+
loadMentionEditor,
|
|
519
|
+
loadTiptapModules
|
|
520
|
+
};
|
|
521
|
+
//# sourceMappingURL=mention-editor-GVLG3W7C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/web-react/mention-editor.tsx","../src/web-react/mention-list.tsx","../src/web-react/mention-serialize.ts"],"sourcesContent":["/**\n * The mention-capable rich input behind `ChatComposer`'s `mention` prop.\n *\n * The five `@tiptap/*` packages are OPTIONAL peers, and every value access to\n * them goes through `loadTiptapModules()`'s dynamic `import()` — never a\n * static named import. A bundler resolves a missing optional peer to a stub\n * module with a default export only, so a static `import { mergeAttributes }`\n * fails the CONSUMER'S build even when nothing ever renders the mention path\n * (measured against Vite 7's `__vite-optional-peer-dep` stub). The dynamic\n * form builds clean and defers the failure to the first actual editor load,\n * where it throws with the missing packages named — the same doctrine as\n * `sequences-react`'s transcription peer. Type-only imports are erased and\n * stay allowed.\n */\n\nimport type { MentionOptions } from '@tiptap/extension-mention'\nimport type { SuggestionKeyDownProps, SuggestionProps } from '@tiptap/suggestion'\nimport type * as TiptapCore from '@tiptap/core'\nimport type * as TiptapExtensionMention from '@tiptap/extension-mention'\nimport type * as TiptapReact from '@tiptap/react'\nimport type * as TiptapStarterKit from '@tiptap/starter-kit'\nimport type * as TiptapSuggestion from '@tiptap/suggestion'\nimport { useEffect, useId, useRef, useState, type ComponentType } from 'react'\n\nimport { PopoverSurface } from './controls'\nimport { MENTION_PILL_CLASS } from './mention-pill'\nimport { MentionList, type MentionListHandle } from './mention-list'\nimport {\n collectMentions,\n parseMentionValue,\n serializeMentionDoc,\n type MentionDocNode,\n} from './mention-serialize'\nimport type { ComposerMentionProp, MentionItem } from './use-file-mentions'\n\n/** The loaded namespaces of the five optional peers, as one bag. */\nexport interface TiptapModules {\n core: typeof TiptapCore\n extensionMention: typeof TiptapExtensionMention\n react: typeof TiptapReact\n starterKit: typeof TiptapStarterKit\n suggestion: typeof TiptapSuggestion\n}\n\nconst PEERS_MISSING_MESSAGE =\n 'ChatComposer `mention` needs the @tiptap/* optional peers — install @tiptap/core, @tiptap/extension-mention, @tiptap/pm, @tiptap/react, @tiptap/starter-kit and @tiptap/suggestion (>=3.28.0 <4.0.0; @tiptap/suggestion also peers on @floating-ui/dom)'\n\n/** The rejection shapes bundlers produce for an UNINSTALLED module. Only\n * these get enriched with the install-set message — a transient chunk-fetch\n * failure must surface its own cause, or Retry sits next to a misleading\n * \"install the peers\" diagnosis. An unmatched resolution error still fails\n * loud with its raw message, so the classifier can only under-enrich. */\nconst RESOLUTION_FAILURE = /could not resolve|cannot find module|failed to resolve/i\n\n/** Resolve the optional peers, or throw naming every one of them. */\nexport async function loadTiptapModules(): Promise<TiptapModules> {\n let modules: TiptapModules\n try {\n const [core, extensionMention, react, starterKit, suggestion] = await Promise.all([\n import('@tiptap/core'),\n import('@tiptap/extension-mention'),\n import('@tiptap/react'),\n import('@tiptap/starter-kit'),\n import('@tiptap/suggestion'),\n ])\n modules = { core, extensionMention, react, starterKit, suggestion }\n } catch (error) {\n if (error instanceof Error && RESOLUTION_FAILURE.test(error.message)) {\n throw new Error(PEERS_MISSING_MESSAGE, { cause: error })\n }\n throw error\n }\n // A CommonJS-in-production bundler stubs a missing optional peer as a\n // SILENT empty namespace rather than a throwing module. Validate the\n // members the editor actually calls, so that shape still fails loud with\n // the peers named instead of as an undefined-property crash mid-render.\n if (\n typeof modules.core.mergeAttributes !== 'function' ||\n typeof modules.react.useEditor !== 'function' ||\n modules.react.EditorContent === undefined ||\n modules.extensionMention.default === undefined ||\n modules.starterKit.default === undefined ||\n typeof modules.suggestion.exitSuggestion !== 'function'\n ) {\n throw new Error(PEERS_MISSING_MESSAGE)\n }\n return modules\n}\n\nexport interface MentionEditorProps {\n value: string\n onChange: (value: string) => void\n /** Fired when Enter (no Shift, popover closed, not composing) should send. */\n onSubmit: () => void\n placeholder: string\n disabled?: boolean\n autoFocus?: boolean\n /** Pixel floor for the input box. The composer computes it from `minRows`\n * against its own line metrics, so the two input modes cannot drift. */\n minHeight: number\n /** Pixel height the input grows to before it scrolls. */\n maxHeight: number\n mention: ComposerMentionProp\n /** Registers a focus callback the composer wires to Cmd/Ctrl+L; called with\n * `null` on unmount so the composer never focuses a destroyed editor. */\n registerFocus?: (focus: (() => void) | null) => void\n /**\n * Clipboard files pulled off a paste. Returns true when consumed so the\n * editor suppresses its default text paste — the same funnel the textarea\n * path routes through to `onAttach`.\n */\n onPasteFiles?: (files: FileList) => boolean\n}\n\n/** Only text, hard breaks, and atomic mention pills — no marks, no formatting. */\nexport function buildComposerStarterKit(tiptap: TiptapModules) {\n return tiptap.starterKit.default.configure({\n blockquote: false,\n bold: false,\n bulletList: false,\n code: false,\n codeBlock: false,\n dropcursor: false,\n heading: false,\n horizontalRule: false,\n italic: false,\n link: false,\n listItem: false,\n listKeymap: false,\n orderedList: false,\n strike: false,\n trailingNode: false,\n underline: false,\n })\n}\n\n/**\n * The mention node: an inline atom (`inline: true, selectable: false,\n * atom: true` from `@tiptap/extension-mention`) carrying `{ id, label, kind }`,\n * rendered as a themed pill showing the label with the full id in `title`. Atom\n * means the cursor can't enter it and a single backspace removes the whole\n * thing. The `suggestion` config is supplied by the caller so the popover's\n * fetch/keyboard wiring stays in the component.\n */\nexport function buildMentionExtension(\n tiptap: TiptapModules,\n trigger: string,\n suggestion: MentionOptions['suggestion'],\n) {\n const { mergeAttributes } = tiptap.core\n return tiptap.extensionMention.default\n .extend({\n addAttributes() {\n return {\n id: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-id'),\n renderHTML: (attrs) => (attrs.id ? { 'data-id': attrs.id } : {}),\n },\n label: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-label'),\n renderHTML: (attrs) => (attrs.label ? { 'data-label': attrs.label } : {}),\n },\n kind: {\n default: null,\n parseHTML: (element) => element.getAttribute('data-kind'),\n renderHTML: (attrs) => (attrs.kind ? { 'data-kind': attrs.kind } : {}),\n },\n }\n },\n })\n .configure({\n HTMLAttributes: { class: MENTION_PILL_CLASS },\n renderText: ({ node }) => `${trigger}${node.attrs.id}`,\n renderHTML: ({ options, node }) => [\n 'span',\n mergeAttributes(options.HTMLAttributes, {\n title: node.attrs.id ?? undefined,\n }),\n `${trigger}${node.attrs.label ?? node.attrs.id}`,\n ],\n suggestion,\n })\n}\n\n/** Matches the composer textarea's own text treatment so the two input modes\n * render identically (`text-base leading-6`, the same padding). `outline-none`\n * is safe for the same reason as on that textarea: the composer card this\n * editor mounts into draws the keyboard indicator through `focus-within:`,\n * so a second outline inside the card would double the ring. */\nconst EDITOR_CLASS =\n 'w-full whitespace-pre-wrap break-words bg-transparent px-1.5 py-1 text-base leading-6 text-foreground outline-none'\n\n/** Trailing debounce on the suggestion fetch so a fast typist doesn't hit the\n * provider once per keystroke. Fixed rather than a prop — 100ms is well under\n * perceived latency and the monotonic `requestId` already makes any race\n * harmless, so there's no tuning knob worth exposing. */\nconst FETCH_DEBOUNCE_MS = 100\n\n/** Stable key for a mention set — order- and id-sensitive, ignores label/kind\n * so cosmetic re-fetches of the same id don't read as a change. JSON-encoded:\n * ids are workspace paths that may contain spaces, so a plain join would make\n * `[\"a b\",\"c\"]` and `[\"a\",\"b c\"]` collide and suppress a real change. */\nfunction mentionsKey(mentions: MentionItem[]): string {\n return JSON.stringify(mentions.map((item) => item.id))\n}\n\n/** `React.lazy` payload: resolve the peers, then build the component. */\nexport async function loadMentionEditor(): Promise<{\n default: ComponentType<MentionEditorProps>\n}> {\n return { default: createMentionEditor(await loadTiptapModules()) }\n}\n\n/**\n * Builds the mention-capable rich input against loaded tiptap modules. Loaded\n * lazily by `ChatComposer` so textarea-only consumers never pull TipTap into\n * their initial bundle. It preserves every textarea behavior — controlled\n * plain-text `value`, Enter to send / Shift+Enter for a newline, placeholder,\n * disabled, autofocus, file-paste routing — and adds `@`-mention pills backed\n * by an async provider.\n *\n * The suggestion list renders through `PopoverSurface` anchored to the input\n * box, like the composer's slash menu: an in-place panel is a panel the host\n * clips away (see the popover canon in AGENTS.md).\n */\nexport function createMentionEditor(tiptap: TiptapModules): ComponentType<MentionEditorProps> {\n const { EditorContent, useEditor } = tiptap.react\n const { exitSuggestion } = tiptap.suggestion\n\n return function MentionEditor({\n value,\n onChange,\n onSubmit,\n placeholder,\n disabled,\n autoFocus,\n minHeight,\n maxHeight,\n mention,\n registerFocus,\n onPasteFiles,\n }: MentionEditorProps) {\n const trigger = mention.trigger ?? '@'\n\n const [open, setOpen] = useState(false)\n const [query, setQuery] = useState('')\n const [items, setItems] = useState<MentionItem[]>([])\n const [loading, setLoading] = useState(false)\n const [errored, setErrored] = useState(false)\n // Mirror of the list's highlight, for `aria-activedescendant` — keyboard\n // focus never leaves the editor, so the highlighted row must be announced\n // through the editable element itself.\n const [activeIndex, setActiveIndex] = useState(0)\n const listboxId = useId()\n\n // The input box PopoverSurface anchors to, and the portaled panel itself.\n const anchorRef = useRef<HTMLDivElement>(null)\n const panelRef = useRef<HTMLDivElement>(null)\n\n // Live refs keep the once-built editor's callbacks reading current props\n // and state without rebuilding the editor (which would drop cursor +\n // content).\n const openRef = useRef(false)\n const commandRef = useRef<((item: MentionItem) => void) | null>(null)\n const listRef = useRef<MentionListHandle | null>(null)\n // Grows for the component's lifetime — one entry per distinct id the user\n // actually PICKED (or that arrived in the document as a mention node).\n // Deliberately never fed from fetch results: restore-eligibility must\n // track selection, not visibility. Uncapped; fine for a chat composer's\n // lifetime, worth an LRU only if this outlives that.\n const knownRef = useRef<Map<string, MentionItem>>(new Map())\n const requestIdRef = useRef(0)\n // Last mention-id set reported via `onMentionsChange`, so a programmatic\n // restore that lands on the same mentions doesn't re-fire the callback.\n // Seeded with the EMPTY-set key (not ''): the initial document can never\n // hold pills (`knownRef` starts empty), so the first computed key is the\n // empty set's — starting anywhere else fires a spurious `[]` callback.\n const lastMentionsKeyRef = useRef(mentionsKey([]))\n const onSubmitRef = useRef(onSubmit)\n onSubmitRef.current = onSubmit\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n const onMentionsChangeRef = useRef(mention.onMentionsChange)\n onMentionsChangeRef.current = mention.onMentionsChange\n const onPasteFilesRef = useRef(onPasteFiles)\n onPasteFilesRef.current = onPasteFiles\n const fetchItemsRef = useRef(mention.fetchItems)\n fetchItemsRef.current = mention.fetchItems\n\n const editor = useEditor(\n {\n immediatelyRender: false,\n editable: !disabled,\n autofocus: autoFocus ? 'end' : false,\n content: parseMentionValue(value, knownRef.current),\n editorProps: {\n attributes: {\n class: EDITOR_CLASS,\n role: 'textbox',\n 'aria-multiline': 'true',\n 'aria-label': 'Message input',\n 'aria-haspopup': 'listbox',\n },\n handleKeyDown: (_view, event) => {\n if (event.key !== 'Enter' || event.shiftKey) return false\n // Composition (IME) commits via Enter — never send mid-composition.\n if (event.isComposing || event.keyCode === 229) return false\n // Popover open ⇒ Enter belongs to the suggestion plugin (selects).\n if (openRef.current) return false\n event.preventDefault()\n onSubmitRef.current()\n return true\n },\n handlePaste: (_view, event) => {\n const files = event.clipboardData?.files\n if (files && files.length > 0 && onPasteFilesRef.current) {\n return onPasteFilesRef.current(files)\n }\n return false\n },\n },\n onUpdate: ({ editor }) => {\n const json = editor.getJSON() as MentionDocNode\n onChangeRef.current(serializeMentionDoc(json))\n const mentions = collectMentions(json)\n for (const item of mentions) knownRef.current.set(item.id, item)\n // Report only when the mention SET changed — typing prose around an\n // unchanged pill must not re-fire the callback on every keystroke.\n const key = mentionsKey(mentions)\n if (key !== lastMentionsKeyRef.current) {\n lastMentionsKeyRef.current = key\n onMentionsChangeRef.current?.(mentions)\n }\n },\n extensions: [\n buildComposerStarterKit(tiptap),\n buildMentionExtension(tiptap, trigger, {\n char: trigger,\n allowSpaces: false,\n // Items are fetched by this component (so it can model loading and\n // error states), not by the suggestion plugin.\n items: () => [],\n command: ({ editor, range, props }) => {\n const item = props as unknown as MentionItem\n editor\n .chain()\n .focus()\n .insertContentAt(range, [\n {\n type: 'mention',\n attrs: {\n id: item.id,\n label: item.label,\n kind: item.kind ?? null,\n },\n },\n { type: 'text', text: ' ' },\n ])\n .run()\n knownRef.current.set(item.id, item)\n },\n render: () => ({\n onStart: (props: SuggestionProps) => {\n openRef.current = true\n commandRef.current = props.command\n setOpen(true)\n setQuery(props.query)\n },\n onUpdate: (props: SuggestionProps) => {\n commandRef.current = props.command\n setQuery(props.query)\n },\n onKeyDown: (props: SuggestionKeyDownProps) => {\n if (props.event.key === 'Escape') {\n exitSuggestion(props.view)\n return true\n }\n return listRef.current?.onKeyDown(props.event) ?? false\n },\n onExit: () => {\n openRef.current = false\n commandRef.current = null\n setOpen(false)\n setItems([])\n setLoading(false)\n setErrored(false)\n },\n }),\n }),\n ],\n },\n [],\n )\n\n // Fetch matches for the active query, trailing-debounced so a fast typist\n // doesn't hit the provider once per keystroke. The monotonic id still\n // drops stale responses (belt-and-suspenders against a provider that\n // resolves out of order); the debounce timer itself is cancelled by the\n // effect cleanup whenever `query` changes again before it fires.\n useEffect(() => {\n if (!open) return\n const requestId = (requestIdRef.current += 1)\n setLoading(true)\n setErrored(false)\n const timer = setTimeout(() => {\n // The provider is called INSIDE the chain so a synchronous throw lands\n // in the same `.catch` as a rejection — outside it, the exception\n // escapes the timer callback and the panel is stuck on \"Searching…\".\n Promise.resolve()\n .then(() => fetchItemsRef.current(query))\n .then((results) => {\n if (requestId !== requestIdRef.current) return\n // Results are NOT written into `knownRef`: merely appearing in a\n // suggestion list must not make an id restore-eligible, or a\n // later restore would promote prose the user typed (never picked)\n // into a structured mention. Only a pick (`command`) or a mention\n // already in the document teaches the restore path an id.\n setItems(results)\n setLoading(false)\n })\n .catch(() => {\n if (requestId !== requestIdRef.current) return\n setErrored(true)\n setLoading(false)\n })\n }, FETCH_DEBOUNCE_MS)\n return () => clearTimeout(timer)\n }, [open, query])\n\n // Programmatic `value` changes (queued-turn restore, retry refill)\n // re-parse into the document; `@<id>` runs come back as pills only for\n // ids already in `knownRef` at parse time. That conservatism is the\n // contract, not a gap: the serialized form cannot distinguish a PICKED\n // pill from `@text` the user simply typed (a pick is the only thing that\n // creates a mention in-session), so resolving unknown runs against the\n // provider at hydration would fabricate structured attachments from\n // plain prose. An id never fetched or inserted in this session therefore\n // stays literal text, exactly as it would have if typed. Guarded against\n // the onChange→value feedback loop by comparing the serialized form.\n //\n // `setContent` runs with `emitUpdate: false` (this is not a user edit),\n // so `onMentionsChange` — normally wired only off `onUpdate` — is fired\n // here explicitly, but only when the restored mention set actually\n // differs from the last one reported, so a restore that changes plain\n // text around unchanged mentions doesn't re-fire spuriously.\n useEffect(() => {\n if (!editor) return\n if (serializeMentionDoc(editor.getJSON() as MentionDocNode) === value) return\n const doc = parseMentionValue(value, knownRef.current)\n editor.commands.setContent(doc, { emitUpdate: false })\n const mentions = collectMentions(doc)\n const key = mentionsKey(mentions)\n if (key !== lastMentionsKeyRef.current) {\n lastMentionsKeyRef.current = key\n onMentionsChangeRef.current?.(mentions)\n }\n }, [editor, value])\n\n useEffect(() => {\n editor?.setEditable(!disabled)\n }, [editor, disabled])\n\n // Announce the popover through the editable element: focus stays in the\n // editor while ↑/↓ move the listbox highlight, so without\n // `aria-activedescendant` a screen reader hears nothing change. Set\n // imperatively — `editorProps.attributes` is fixed at editor creation.\n useEffect(() => {\n if (!editor) return\n const dom = editor.view.dom\n dom.setAttribute('aria-expanded', open ? 'true' : 'false')\n if (open) dom.setAttribute('aria-controls', listboxId)\n else dom.removeAttribute('aria-controls')\n const hasRows = open && !loading && !errored && items.length > 0\n if (hasRows) dom.setAttribute('aria-activedescendant', `${listboxId}-opt-${activeIndex}`)\n else dom.removeAttribute('aria-activedescendant')\n }, [editor, open, loading, errored, items.length, activeIndex, listboxId])\n\n useEffect(() => {\n if (!editor || !registerFocus) return\n registerFocus(() => editor.commands.focus())\n // Unregister on unmount so the composer's focus shortcut can never call\n // into a destroyed editor.\n return () => registerFocus(null)\n }, [editor, registerFocus])\n\n return (\n <div className=\"relative\">\n <div ref={anchorRef} className=\"overflow-y-auto\" style={{ maxHeight, minHeight }}>\n <EditorContent editor={editor} />\n {value.length === 0 && (\n <div className=\"pointer-events-none absolute left-1.5 top-1 text-base leading-6 text-muted-foreground\">\n {placeholder}\n </div>\n )}\n </div>\n\n {/* The panel's surface classes (and the consumer's `popoverClassName`)\n live on MentionList itself; the surface only places it. The outer\n scroll keeps rows reachable when the viewport cap undercuts the\n list's own max height. */}\n <PopoverSurface\n open={open}\n triggerRef={anchorRef}\n panelRef={panelRef}\n className=\"overflow-y-auto\"\n >\n <MentionList\n ref={listRef}\n id={listboxId}\n items={items}\n loading={loading}\n error={errored}\n emptyText={mention.emptyText}\n renderItem={mention.renderItem}\n onSelect={(item) => commandRef.current?.(item)}\n onActiveChange={setActiveIndex}\n className={mention.popoverClassName}\n />\n </PopoverSurface>\n </div>\n )\n }\n}\n","import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'\nimport type { ReactNode } from 'react'\n\nimport { joinClasses } from './class-names'\nimport type { MentionItem } from './use-file-mentions'\n\n/** Imperative surface the editor's suggestion keymap drives. */\nexport interface MentionListHandle {\n /** Returns true when the key was consumed — the editor must not also act. */\n onKeyDown: (event: KeyboardEvent) => boolean\n}\n\nexport interface MentionListProps {\n items: MentionItem[]\n loading: boolean\n error: boolean\n /** Shown when the fetch resolved to zero items. Default \"No matches\". */\n emptyText?: string\n renderItem?: (item: MentionItem) => ReactNode\n onSelect: (item: MentionItem) => void\n /** Root `role=\"listbox\"` element id; option rows derive stable ids from it\n * (`<id>-opt-<index>`) so the editor can point `aria-activedescendant` at\n * the highlighted row. */\n id?: string\n /** Fired whenever the highlight moves (keys, hover, or a result-set\n * re-home) — what keeps the editor's `aria-activedescendant` current. */\n onActiveChange?: (index: number) => void\n /** Extra classes merged onto the panel's root element, applied last so\n * they win over the component's own. */\n className?: string\n}\n\n/**\n * The mention suggestion list: a flat, keyboard-driven menu with loading,\n * empty, and error states. Selection is owned here so ↑/↓ and Enter/Tab\n * resolve against the highlighted row; every key it handles is reported\n * consumed so the composer's Enter-to-send never fires while open. The panel\n * carries its own surface classes but no positioning — the editor places it\n * through `PopoverSurface`, per the popover canon.\n */\nexport const MentionList = forwardRef<MentionListHandle, MentionListProps>(\n function MentionList(\n { items, loading, error, emptyText = 'No matches', renderItem, onSelect, id, onActiveChange, className },\n ref,\n ) {\n const [selected, setSelected] = useState(0)\n // Mirrors `selected` so the key handler reads the live index even when keys\n // arrive faster than React commits (e.g. synchronous test-driven calls).\n const selectedRef = useRef(0)\n const onActiveChangeRef = useRef(onActiveChange)\n onActiveChangeRef.current = onActiveChange\n // One entry per rendered row, so the highlight can be scrolled into view\n // from an effect (once per highlight move) instead of a ref callback\n // (which re-fires on every re-render of the row).\n const optionRefs = useRef<Array<HTMLButtonElement | null>>([])\n const move = (next: number) => {\n selectedRef.current = next\n setSelected(next)\n onActiveChangeRef.current?.(next)\n }\n\n // Keyboard navigation must keep the highlight visible inside the panel's\n // own scroll (`max-h-64 overflow-y-auto`).\n useEffect(() => {\n optionRefs.current[selected]?.scrollIntoView({ block: 'nearest' })\n }, [selected, items])\n\n // A new result set re-homes the highlight to the top.\n useEffect(() => {\n move(0)\n }, [items])\n\n useImperativeHandle(\n ref,\n () => ({\n onKeyDown(event) {\n // The handler must agree with the render: while loading or errored\n // the rows are not painted, so `items` still holding the PREVIOUS\n // query's results must not make Enter select an invisible file.\n const count = loading || error ? 0 : items.length\n switch (event.key) {\n case 'ArrowDown':\n if (count > 0) move((selectedRef.current + 1) % count)\n return true\n case 'ArrowUp':\n if (count > 0) move((selectedRef.current - 1 + count) % count)\n return true\n case 'Enter':\n // Consume even with nothing to select so the message never\n // submits while the popover is open. Clamp the index: a\n // shrunken result set re-homes the highlight in an effect, and\n // a key can arrive before that effect commits.\n if (count > 0) onSelect(items[Math.min(selectedRef.current, count - 1)]!)\n return true\n case 'Tab':\n // Tab selects only when a row exists; with nothing selectable\n // (loading, error, empty) it must fall through to normal focus\n // navigation — consuming it would trap keyboard focus.\n if (count === 0) return false\n onSelect(items[Math.min(selectedRef.current, count - 1)]!)\n return true\n default:\n return false\n }\n },\n }),\n [items, loading, error, onSelect],\n )\n\n return (\n <div\n role=\"listbox\"\n id={id}\n aria-label=\"File mentions\"\n className={joinClasses(\n 'max-h-64 min-w-[16rem] max-w-sm overflow-y-auto rounded-xl border border-border',\n 'bg-popover p-1 text-popover-foreground shadow-lg',\n className,\n )}\n >\n {loading && (\n <div className=\"flex items-center gap-2 px-2.5 py-2 text-sm text-muted-foreground\">\n <span className=\"h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary border-t-transparent\" />\n Searching…\n </div>\n )}\n\n {!loading && error && (\n <div className=\"px-2.5 py-2 text-sm text-destructive\">Couldn’t load matches</div>\n )}\n\n {!loading && !error && items.length === 0 && (\n <div className=\"px-2.5 py-2 text-sm text-muted-foreground\">{emptyText}</div>\n )}\n\n {!loading &&\n !error &&\n items.map((item, index) => {\n const active = index === selected\n return (\n <button\n type=\"button\"\n key={item.id}\n id={id ? `${id}-opt-${index}` : undefined}\n role=\"option\"\n aria-selected={active}\n ref={(el) => {\n optionRefs.current[index] = el\n }}\n // Pointer down only PREVENTS the focus steal (a blur would\n // tear down the suggestion mid-select); selection happens on\n // click so mouse, touch, keyboard, and assistive-technology\n // synthesized activations all share the native path — and a\n // secondary-button press never inserts a mention.\n onMouseDown={(event) => {\n event.preventDefault()\n }}\n onClick={() => onSelect(item)}\n // Routes through the same path arrows use so the imperative\n // Enter/Tab handler (which reads `selectedRef`, not `selected`)\n // agrees with the row the pointer is over.\n onMouseEnter={() => move(index)}\n className={joinClasses(\n 'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-sm',\n active ? 'bg-primary/10 text-foreground' : 'text-muted-foreground',\n )}\n >\n {renderItem ? (\n renderItem(item)\n ) : (\n <>\n <span className=\"truncate text-foreground\">{item.label}</span>\n {item.detail && (\n <span className=\"ml-auto truncate text-xs text-muted-foreground\">\n {item.detail}\n </span>\n )}\n </>\n )}\n </button>\n )\n })}\n </div>\n )\n },\n)\n","/**\n * Round-trip between the mention editor's document and the composer's plain\n * `value` string. A mention node serializes to `@<id>`; parsing restores a\n * pill only for ids the session already knows (fetched, inserted, or\n * restored), so unknown `@…` runs stay literal text.\n *\n * The document shape is kept structural (no TipTap import) so this round-trip\n * is unit-testable on its own and never pulls the editor chunk into a bundle.\n */\n\nimport { charAt, charBefore, PATH_CONTINUATION_CHAR, WORD_CHAR } from './mention-boundaries'\nimport type { MentionItem } from './use-file-mentions'\n\n/**\n * The subset of a TipTap/ProseMirror JSON document the mention editor\n * produces: a `doc` of paragraphs, each holding text, hard breaks, and atomic\n * mention nodes.\n */\nexport interface MentionDocNode {\n type: string\n text?: string\n attrs?: { id?: string | null; label?: string | null; kind?: string | null }\n content?: MentionDocNode[]\n}\n\nconst MENTION_TYPE = 'mention'\n\n/**\n * Plain-text serialization of the editor document. Paragraphs join with a\n * newline, hard breaks become a newline, and a mention node becomes `@<id>` —\n * the stable text form the controlled `value` carries.\n */\nexport function serializeMentionDoc(doc: MentionDocNode): string {\n return (doc.content ?? []).map(serializeBlock).join('\\n')\n}\n\nfunction serializeBlock(node: MentionDocNode): string {\n if (node.type === 'paragraph') {\n return (node.content ?? []).map(serializeInline).join('')\n }\n return ''\n}\n\nfunction serializeInline(node: MentionDocNode): string {\n if (node.type === 'text') return node.text ?? ''\n if (node.type === 'hardBreak') return '\\n'\n if (node.type === MENTION_TYPE) return `@${node.attrs?.id ?? ''}`\n return ''\n}\n\n/** Every mention node in the document, in order, as `MentionItem`s. */\nexport function collectMentions(doc: MentionDocNode): MentionItem[] {\n const out: MentionItem[] = []\n const walk = (node: MentionDocNode) => {\n if (node.type === MENTION_TYPE && node.attrs?.id) {\n out.push({\n id: node.attrs.id,\n label: node.attrs.label ?? node.attrs.id,\n kind: node.attrs.kind ?? undefined,\n })\n }\n for (const child of node.content ?? []) walk(child)\n }\n walk(doc)\n return out\n}\n\n/**\n * Parse a controlled `value` string back into an editor document. `@<id>`\n * runs that match a currently-known mention restore as atomic mention nodes;\n * every other `@…` stays literal text. Each line becomes a paragraph — a\n * deliberate asymmetry with `serializeMentionDoc`, which also flattens an\n * in-paragraph `hardBreak` to `\\n`: the string round-trips byte-identically,\n * but a restored Shift+Enter comes back as a paragraph break. The two render\n * identically under the composer's styling (no paragraph margins), so the\n * plain-text `value` contract stays the source of truth.\n */\nexport function parseMentionValue(\n value: string,\n known: Map<string, MentionItem>,\n): MentionDocNode {\n const lines = value.split('\\n')\n return {\n type: 'doc',\n content: lines.map((line) => ({\n type: 'paragraph',\n content: parseLine(line, known),\n })),\n }\n}\n\nfunction parseLine(line: string, known: Map<string, MentionItem>): MentionDocNode[] {\n const content: MentionDocNode[] = []\n let text = ''\n const flush = () => {\n if (text) {\n content.push({ type: 'text', text })\n text = ''\n }\n }\n\n let i = 0\n while (i < line.length) {\n // A word character right before the `@` means it is part of a longer\n // token (an email local part, a handle), never a mention start — the\n // same rule the transcript segmenter applies to persisted messages.\n // Read as a full code point: an astral letter's low surrogate would\n // fail WORD_CHAR and let an embedded `@` start a mention.\n const before = charBefore(line, i)\n const partOfLongerToken = before !== undefined && WORD_CHAR.test(before)\n if (line[i] === '@' && !partOfLongerToken) {\n const id = matchKnownId(line, i + 1, known)\n if (id) {\n const item = known.get(id)!\n flush()\n content.push({\n type: MENTION_TYPE,\n attrs: {\n id: item.id,\n label: item.label,\n kind: item.kind ?? null,\n },\n })\n i += 1 + id.length\n continue\n }\n }\n text += line[i]\n i += 1\n }\n flush()\n return content\n}\n\n/**\n * The longest known id that starts at `pos` and ends on a boundary: end of\n * line, or any full character (never a lone surrogate) that could not\n * continue the same path — the transcript segmenter's rule, so `@a.tsx,`\n * restores its pill while `@a.tsx.bak` never matches the shorter `a.tsx`\n * mid-filename. The scan is linear in `known.size` per `@` run on purpose:\n * `known` holds only the ids PICKED this session (a handful), never the\n * file index, so an index structure would buy nothing.\n */\nfunction matchKnownId(\n line: string,\n pos: number,\n known: Map<string, MentionItem>,\n): string | null {\n let best: string | null = null\n for (const id of known.keys()) {\n if (id.length === 0) continue\n if (best !== null && id.length <= best.length) continue\n if (!line.startsWith(id, pos)) continue\n const next = charAt(line, pos + id.length)\n if (next !== undefined && PATH_CONTINUATION_CHAR.test(next)) continue\n best = id\n }\n return best\n}\n"],"mappings":";;;;;;;;;;;;;AAsBA,SAAS,aAAAA,YAAW,OAAO,UAAAC,SAAQ,YAAAC,iBAAoC;;;ACtBvE,SAAS,YAAY,WAAW,qBAAqB,QAAQ,gBAAgB;AAyHnE,SAiDQ,UAhDN,KADF;AAjFH,IAAM,cAAc;AAAA,EACzB,SAASC,aACP,EAAE,OAAO,SAAS,OAAO,YAAY,cAAc,YAAY,UAAU,IAAI,gBAAgB,UAAU,GACvG,KACA;AACA,UAAM,CAAC,UAAU,WAAW,IAAI,SAAS,CAAC;AAG1C,UAAM,cAAc,OAAO,CAAC;AAC5B,UAAM,oBAAoB,OAAO,cAAc;AAC/C,sBAAkB,UAAU;AAI5B,UAAM,aAAa,OAAwC,CAAC,CAAC;AAC7D,UAAM,OAAO,CAAC,SAAiB;AAC7B,kBAAY,UAAU;AACtB,kBAAY,IAAI;AAChB,wBAAkB,UAAU,IAAI;AAAA,IAClC;AAIA,cAAU,MAAM;AACd,iBAAW,QAAQ,QAAQ,GAAG,eAAe,EAAE,OAAO,UAAU,CAAC;AAAA,IACnE,GAAG,CAAC,UAAU,KAAK,CAAC;AAGpB,cAAU,MAAM;AACd,WAAK,CAAC;AAAA,IACR,GAAG,CAAC,KAAK,CAAC;AAEV;AAAA,MACE;AAAA,MACA,OAAO;AAAA,QACL,UAAU,OAAO;AAIf,gBAAM,QAAQ,WAAW,QAAQ,IAAI,MAAM;AAC3C,kBAAQ,MAAM,KAAK;AAAA,YACjB,KAAK;AACH,kBAAI,QAAQ,EAAG,OAAM,YAAY,UAAU,KAAK,KAAK;AACrD,qBAAO;AAAA,YACT,KAAK;AACH,kBAAI,QAAQ,EAAG,OAAM,YAAY,UAAU,IAAI,SAAS,KAAK;AAC7D,qBAAO;AAAA,YACT,KAAK;AAKH,kBAAI,QAAQ,EAAG,UAAS,MAAM,KAAK,IAAI,YAAY,SAAS,QAAQ,CAAC,CAAC,CAAE;AACxE,qBAAO;AAAA,YACT,KAAK;AAIH,kBAAI,UAAU,EAAG,QAAO;AACxB,uBAAS,MAAM,KAAK,IAAI,YAAY,SAAS,QAAQ,CAAC,CAAC,CAAE;AACzD,qBAAO;AAAA,YACT;AACE,qBAAO;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,MACA,CAAC,OAAO,SAAS,OAAO,QAAQ;AAAA,IAClC;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL;AAAA,QACA,cAAW;AAAA,QACX,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QAEC;AAAA,qBACC,qBAAC,SAAI,WAAU,qEACb;AAAA,gCAAC,UAAK,WAAU,sFAAqF;AAAA,YAAE;AAAA,aAEzG;AAAA,UAGD,CAAC,WAAW,SACX,oBAAC,SAAI,WAAU,wCAAuC,wCAAqB;AAAA,UAG5E,CAAC,WAAW,CAAC,SAAS,MAAM,WAAW,KACtC,oBAAC,SAAI,WAAU,6CAA6C,qBAAU;AAAA,UAGvE,CAAC,WACA,CAAC,SACD,MAAM,IAAI,CAAC,MAAM,UAAU;AACzB,kBAAM,SAAS,UAAU;AACzB,mBACE;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBAEL,IAAI,KAAK,GAAG,EAAE,QAAQ,KAAK,KAAK;AAAA,gBAChC,MAAK;AAAA,gBACL,iBAAe;AAAA,gBACf,KAAK,CAAC,OAAO;AACX,6BAAW,QAAQ,KAAK,IAAI;AAAA,gBAC9B;AAAA,gBAMA,aAAa,CAAC,UAAU;AACtB,wBAAM,eAAe;AAAA,gBACvB;AAAA,gBACA,SAAS,MAAM,SAAS,IAAI;AAAA,gBAI5B,cAAc,MAAM,KAAK,KAAK;AAAA,gBAC9B,WAAW;AAAA,kBACT;AAAA,kBACA,SAAS,kCAAkC;AAAA,gBAC7C;AAAA,gBAEC,uBACC,WAAW,IAAI,IAEf,iCACE;AAAA,sCAAC,UAAK,WAAU,4BAA4B,eAAK,OAAM;AAAA,kBACtD,KAAK,UACJ,oBAAC,UAAK,WAAU,kDACb,eAAK,QACR;AAAA,mBAEJ;AAAA;AAAA,cAnCG,KAAK;AAAA,YAqCZ;AAAA,UAEJ,CAAC;AAAA;AAAA;AAAA,IACL;AAAA,EAEJ;AACF;;;AChKA,IAAM,eAAe;AAOd,SAAS,oBAAoB,KAA6B;AAC/D,UAAQ,IAAI,WAAW,CAAC,GAAG,IAAI,cAAc,EAAE,KAAK,IAAI;AAC1D;AAEA,SAAS,eAAe,MAA8B;AACpD,MAAI,KAAK,SAAS,aAAa;AAC7B,YAAQ,KAAK,WAAW,CAAC,GAAG,IAAI,eAAe,EAAE,KAAK,EAAE;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,MAA8B;AACrD,MAAI,KAAK,SAAS,OAAQ,QAAO,KAAK,QAAQ;AAC9C,MAAI,KAAK,SAAS,YAAa,QAAO;AACtC,MAAI,KAAK,SAAS,aAAc,QAAO,IAAI,KAAK,OAAO,MAAM,EAAE;AAC/D,SAAO;AACT;AAGO,SAAS,gBAAgB,KAAoC;AAClE,QAAM,MAAqB,CAAC;AAC5B,QAAM,OAAO,CAAC,SAAyB;AACrC,QAAI,KAAK,SAAS,gBAAgB,KAAK,OAAO,IAAI;AAChD,UAAI,KAAK;AAAA,QACP,IAAI,KAAK,MAAM;AAAA,QACf,OAAO,KAAK,MAAM,SAAS,KAAK,MAAM;AAAA,QACtC,MAAM,KAAK,MAAM,QAAQ;AAAA,MAC3B,CAAC;AAAA,IACH;AACA,eAAW,SAAS,KAAK,WAAW,CAAC,EAAG,MAAK,KAAK;AAAA,EACpD;AACA,OAAK,GAAG;AACR,SAAO;AACT;AAYO,SAAS,kBACd,OACA,OACgB;AAChB,QAAM,QAAQ,MAAM,MAAM,IAAI;AAC9B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS,MAAM,IAAI,CAAC,UAAU;AAAA,MAC5B,MAAM;AAAA,MACN,SAAS,UAAU,MAAM,KAAK;AAAA,IAChC,EAAE;AAAA,EACJ;AACF;AAEA,SAAS,UAAU,MAAc,OAAmD;AAClF,QAAM,UAA4B,CAAC;AACnC,MAAI,OAAO;AACX,QAAM,QAAQ,MAAM;AAClB,QAAI,MAAM;AACR,cAAQ,KAAK,EAAE,MAAM,QAAQ,KAAK,CAAC;AACnC,aAAO;AAAA,IACT;AAAA,EACF;AAEA,MAAI,IAAI;AACR,SAAO,IAAI,KAAK,QAAQ;AAMtB,UAAM,SAAS,WAAW,MAAM,CAAC;AACjC,UAAM,oBAAoB,WAAW,UAAa,UAAU,KAAK,MAAM;AACvE,QAAI,KAAK,CAAC,MAAM,OAAO,CAAC,mBAAmB;AACzC,YAAM,KAAK,aAAa,MAAM,IAAI,GAAG,KAAK;AAC1C,UAAI,IAAI;AACN,cAAM,OAAO,MAAM,IAAI,EAAE;AACzB,cAAM;AACN,gBAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN,OAAO;AAAA,YACL,IAAI,KAAK;AAAA,YACT,OAAO,KAAK;AAAA,YACZ,MAAM,KAAK,QAAQ;AAAA,UACrB;AAAA,QACF,CAAC;AACD,aAAK,IAAI,GAAG;AACZ;AAAA,MACF;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AACd,SAAK;AAAA,EACP;AACA,QAAM;AACN,SAAO;AACT;AAWA,SAAS,aACP,MACA,KACA,OACe;AACf,MAAI,OAAsB;AAC1B,aAAW,MAAM,MAAM,KAAK,GAAG;AAC7B,QAAI,GAAG,WAAW,EAAG;AACrB,QAAI,SAAS,QAAQ,GAAG,UAAU,KAAK,OAAQ;AAC/C,QAAI,CAAC,KAAK,WAAW,IAAI,GAAG,EAAG;AAC/B,UAAM,OAAO,OAAO,MAAM,MAAM,GAAG,MAAM;AACzC,QAAI,SAAS,UAAa,uBAAuB,KAAK,IAAI,EAAG;AAC7D,WAAO;AAAA,EACT;AACA,SAAO;AACT;;;AF2UQ,SACE,OAAAC,MADF,QAAAC,aAAA;AA7bR,IAAM,wBACJ;AAOF,IAAM,qBAAqB;AAG3B,eAAsB,oBAA4C;AAChE,MAAI;AACJ,MAAI;AACF,UAAM,CAAC,MAAM,kBAAkB,OAAO,YAAY,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,MAChF,OAAO,cAAc;AAAA,MACrB,OAAO,2BAA2B;AAAA,MAClC,OAAO,eAAe;AAAA,MACtB,OAAO,qBAAqB;AAAA,MAC5B,OAAO,oBAAoB;AAAA,IAC7B,CAAC;AACD,cAAU,EAAE,MAAM,kBAAkB,OAAO,YAAY,WAAW;AAAA,EACpE,SAAS,OAAO;AACd,QAAI,iBAAiB,SAAS,mBAAmB,KAAK,MAAM,OAAO,GAAG;AACpE,YAAM,IAAI,MAAM,uBAAuB,EAAE,OAAO,MAAM,CAAC;AAAA,IACzD;AACA,UAAM;AAAA,EACR;AAKA,MACE,OAAO,QAAQ,KAAK,oBAAoB,cACxC,OAAO,QAAQ,MAAM,cAAc,cACnC,QAAQ,MAAM,kBAAkB,UAChC,QAAQ,iBAAiB,YAAY,UACrC,QAAQ,WAAW,YAAY,UAC/B,OAAO,QAAQ,WAAW,mBAAmB,YAC7C;AACA,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,SAAO;AACT;AA4BO,SAAS,wBAAwB,QAAuB;AAC7D,SAAO,OAAO,WAAW,QAAQ,UAAU;AAAA,IACzC,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,aAAa;AAAA,IACb,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,WAAW;AAAA,EACb,CAAC;AACH;AAUO,SAAS,sBACd,QACA,SACA,YACA;AACA,QAAM,EAAE,gBAAgB,IAAI,OAAO;AACnC,SAAO,OAAO,iBAAiB,QAC5B,OAAO;AAAA,IACN,gBAAgB;AACd,aAAO;AAAA,QACL,IAAI;AAAA,UACF,SAAS;AAAA,UACT,WAAW,CAAC,YAAY,QAAQ,aAAa,SAAS;AAAA,UACtD,YAAY,CAAC,UAAW,MAAM,KAAK,EAAE,WAAW,MAAM,GAAG,IAAI,CAAC;AAAA,QAChE;AAAA,QACA,OAAO;AAAA,UACL,SAAS;AAAA,UACT,WAAW,CAAC,YAAY,QAAQ,aAAa,YAAY;AAAA,UACzD,YAAY,CAAC,UAAW,MAAM,QAAQ,EAAE,cAAc,MAAM,MAAM,IAAI,CAAC;AAAA,QACzE;AAAA,QACA,MAAM;AAAA,UACJ,SAAS;AAAA,UACT,WAAW,CAAC,YAAY,QAAQ,aAAa,WAAW;AAAA,UACxD,YAAY,CAAC,UAAW,MAAM,OAAO,EAAE,aAAa,MAAM,KAAK,IAAI,CAAC;AAAA,QACtE;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC,EACA,UAAU;AAAA,IACT,gBAAgB,EAAE,OAAO,mBAAmB;AAAA,IAC5C,YAAY,CAAC,EAAE,KAAK,MAAM,GAAG,OAAO,GAAG,KAAK,MAAM,EAAE;AAAA,IACpD,YAAY,CAAC,EAAE,SAAS,KAAK,MAAM;AAAA,MACjC;AAAA,MACA,gBAAgB,QAAQ,gBAAgB;AAAA,QACtC,OAAO,KAAK,MAAM,MAAM;AAAA,MAC1B,CAAC;AAAA,MACD,GAAG,OAAO,GAAG,KAAK,MAAM,SAAS,KAAK,MAAM,EAAE;AAAA,IAChD;AAAA,IACA;AAAA,EACF,CAAC;AACL;AAOA,IAAM,eACJ;AAMF,IAAM,oBAAoB;AAM1B,SAAS,YAAY,UAAiC;AACpD,SAAO,KAAK,UAAU,SAAS,IAAI,CAAC,SAAS,KAAK,EAAE,CAAC;AACvD;AAGA,eAAsB,oBAEnB;AACD,SAAO,EAAE,SAAS,oBAAoB,MAAM,kBAAkB,CAAC,EAAE;AACnE;AAcO,SAAS,oBAAoB,QAA0D;AAC5F,QAAM,EAAE,eAAe,UAAU,IAAI,OAAO;AAC5C,QAAM,EAAE,eAAe,IAAI,OAAO;AAElC,SAAO,SAAS,cAAc;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GAAuB;AACrB,UAAM,UAAU,QAAQ,WAAW;AAEnC,UAAM,CAAC,MAAM,OAAO,IAAIC,UAAS,KAAK;AACtC,UAAM,CAAC,OAAO,QAAQ,IAAIA,UAAS,EAAE;AACrC,UAAM,CAAC,OAAO,QAAQ,IAAIA,UAAwB,CAAC,CAAC;AACpD,UAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAC5C,UAAM,CAAC,SAAS,UAAU,IAAIA,UAAS,KAAK;AAI5C,UAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,CAAC;AAChD,UAAM,YAAY,MAAM;AAGxB,UAAM,YAAYC,QAAuB,IAAI;AAC7C,UAAM,WAAWA,QAAuB,IAAI;AAK5C,UAAM,UAAUA,QAAO,KAAK;AAC5B,UAAM,aAAaA,QAA6C,IAAI;AACpE,UAAM,UAAUA,QAAiC,IAAI;AAMrD,UAAM,WAAWA,QAAiC,oBAAI,IAAI,CAAC;AAC3D,UAAM,eAAeA,QAAO,CAAC;AAM7B,UAAM,qBAAqBA,QAAO,YAAY,CAAC,CAAC,CAAC;AACjD,UAAM,cAAcA,QAAO,QAAQ;AACnC,gBAAY,UAAU;AACtB,UAAM,cAAcA,QAAO,QAAQ;AACnC,gBAAY,UAAU;AACtB,UAAM,sBAAsBA,QAAO,QAAQ,gBAAgB;AAC3D,wBAAoB,UAAU,QAAQ;AACtC,UAAM,kBAAkBA,QAAO,YAAY;AAC3C,oBAAgB,UAAU;AAC1B,UAAM,gBAAgBA,QAAO,QAAQ,UAAU;AAC/C,kBAAc,UAAU,QAAQ;AAEhC,UAAM,SAAS;AAAA,MACb;AAAA,QACE,mBAAmB;AAAA,QACnB,UAAU,CAAC;AAAA,QACX,WAAW,YAAY,QAAQ;AAAA,QAC/B,SAAS,kBAAkB,OAAO,SAAS,OAAO;AAAA,QAClD,aAAa;AAAA,UACX,YAAY;AAAA,YACV,OAAO;AAAA,YACP,MAAM;AAAA,YACN,kBAAkB;AAAA,YAClB,cAAc;AAAA,YACd,iBAAiB;AAAA,UACnB;AAAA,UACA,eAAe,CAAC,OAAO,UAAU;AAC/B,gBAAI,MAAM,QAAQ,WAAW,MAAM,SAAU,QAAO;AAEpD,gBAAI,MAAM,eAAe,MAAM,YAAY,IAAK,QAAO;AAEvD,gBAAI,QAAQ,QAAS,QAAO;AAC5B,kBAAM,eAAe;AACrB,wBAAY,QAAQ;AACpB,mBAAO;AAAA,UACT;AAAA,UACA,aAAa,CAAC,OAAO,UAAU;AAC7B,kBAAM,QAAQ,MAAM,eAAe;AACnC,gBAAI,SAAS,MAAM,SAAS,KAAK,gBAAgB,SAAS;AACxD,qBAAO,gBAAgB,QAAQ,KAAK;AAAA,YACtC;AACA,mBAAO;AAAA,UACT;AAAA,QACF;AAAA,QACA,UAAU,CAAC,EAAE,QAAAC,QAAO,MAAM;AACxB,gBAAM,OAAOA,QAAO,QAAQ;AAC5B,sBAAY,QAAQ,oBAAoB,IAAI,CAAC;AAC7C,gBAAM,WAAW,gBAAgB,IAAI;AACrC,qBAAW,QAAQ,SAAU,UAAS,QAAQ,IAAI,KAAK,IAAI,IAAI;AAG/D,gBAAM,MAAM,YAAY,QAAQ;AAChC,cAAI,QAAQ,mBAAmB,SAAS;AACtC,+BAAmB,UAAU;AAC7B,gCAAoB,UAAU,QAAQ;AAAA,UACxC;AAAA,QACF;AAAA,QACA,YAAY;AAAA,UACV,wBAAwB,MAAM;AAAA,UAC9B,sBAAsB,QAAQ,SAAS;AAAA,YACrC,MAAM;AAAA,YACN,aAAa;AAAA;AAAA;AAAA,YAGb,OAAO,MAAM,CAAC;AAAA,YACd,SAAS,CAAC,EAAE,QAAAA,SAAQ,OAAO,MAAM,MAAM;AACrC,oBAAM,OAAO;AACb,cAAAA,QACG,MAAM,EACN,MAAM,EACN,gBAAgB,OAAO;AAAA,gBACtB;AAAA,kBACE,MAAM;AAAA,kBACN,OAAO;AAAA,oBACL,IAAI,KAAK;AAAA,oBACT,OAAO,KAAK;AAAA,oBACZ,MAAM,KAAK,QAAQ;AAAA,kBACrB;AAAA,gBACF;AAAA,gBACA,EAAE,MAAM,QAAQ,MAAM,IAAI;AAAA,cAC5B,CAAC,EACA,IAAI;AACP,uBAAS,QAAQ,IAAI,KAAK,IAAI,IAAI;AAAA,YACpC;AAAA,YACA,QAAQ,OAAO;AAAA,cACb,SAAS,CAAC,UAA2B;AACnC,wBAAQ,UAAU;AAClB,2BAAW,UAAU,MAAM;AAC3B,wBAAQ,IAAI;AACZ,yBAAS,MAAM,KAAK;AAAA,cACtB;AAAA,cACA,UAAU,CAAC,UAA2B;AACpC,2BAAW,UAAU,MAAM;AAC3B,yBAAS,MAAM,KAAK;AAAA,cACtB;AAAA,cACA,WAAW,CAAC,UAAkC;AAC5C,oBAAI,MAAM,MAAM,QAAQ,UAAU;AAChC,iCAAe,MAAM,IAAI;AACzB,yBAAO;AAAA,gBACT;AACA,uBAAO,QAAQ,SAAS,UAAU,MAAM,KAAK,KAAK;AAAA,cACpD;AAAA,cACA,QAAQ,MAAM;AACZ,wBAAQ,UAAU;AAClB,2BAAW,UAAU;AACrB,wBAAQ,KAAK;AACb,yBAAS,CAAC,CAAC;AACX,2BAAW,KAAK;AAChB,2BAAW,KAAK;AAAA,cAClB;AAAA,YACF;AAAA,UACF,CAAC;AAAA,QACH;AAAA,MACF;AAAA,MACA,CAAC;AAAA,IACH;AAOA,IAAAC,WAAU,MAAM;AACd,UAAI,CAAC,KAAM;AACX,YAAM,YAAa,aAAa,WAAW;AAC3C,iBAAW,IAAI;AACf,iBAAW,KAAK;AAChB,YAAM,QAAQ,WAAW,MAAM;AAI7B,gBAAQ,QAAQ,EACb,KAAK,MAAM,cAAc,QAAQ,KAAK,CAAC,EACvC,KAAK,CAAC,YAAY;AACjB,cAAI,cAAc,aAAa,QAAS;AAMxC,mBAAS,OAAO;AAChB,qBAAW,KAAK;AAAA,QAClB,CAAC,EACA,MAAM,MAAM;AACX,cAAI,cAAc,aAAa,QAAS;AACxC,qBAAW,IAAI;AACf,qBAAW,KAAK;AAAA,QAClB,CAAC;AAAA,MACL,GAAG,iBAAiB;AACpB,aAAO,MAAM,aAAa,KAAK;AAAA,IACjC,GAAG,CAAC,MAAM,KAAK,CAAC;AAkBhB,IAAAA,WAAU,MAAM;AACd,UAAI,CAAC,OAAQ;AACb,UAAI,oBAAoB,OAAO,QAAQ,CAAmB,MAAM,MAAO;AACvE,YAAM,MAAM,kBAAkB,OAAO,SAAS,OAAO;AACrD,aAAO,SAAS,WAAW,KAAK,EAAE,YAAY,MAAM,CAAC;AACrD,YAAM,WAAW,gBAAgB,GAAG;AACpC,YAAM,MAAM,YAAY,QAAQ;AAChC,UAAI,QAAQ,mBAAmB,SAAS;AACtC,2BAAmB,UAAU;AAC7B,4BAAoB,UAAU,QAAQ;AAAA,MACxC;AAAA,IACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,IAAAA,WAAU,MAAM;AACd,cAAQ,YAAY,CAAC,QAAQ;AAAA,IAC/B,GAAG,CAAC,QAAQ,QAAQ,CAAC;AAMrB,IAAAA,WAAU,MAAM;AACd,UAAI,CAAC,OAAQ;AACb,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,aAAa,iBAAiB,OAAO,SAAS,OAAO;AACzD,UAAI,KAAM,KAAI,aAAa,iBAAiB,SAAS;AAAA,UAChD,KAAI,gBAAgB,eAAe;AACxC,YAAM,UAAU,QAAQ,CAAC,WAAW,CAAC,WAAW,MAAM,SAAS;AAC/D,UAAI,QAAS,KAAI,aAAa,yBAAyB,GAAG,SAAS,QAAQ,WAAW,EAAE;AAAA,UACnF,KAAI,gBAAgB,uBAAuB;AAAA,IAClD,GAAG,CAAC,QAAQ,MAAM,SAAS,SAAS,MAAM,QAAQ,aAAa,SAAS,CAAC;AAEzE,IAAAA,WAAU,MAAM;AACd,UAAI,CAAC,UAAU,CAAC,cAAe;AAC/B,oBAAc,MAAM,OAAO,SAAS,MAAM,CAAC;AAG3C,aAAO,MAAM,cAAc,IAAI;AAAA,IACjC,GAAG,CAAC,QAAQ,aAAa,CAAC;AAE1B,WACE,gBAAAJ,MAAC,SAAI,WAAU,YACb;AAAA,sBAAAA,MAAC,SAAI,KAAK,WAAW,WAAU,mBAAkB,OAAO,EAAE,WAAW,UAAU,GAC7E;AAAA,wBAAAD,KAAC,iBAAc,QAAgB;AAAA,QAC9B,MAAM,WAAW,KAChB,gBAAAA,KAAC,SAAI,WAAU,yFACZ,uBACH;AAAA,SAEJ;AAAA,MAMA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,YAAY;AAAA,UACZ;AAAA,UACA,WAAU;AAAA,UAEV,0BAAAA;AAAA,YAAC;AAAA;AAAA,cACC,KAAK;AAAA,cACL,IAAI;AAAA,cACJ;AAAA,cACA;AAAA,cACA,OAAO;AAAA,cACP,WAAW,QAAQ;AAAA,cACnB,YAAY,QAAQ;AAAA,cACpB,UAAU,CAAC,SAAS,WAAW,UAAU,IAAI;AAAA,cAC7C,gBAAgB;AAAA,cAChB,WAAW,QAAQ;AAAA;AAAA,UACrB;AAAA;AAAA,MACF;AAAA,OACF;AAAA,EAEJ;AACF;","names":["useEffect","useRef","useState","MentionList","jsx","jsxs","useState","useRef","editor","useEffect"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { Generation } from '../../studio';
|
|
3
|
+
import type { StudioMediaActions } from '../../studio/ports';
|
|
4
|
+
export declare function StudioProviders({ children }: {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export declare const storyMediaActions: StudioMediaActions;
|
|
8
|
+
/** Composer screens need their model catalog without pretending generation works. */
|
|
9
|
+
export declare function installStudioComposerFetchStub(): void;
|
|
10
|
+
export declare const noOpGenerated: (generation: Generation) => void;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { Generation } from '../../studio';
|
|
1
|
+
import type { FetchGenerationsPage, Generation, GenerationPage } from '../../studio';
|
|
2
|
+
/** Tiny browser-native silent WAV used by stories that exercise real playback. */
|
|
3
|
+
export declare function silentWavDataUri(seconds: number): string;
|
|
2
4
|
export declare const teaserA: Generation;
|
|
3
5
|
export declare const storyboardGeneration: Generation;
|
|
4
6
|
export declare const videoGeneration: Generation;
|
|
5
7
|
export declare const avatarGeneration: Generation;
|
|
6
8
|
export declare const speechGeneration: Generation;
|
|
9
|
+
export declare const speechGenerationPlayable: Generation;
|
|
7
10
|
export declare const transcriptionGeneration: Generation;
|
|
8
11
|
export declare const queuedGeneration: Generation;
|
|
9
12
|
export declare const runningGeneration: Generation;
|
|
@@ -14,5 +17,10 @@ export declare const teaserBatch: Generation[];
|
|
|
14
17
|
/** Populated library: the newest batch first, every type and status represented. */
|
|
15
18
|
export declare const libraryGenerations: Generation[];
|
|
16
19
|
export declare const libraryTotalCost = 1.87;
|
|
17
|
-
/**
|
|
20
|
+
/** Large enough to exercise search, filtering, and a second cursor page. */
|
|
21
|
+
export declare const historyGenerations: Generation[];
|
|
22
|
+
export declare function makeGenerationPage(items: Generation[], nextCursor?: string): GenerationPage;
|
|
23
|
+
/** In-memory version of the history endpoint, including abortable latency. */
|
|
24
|
+
export declare function fakeFetchGenerationsPage(all: Generation[]): FetchGenerationsPage;
|
|
25
|
+
/** Demo story route for opening a persisted generation in the vault. */
|
|
18
26
|
export declare const demoVaultHref: (filePath?: string | null) => string;
|