@sybilion/uilib 1.3.33 → 1.3.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +2 -2
  2. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.js +18 -4
  3. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +39 -52
  4. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.styl.js +2 -2
  5. package/dist/esm/components/ui/Chat/ChatPrompt/ChatPromptComposer.js +25 -0
  6. package/dist/esm/components/ui/Chat/ChatPrompt/chatPromptDoc.js +17 -0
  7. package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +163 -0
  8. package/dist/esm/components/ui/Tooltip/Tooltip.styl.js +1 -1
  9. package/dist/esm/index.js +2 -0
  10. package/dist/esm/tiptap/slash-mention/SlashSuggestionList.js +53 -0
  11. package/dist/esm/tiptap/slash-mention/SlashSuggestionList.styl.js +7 -0
  12. package/dist/esm/tiptap/slash-mention/createSlashMentionExtension.js +151 -0
  13. package/dist/esm/tiptap/slash-mention/defaultChatSlashItems.js +12 -0
  14. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +3 -0
  15. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  16. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +5 -0
  17. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
  18. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.d.ts +6 -0
  19. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.d.ts +13 -0
  20. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/chatPromptDoc.d.ts +3 -0
  21. package/dist/esm/types/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.d.ts +20 -0
  22. package/dist/esm/types/src/components/ui/Chat/index.d.ts +1 -0
  23. package/dist/esm/types/src/components/ui/Input/Input.d.ts +1 -1
  24. package/dist/esm/types/src/docs/pages/ChatSlashCommandsPage.d.ts +1 -0
  25. package/dist/esm/types/src/index.d.ts +1 -0
  26. package/dist/esm/types/src/tiptap/slash-mention/SlashSuggestionList.d.ts +12 -0
  27. package/dist/esm/types/src/tiptap/slash-mention/createSlashMentionExtension.d.ts +21 -0
  28. package/dist/esm/types/src/tiptap/slash-mention/defaultChatSlashItems.d.ts +4 -0
  29. package/dist/esm/types/src/tiptap/slash-mention/index.d.ts +5 -0
  30. package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +25 -0
  31. package/package.json +15 -1
  32. package/src/components/ui/Chat/Chat.types.ts +4 -0
  33. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +4 -0
  34. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +5 -0
  35. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.helpers.ts +43 -0
  36. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl +33 -5
  37. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.styl.d.ts +2 -1
  38. package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +50 -106
  39. package/src/components/ui/Chat/ChatPrompt/ChatPromptComposer.tsx +93 -0
  40. package/src/components/ui/Chat/ChatPrompt/chatPromptDoc.ts +18 -0
  41. package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +214 -0
  42. package/src/components/ui/Chat/index.ts +1 -0
  43. package/src/components/ui/Tooltip/Tooltip.styl +1 -0
  44. package/src/docs/pages/ChatSlashCommandsPage.tsx +139 -0
  45. package/src/docs/pages/TooltipPage.tsx +1 -1
  46. package/src/docs/registry.ts +6 -0
  47. package/src/index.ts +1 -0
  48. package/src/tiptap/slash-mention/SlashSuggestionList.styl +48 -0
  49. package/src/tiptap/slash-mention/SlashSuggestionList.styl.d.ts +11 -0
  50. package/src/tiptap/slash-mention/SlashSuggestionList.tsx +109 -0
  51. package/src/tiptap/slash-mention/createSlashMentionExtension.ts +217 -0
  52. package/src/tiptap/slash-mention/defaultChatSlashItems.ts +18 -0
  53. package/src/tiptap/slash-mention/index.ts +16 -0
  54. package/src/tiptap/slash-mention/types.ts +29 -0
@@ -14,7 +14,7 @@ import { filterToTextAttachments, isAttachmentsDropzoneEnabled, buildAcceptAttr
14
14
  import { extractChatAttachmentItems } from '../chatAttachmentExtract.js';
15
15
  import S from './ChatChrome.styl.js';
16
16
 
17
- function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showBranchActionsRow, showSyntheticBranchButtons, unusedBranchKeys, isScriptComplete, onGenerateDashboard, generatingDashboard, onGenerateDashboardClick, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments = false, onAttachmentsDropped, }) {
17
+ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showBranchActionsRow, showSyntheticBranchButtons, unusedBranchKeys, isScriptComplete, onGenerateDashboard, generatingDashboard, onGenerateDashboardClick, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments = false, onAttachmentsDropped, slashCommandItems, promptPlaceholder, }) {
18
18
  const filteredAllowedAttachments = useMemo(() => filterToTextAttachments(allowedAttachments), [allowedAttachments]);
19
19
  const attachmentsDropzoneEnabled = isAttachmentsDropzoneEnabled(allowedAttachments, allowPdfAttachments);
20
20
  const attachmentAccept = useMemo(() => buildAcceptAttr(filteredAllowedAttachments, allowPdfAttachments), [filteredAllowedAttachments, allowPdfAttachments]);
@@ -80,7 +80,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
80
80
  })
81
81
  : null, isScriptComplete &&
82
82
  onGenerateDashboard &&
83
- !generatingDashboard ? (jsxs(Button, { type: "button", variant: "default", size: "lg", disabled: isLoading, onClick: onGenerateDashboardClick, children: [jsx(ChartLineIcon, {}), "Generate Dashboard"] })) : null] })), showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "Thinking..." }))] }) })), jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptBusy, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
83
+ !generatingDashboard ? (jsxs(Button, { type: "button", variant: "default", size: "lg", disabled: isLoading, onClick: onGenerateDashboardClick, children: [jsx(ChartLineIcon, {}), "Generate Dashboard"] })) : null] })), showInlinePresets && renderPresets('inline'), isLoading && isLastMessageFromUser && (jsx(TextShimmer, { duration: 1, spread: 5, className: S.loader, children: "Thinking..." }))] }) })), jsxs("div", { className: cn(S.footer, footerClassName), children: [isEmpty ? (jsx("div", { className: S.notice, children: "Forecast Assistant can make mistakes." })) : null, jsx(Chat.Prompt, { onSubmit: handlePromptSubmitWithAttachments, disabled: promptBusy, attachments: pendingAttachments, onRemoveAttachment: handleRemoveAttachment, prefillMessage: promptPrefill ?? undefined, placeholder: promptPlaceholder, slashCommandItems: slashCommandItems, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
84
84
  }
85
85
 
86
86
  export { ChatChrome };
@@ -1,6 +1,19 @@
1
1
  /** Keep in sync with `ChatPrompt.styl` `INPUT_MAX_HEIGHT` / `min-height`. */
2
2
  const PROMPT_INPUT_MAX_HEIGHT_PX = 200;
3
3
  const PROMPT_INPUT_MIN_HEIGHT_PX = 40;
4
+ /** ProseMirror `view.dom` exists only after TipTap mounts `<EditorContent />`; accessing `.view` can throw earlier. */
5
+ function chatPromptSafeEditorDom(editor) {
6
+ if (!editor || editor.isDestroyed)
7
+ return null;
8
+ try {
9
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
+ const dom = editor.view?.dom;
11
+ return dom instanceof HTMLElement ? dom : null;
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ }
4
17
  function chatPromptTextareaFloorPx(cs) {
5
18
  const padY = (Number.parseFloat(cs.paddingTop) || 0) +
6
19
  (Number.parseFloat(cs.paddingBottom) || 0);
@@ -10,12 +23,13 @@ function chatPromptTextareaFloorPx(cs) {
10
23
  : Number.parseFloat(cs.lineHeight) || fs * 1.25;
11
24
  return Math.max(PROMPT_INPUT_MIN_HEIGHT_PX, Math.ceil(linePx + padY));
12
25
  }
13
- /** Autosizing textarea inside flex shells: empty floor only; typed collapse measure + clamp. */
14
- function syncChatPromptTextareaHeight(el, message) {
26
+ /** Same sizing rules for TipTap `.ProseMirror` root (`text` mirrors doc plain text length for empty-state). */
27
+ function syncChatPromptComposerHeight(el, text) {
15
28
  const floor = chatPromptTextareaFloorPx(getComputedStyle(el));
16
29
  el.style.overflowY = 'hidden';
17
30
  let contentHeight;
18
- if (message === '') {
31
+ const empty = text.trim() === '';
32
+ if (empty) {
19
33
  contentHeight = floor;
20
34
  }
21
35
  else {
@@ -28,4 +42,4 @@ function syncChatPromptTextareaHeight(el, message) {
28
42
  contentHeight > PROMPT_INPUT_MAX_HEIGHT_PX ? 'auto' : 'hidden';
29
43
  }
30
44
 
31
- export { PROMPT_INPUT_MAX_HEIGHT_PX, PROMPT_INPUT_MIN_HEIGHT_PX, chatPromptTextareaFloorPx, syncChatPromptTextareaHeight };
45
+ export { PROMPT_INPUT_MAX_HEIGHT_PX, PROMPT_INPUT_MIN_HEIGHT_PX, chatPromptSafeEditorDom, chatPromptTextareaFloorPx, syncChatPromptComposerHeight };
@@ -1,60 +1,47 @@
1
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import cn from 'classnames';
3
- import { useState, useRef, useLayoutEffect, useEffect } from 'react';
4
- import useEvent from '../../../../hooks/useEvent.js';
5
- import { PaperclipIcon, SendHorizontalIcon } from 'lucide-react';
6
- import { Button } from '../../Button/Button.js';
7
- import { Input } from '../../Input/Input.js';
8
- import { syncChatPromptTextareaHeight } from './ChatPrompt.helpers.js';
3
+ import { useRef, useCallback } from 'react';
9
4
  import S from './ChatPrompt.styl.js';
10
5
  import { ChatPromptAttachments } from './ChatPromptAttachments.js';
6
+ import { ChatPromptComposer } from './ChatPromptComposer.js';
7
+ import { useChatPromptEditor } from './useChatPromptEditor.js';
11
8
 
12
- function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, attachments = [], onRemoveAttachment, disabled = false, attachmentAccept, onAttachmentFiles, }) {
13
- const [message, setMessage] = useState('');
14
- const inputRef = useRef(null);
15
- const fileInputRef = useRef(null);
16
- const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
17
- useLayoutEffect(() => {
18
- const el = inputRef.current;
19
- if (!el)
20
- return;
21
- syncChatPromptTextareaHeight(el, message);
22
- }, [message]);
23
- useEffect(() => {
24
- if (prefillMessage != null && prefillMessage !== '') {
25
- setMessage(prefillMessage);
26
- }
27
- }, [prefillMessage]);
28
- const handleSubmit = (e) => {
29
- const trimmedMessage = message.trim();
30
- const hasAttachments = attachments.length > 0;
31
- if (trimmedMessage || hasAttachments) {
32
- e.preventDefault();
33
- onSubmit(trimmedMessage, hasAttachments ? attachments : undefined);
34
- setMessage('');
35
- }
36
- };
37
- const handleFileInputChange = (e) => {
38
- const files = Array.from(e.target.files ?? []);
39
- e.target.value = '';
40
- if (files.length > 0) {
41
- onAttachmentFiles?.(files);
42
- }
43
- };
44
- useEvent({
45
- event: 'keydown',
46
- callback: (e) => {
47
- if (e.key === 'Enter' && !(e.metaKey || e.shiftKey || e.ctrlKey)) {
48
- e.preventDefault();
49
- handleSubmit(e);
50
- setMessage('');
51
- }
52
- },
9
+ function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, slashCommandItems, attachments = [], onRemoveAttachment, disabled = false, attachmentAccept, onAttachmentFiles, }) {
10
+ const attachmentsCount = attachments.length;
11
+ const emitSubmitRef = useRef(() => { });
12
+ const { editor, trimmedMessage, resetAfterSend, handleComposerKeyDown } = useChatPromptEditor({
13
+ disabled,
14
+ placeholder,
15
+ slashCommandItems,
16
+ prefillMessage,
17
+ attachmentsCount,
18
+ onEnterSubmit: () => emitSubmitRef.current(),
53
19
  });
54
- return (jsxs("form", { onSubmit: handleSubmit, className: cn(S.root, className), children: [jsx(ChatPromptAttachments, { attachments: attachments, onRemove: index => onRemoveAttachment?.(index), disabled: disabled }), jsxs("div", { className: S.composer, children: [showAttachButton ? (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", accept: attachmentAccept, multiple: true, className: S.fileInput, disabled: disabled, onChange: handleFileInputChange }), jsx(Button, { type: "button", variant: "ghost", icon: true, size: "sm", className: S.attachButton, "aria-label": "Attach file", disabled: disabled, onClick: e => {
55
- e.preventDefault();
56
- fileInputRef.current?.click();
57
- }, children: jsx(PaperclipIcon, { size: 16 }) })] })) : null, jsx(Input, { ref: inputRef, type: "textarea", rows: 1, value: message, onChange: e => setMessage(e.target.value), placeholder: placeholder || 'Type a message...', className: cn(S.input) }), jsx("div", { className: S.submitColumn, children: jsx(Button, { type: "submit", size: "sm", disabled: disabled || (!message.trim() && attachments.length === 0), children: jsx(SendHorizontalIcon, { size: 16 }) }) })] }), footer] }));
20
+ const emitSubmitAndClear = useCallback(() => {
21
+ if (!editor)
22
+ return;
23
+ if (!trimmedMessage && attachmentsCount === 0)
24
+ return;
25
+ const msg = trimmedMessage;
26
+ resetAfterSend();
27
+ onSubmit(msg, attachmentsCount > 0 ? attachments : undefined);
28
+ }, [
29
+ attachments,
30
+ attachmentsCount,
31
+ editor,
32
+ onSubmit,
33
+ resetAfterSend,
34
+ trimmedMessage,
35
+ ]);
36
+ emitSubmitRef.current = emitSubmitAndClear;
37
+ const handleSubmitForm = useCallback((e) => {
38
+ e?.preventDefault();
39
+ emitSubmitAndClear();
40
+ }, [emitSubmitAndClear]);
41
+ if (!editor) {
42
+ return null;
43
+ }
44
+ return (jsxs("form", { onSubmit: handleSubmitForm, className: cn(S.root, className), children: [jsx(ChatPromptAttachments, { attachments: attachments, onRemove: index => onRemoveAttachment?.(index), disabled: disabled }), jsx(ChatPromptComposer, { editor: editor, disabled: disabled, trimmedMessage: trimmedMessage, attachments: attachments, attachmentAccept: attachmentAccept, onAttachmentFiles: onAttachmentFiles, onComposerKeyDown: handleComposerKeyDown }), footer] }));
58
45
  }
59
46
 
60
47
  export { ChatPrompt };
@@ -1,7 +1,7 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_fileInput__xdgPn{display:none}.ChatPrompt_attachButton__gi-qF{align-self:flex-end;flex-shrink:0}.ChatPrompt_input__QPKBV{border:none;border-radius:0!important;flex:1;max-height:200px;min-height:40px;min-width:0;overflow-y:auto;padding:var(--p-2) 0 0!important;resize:none}.ChatPrompt_input__QPKBV,.ChatPrompt_input__QPKBV:focus{box-shadow:none!important}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_attachments__KG-fG{display:flex;flex-wrap:wrap;gap:var(--p-2);margin-bottom:var(--p-2)}.ChatPrompt_attachmentItem__QJk7J{flex:1 1 300px;max-width:300px;min-width:0}";
4
- var S = {"root":"ChatPrompt_root__5G5bq","composer":"ChatPrompt_composer__H3c3N","fileInput":"ChatPrompt_fileInput__xdgPn","attachButton":"ChatPrompt_attachButton__gi-qF","input":"ChatPrompt_input__QPKBV","submitColumn":"ChatPrompt_submitColumn__0rY1R","attachments":"ChatPrompt_attachments__KG-fG","attachmentItem":"ChatPrompt_attachmentItem__QJk7J"};
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.ChatPrompt_root__5G5bq{align-items:stretch;display:flex;flex-direction:column;gap:var(--p-2);padding:var(--p-6);padding-top:var(--p-5);position:relative}.ChatPrompt_composer__H3c3N{align-items:flex-end;display:flex;flex-direction:row;gap:var(--p-3);position:relative;width:100%}.ChatPrompt_fileInput__xdgPn{display:none}.ChatPrompt_attachButton__gi-qF{align-self:flex-end;flex-shrink:0}.ChatPrompt_editorWrap__Q7gat{align-self:stretch;flex:1;min-width:0}.ChatPrompt_editorMount__Phh4D{background:transparent;border:none;border-radius:0!important;box-shadow:none!important;display:flex;flex:1;flex-direction:column;max-height:200px;min-height:40px;min-width:0;padding:0!important}.ChatPrompt_editorMount__Phh4D:focus-within{box-shadow:none!important}.ChatPrompt_editorMount__Phh4D .ProseMirror{border:none!important;box-shadow:none!important;flex:1;margin:0;max-height:200px!important;min-height:40px!important;outline:none!important;overflow-x:hidden!important;overflow-y:auto!important;padding:var(--p-2) 0 0!important;resize:none!important;white-space:pre-wrap;word-break:break-word}.ChatPrompt_editorMount__Phh4D .ProseMirror p.is-empty:before{color:var(--muted-foreground);content:attr(data-placeholder);float:left;height:0;pointer-events:none}.ChatPrompt_submitColumn__0rY1R{align-items:center;display:flex;flex-direction:column;flex-shrink:0;justify-content:flex-end}.ChatPrompt_submitColumn__0rY1R>button:focus{box-shadow:0 0 0 2px var(--brand-color-500)!important}.ChatPrompt_submitColumn__0rY1R>button:first-child{border:none;position:relative;transition:all .2s}.ChatPrompt_submitColumn__0rY1R>button:first-child:focus{transform:scale(1.2)}.ChatPrompt_submitColumn__0rY1R>button:first-child:before{bottom:-100%;content:\"\";left:-100%;position:absolute;right:-100%;top:-100%}.ChatPrompt_attachments__KG-fG{display:flex;flex-wrap:wrap;gap:var(--p-2);margin-bottom:var(--p-2)}.ChatPrompt_attachmentItem__QJk7J{flex:1 1 300px;max-width:300px;min-width:0}";
4
+ var S = {"root":"ChatPrompt_root__5G5bq","composer":"ChatPrompt_composer__H3c3N","fileInput":"ChatPrompt_fileInput__xdgPn","attachButton":"ChatPrompt_attachButton__gi-qF","editorWrap":"ChatPrompt_editorWrap__Q7gat","editorMount":"ChatPrompt_editorMount__Phh4D","submitColumn":"ChatPrompt_submitColumn__0rY1R","attachments":"ChatPrompt_attachments__KG-fG","attachmentItem":"ChatPrompt_attachmentItem__QJk7J"};
5
5
  styleInject(css_248z);
6
6
 
7
7
  export { S as default };
@@ -0,0 +1,25 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { useRef, useCallback } from 'react';
3
+ import { EditorContent } from '@tiptap/react';
4
+ import { PaperclipIcon, SendHorizontalIcon } from 'lucide-react';
5
+ import { Button } from '../../Button/Button.js';
6
+ import S from './ChatPrompt.styl.js';
7
+
8
+ function ChatPromptComposer({ editor, disabled, trimmedMessage, attachments, attachmentAccept, onAttachmentFiles, onComposerKeyDown, }) {
9
+ const fileInputRef = useRef(null);
10
+ const showAttachButton = Boolean(attachmentAccept && onAttachmentFiles);
11
+ const handleFileInputChange = useCallback((e) => {
12
+ const files = Array.from(e.target.files ?? []);
13
+ e.target.value = '';
14
+ if (files.length > 0) {
15
+ onAttachmentFiles?.(files);
16
+ }
17
+ }, [onAttachmentFiles]);
18
+ const canSubmit = Boolean(trimmedMessage || attachments.length > 0);
19
+ return (jsxs("div", { className: S.composer, children: [showAttachButton ? (jsxs(Fragment, { children: [jsx("input", { ref: fileInputRef, type: "file", accept: attachmentAccept, multiple: true, className: S.fileInput, disabled: disabled, onChange: handleFileInputChange }), jsx(Button, { type: "button", variant: "ghost", icon: true, size: "sm", className: S.attachButton, "aria-label": "Attach file", disabled: disabled, onClick: e => {
20
+ e.preventDefault();
21
+ fileInputRef.current?.click();
22
+ }, children: jsx(PaperclipIcon, { size: 16 }) })] })) : null, jsx("div", { className: S.editorWrap, onKeyDown: onComposerKeyDown, children: jsx(EditorContent, { editor: editor, className: S.editorMount }) }), jsx("div", { className: S.submitColumn, children: jsx(Button, { type: "submit", size: "sm", disabled: disabled || !canSubmit, children: jsx(SendHorizontalIcon, { size: 16 }) }) })] }));
23
+ }
24
+
25
+ export { ChatPromptComposer };
@@ -0,0 +1,17 @@
1
+ const CHAT_PROMPT_EMPTY_DOC = {
2
+ type: 'doc',
3
+ content: [{ type: 'paragraph' }],
4
+ };
5
+ function chatPromptParagraphDoc(text) {
6
+ return {
7
+ type: 'doc',
8
+ content: [
9
+ {
10
+ type: 'paragraph',
11
+ content: text ? [{ type: 'text', text }] : [],
12
+ },
13
+ ],
14
+ };
15
+ }
16
+
17
+ export { CHAT_PROMPT_EMPTY_DOC, chatPromptParagraphDoc };
@@ -0,0 +1,163 @@
1
+ import { useRef, useCallback, useMemo, useState, useEffect, useLayoutEffect } from 'react';
2
+ import { DEFAULT_CHAT_SLASH_ITEMS } from '../../../../tiptap/slash-mention/defaultChatSlashItems.js';
3
+ import { createSlashMentionExtension } from '../../../../tiptap/slash-mention/createSlashMentionExtension.js';
4
+ import { Placeholder } from '@tiptap/extensions';
5
+ import { useEditor } from '@tiptap/react';
6
+ import StarterKit from '@tiptap/starter-kit';
7
+ import { chatPromptSafeEditorDom, syncChatPromptComposerHeight } from './ChatPrompt.helpers.js';
8
+ import { CHAT_PROMPT_EMPTY_DOC, chatPromptParagraphDoc } from './chatPromptDoc.js';
9
+
10
+ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, prefillMessage, attachmentsCount = 0, onEnterSubmit, }) {
11
+ const slashOpenRef = useRef(false);
12
+ const suggestionActiveUpdater = useCallback((active) => {
13
+ slashOpenRef.current = active;
14
+ }, []);
15
+ const slashFinger = JSON.stringify(slashCommandItems ?? null);
16
+ const slashItemsStable = useMemo(() => {
17
+ return slashCommandItems ?? DEFAULT_CHAT_SLASH_ITEMS;
18
+ }, [slashCommandItems, slashFinger]);
19
+ const placeholderText = useMemo(() => {
20
+ if (placeholder === undefined || placeholder === null) {
21
+ return 'Type a message…';
22
+ }
23
+ const t = String(placeholder).trim();
24
+ return t === '' ? 'Type a message…' : t;
25
+ }, [placeholder]);
26
+ const ariaLabelComposer = useMemo(() => {
27
+ if (placeholder === undefined || placeholder === null)
28
+ return 'Message';
29
+ const t = String(placeholder).trim();
30
+ return t === '' ? 'Message' : t;
31
+ }, [placeholder]);
32
+ const extensions = useMemo(() => {
33
+ const exts = [
34
+ StarterKit.configure({
35
+ heading: false,
36
+ bulletList: false,
37
+ orderedList: false,
38
+ blockquote: false,
39
+ codeBlock: false,
40
+ horizontalRule: false,
41
+ }),
42
+ Placeholder.configure({
43
+ placeholder: placeholderText,
44
+ showOnlyWhenEditable: true,
45
+ showOnlyCurrent: false,
46
+ }),
47
+ ];
48
+ if (slashItemsStable.length > 0) {
49
+ exts.push(createSlashMentionExtension({
50
+ items: slashItemsStable,
51
+ onSuggestionUiActiveChange: suggestionActiveUpdater,
52
+ }));
53
+ }
54
+ return exts;
55
+ }, [slashItemsStable, placeholderText, suggestionActiveUpdater]);
56
+ const [plainDraft, setPlainDraft] = useState('');
57
+ const editorDomRef = useRef(null);
58
+ const bindEditorDom = useCallback(({ editor }) => {
59
+ setPlainDraft(editor.getText());
60
+ queueMicrotask(() => {
61
+ const dom = chatPromptSafeEditorDom(editor);
62
+ if (!dom)
63
+ return;
64
+ editorDomRef.current = dom;
65
+ syncChatPromptComposerHeight(dom, editor.getText());
66
+ });
67
+ }, []);
68
+ const trimmedMessage = plainDraft.trim();
69
+ const editor = useEditor({
70
+ extensions,
71
+ content: CHAT_PROMPT_EMPTY_DOC,
72
+ editable: !disabled,
73
+ immediatelyRender: true,
74
+ editorProps: {
75
+ attributes: {
76
+ spellcheck: 'true',
77
+ 'aria-label': ariaLabelComposer,
78
+ },
79
+ },
80
+ onTransaction: ({ editor: ed }) => {
81
+ const dom = chatPromptSafeEditorDom(ed);
82
+ if (dom) {
83
+ syncChatPromptComposerHeight(dom, ed.getText());
84
+ }
85
+ setPlainDraft(ed.getText());
86
+ },
87
+ onCreate: bindEditorDom,
88
+ }, [extensions, disabled, bindEditorDom, ariaLabelComposer]);
89
+ useEffect(() => {
90
+ if (!editor)
91
+ return;
92
+ editor.setEditable(!disabled);
93
+ }, [editor, disabled]);
94
+ useEffect(() => {
95
+ if (!editor)
96
+ return;
97
+ const bind = () => {
98
+ editorDomRef.current = chatPromptSafeEditorDom(editor);
99
+ };
100
+ bind();
101
+ let rafId = null;
102
+ if (typeof window !== 'undefined') {
103
+ rafId = window.requestAnimationFrame(bind);
104
+ }
105
+ return () => {
106
+ if (rafId != null)
107
+ cancelAnimationFrame(rafId);
108
+ };
109
+ }, [editor]);
110
+ useEffect(() => {
111
+ if (!editor || prefillMessage == null || prefillMessage === '')
112
+ return;
113
+ editor.commands.setContent(chatPromptParagraphDoc(prefillMessage));
114
+ const t = editor.getText();
115
+ setPlainDraft(t);
116
+ queueMicrotask(() => {
117
+ const dom = chatPromptSafeEditorDom(editor);
118
+ if (dom)
119
+ syncChatPromptComposerHeight(dom, t);
120
+ });
121
+ }, [editor, prefillMessage]);
122
+ useLayoutEffect(() => {
123
+ if (!editor)
124
+ return;
125
+ const dom = chatPromptSafeEditorDom(editor);
126
+ if (!dom)
127
+ return;
128
+ syncChatPromptComposerHeight(dom, plainDraft);
129
+ }, [editor, plainDraft]);
130
+ const onEnterSubmitRef = useRef(onEnterSubmit);
131
+ onEnterSubmitRef.current = onEnterSubmit;
132
+ const resetAfterSend = useCallback(() => {
133
+ if (!editor)
134
+ return;
135
+ editor.commands.clearContent();
136
+ queueMicrotask(() => {
137
+ const dom = chatPromptSafeEditorDom(editor);
138
+ if (dom)
139
+ syncChatPromptComposerHeight(dom, '');
140
+ });
141
+ setPlainDraft('');
142
+ }, [editor]);
143
+ const handleComposerKeyDown = useCallback((e) => {
144
+ if (!(e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey))
145
+ return;
146
+ if (!editorDomRef.current?.contains(e.target))
147
+ return;
148
+ if (slashOpenRef.current)
149
+ return;
150
+ if (!trimmedMessage && attachmentsCount === 0)
151
+ return;
152
+ e.preventDefault();
153
+ onEnterSubmitRef.current();
154
+ }, [attachmentsCount, trimmedMessage]);
155
+ return {
156
+ editor,
157
+ trimmedMessage,
158
+ resetAfterSend,
159
+ handleComposerKeyDown,
160
+ };
161
+ }
162
+
163
+ export { useChatPromptEditor };
@@ -1,6 +1,6 @@
1
1
  import styleInject from 'style-inject';
2
2
 
3
- var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.Tooltip_tooltipContent__b3pS-{backdrop-filter:blur(10px);background-color:var(--popover);border:1px solid var(--border);border-radius:var(--p-3);box-shadow:0 10px 15px -3px rgba(0,0,0,.1);color:var(--popover-foreground);font-size:12px;padding:6px 12px;text-wrap:balance;transform-origin:var(--radix-tooltip-content-transform-origin);width:-moz-fit-content;width:fit-content;word-break:break-word;z-index:50}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-[data-state=instant-open],.Tooltip_tooltipContent__b3pS-[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=bottom]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-top-2__8uuS- .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=left]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-right-2__Uu79F .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=right]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-left-2__23kHm .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=top]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-bottom-2__O-Aa8 .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=top]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=instant-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in}.Tooltip_tooltipContentOverTrigger__VQAU3{box-sizing:border-box;height:auto}.Tooltip_tooltipArrow__87DVL{background-color:var(--popover);border-bottom:1px solid var(--border);border-left-width:1px;border-left:0 solid var(--border);border-radius:2px;border-right:1px solid var(--border);border-top-width:1px;border-top:0 solid var(--border);fill:var(--popover);height:10px;transform:translateY(calc(-50% + .5px)) rotate(45deg);width:10px;z-index:50}@keyframes Tooltip_fade-in__ZQqZv{0%{opacity:0}to{opacity:1}}@keyframes Tooltip_fade-out__UOBET{0%{opacity:1}to{opacity:0}}@keyframes Tooltip_zoom-in__SbWQb{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes Tooltip_zoom-out__fodOk{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes Tooltip_slide-in-from-top-2__8uuS-{0%{opacity:0;transform:translateY(-.5rem)}to{opacity:1;transform:translateY(0)}}@keyframes Tooltip_slide-in-from-right-2__Uu79F{0%{opacity:0;transform:translateX(.5rem)}to{opacity:1;transform:translateX(0)}}@keyframes Tooltip_slide-in-from-left-2__23kHm{0%{opacity:0;transform:translateX(-.5rem)}to{opacity:1;transform:translateX(0)}}@keyframes Tooltip_slide-in-from-bottom-2__O-Aa8{0%{opacity:0;transform:translateY(.5rem)}to{opacity:1;transform:translateY(0)}}";
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.Tooltip_tooltipContent__b3pS-{backdrop-filter:blur(10px);background-color:var(--popover);border:1px solid var(--border);border-radius:var(--p-3);box-shadow:0 10px 15px -3px rgba(0,0,0,.1);color:var(--popover-foreground);font-size:12px;min-width:100%;padding:6px 12px;text-wrap:balance;transform-origin:var(--radix-tooltip-content-transform-origin);width:-moz-fit-content;width:fit-content;word-break:break-word;z-index:50}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-[data-state=instant-open],.Tooltip_tooltipContent__b3pS-[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=bottom]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-top-2__8uuS- .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=left]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-right-2__Uu79F .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=right]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-left-2__23kHm .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=delayed-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=instant-open][data-side=top],.Tooltip_tooltipContent__b3pS-[data-state=open][data-side=top]{animation:Tooltip_fade-in__ZQqZv .15s ease-out,Tooltip_zoom-in__SbWQb .15s ease-out,Tooltip_slide-in-from-bottom-2__O-Aa8 .15s ease-out}.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=bottom],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=left],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=right],.Tooltip_tooltipContent__b3pS-[data-state=closed][data-side=top]{animation:Tooltip_fade-out__UOBET .1s ease-in,Tooltip_zoom-out__fodOk .1s ease-in}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=delayed-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=instant-open],.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=open]{animation:Tooltip_fade-in__ZQqZv .15s ease-out}.Tooltip_tooltipContent__b3pS-.Tooltip_tooltipContentOverTrigger__VQAU3[data-state=closed]{animation:Tooltip_fade-out__UOBET .1s ease-in}.Tooltip_tooltipContentOverTrigger__VQAU3{box-sizing:border-box;height:auto}.Tooltip_tooltipArrow__87DVL{background-color:var(--popover);border-bottom:1px solid var(--border);border-left-width:1px;border-left:0 solid var(--border);border-radius:2px;border-right:1px solid var(--border);border-top-width:1px;border-top:0 solid var(--border);fill:var(--popover);height:10px;transform:translateY(calc(-50% + .5px)) rotate(45deg);width:10px;z-index:50}@keyframes Tooltip_fade-in__ZQqZv{0%{opacity:0}to{opacity:1}}@keyframes Tooltip_fade-out__UOBET{0%{opacity:1}to{opacity:0}}@keyframes Tooltip_zoom-in__SbWQb{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes Tooltip_zoom-out__fodOk{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.95)}}@keyframes Tooltip_slide-in-from-top-2__8uuS-{0%{opacity:0;transform:translateY(-.5rem)}to{opacity:1;transform:translateY(0)}}@keyframes Tooltip_slide-in-from-right-2__Uu79F{0%{opacity:0;transform:translateX(.5rem)}to{opacity:1;transform:translateX(0)}}@keyframes Tooltip_slide-in-from-left-2__23kHm{0%{opacity:0;transform:translateX(-.5rem)}to{opacity:1;transform:translateX(0)}}@keyframes Tooltip_slide-in-from-bottom-2__O-Aa8{0%{opacity:0;transform:translateY(.5rem)}to{opacity:1;transform:translateY(0)}}";
4
4
  var S = {"tooltipContent":"Tooltip_tooltipContent__b3pS-","fade-in":"Tooltip_fade-in__ZQqZv","zoom-in":"Tooltip_zoom-in__SbWQb","fade-out":"Tooltip_fade-out__UOBET","zoom-out":"Tooltip_zoom-out__fodOk","slide-in-from-top-2":"Tooltip_slide-in-from-top-2__8uuS-","slide-in-from-right-2":"Tooltip_slide-in-from-right-2__Uu79F","slide-in-from-left-2":"Tooltip_slide-in-from-left-2__23kHm","slide-in-from-bottom-2":"Tooltip_slide-in-from-bottom-2__O-Aa8","tooltipContentOverTrigger":"Tooltip_tooltipContentOverTrigger__VQAU3","tooltipArrow":"Tooltip_tooltipArrow__87DVL"};
5
5
  styleInject(css_248z);
6
6
 
package/dist/esm/index.js CHANGED
@@ -7,6 +7,8 @@ export { SybilionAuthProvider, createSybilionApiFetch, getSybilionApiOriginFromS
7
7
  export { SYBILION_AUTH_LOGIN_PATH, normalizeApiBaseUrl } from './sybilion-auth/authPaths.js';
8
8
  export { exchangeAuth0AccessTokenForSybilionJwt } from './sybilion-auth/exchangeSybilionToken.js';
9
9
  export { ChatContext, ChatProvider, isChatEmpty, outboundPendingKey, useChat, useChatOutboundPending, useChats, useChatsForDataset, useChatsForScopeId, useCurrentChat, useIsChatLoading, useSyncChatPanelBusy } from './contexts/chat-context.js';
10
+ export { DEFAULT_CHAT_SLASH_ITEMS, filterSlashItems } from './tiptap/slash-mention/defaultChatSlashItems.js';
11
+ export { createSlashMentionExtension, slashMentionSuggestionRender } from './tiptap/slash-mention/createSlashMentionExtension.js';
10
12
  export { AnalysesSelector } from './components/ui/AnalysesSelector/AnalysesSelector.js';
11
13
  export { AnalysisLineIcon } from './components/ui/AnalysisLineIcon/AnalysisLineIcon.js';
12
14
  export { AppHeaderHost, AppHeaderPortal } from './components/ui/AppHeader/AppHeader.js';
@@ -0,0 +1,53 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import cn from 'classnames';
3
+ import { useState, useEffect, useCallback, useImperativeHandle } from 'react';
4
+ import S from './SlashSuggestionList.styl.js';
5
+
6
+ function SlashSuggestionListInner({ items, command, listHandleRef, }) {
7
+ const [selected, setSelected] = useState(0);
8
+ useEffect(() => {
9
+ setSelected(s => Math.min(s, Math.max(items.length - 1, 0)));
10
+ }, [items.length]);
11
+ const safeSel = Math.min(selected, Math.max(items.length - 1, 0));
12
+ const onPick = useCallback((index) => {
13
+ const item = items[index];
14
+ if (!item)
15
+ return;
16
+ command(item);
17
+ }, [command, items]);
18
+ const onKeyboardEvent = useCallback((event) => {
19
+ if (items.length === 0)
20
+ return false;
21
+ if (event.key === 'ArrowDown') {
22
+ event.preventDefault();
23
+ setSelected(s => Math.min(s + 1, items.length - 1));
24
+ return true;
25
+ }
26
+ if (event.key === 'ArrowUp') {
27
+ event.preventDefault();
28
+ setSelected(s => Math.max(s - 1, 0));
29
+ return true;
30
+ }
31
+ if (event.key === 'Enter') {
32
+ event.preventDefault();
33
+ const max = Math.max(items.length - 1, 0);
34
+ const idx = Math.min(selected, max);
35
+ const item = items[idx];
36
+ if (item)
37
+ command(item);
38
+ return true;
39
+ }
40
+ return false;
41
+ }, [command, items, selected]);
42
+ useImperativeHandle(listHandleRef, () => ({ onKeyboardEvent }), [
43
+ onKeyboardEvent,
44
+ ]);
45
+ return (jsx("div", { className: S.root, role: "listbox", "aria-label": "Slash commands", children: items.map((item, idx) => (jsxs("button", { type: "button", role: "option", "aria-selected": idx === safeSel, className: cn(S.item, idx === safeSel && S.itemHighlighted), onMouseDown: e => e.preventDefault(), onMouseEnter: () => setSelected(idx), onClick: () => onPick(idx), children: [jsxs("span", { className: S.itemLabel, children: ["/", item.label] }), item.description ? (jsx("span", { className: S.itemDesc, children: item.description })) : null] }, `${item.id}-${idx}`))) }));
46
+ }
47
+ function SlashSuggestionList(props) {
48
+ if (props.items.length === 0)
49
+ return null;
50
+ return (jsx(SlashSuggestionListInner, { items: props.items, command: props.command, listHandleRef: props.listHandleRef }));
51
+ }
52
+
53
+ export { SlashSuggestionList };
@@ -0,0 +1,7 @@
1
+ import styleInject from 'style-inject';
2
+
3
+ var css_248z = "@media (max-width:768px){:root{--page-x-padding:var(--p-6);--page-y-padding:var(--p-6)}}.SlashSuggestionList_root__9KCb1{background:var(--popover,var(--card));border:1px solid var(--border);border-radius:8px;border-radius:var(--radius-lg,8px);box-shadow:0 8px 24px rgba(0,0,0,.12);color:var(--foreground);display:flex;flex-direction:column;font-size:var(--text-sm);list-style:none;margin:0;max-height:40vh;max-width:90vw;min-width:220px;overflow-y:auto;padding:var(--p-1)}.dark .SlashSuggestionList_root__9KCb1{box-shadow:0 8px 24px rgba(0,0,0,.5)}.SlashSuggestionList_item__9JBeY{align-items:flex-start;background:transparent;border:none;border-radius:6px;border-radius:var(--radius-md,6px);color:inherit;cursor:pointer;display:flex;flex-direction:column;margin:0;padding:var(--p-1) var(--p-2);text-align:left;width:100%}.SlashSuggestionList_itemHighlighted__cStZr,.SlashSuggestionList_item__9JBeY:hover{background:var(--muted)}.SlashSuggestionList_itemLabel__OMaov{font-weight:600}.SlashSuggestionList_itemDesc__fAdcA{color:var(--muted-foreground);font-size:var(--text-xs)}";
4
+ var S = {"root":"SlashSuggestionList_root__9KCb1","item":"SlashSuggestionList_item__9JBeY","itemHighlighted":"SlashSuggestionList_itemHighlighted__cStZr","itemLabel":"SlashSuggestionList_itemLabel__OMaov","itemDesc":"SlashSuggestionList_itemDesc__fAdcA"};
5
+ styleInject(css_248z);
6
+
7
+ export { S as default };
@@ -0,0 +1,151 @@
1
+ import { mergeAttributes } from '@tiptap/core';
2
+ import Mention from '@tiptap/extension-mention';
3
+ import { ReactRenderer } from '@tiptap/react';
4
+ import { SlashSuggestionList } from './SlashSuggestionList.js';
5
+ import { filterSlashItems } from './defaultChatSlashItems.js';
6
+
7
+ function slashMentionSuggestionRender(uiRef) {
8
+ let popup = null;
9
+ const place = (props) => {
10
+ if (!popup?.element)
11
+ return;
12
+ const rect = props.clientRect?.();
13
+ if (!rect)
14
+ return;
15
+ const el = popup.element;
16
+ el.style.left = `${rect.left}px`;
17
+ el.style.top = `${rect.bottom + 4}px`;
18
+ };
19
+ return {
20
+ onStart: props => {
21
+ uiRef.current = null;
22
+ popup?.destroy?.();
23
+ popup = new ReactRenderer(SlashSuggestionList, {
24
+ editor: props.editor,
25
+ props: {
26
+ items: props.items,
27
+ command: props.command,
28
+ listHandleRef: uiRef,
29
+ },
30
+ });
31
+ popup.element.style.position = 'fixed';
32
+ popup.element.style.margin = '0';
33
+ popup.element.style.zIndex = '10002';
34
+ document.body.append(popup.element);
35
+ place(props);
36
+ },
37
+ onUpdate: props => {
38
+ if (!popup)
39
+ return;
40
+ popup.updateProps({
41
+ items: props.items,
42
+ command: props.command,
43
+ listHandleRef: uiRef,
44
+ });
45
+ place(props);
46
+ },
47
+ onExit: () => {
48
+ popup?.destroy();
49
+ popup?.element?.remove?.();
50
+ popup = null;
51
+ uiRef.current = null;
52
+ },
53
+ onKeyDown: ({ event }) => uiRef.current?.onKeyboardEvent(event) ?? false,
54
+ };
55
+ }
56
+ function insertDefaultMention(editor, range, props, slashChar) {
57
+ const nodeAfter = editor.view.state.selection.$to.nodeAfter;
58
+ const extend = nodeAfter?.text?.startsWith(' ') ? 1 : 0;
59
+ const adjusted = extend ? { ...range, to: range.to + extend } : range;
60
+ editor
61
+ .chain()
62
+ .focus()
63
+ .insertContentAt(adjusted, [
64
+ {
65
+ type: 'mention',
66
+ attrs: {
67
+ id: props.id,
68
+ label: props.label,
69
+ mentionSuggestionChar: slashChar,
70
+ },
71
+ },
72
+ { type: 'text', text: ' ' },
73
+ ])
74
+ .run();
75
+ queueMicrotask(() => {
76
+ editor.view.dom.ownerDocument?.defaultView
77
+ ?.getSelection?.()
78
+ ?.collapseToEnd();
79
+ });
80
+ }
81
+ function allowSlashTrigger({ state, range, }) {
82
+ const type = state.schema.nodes['mention'];
83
+ const $from = state.doc.resolve(range.from);
84
+ if (!type || !$from.parent.type.contentMatch.matchType(type))
85
+ return false;
86
+ if ($from.parentOffset === 0)
87
+ return true;
88
+ const before = state.doc.textBetween(range.from - 1, range.from);
89
+ return /\s/.test(before);
90
+ }
91
+ function createSlashMentionExtension({ items: resolvedItems, slashChar = '/', pluginKey, onItemCommand, onSuggestionUiActiveChange, }) {
92
+ const uiRef = {
93
+ current: null,
94
+ };
95
+ return Mention.configure({
96
+ renderText({ node }) {
97
+ return `/${node.attrs.id}`;
98
+ },
99
+ renderHTML({ options, node, suggestion }) {
100
+ const suggestionChar = suggestion?.char ?? slashChar;
101
+ const id = typeof node.attrs.id === 'string'
102
+ ? node.attrs.id
103
+ : String(node.attrs.id ?? '');
104
+ return [
105
+ 'span',
106
+ mergeAttributes({
107
+ 'data-type': 'mention',
108
+ 'data-slash-command': id,
109
+ class: 'slash-mention',
110
+ }, options.HTMLAttributes),
111
+ `${suggestionChar}${id}`,
112
+ ];
113
+ },
114
+ suggestion: {
115
+ char: slashChar,
116
+ pluginKey,
117
+ allowedPrefixes: [' ', '\n', '\t', '\r'],
118
+ allow: props => allowSlashTrigger(props),
119
+ items: ({ query }) => Promise.resolve(filterSlashItems(resolvedItems, query)),
120
+ command: ({ editor, range, props }) => {
121
+ const item = props;
122
+ if (onItemCommand?.({ editor, range, item }) === true) {
123
+ queueMicrotask(() => {
124
+ editor.view.dom.ownerDocument?.defaultView
125
+ ?.getSelection?.()
126
+ ?.collapseToEnd();
127
+ });
128
+ return null;
129
+ }
130
+ insertDefaultMention(editor, range, item, slashChar);
131
+ return null;
132
+ },
133
+ render: () => {
134
+ const menu = slashMentionSuggestionRender(uiRef);
135
+ return {
136
+ ...menu,
137
+ onStart: props => {
138
+ onSuggestionUiActiveChange?.(true);
139
+ menu.onStart?.(props);
140
+ },
141
+ onExit: props => {
142
+ menu.onExit?.(props);
143
+ onSuggestionUiActiveChange?.(false);
144
+ },
145
+ };
146
+ },
147
+ },
148
+ });
149
+ }
150
+
151
+ export { createSlashMentionExtension, slashMentionSuggestionRender };