@tangle-network/sandbox-ui 0.85.0 → 0.86.0
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/dist/chat.d.ts +33 -2
- package/dist/chat.js +1 -1
- package/dist/{chunk-666CEEHW.js → chunk-PKZVCCZ6.js} +61 -15
- package/dist/{chunk-MEDE37J5.js → chunk-QORIZARI.js} +25 -1
- package/dist/globals.css +3 -0
- package/dist/hooks.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/mention-editor-7FLUUWY5.js +480 -0
- package/dist/sdk-hooks.js +1 -1
- package/dist/styles.css +3 -0
- package/package.json +5 -2
package/dist/chat.d.ts
CHANGED
|
@@ -307,6 +307,31 @@ declare function validateComposerFiles(files: File[] | FileList, config: Compose
|
|
|
307
307
|
rejected: ComposerFileRejection[];
|
|
308
308
|
};
|
|
309
309
|
|
|
310
|
+
/** A selectable item in the `@`-mention popover. Generic — file semantics live
|
|
311
|
+
* in the consuming app, not here. */
|
|
312
|
+
interface MentionItem {
|
|
313
|
+
id: string;
|
|
314
|
+
label: string;
|
|
315
|
+
detail?: string;
|
|
316
|
+
kind?: string;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Opt-in `@`-mention support. Present ⇒ the composer swaps its textarea for a
|
|
320
|
+
* TipTap rich input that renders mentions as atomic pills; absent ⇒ exactly
|
|
321
|
+
* today's plain-textarea behavior. Data-agnostic: the app supplies items.
|
|
322
|
+
*/
|
|
323
|
+
interface AgentComposerMention {
|
|
324
|
+
/** The character that opens the popover. Default "@". */
|
|
325
|
+
trigger?: string;
|
|
326
|
+
/** Async provider called with the query typed after the trigger. */
|
|
327
|
+
fetchItems: (query: string) => Promise<MentionItem[]>;
|
|
328
|
+
/** Fired with the mentions currently in the document whenever they change. */
|
|
329
|
+
onMentionsChange?: (mentions: MentionItem[]) => void;
|
|
330
|
+
/** Custom row renderer for a popover item. */
|
|
331
|
+
renderItem?: (item: MentionItem) => React.ReactNode;
|
|
332
|
+
/** Shown when a fetch resolves to zero items. Default "No matches". */
|
|
333
|
+
emptyText?: string;
|
|
334
|
+
}
|
|
310
335
|
/** A staged attachment shown as a chip above the input. */
|
|
311
336
|
interface ComposerFile {
|
|
312
337
|
id: string;
|
|
@@ -406,6 +431,12 @@ interface AgentComposerProps {
|
|
|
406
431
|
* message body becomes. Default false.
|
|
407
432
|
*/
|
|
408
433
|
canSubmitAttachmentsOnly?: boolean;
|
|
434
|
+
/**
|
|
435
|
+
* Opt-in `@`-mentions. When set, the textarea is replaced by a rich input
|
|
436
|
+
* that renders mentions as atomic pills and serializes them to `@<id>` in
|
|
437
|
+
* `value`. Omit for exactly today's plain-textarea behavior.
|
|
438
|
+
*/
|
|
439
|
+
mention?: AgentComposerMention;
|
|
409
440
|
/** Cmd/Ctrl+L focuses the input and shows a hint. Default false. */
|
|
410
441
|
focusShortcut?: boolean;
|
|
411
442
|
/** Minimum textarea rows before it grows. Default 2. */
|
|
@@ -432,7 +463,7 @@ interface AgentComposerProps {
|
|
|
432
463
|
* button (`busy` + `onCancel`), a `controls` override slot for bespoke
|
|
433
464
|
* picker rows, and a Cmd/Ctrl+L focus shortcut.
|
|
434
465
|
*/
|
|
435
|
-
declare function AgentComposer({ value, onChange, onSubmit, placeholder, disabled, busy, onCancel, harness, profile, model, reasoning, context, controls, trailing, onAttach, onAttachFolder, attachments, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, canSubmitWhileBusy, canSubmitAttachmentsOnly, focusShortcut, minRows, maxHeight, className, sendLabel, autoFocus, }: AgentComposerProps): react_jsx_runtime.JSX.Element;
|
|
466
|
+
declare function AgentComposer({ value, onChange, onSubmit, placeholder, disabled, busy, onCancel, harness, profile, model, reasoning, context, controls, trailing, onAttach, onAttachFolder, attachments, onRemoveFile, onRetryFile, accept, onRejectFiles, dropTitle, dropDescription, canSubmitWhileBusy, canSubmitAttachmentsOnly, mention, focusShortcut, minRows, maxHeight, className, sendLabel, autoFocus, }: AgentComposerProps): react_jsx_runtime.JSX.Element;
|
|
436
467
|
|
|
437
468
|
/**
|
|
438
469
|
* Whether `harness` can run `modelId`. Router-backed harnesses, and provider-less
|
|
@@ -546,4 +577,4 @@ declare function createFetchTransport(opts: {
|
|
|
546
577
|
fetchImpl?: typeof fetch;
|
|
547
578
|
}): ArtifactAgentDockTransport;
|
|
548
579
|
|
|
549
|
-
export { AgentComposer, type AgentComposerProps, type AgentProfileCapability, type AgentProfileDraft, type AgentProfileOption, AgentProfilePicker, type AgentProfilePickerProps, AgentSessionControls, type AgentSessionControlsProps, type AgentSessionHarnessControl, type AgentSessionModelControl, type AgentSessionProfileControl, type AgentSessionReasoningControl, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, type ComposerFile, type ComposerFileRejection, type ComposerFileValidationConfig, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport, isAcceptedType, isModelCompatibleWithHarness, snapModelToHarness, validateComposerFiles };
|
|
580
|
+
export { AgentComposer, type AgentComposerMention, type AgentComposerProps, type AgentProfileCapability, type AgentProfileDraft, type AgentProfileOption, AgentProfilePicker, type AgentProfilePickerProps, AgentSessionControls, type AgentSessionControlsProps, type AgentSessionHarnessControl, type AgentSessionModelControl, type AgentSessionProfileControl, type AgentSessionReasoningControl, ArtifactAgentDock, type ArtifactAgentDockProps, type ArtifactAgentDockTransport, type ArtifactDockMessage, type ArtifactDockStreamEvent, type ArtifactKind, type ArtifactScope, type ComposerFile, type ComposerFileRejection, type ComposerFileValidationConfig, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, type MentionItem, type ReasoningLevel, type ReasoningLevelOption, ReasoningLevelPicker, type ReasoningLevelPickerProps, createFetchTransport, isAcceptedType, isModelCompatibleWithHarness, snapModelToHarness, validateComposerFiles };
|
package/dist/chat.js
CHANGED
|
@@ -1147,6 +1147,7 @@ function imageExtension(file) {
|
|
|
1147
1147
|
const fromName = /\.([a-z0-9]+)$/i.exec(file.name)?.[1];
|
|
1148
1148
|
return fromName?.toLowerCase() ?? "png";
|
|
1149
1149
|
}
|
|
1150
|
+
var MentionEditor = React4.lazy(() => import("./mention-editor-7FLUUWY5.js"));
|
|
1150
1151
|
function AgentComposer({
|
|
1151
1152
|
value,
|
|
1152
1153
|
onChange,
|
|
@@ -1173,6 +1174,7 @@ function AgentComposer({
|
|
|
1173
1174
|
dropDescription = "They attach to your next message.",
|
|
1174
1175
|
canSubmitWhileBusy = false,
|
|
1175
1176
|
canSubmitAttachmentsOnly = false,
|
|
1177
|
+
mention,
|
|
1176
1178
|
focusShortcut = false,
|
|
1177
1179
|
minRows = 2,
|
|
1178
1180
|
maxHeight = 200,
|
|
@@ -1181,6 +1183,7 @@ function AgentComposer({
|
|
|
1181
1183
|
autoFocus
|
|
1182
1184
|
}) {
|
|
1183
1185
|
const textareaRef = React4.useRef(null);
|
|
1186
|
+
const richFocusRef = React4.useRef(null);
|
|
1184
1187
|
const fileInputRef = React4.useRef(null);
|
|
1185
1188
|
const folderInputRef = React4.useRef(null);
|
|
1186
1189
|
const [dragOver, setDragOver] = React4.useState(false);
|
|
@@ -1201,17 +1204,21 @@ function AgentComposer({
|
|
|
1201
1204
|
function onKeyDown(event) {
|
|
1202
1205
|
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "l") {
|
|
1203
1206
|
event.preventDefault();
|
|
1204
|
-
|
|
1207
|
+
if (mention) richFocusRef.current?.();
|
|
1208
|
+
else textareaRef.current?.focus();
|
|
1205
1209
|
}
|
|
1206
1210
|
}
|
|
1207
1211
|
document.addEventListener("keydown", onKeyDown);
|
|
1208
1212
|
return () => document.removeEventListener("keydown", onKeyDown);
|
|
1209
|
-
}, [focusShortcut, disabled]);
|
|
1213
|
+
}, [focusShortcut, disabled, mention]);
|
|
1210
1214
|
const canSend = !disabled && (!busy || canSubmitWhileBusy) && (value.trim().length > 0 || canSubmitAttachmentsOnly && attachments.length > 0);
|
|
1211
1215
|
const submit = () => {
|
|
1212
1216
|
if (!canSend) return;
|
|
1213
1217
|
onSubmit();
|
|
1214
1218
|
};
|
|
1219
|
+
const registerRichFocus = React4.useCallback((focus) => {
|
|
1220
|
+
richFocusRef.current = focus;
|
|
1221
|
+
}, []);
|
|
1215
1222
|
const dropEnabled = Boolean(onAttach);
|
|
1216
1223
|
const handleDragEnter = (event) => {
|
|
1217
1224
|
if (!dropEnabled) return;
|
|
@@ -1254,23 +1261,28 @@ function AgentComposer({
|
|
|
1254
1261
|
const files = event.dataTransfer?.files;
|
|
1255
1262
|
if (files?.length) deliverFiles(Array.from(files));
|
|
1256
1263
|
};
|
|
1264
|
+
const renamePastedFiles = (files) => files.map((file) => {
|
|
1265
|
+
if (file.type.startsWith("image/") && isGenericImageName(file.name)) {
|
|
1266
|
+
pastedImageCount.current += 1;
|
|
1267
|
+
return new File(
|
|
1268
|
+
[file],
|
|
1269
|
+
`pasted-image-${pastedImageCount.current}.${imageExtension(file)}`,
|
|
1270
|
+
{ type: file.type }
|
|
1271
|
+
);
|
|
1272
|
+
}
|
|
1273
|
+
return file;
|
|
1274
|
+
});
|
|
1257
1275
|
const handlePaste = (event) => {
|
|
1258
1276
|
if (!onAttach) return;
|
|
1259
1277
|
const clipboardFiles = event.clipboardData?.files;
|
|
1260
1278
|
if (!clipboardFiles || clipboardFiles.length === 0) return;
|
|
1261
1279
|
event.preventDefault();
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
{ type: file.type }
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
return file;
|
|
1272
|
-
});
|
|
1273
|
-
deliverFiles(files);
|
|
1280
|
+
deliverFiles(renamePastedFiles(Array.from(clipboardFiles)));
|
|
1281
|
+
};
|
|
1282
|
+
const handleEditorPasteFiles = (clipboardFiles) => {
|
|
1283
|
+
if (!onAttach || clipboardFiles.length === 0) return false;
|
|
1284
|
+
deliverFiles(renamePastedFiles(Array.from(clipboardFiles)));
|
|
1285
|
+
return true;
|
|
1274
1286
|
};
|
|
1275
1287
|
const folderChips = attachments.filter((file) => file.kind === "folder");
|
|
1276
1288
|
const fileChips = attachments.filter((file) => file.kind !== "folder");
|
|
@@ -1353,7 +1365,41 @@ function AgentComposer({
|
|
|
1353
1365
|
file.id
|
|
1354
1366
|
);
|
|
1355
1367
|
}) }),
|
|
1356
|
-
/* @__PURE__ */ jsx4(
|
|
1368
|
+
mention ? /* @__PURE__ */ jsx4(
|
|
1369
|
+
React4.Suspense,
|
|
1370
|
+
{
|
|
1371
|
+
fallback: /* @__PURE__ */ jsx4(
|
|
1372
|
+
"textarea",
|
|
1373
|
+
{
|
|
1374
|
+
rows: minRows,
|
|
1375
|
+
value,
|
|
1376
|
+
disabled: true,
|
|
1377
|
+
placeholder,
|
|
1378
|
+
"aria-label": "Message input",
|
|
1379
|
+
className: cn(
|
|
1380
|
+
"w-full resize-none bg-transparent px-2 py-1 text-sm leading-relaxed text-foreground outline-none",
|
|
1381
|
+
"placeholder:text-muted-foreground"
|
|
1382
|
+
)
|
|
1383
|
+
}
|
|
1384
|
+
),
|
|
1385
|
+
children: /* @__PURE__ */ jsx4(
|
|
1386
|
+
MentionEditor,
|
|
1387
|
+
{
|
|
1388
|
+
value,
|
|
1389
|
+
onChange,
|
|
1390
|
+
onSubmit: submit,
|
|
1391
|
+
placeholder,
|
|
1392
|
+
disabled,
|
|
1393
|
+
autoFocus,
|
|
1394
|
+
minRows,
|
|
1395
|
+
maxHeight,
|
|
1396
|
+
mention,
|
|
1397
|
+
registerFocus: registerRichFocus,
|
|
1398
|
+
onPasteFiles: onAttach ? handleEditorPasteFiles : void 0
|
|
1399
|
+
}
|
|
1400
|
+
)
|
|
1401
|
+
}
|
|
1402
|
+
) : /* @__PURE__ */ jsx4(
|
|
1357
1403
|
"textarea",
|
|
1358
1404
|
{
|
|
1359
1405
|
ref: textareaRef,
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
// src/hooks/use-session-stream.ts
|
|
2
2
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/hooks/wire-encoding.ts
|
|
5
|
+
function utf8ToLatin1(text) {
|
|
6
|
+
return encodeURIComponent(text).replace(
|
|
7
|
+
/%([0-9A-F]{2})/g,
|
|
8
|
+
(_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
function encodeTextForWire(text) {
|
|
12
|
+
if (typeof btoa === "function") {
|
|
13
|
+
return btoa(utf8ToLatin1(text));
|
|
14
|
+
}
|
|
15
|
+
const bufferCtor = globalThis.Buffer;
|
|
16
|
+
if (bufferCtor) {
|
|
17
|
+
return bufferCtor.from(text, "utf8").toString("base64");
|
|
18
|
+
}
|
|
19
|
+
throw new Error(
|
|
20
|
+
"encodeTextForWire: no base64 encoder available (btoa and Buffer both undefined)"
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/hooks/use-session-stream.ts
|
|
3
25
|
function mapApiMessage(msg, counterRef) {
|
|
4
26
|
const created = msg.info.timestamp ? new Date(msg.info.timestamp).getTime() : Date.now();
|
|
5
27
|
const message = {
|
|
@@ -235,7 +257,9 @@ function useSessionStream({
|
|
|
235
257
|
await fetchJson(url, token, {
|
|
236
258
|
method: "POST",
|
|
237
259
|
body: JSON.stringify({
|
|
238
|
-
|
|
260
|
+
// The sidecar base64-decodes every text part at the route boundary;
|
|
261
|
+
// a raw-UTF-8 part is rejected 400 (or mis-decoded). See wire-encoding.ts.
|
|
262
|
+
parts: [{ type: "text", text: encodeTextForWire(text) }],
|
|
239
263
|
...options?.agent ? { agent: options.agent } : {},
|
|
240
264
|
...options?.model ? { model: options.model } : {},
|
|
241
265
|
...options?.system ? { system: options.system } : {},
|
package/dist/globals.css
CHANGED
package/dist/hooks.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { AgentWorkbench, ApprovalConfidenceStat, ApprovalItem, ApprovalQueue, Ap
|
|
|
5
5
|
export { ArtifactView, BRAND_PRISM_THEME, CodeSurface, CodeSurfaceProps, CodeView, CodeViewProps, DiffStats, DiffView, DiffViewProps, FileBreadcrumb, FileBreadcrumbProps, GitStatusBadge, PanelHeader, PanelHeaderProps, PillTabItem, PillTabs, PillTabsProps, PreviewView, PreviewViewProps, SandboxArtifact, SandboxArtifactPane, SandboxArtifactPaneProps, SandboxArtifactTerminal, buildUnifiedPatch, computeDiffStats, resolveLanguage } from './workbench.js';
|
|
6
6
|
export { OpenUIAction, OpenUIActionsNode, OpenUIArtifactRenderer, OpenUIArtifactRendererProps, OpenUIBadgeNode, OpenUICardNode, OpenUICodeNode, OpenUIComponentNode, OpenUIGridNode, OpenUIHeadingNode, OpenUIKeyValueNode, OpenUIMarkdownNode, OpenUIPrimitive, OpenUISeparatorNode, OpenUIStackNode, OpenUIStatNode, OpenUITableNode, OpenUITextNode } from '@tangle-network/ui/openui';
|
|
7
7
|
export { AgentTimeline, AgentTimelineArtifactItem, AgentTimelineCustomItem, AgentTimelineItem, AgentTimelineMessageItem, AgentTimelineProps, AgentTimelineStatusItem, AgentTimelineTone, AgentTimelineToolGroupItem, AgentTimelineToolItem, ChatContainer, ChatContainerProps, ChatMessage, ChatMessageProps, MessageList, MessageListProps, MessageRole, ThinkingIndicator, ThinkingIndicatorProps, UserMessage, UserMessageProps } from '@tangle-network/ui/chat';
|
|
8
|
-
export { AgentComposer, AgentComposerProps, AgentProfileCapability, AgentProfileDraft, AgentProfileOption, AgentProfilePicker, AgentProfilePickerProps, AgentSessionControls, AgentSessionControlsProps, AgentSessionHarnessControl, AgentSessionModelControl, AgentSessionProfileControl, AgentSessionReasoningControl, ArtifactAgentDock, ArtifactAgentDockProps, ArtifactAgentDockTransport, ArtifactDockMessage, ArtifactDockStreamEvent, ArtifactKind, ArtifactScope, ComposerFile, ComposerFileRejection, ComposerFileValidationConfig, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, ReasoningLevel, ReasoningLevelOption, ReasoningLevelPicker, ReasoningLevelPickerProps, createFetchTransport, isAcceptedType, isModelCompatibleWithHarness, snapModelToHarness, validateComposerFiles } from './chat.js';
|
|
8
|
+
export { AgentComposer, AgentComposerMention, AgentComposerProps, AgentProfileCapability, AgentProfileDraft, AgentProfileOption, AgentProfilePicker, AgentProfilePickerProps, AgentSessionControls, AgentSessionControlsProps, AgentSessionHarnessControl, AgentSessionModelControl, AgentSessionProfileControl, AgentSessionReasoningControl, ArtifactAgentDock, ArtifactAgentDockProps, ArtifactAgentDockTransport, ArtifactDockMessage, ArtifactDockStreamEvent, ArtifactKind, ArtifactScope, ComposerFile, ComposerFileRejection, ComposerFileValidationConfig, DEFAULT_REASONING_LEVEL_OPTIONS, HARNESS_REASONING_OPTIONS, MentionItem, ReasoningLevel, ReasoningLevelOption, ReasoningLevelPicker, ReasoningLevelPickerProps, createFetchTransport, isAcceptedType, isModelCompatibleWithHarness, snapModelToHarness, validateComposerFiles } from './chat.js';
|
|
9
9
|
export { ExpandedToolDetail, ExpandedToolDetailProps, InlineThinkingItem, InlineThinkingItemProps, InlineToolItem, InlineToolItemProps, LiveDuration, RunGroup, RunGroupProps } from '@tangle-network/ui/run';
|
|
10
10
|
export { CommandPreview, DiffPreview, GlobResultsPreview, GrepResultsPreview, QuestionPreview, WebSearchPreview, WriteFilePreview } from '@tangle-network/ui/tool-previews';
|
|
11
11
|
export { FileArtifactPane, FileArtifactPaneProps, FileNode, FilePreview, FilePreviewProps, FileTabData, FileTabs, FileTabsProps, FileTree, FileTreeProps, FileTreeVisibilityOptions, RichFileTree, RichFileTreeGitEntry, RichFileTreeGitStatus, RichFileTreeProps, RichFileTreeThemeVars, filterFileTree } from '@tangle-network/ui/files';
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
import {
|
|
22
22
|
useSessionStream,
|
|
23
23
|
useSidecarAuth
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-QORIZARI.js";
|
|
25
25
|
import {
|
|
26
26
|
activeSessionsAtom,
|
|
27
27
|
addMessage,
|
|
@@ -91,7 +91,7 @@ import {
|
|
|
91
91
|
snapHarnessToModel,
|
|
92
92
|
snapModelToHarness,
|
|
93
93
|
validateComposerFiles
|
|
94
|
-
} from "./chunk-
|
|
94
|
+
} from "./chunk-PKZVCCZ6.js";
|
|
95
95
|
import {
|
|
96
96
|
TOOL_CATEGORY_ICONS,
|
|
97
97
|
cn,
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-EI44GEQ5.js";
|
|
4
|
+
import "./chunk-U67V476Y.js";
|
|
5
|
+
|
|
6
|
+
// src/chat/mention-editor.tsx
|
|
7
|
+
import { mergeAttributes } from "@tiptap/core";
|
|
8
|
+
import Mention from "@tiptap/extension-mention";
|
|
9
|
+
import { EditorContent, useEditor } from "@tiptap/react";
|
|
10
|
+
import StarterKit from "@tiptap/starter-kit";
|
|
11
|
+
import { exitSuggestion } from "@tiptap/suggestion";
|
|
12
|
+
import * as React2 from "react";
|
|
13
|
+
|
|
14
|
+
// src/chat/mention-list.tsx
|
|
15
|
+
import { Loader2 } from "lucide-react";
|
|
16
|
+
import * as React from "react";
|
|
17
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
+
var MentionList = React.forwardRef(
|
|
19
|
+
function MentionList2({ items, loading, error, emptyText = "No matches", renderItem, onSelect }, ref) {
|
|
20
|
+
const [selected, setSelected] = React.useState(0);
|
|
21
|
+
const selectedRef = React.useRef(0);
|
|
22
|
+
const move = (next) => {
|
|
23
|
+
selectedRef.current = next;
|
|
24
|
+
setSelected(next);
|
|
25
|
+
};
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
move(0);
|
|
28
|
+
}, [items]);
|
|
29
|
+
React.useImperativeHandle(
|
|
30
|
+
ref,
|
|
31
|
+
() => ({
|
|
32
|
+
onKeyDown(event) {
|
|
33
|
+
const count = items.length;
|
|
34
|
+
switch (event.key) {
|
|
35
|
+
case "ArrowDown":
|
|
36
|
+
if (count > 0) move((selectedRef.current + 1) % count);
|
|
37
|
+
return true;
|
|
38
|
+
case "ArrowUp":
|
|
39
|
+
if (count > 0) move((selectedRef.current - 1 + count) % count);
|
|
40
|
+
return true;
|
|
41
|
+
case "Enter":
|
|
42
|
+
case "Tab":
|
|
43
|
+
if (count > 0) onSelect(items[selectedRef.current]);
|
|
44
|
+
return true;
|
|
45
|
+
default:
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}),
|
|
50
|
+
[items, onSelect]
|
|
51
|
+
);
|
|
52
|
+
return /* @__PURE__ */ jsxs(
|
|
53
|
+
"div",
|
|
54
|
+
{
|
|
55
|
+
role: "listbox",
|
|
56
|
+
"aria-label": "File mentions",
|
|
57
|
+
className: cn(
|
|
58
|
+
"max-h-64 min-w-[16rem] max-w-sm overflow-y-auto rounded-xl border border-[var(--md3-outline-variant)]",
|
|
59
|
+
"bg-surface-container-high p-1 shadow-lg"
|
|
60
|
+
),
|
|
61
|
+
children: [
|
|
62
|
+
loading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2.5 py-2 text-muted-foreground text-sm", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }),
|
|
64
|
+
"Searching\u2026"
|
|
65
|
+
] }),
|
|
66
|
+
!loading && error && /* @__PURE__ */ jsx("div", { className: "px-2.5 py-2 text-[var(--status-error,#ff4d6d)] text-sm", children: "Couldn\u2019t load matches" }),
|
|
67
|
+
!loading && !error && items.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-2.5 py-2 text-muted-foreground text-sm", children: emptyText }),
|
|
68
|
+
!loading && !error && items.map((item, index) => {
|
|
69
|
+
const active = index === selected;
|
|
70
|
+
return /* @__PURE__ */ jsx(
|
|
71
|
+
"button",
|
|
72
|
+
{
|
|
73
|
+
type: "button",
|
|
74
|
+
role: "option",
|
|
75
|
+
"aria-selected": active,
|
|
76
|
+
onMouseDown: (event) => {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
onSelect(item);
|
|
79
|
+
},
|
|
80
|
+
onMouseEnter: () => move(index),
|
|
81
|
+
className: cn(
|
|
82
|
+
"flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-sm",
|
|
83
|
+
active ? "bg-primary/10 text-foreground" : "text-muted-foreground"
|
|
84
|
+
),
|
|
85
|
+
children: renderItem ? renderItem(item) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
86
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-foreground", children: item.label }),
|
|
87
|
+
item.detail && /* @__PURE__ */ jsx("span", { className: "ml-auto truncate text-muted-foreground text-xs", children: item.detail })
|
|
88
|
+
] })
|
|
89
|
+
},
|
|
90
|
+
item.id
|
|
91
|
+
);
|
|
92
|
+
})
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// src/chat/mention-serialize.ts
|
|
100
|
+
var MENTION_TYPE = "mention";
|
|
101
|
+
function serializeMentionDoc(doc) {
|
|
102
|
+
return (doc.content ?? []).map(serializeBlock).join("\n");
|
|
103
|
+
}
|
|
104
|
+
function serializeBlock(node) {
|
|
105
|
+
if (node.type === "paragraph") {
|
|
106
|
+
return (node.content ?? []).map(serializeInline).join("");
|
|
107
|
+
}
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
function serializeInline(node) {
|
|
111
|
+
if (node.type === "text") return node.text ?? "";
|
|
112
|
+
if (node.type === "hardBreak") return "\n";
|
|
113
|
+
if (node.type === MENTION_TYPE) return `@${node.attrs?.id ?? ""}`;
|
|
114
|
+
return "";
|
|
115
|
+
}
|
|
116
|
+
function collectMentions(doc) {
|
|
117
|
+
const out = [];
|
|
118
|
+
const walk = (node) => {
|
|
119
|
+
if (node.type === MENTION_TYPE && node.attrs?.id) {
|
|
120
|
+
out.push({
|
|
121
|
+
id: node.attrs.id,
|
|
122
|
+
label: node.attrs.label ?? node.attrs.id,
|
|
123
|
+
kind: node.attrs.kind ?? void 0
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
for (const child of node.content ?? []) walk(child);
|
|
127
|
+
};
|
|
128
|
+
walk(doc);
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
function parseMentionValue(value, known) {
|
|
132
|
+
const lines = value.split("\n");
|
|
133
|
+
return {
|
|
134
|
+
type: "doc",
|
|
135
|
+
content: lines.map((line) => ({
|
|
136
|
+
type: "paragraph",
|
|
137
|
+
content: parseLine(line, known)
|
|
138
|
+
}))
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function parseLine(line, known) {
|
|
142
|
+
const content = [];
|
|
143
|
+
let text = "";
|
|
144
|
+
const flush = () => {
|
|
145
|
+
if (text) {
|
|
146
|
+
content.push({ type: "text", text });
|
|
147
|
+
text = "";
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
let i = 0;
|
|
151
|
+
while (i < line.length) {
|
|
152
|
+
if (line[i] === "@") {
|
|
153
|
+
const id = matchKnownId(line, i + 1, known);
|
|
154
|
+
if (id) {
|
|
155
|
+
const item = known.get(id);
|
|
156
|
+
flush();
|
|
157
|
+
content.push({
|
|
158
|
+
type: MENTION_TYPE,
|
|
159
|
+
attrs: {
|
|
160
|
+
id: item.id,
|
|
161
|
+
label: item.label,
|
|
162
|
+
kind: item.kind ?? null
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
i += 1 + id.length;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
text += line[i];
|
|
170
|
+
i += 1;
|
|
171
|
+
}
|
|
172
|
+
flush();
|
|
173
|
+
return content;
|
|
174
|
+
}
|
|
175
|
+
function matchKnownId(line, pos, known) {
|
|
176
|
+
let best = null;
|
|
177
|
+
for (const id of known.keys()) {
|
|
178
|
+
if (id.length === 0) continue;
|
|
179
|
+
if (best !== null && id.length <= best.length) continue;
|
|
180
|
+
if (!line.startsWith(id, pos)) continue;
|
|
181
|
+
const after = pos + id.length;
|
|
182
|
+
if (after < line.length && !/\s/.test(line[after])) continue;
|
|
183
|
+
best = id;
|
|
184
|
+
}
|
|
185
|
+
return best;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/chat/mention-editor.tsx
|
|
189
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
190
|
+
function buildComposerStarterKit() {
|
|
191
|
+
return StarterKit.configure({
|
|
192
|
+
blockquote: false,
|
|
193
|
+
bold: false,
|
|
194
|
+
bulletList: false,
|
|
195
|
+
code: false,
|
|
196
|
+
codeBlock: false,
|
|
197
|
+
dropcursor: false,
|
|
198
|
+
heading: false,
|
|
199
|
+
horizontalRule: false,
|
|
200
|
+
italic: false,
|
|
201
|
+
link: false,
|
|
202
|
+
listItem: false,
|
|
203
|
+
listKeymap: false,
|
|
204
|
+
orderedList: false,
|
|
205
|
+
strike: false,
|
|
206
|
+
trailingNode: false,
|
|
207
|
+
underline: false
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
var MENTION_PILL_CLASS = cn(
|
|
211
|
+
"rounded-md bg-primary/10 px-1 py-0.5 font-medium text-primary"
|
|
212
|
+
);
|
|
213
|
+
function buildMentionExtension(trigger, suggestion) {
|
|
214
|
+
return Mention.extend({
|
|
215
|
+
addAttributes() {
|
|
216
|
+
return {
|
|
217
|
+
id: {
|
|
218
|
+
default: null,
|
|
219
|
+
parseHTML: (element) => element.getAttribute("data-id"),
|
|
220
|
+
renderHTML: (attrs) => attrs.id ? { "data-id": attrs.id } : {}
|
|
221
|
+
},
|
|
222
|
+
label: {
|
|
223
|
+
default: null,
|
|
224
|
+
parseHTML: (element) => element.getAttribute("data-label"),
|
|
225
|
+
renderHTML: (attrs) => attrs.label ? { "data-label": attrs.label } : {}
|
|
226
|
+
},
|
|
227
|
+
kind: {
|
|
228
|
+
default: null,
|
|
229
|
+
parseHTML: (element) => element.getAttribute("data-kind"),
|
|
230
|
+
renderHTML: (attrs) => attrs.kind ? { "data-kind": attrs.kind } : {}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}).configure({
|
|
235
|
+
HTMLAttributes: { class: MENTION_PILL_CLASS },
|
|
236
|
+
renderText: ({ node }) => `${trigger}${node.attrs.id}`,
|
|
237
|
+
renderHTML: ({ options, node }) => [
|
|
238
|
+
"span",
|
|
239
|
+
mergeAttributes(options.HTMLAttributes, {
|
|
240
|
+
title: node.attrs.id ?? void 0
|
|
241
|
+
}),
|
|
242
|
+
`${trigger}${node.attrs.label ?? node.attrs.id}`
|
|
243
|
+
],
|
|
244
|
+
suggestion
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
var EDITOR_CLASS = cn(
|
|
248
|
+
"w-full whitespace-pre-wrap break-words bg-transparent px-2 py-1",
|
|
249
|
+
"text-foreground text-sm leading-relaxed outline-none"
|
|
250
|
+
);
|
|
251
|
+
var FETCH_DEBOUNCE_MS = 100;
|
|
252
|
+
function mentionsKey(mentions) {
|
|
253
|
+
return mentions.map((item) => item.id).join("\0");
|
|
254
|
+
}
|
|
255
|
+
function MentionEditor({
|
|
256
|
+
value,
|
|
257
|
+
onChange,
|
|
258
|
+
onSubmit,
|
|
259
|
+
placeholder,
|
|
260
|
+
disabled,
|
|
261
|
+
autoFocus,
|
|
262
|
+
minRows,
|
|
263
|
+
maxHeight,
|
|
264
|
+
mention,
|
|
265
|
+
registerFocus,
|
|
266
|
+
onPasteFiles
|
|
267
|
+
}) {
|
|
268
|
+
const trigger = mention.trigger ?? "@";
|
|
269
|
+
const [anchor, setAnchor] = React2.useState(null);
|
|
270
|
+
const [open, setOpen] = React2.useState(false);
|
|
271
|
+
const [query, setQuery] = React2.useState("");
|
|
272
|
+
const [items, setItems] = React2.useState([]);
|
|
273
|
+
const [loading, setLoading] = React2.useState(false);
|
|
274
|
+
const [errored, setErrored] = React2.useState(false);
|
|
275
|
+
const openRef = React2.useRef(false);
|
|
276
|
+
const commandRef = React2.useRef(null);
|
|
277
|
+
const listRef = React2.useRef(null);
|
|
278
|
+
const knownRef = React2.useRef(/* @__PURE__ */ new Map());
|
|
279
|
+
const requestIdRef = React2.useRef(0);
|
|
280
|
+
const clientRectRef = React2.useRef(null);
|
|
281
|
+
const lastMentionsKeyRef = React2.useRef("");
|
|
282
|
+
const onSubmitRef = React2.useRef(onSubmit);
|
|
283
|
+
onSubmitRef.current = onSubmit;
|
|
284
|
+
const onChangeRef = React2.useRef(onChange);
|
|
285
|
+
onChangeRef.current = onChange;
|
|
286
|
+
const onMentionsChangeRef = React2.useRef(mention.onMentionsChange);
|
|
287
|
+
onMentionsChangeRef.current = mention.onMentionsChange;
|
|
288
|
+
const onPasteFilesRef = React2.useRef(onPasteFiles);
|
|
289
|
+
onPasteFilesRef.current = onPasteFiles;
|
|
290
|
+
const fetchItemsRef = React2.useRef(mention.fetchItems);
|
|
291
|
+
fetchItemsRef.current = mention.fetchItems;
|
|
292
|
+
const editor = useEditor(
|
|
293
|
+
{
|
|
294
|
+
immediatelyRender: false,
|
|
295
|
+
editable: !disabled,
|
|
296
|
+
autofocus: autoFocus ? "end" : false,
|
|
297
|
+
content: parseMentionValue(value, knownRef.current),
|
|
298
|
+
editorProps: {
|
|
299
|
+
attributes: {
|
|
300
|
+
class: EDITOR_CLASS,
|
|
301
|
+
role: "textbox",
|
|
302
|
+
"aria-multiline": "true",
|
|
303
|
+
"aria-label": "Message input"
|
|
304
|
+
},
|
|
305
|
+
handleKeyDown: (_view, event) => {
|
|
306
|
+
if (event.key !== "Enter" || event.shiftKey) return false;
|
|
307
|
+
if (event.isComposing || event.keyCode === 229) return false;
|
|
308
|
+
if (openRef.current) return false;
|
|
309
|
+
event.preventDefault();
|
|
310
|
+
onSubmitRef.current();
|
|
311
|
+
return true;
|
|
312
|
+
},
|
|
313
|
+
handlePaste: (_view, event) => {
|
|
314
|
+
const files = event.clipboardData?.files;
|
|
315
|
+
if (files && files.length > 0 && onPasteFilesRef.current) {
|
|
316
|
+
return onPasteFilesRef.current(files);
|
|
317
|
+
}
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
onUpdate: ({ editor: editor2 }) => {
|
|
322
|
+
const json = editor2.getJSON();
|
|
323
|
+
onChangeRef.current(serializeMentionDoc(json));
|
|
324
|
+
const mentions = collectMentions(json);
|
|
325
|
+
for (const item of mentions) knownRef.current.set(item.id, item);
|
|
326
|
+
lastMentionsKeyRef.current = mentionsKey(mentions);
|
|
327
|
+
onMentionsChangeRef.current?.(mentions);
|
|
328
|
+
},
|
|
329
|
+
extensions: [
|
|
330
|
+
buildComposerStarterKit(),
|
|
331
|
+
buildMentionExtension(trigger, {
|
|
332
|
+
char: trigger,
|
|
333
|
+
allowSpaces: false,
|
|
334
|
+
// Items are fetched by this component (so it can model loading and
|
|
335
|
+
// error states), not by the suggestion plugin.
|
|
336
|
+
items: () => [],
|
|
337
|
+
command: ({ editor: editor2, range, props }) => {
|
|
338
|
+
const item = props;
|
|
339
|
+
editor2.chain().focus().insertContentAt(range, [
|
|
340
|
+
{
|
|
341
|
+
type: "mention",
|
|
342
|
+
attrs: {
|
|
343
|
+
id: item.id,
|
|
344
|
+
label: item.label,
|
|
345
|
+
kind: item.kind ?? null
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{ type: "text", text: " " }
|
|
349
|
+
]).run();
|
|
350
|
+
knownRef.current.set(item.id, item);
|
|
351
|
+
},
|
|
352
|
+
render: () => ({
|
|
353
|
+
onStart: (props) => {
|
|
354
|
+
openRef.current = true;
|
|
355
|
+
commandRef.current = props.command;
|
|
356
|
+
clientRectRef.current = props.clientRect ?? null;
|
|
357
|
+
setOpen(true);
|
|
358
|
+
setQuery(props.query);
|
|
359
|
+
setAnchor(props.clientRect?.() ?? null);
|
|
360
|
+
},
|
|
361
|
+
onUpdate: (props) => {
|
|
362
|
+
commandRef.current = props.command;
|
|
363
|
+
clientRectRef.current = props.clientRect ?? null;
|
|
364
|
+
setQuery(props.query);
|
|
365
|
+
setAnchor(props.clientRect?.() ?? null);
|
|
366
|
+
},
|
|
367
|
+
onKeyDown: (props) => {
|
|
368
|
+
if (props.event.key === "Escape") {
|
|
369
|
+
exitSuggestion(props.view);
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return listRef.current?.onKeyDown(props.event) ?? false;
|
|
373
|
+
},
|
|
374
|
+
onExit: () => {
|
|
375
|
+
openRef.current = false;
|
|
376
|
+
commandRef.current = null;
|
|
377
|
+
setOpen(false);
|
|
378
|
+
setItems([]);
|
|
379
|
+
setLoading(false);
|
|
380
|
+
setErrored(false);
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
]
|
|
385
|
+
},
|
|
386
|
+
[]
|
|
387
|
+
);
|
|
388
|
+
React2.useEffect(() => {
|
|
389
|
+
if (!open) return;
|
|
390
|
+
const requestId = requestIdRef.current += 1;
|
|
391
|
+
setLoading(true);
|
|
392
|
+
setErrored(false);
|
|
393
|
+
const timer = setTimeout(() => {
|
|
394
|
+
Promise.resolve(fetchItemsRef.current(query)).then((results) => {
|
|
395
|
+
if (requestId !== requestIdRef.current) return;
|
|
396
|
+
for (const item of results) knownRef.current.set(item.id, item);
|
|
397
|
+
setItems(results);
|
|
398
|
+
setLoading(false);
|
|
399
|
+
}).catch(() => {
|
|
400
|
+
if (requestId !== requestIdRef.current) return;
|
|
401
|
+
setErrored(true);
|
|
402
|
+
setLoading(false);
|
|
403
|
+
});
|
|
404
|
+
}, FETCH_DEBOUNCE_MS);
|
|
405
|
+
return () => clearTimeout(timer);
|
|
406
|
+
}, [open, query]);
|
|
407
|
+
React2.useEffect(() => {
|
|
408
|
+
if (!open) return;
|
|
409
|
+
const reposition = () => setAnchor(clientRectRef.current?.() ?? null);
|
|
410
|
+
window.addEventListener("scroll", reposition, true);
|
|
411
|
+
window.addEventListener("resize", reposition);
|
|
412
|
+
return () => {
|
|
413
|
+
window.removeEventListener("scroll", reposition, true);
|
|
414
|
+
window.removeEventListener("resize", reposition);
|
|
415
|
+
};
|
|
416
|
+
}, [open]);
|
|
417
|
+
React2.useEffect(() => {
|
|
418
|
+
if (!editor) return;
|
|
419
|
+
if (serializeMentionDoc(editor.getJSON()) === value) return;
|
|
420
|
+
const doc = parseMentionValue(value, knownRef.current);
|
|
421
|
+
editor.commands.setContent(doc, { emitUpdate: false });
|
|
422
|
+
const mentions = collectMentions(doc);
|
|
423
|
+
const key = mentionsKey(mentions);
|
|
424
|
+
if (key !== lastMentionsKeyRef.current) {
|
|
425
|
+
lastMentionsKeyRef.current = key;
|
|
426
|
+
onMentionsChangeRef.current?.(mentions);
|
|
427
|
+
}
|
|
428
|
+
}, [editor, value]);
|
|
429
|
+
React2.useEffect(() => {
|
|
430
|
+
editor?.setEditable(!disabled);
|
|
431
|
+
}, [editor, disabled]);
|
|
432
|
+
React2.useEffect(() => {
|
|
433
|
+
if (!editor || !registerFocus) return;
|
|
434
|
+
registerFocus(() => editor.commands.focus());
|
|
435
|
+
}, [editor, registerFocus]);
|
|
436
|
+
return /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
437
|
+
/* @__PURE__ */ jsxs2(
|
|
438
|
+
"div",
|
|
439
|
+
{
|
|
440
|
+
className: "overflow-y-auto",
|
|
441
|
+
style: { maxHeight, minHeight: `${minRows * 1.5}rem` },
|
|
442
|
+
children: [
|
|
443
|
+
/* @__PURE__ */ jsx2(EditorContent, { editor }),
|
|
444
|
+
value.length === 0 && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute top-1 left-2 text-muted-foreground text-sm leading-relaxed", children: placeholder })
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
open && /* @__PURE__ */ jsx2(
|
|
449
|
+
"div",
|
|
450
|
+
{
|
|
451
|
+
className: "absolute z-20",
|
|
452
|
+
style: anchorStyle(anchor),
|
|
453
|
+
"data-testid": "mention-popover",
|
|
454
|
+
children: /* @__PURE__ */ jsx2(
|
|
455
|
+
MentionList,
|
|
456
|
+
{
|
|
457
|
+
ref: listRef,
|
|
458
|
+
items,
|
|
459
|
+
loading,
|
|
460
|
+
error: errored,
|
|
461
|
+
emptyText: mention.emptyText,
|
|
462
|
+
renderItem: mention.renderItem,
|
|
463
|
+
onSelect: (item) => commandRef.current?.(item)
|
|
464
|
+
}
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
] });
|
|
469
|
+
}
|
|
470
|
+
function anchorStyle(rect) {
|
|
471
|
+
if (!rect || rect.left === 0 && rect.bottom === 0) {
|
|
472
|
+
return { position: "absolute", top: "100%", left: 0 };
|
|
473
|
+
}
|
|
474
|
+
return { position: "fixed", top: rect.bottom + 4, left: rect.left };
|
|
475
|
+
}
|
|
476
|
+
export {
|
|
477
|
+
buildComposerStarterKit,
|
|
478
|
+
buildMentionExtension,
|
|
479
|
+
MentionEditor as default
|
|
480
|
+
};
|
package/dist/sdk-hooks.js
CHANGED
package/dist/styles.css
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/sandbox-ui",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Unified UI component library for Tangle Sandbox
|
|
3
|
+
"version": "0.86.0",
|
|
4
|
+
"description": "Unified UI component library for Tangle Sandbox — primitives, chat, dashboard, terminal, editor, and workspace components",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/tangle-network/sandbox-ui"
|
|
@@ -192,8 +192,11 @@
|
|
|
192
192
|
"@tiptap/core": "^3.20.4",
|
|
193
193
|
"@tiptap/extension-collaboration": "^3.20.4",
|
|
194
194
|
"@tiptap/extension-collaboration-caret": "^3.7.0",
|
|
195
|
+
"@tiptap/extension-mention": "^3.22.5",
|
|
196
|
+
"@tiptap/pm": "^3.22.5",
|
|
195
197
|
"@tiptap/react": "^3.20.4",
|
|
196
198
|
"@tiptap/starter-kit": "^3.20.4",
|
|
199
|
+
"@tiptap/suggestion": "^3.22.5",
|
|
197
200
|
"@types/react": "^19.1.2",
|
|
198
201
|
"@types/react-dom": "^19.1.2",
|
|
199
202
|
"@xterm/addon-fit": "^0.11.0",
|