@sybilion/uilib 1.3.37 → 1.3.39
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/esm/components/ui/Chat/Chat.types.js +1 -3
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +7 -10
- package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +2 -3
- package/dist/esm/components/ui/Chat/ChatPrompt/ChatPrompt.js +2 -1
- package/dist/esm/components/ui/Chat/ChatPrompt/useChatPromptEditor.js +5 -2
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -2
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +12 -42
- package/dist/esm/index.js +1 -1
- package/dist/esm/tiptap/slash-mention/createSlashMentionExtension.js +24 -5
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +3 -3
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +3 -6
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/ChatPrompt.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.d.ts +3 -2
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +4 -4
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +2 -2
- package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +5 -3
- package/package.json +1 -1
- package/src/components/ui/Chat/Chat.types.ts +6 -4
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +24 -46
- package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +6 -8
- package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +2 -13
- package/src/components/ui/Chat/ChatPrompt/ChatPrompt.tsx +2 -0
- package/src/components/ui/Chat/ChatPrompt/useChatPromptEditor.ts +10 -2
- package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -2
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +23 -58
- package/src/components/ui/Chat/index.ts +9 -2
- package/src/docs/pages/ChatAttachmentsDropzonePage.tsx +0 -5
- package/src/docs/pages/ChatPage.tsx +0 -5
- package/src/docs/pages/ChatSlashCommandsPage.tsx +40 -16
- package/src/docs/pages/ChatUserCsvAttachmentPage.tsx +0 -5
- package/src/tiptap/slash-mention/createSlashMentionExtension.ts +24 -5
- package/src/tiptap/slash-mention/types.ts +5 -3
|
@@ -4,7 +4,5 @@ var MessageRole;
|
|
|
4
4
|
MessageRole["ASSISTANT"] = "assistant";
|
|
5
5
|
MessageRole["SYSTEM"] = "system";
|
|
6
6
|
})(MessageRole || (MessageRole = {}));
|
|
7
|
-
/** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
|
|
8
|
-
const GENERATING_DASHBOARD_SYSTEM_TEXT = 'Generating dashboard…';
|
|
9
7
|
|
|
10
|
-
export {
|
|
8
|
+
export { MessageRole };
|
|
@@ -4,7 +4,7 @@ import { useMemo, useState, useCallback, useEffect } from 'react';
|
|
|
4
4
|
import { displayLabelForBranchKeyFromMessages, humanizeBranchKey } from '../ChatMessage/presetScript.js';
|
|
5
5
|
import { TextShimmer } from '../../TextShimmer/TextShimmer.js';
|
|
6
6
|
import { Scroll } from '@homecode/ui';
|
|
7
|
-
import { X, PaperPlaneRightIcon
|
|
7
|
+
import { X, PaperPlaneRightIcon } from '@phosphor-icons/react';
|
|
8
8
|
import { Button } from '../../Button/Button.js';
|
|
9
9
|
import { DropZone } from '../../DropZone/DropZone.js';
|
|
10
10
|
import { PanelResizeHandle } from '../../Sidebar/Sidebar.js';
|
|
@@ -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,
|
|
17
|
+
function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments = false, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, 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]);
|
|
@@ -73,14 +73,11 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
|
|
|
73
73
|
: undefined, onScriptContinue: isLast && scriptContinueLabel
|
|
74
74
|
? onScriptContinue
|
|
75
75
|
: undefined, renderMessageChart: renderMessageChart }, msg.id));
|
|
76
|
-
}),
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
: null, isScriptComplete &&
|
|
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, placeholder: promptPlaceholder, slashCommandItems: slashCommandItems, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
|
|
76
|
+
}), showSyntheticBranchButtons ? (jsx("div", { className: S.branchRow, children: unusedBranchKeys.map(key => {
|
|
77
|
+
const label = displayLabelForBranchKeyFromMessages(key, messages) ??
|
|
78
|
+
humanizeBranchKey(key);
|
|
79
|
+
return (jsx("span", { className: S.branchBtnWrap, children: jsxs(Button, { type: "button", variant: "outline", size: "sm", disabled: isLoading, onClick: () => onQuickReply(key, label), children: [jsx(PaperPlaneRightIcon, {}), label] }) }, key));
|
|
80
|
+
}) })) : 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, onSlashItemCommand: onSlashItemCommand, attachmentAccept: attachmentsDropzoneEnabled ? attachmentAccept : undefined, onAttachmentFiles: attachmentsDropzoneEnabled ? handleAttachmentFiles : undefined })] })] })] })] }));
|
|
84
81
|
}
|
|
85
82
|
|
|
86
83
|
export { ChatChrome };
|
|
@@ -3,8 +3,7 @@ import cn from 'classnames';
|
|
|
3
3
|
import { InteractiveContent } from '../../InteractiveContent/InteractiveContent.js';
|
|
4
4
|
import 'lucide-react';
|
|
5
5
|
import '../../InteractiveContent/InteractiveContent.styl.js';
|
|
6
|
-
import {
|
|
7
|
-
import { MessageRole, GENERATING_DASHBOARD_SYSTEM_TEXT } from '../Chat.types.js';
|
|
6
|
+
import { MessageRole } from '../Chat.types.js';
|
|
8
7
|
import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments.js';
|
|
9
8
|
import { AgentMessageContent } from './AgentMessageContent.js';
|
|
10
9
|
import S from './ChatMessage.styl.js';
|
|
@@ -16,7 +15,7 @@ function ChatMessage({ role, text, userTextFileAttachments, onQuickReply, suppre
|
|
|
16
15
|
});
|
|
17
16
|
const isAssistant = role === MessageRole.ASSISTANT;
|
|
18
17
|
const isSystem = role === MessageRole.SYSTEM;
|
|
19
|
-
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text
|
|
18
|
+
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: text })) : isAssistant ? (jsx(AgentMessageContent, { text: text, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: quickReplyDisabled, isLastMessage: isLastMessage, scriptContinue: scriptContinue, onScriptContinue: onScriptContinue, renderMessageChart: renderMessageChart })) : (jsxs("div", { className: S.userColumn, children: [jsx("div", { className: S.text, children: jsx(InteractiveContent, { text: text }) }), fileAttachments.map(attachment => (jsx(UserTextFileAttachmentBubble, { attachment: attachment }, `${attachment.displayName}:${attachment.filename}`)))] })) }));
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
export { ChatMessage };
|
|
@@ -6,13 +6,14 @@ import { ChatPromptAttachments } from './ChatPromptAttachments.js';
|
|
|
6
6
|
import { ChatPromptComposer } from './ChatPromptComposer.js';
|
|
7
7
|
import { useChatPromptEditor } from './useChatPromptEditor.js';
|
|
8
8
|
|
|
9
|
-
function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, slashCommandItems, attachments = [], onRemoveAttachment, disabled = false, attachmentAccept, onAttachmentFiles, }) {
|
|
9
|
+
function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, slashCommandItems, onSlashItemCommand, attachments = [], onRemoveAttachment, disabled = false, attachmentAccept, onAttachmentFiles, }) {
|
|
10
10
|
const attachmentsCount = attachments.length;
|
|
11
11
|
const emitSubmitRef = useRef(() => { });
|
|
12
12
|
const { editor, trimmedMessage, resetAfterSend, handleComposerKeyDown } = useChatPromptEditor({
|
|
13
13
|
disabled,
|
|
14
14
|
placeholder,
|
|
15
15
|
slashCommandItems,
|
|
16
|
+
onSlashItemCommand,
|
|
16
17
|
prefillMessage,
|
|
17
18
|
attachmentsCount,
|
|
18
19
|
onEnterSubmit: () => emitSubmitRef.current(),
|
|
@@ -7,8 +7,10 @@ import StarterKit from '@tiptap/starter-kit';
|
|
|
7
7
|
import { chatPromptSafeEditorDom, syncChatPromptComposerHeight } from './ChatPrompt.helpers.js';
|
|
8
8
|
import { CHAT_PROMPT_EMPTY_DOC, chatPromptParagraphDoc } from './chatPromptDoc.js';
|
|
9
9
|
|
|
10
|
-
function useChatPromptEditor({ disabled, placeholder, slashCommandItems, prefillMessage, attachmentsCount = 0, onEnterSubmit, }) {
|
|
10
|
+
function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlashItemCommand, prefillMessage, attachmentsCount = 0, onEnterSubmit, }) {
|
|
11
11
|
const slashOpenRef = useRef(false);
|
|
12
|
+
const onSlashItemCommandRef = useRef(onSlashItemCommand);
|
|
13
|
+
onSlashItemCommandRef.current = onSlashItemCommand;
|
|
12
14
|
const suggestionActiveUpdater = useCallback((active) => {
|
|
13
15
|
slashOpenRef.current = active;
|
|
14
16
|
}, []);
|
|
@@ -50,6 +52,7 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, prefill
|
|
|
50
52
|
items: slashItemsStable,
|
|
51
53
|
suggestionPlacement: 'above',
|
|
52
54
|
onSuggestionUiActiveChange: suggestionActiveUpdater,
|
|
55
|
+
onItemCommand: ctx => onSlashItemCommandRef.current?.(ctx) === true,
|
|
53
56
|
}));
|
|
54
57
|
}
|
|
55
58
|
return exts;
|
|
@@ -86,7 +89,7 @@ function useChatPromptEditor({ disabled, placeholder, slashCommandItems, prefill
|
|
|
86
89
|
setPlainDraft(ed.getText());
|
|
87
90
|
},
|
|
88
91
|
onCreate: bindEditorDom,
|
|
89
|
-
}, [extensions,
|
|
92
|
+
}, [extensions, bindEditorDom, ariaLabelComposer]);
|
|
90
93
|
useEffect(() => {
|
|
91
94
|
if (!editor)
|
|
92
95
|
return;
|
|
@@ -4,20 +4,20 @@ import { Button } from '../../Button/Button.js';
|
|
|
4
4
|
import { ChatChrome } from '../ChatChrome/ChatChrome.js';
|
|
5
5
|
import { useChatPanelChromeModel } from './useChatPanelChromeModel.js';
|
|
6
6
|
|
|
7
|
-
function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete,
|
|
7
|
+
function ChatSheet({ triggerLabel = 'Open Chat', triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, inline = false, }) {
|
|
8
8
|
const model = useChatPanelChromeModel({
|
|
9
9
|
embedAsPage: inline,
|
|
10
10
|
presets,
|
|
11
11
|
scopeId,
|
|
12
12
|
onMessage,
|
|
13
13
|
onScriptComplete,
|
|
14
|
-
onGenerateDashboard,
|
|
15
14
|
renderMessageChart,
|
|
16
15
|
emptyState,
|
|
17
16
|
allowedAttachments,
|
|
18
17
|
allowPdfAttachments,
|
|
19
18
|
onAttachmentsDropped,
|
|
20
19
|
slashCommandItems,
|
|
20
|
+
onSlashItemCommand,
|
|
21
21
|
});
|
|
22
22
|
if (actionsRef) {
|
|
23
23
|
actionsRef.current = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
3
|
-
import { MessageRole
|
|
3
|
+
import { MessageRole } from '../Chat.types.js';
|
|
4
4
|
import { isGraphIntakeAssistantStepComplete, matchUserTextToQuickReply, isPresetScriptGraph, branchesFromPresetScriptGraph, parseScriptLine, textHasQuickReplyMarkers, branchKeysUsedFromChatHistory, branchKeysUsedByUserMessages, extractQuickReplyLabelKeyPairsFromText, entryBranchKeyBeforeLastAssistant } from '../ChatMessage/presetScript.js';
|
|
5
5
|
import { buildChatSendMessagePayload, displayTextFromSendPayload } from '../buildChatSendMessagePayload.js';
|
|
6
|
-
import { usedPresetIdsFromMessages
|
|
6
|
+
import { usedPresetIdsFromMessages } from '../chat-preset-utils.js';
|
|
7
7
|
import { useChatsForScopeId, useChat, useChatOutboundPending, useSyncChatPanelBusy, isChatEmpty } from '../../../../contexts/chat-context.js';
|
|
8
8
|
import useEvent from '../../../../hooks/useEvent.js';
|
|
9
9
|
import { useIsMobile } from '../../../../hooks/useIsMobile.js';
|
|
@@ -22,7 +22,7 @@ const CHAT_NAV_COLLAPSE_BREAKPOINT_PX = 1400;
|
|
|
22
22
|
const CHAT_QUERY_PARAM = 'chat';
|
|
23
23
|
const CHAT_OPEN_VALUE = 'open';
|
|
24
24
|
const PROMPT_QUERY_PARAM = 'prompt';
|
|
25
|
-
function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete,
|
|
25
|
+
function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, }) {
|
|
26
26
|
const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
|
|
27
27
|
const isMobile = useIsMobile();
|
|
28
28
|
const { chatPanelContainer, isOpen: sidebarNavOpen, setOpen: setSidebarNavOpen, chatWidthPx, setChatWidthPx, getShellWidth, setChatPanelOpen, } = useSidebar();
|
|
@@ -58,9 +58,8 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
58
58
|
const [quickReplyBranchesByChat, setQuickReplyBranchesByChat] = useState({});
|
|
59
59
|
const [usedScriptBranchKeysByChat, setUsedScriptBranchKeysByChat] = useState({});
|
|
60
60
|
const [intakeByChatId, setIntakeByChatId] = useState({});
|
|
61
|
-
/** Preset intake finished for this session
|
|
61
|
+
/** Preset intake finished for this session (e.g. `onScriptComplete` callback). */
|
|
62
62
|
const [scriptCompleteByChatId, setScriptCompleteByChatId] = useState({});
|
|
63
|
-
const [generatingDashboard, setGeneratingDashboard] = useState(false);
|
|
64
63
|
const scriptAdvanceLockRef = useRef(false);
|
|
65
64
|
const quickReplyLockRef = useRef(false);
|
|
66
65
|
const scrollRef = useRef(null);
|
|
@@ -712,37 +711,14 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
712
711
|
lastMsg?.role === MessageRole.ASSISTANT &&
|
|
713
712
|
!linearScriptActive &&
|
|
714
713
|
(!graphActive || (!lastHasQuickMarkers && unusedBranchKeys.length === 0));
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
(
|
|
719
|
-
const handleGenerateDashboard = useCallback(async () => {
|
|
720
|
-
if (!currentChatId || !onGenerateDashboard)
|
|
714
|
+
const onPromptSubmitWithSlashCommands = useCallback(async (message, attachments) => {
|
|
715
|
+
const trimmed = message.trim();
|
|
716
|
+
const slashItem = slashCommandItems?.find(item => trimmed === `/${item.id}`);
|
|
717
|
+
if (slashItem && onSlashItemCommand?.({ item: slashItem }) === true) {
|
|
721
718
|
return;
|
|
722
|
-
const transcript = formatChatTranscript((chat?.messages ?? []).filter(m => m.role !== MessageRole.SYSTEM));
|
|
723
|
-
setGeneratingDashboard(true);
|
|
724
|
-
const systemPlaceholderId = addMessage(currentChatId, MessageRole.SYSTEM, GENERATING_DASHBOARD_SYSTEM_TEXT);
|
|
725
|
-
try {
|
|
726
|
-
await Promise.resolve(onGenerateDashboard(transcript));
|
|
727
|
-
setScriptCompleteByChatId(prev => {
|
|
728
|
-
const next = { ...prev };
|
|
729
|
-
delete next[currentChatId];
|
|
730
|
-
return next;
|
|
731
|
-
});
|
|
732
719
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
removeMessageById(currentChatId, systemPlaceholderId);
|
|
736
|
-
}
|
|
737
|
-
setGeneratingDashboard(false);
|
|
738
|
-
}
|
|
739
|
-
}, [
|
|
740
|
-
currentChatId,
|
|
741
|
-
onGenerateDashboard,
|
|
742
|
-
chat?.messages,
|
|
743
|
-
addMessage,
|
|
744
|
-
removeMessageById,
|
|
745
|
-
]);
|
|
720
|
+
return handlePromptSubmit(message, attachments);
|
|
721
|
+
}, [handlePromptSubmit, slashCommandItems, onSlashItemCommand]);
|
|
746
722
|
const onDragChatWidth = useCallback((rawPx) => setChatWidthPx(rawPx, { persist: false }), [setChatWidthPx]);
|
|
747
723
|
const onDragChatComplete = useCallback((finalRawPx) => setChatWidthPx(finalRawPx, { persist: true }), [setChatWidthPx]);
|
|
748
724
|
const chromeProps = {
|
|
@@ -766,20 +742,13 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
766
742
|
scriptContinueLabel,
|
|
767
743
|
onScriptContinue,
|
|
768
744
|
renderMessageChart,
|
|
769
|
-
showBranchActionsRow,
|
|
770
745
|
showSyntheticBranchButtons,
|
|
771
746
|
unusedBranchKeys,
|
|
772
|
-
isScriptComplete,
|
|
773
|
-
onGenerateDashboard,
|
|
774
|
-
generatingDashboard,
|
|
775
|
-
onGenerateDashboardClick: () => {
|
|
776
|
-
void handleGenerateDashboard();
|
|
777
|
-
},
|
|
778
747
|
showInlinePresets,
|
|
779
748
|
isLastMessageFromUser,
|
|
780
749
|
scrollRef,
|
|
781
750
|
effectiveScopeId,
|
|
782
|
-
onPromptSubmit:
|
|
751
|
+
onPromptSubmit: onPromptSubmitWithSlashCommands,
|
|
783
752
|
onChatDeleted: endLocalDemoFlow,
|
|
784
753
|
promptPrefill: promptLinkPrefill,
|
|
785
754
|
emptyState: resolvedEmptyState,
|
|
@@ -787,6 +756,7 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
787
756
|
allowPdfAttachments,
|
|
788
757
|
onAttachmentsDropped,
|
|
789
758
|
slashCommandItems,
|
|
759
|
+
onSlashItemCommand,
|
|
790
760
|
};
|
|
791
761
|
const toggleOpen = () => onOpenChange(!isOpen);
|
|
792
762
|
return {
|
package/dist/esm/index.js
CHANGED
|
@@ -22,7 +22,7 @@ export { ChartLegend, ChartTooltip } from './components/ui/Chart/Chart.js';
|
|
|
22
22
|
export { THEMES } from './components/ui/Chart/Chart.types.js';
|
|
23
23
|
export { ChartAreaInteractive, chartConfig } from './components/ui/ChartAreaInteractive/ChartAreaInteractive.js';
|
|
24
24
|
export { Chat } from './components/ui/Chat/Chat.js';
|
|
25
|
-
export { usedPresetIdsFromMessages } from './components/ui/Chat/chat-preset-utils.js';
|
|
25
|
+
export { formatChatTranscript, usedPresetIdsFromMessages } from './components/ui/Chat/chat-preset-utils.js';
|
|
26
26
|
export { ChatChrome } from './components/ui/Chat/ChatChrome/ChatChrome.js';
|
|
27
27
|
export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments } from './components/ui/Chat/chatAttachmentAccept.js';
|
|
28
28
|
export { buildChatSendMessagePayload, displayTextFromSendPayload, normalizeUserTextFileAttachments } from './components/ui/Chat/buildChatSendMessagePayload.js';
|
|
@@ -68,6 +68,28 @@ function slashMentionSuggestionRender(uiRef, placement = 'below') {
|
|
|
68
68
|
onKeyDown: ({ event }) => uiRef.current?.onKeyboardEvent(event) ?? false,
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
|
+
function clearSlashTriggerEditor(editor, range) {
|
|
72
|
+
if (editor.isDestroyed)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
editor.chain().focus().deleteRange(range).clearContent().run();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Editor view may be tearing down during suggestion exit.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function collapseEditorSelectionEnd(editor) {
|
|
82
|
+
if (editor.isDestroyed)
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
editor.view?.dom?.ownerDocument?.defaultView
|
|
86
|
+
?.getSelection?.()
|
|
87
|
+
?.collapseToEnd();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// view.dom throws when editor is not mounted
|
|
91
|
+
}
|
|
92
|
+
}
|
|
71
93
|
function insertDefaultMention(editor, range, props, slashChar) {
|
|
72
94
|
const nodeAfter = editor.view.state.selection.$to.nodeAfter;
|
|
73
95
|
const extend = nodeAfter?.text?.startsWith(' ') ? 1 : 0;
|
|
@@ -135,11 +157,8 @@ function createSlashMentionExtension({ items: resolvedItems, slashChar = '/', pl
|
|
|
135
157
|
command: ({ editor, range, props }) => {
|
|
136
158
|
const item = props;
|
|
137
159
|
if (onItemCommand?.({ editor, range, item }) === true) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
?.getSelection?.()
|
|
141
|
-
?.collapseToEnd();
|
|
142
|
-
});
|
|
160
|
+
clearSlashTriggerEditor(editor, range);
|
|
161
|
+
queueMicrotask(() => collapseEditorSelectionEnd(editor));
|
|
143
162
|
return null;
|
|
144
163
|
}
|
|
145
164
|
insertDefaultMention(editor, range, item, slashChar);
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
-
import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
|
|
2
|
+
import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
|
|
3
3
|
import type { PresetScriptGraph } from './ChatMessage/presetScript';
|
|
4
4
|
export declare enum MessageRole {
|
|
5
5
|
USER = "user",
|
|
6
6
|
ASSISTANT = "assistant",
|
|
7
7
|
SYSTEM = "system"
|
|
8
8
|
}
|
|
9
|
-
/** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
|
|
10
|
-
export declare const GENERATING_DASHBOARD_SYSTEM_TEXT = "Generating dashboard\u2026";
|
|
11
9
|
/** USER-only: text file attached to a message; shown as downloadable file row(s). */
|
|
12
10
|
export type UserTextFileAttachment = {
|
|
13
11
|
displayName: string;
|
|
@@ -84,6 +82,8 @@ export interface ChatPromptProps {
|
|
|
84
82
|
onAttachmentFiles?: (files: File[]) => void;
|
|
85
83
|
/** Slash menu (`/`); omit or pass empty to disable Mention trigger (no default items). */
|
|
86
84
|
slashCommandItems?: SlashCommandItem[];
|
|
85
|
+
/** Custom slash pick handler; return true to skip default mention insert. */
|
|
86
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
87
87
|
}
|
|
88
88
|
export interface ChatMessageProps {
|
|
89
89
|
role: MessageRole;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChatChromeProps } from './ChatChrome.types';
|
|
2
|
-
export declare function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart,
|
|
2
|
+
export declare function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPresets, messages, onQuickReply, suppressedQuickReplyKeys, isLoading, scriptContinueLabel, onScriptContinue, renderMessageChart, showSyntheticBranchButtons, unusedBranchKeys, showInlinePresets, isLastMessageFromUser, scrollRef, effectiveScopeId, onPromptSubmit, onChatDeleted, promptPrefill, footerClassName, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, promptPlaceholder, }: ChatChromeProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -2,7 +2,7 @@ import type { RefObject } from 'react';
|
|
|
2
2
|
import type { ChatAttachmentDropItem, Message } from '#uilib/components/ui/Chat/Chat.types';
|
|
3
3
|
import type { ChatEmptyStateProps } from '#uilib/components/ui/Chat/ChatEmptyState/ChatEmptyState.types';
|
|
4
4
|
import type { ChatPresetsLayout } from '#uilib/components/ui/Chat/ChatPresets';
|
|
5
|
-
import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
|
|
5
|
+
import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
|
|
6
6
|
import type { ScrollRef } from '@homecode/ui';
|
|
7
7
|
export type ChatChromeResizeHandleConfig = {
|
|
8
8
|
isActive: boolean;
|
|
@@ -26,13 +26,8 @@ export interface ChatChromeProps {
|
|
|
26
26
|
scriptContinueLabel: string | undefined;
|
|
27
27
|
onScriptContinue: (() => void) | undefined;
|
|
28
28
|
renderMessageChart?: () => React.ReactNode;
|
|
29
|
-
showBranchActionsRow: boolean;
|
|
30
29
|
showSyntheticBranchButtons: boolean;
|
|
31
30
|
unusedBranchKeys: string[];
|
|
32
|
-
isScriptComplete: boolean;
|
|
33
|
-
onGenerateDashboard: ((transcript: string) => void | Promise<void>) | undefined;
|
|
34
|
-
generatingDashboard: boolean;
|
|
35
|
-
onGenerateDashboardClick: () => void;
|
|
36
31
|
showInlinePresets: boolean;
|
|
37
32
|
isLastMessageFromUser: boolean;
|
|
38
33
|
scrollRef: RefObject<ScrollRef | null>;
|
|
@@ -51,6 +46,8 @@ export interface ChatChromeProps {
|
|
|
51
46
|
onAttachmentsDropped?: (items: ChatAttachmentDropItem[]) => void | Promise<void>;
|
|
52
47
|
/** Slash menu (`/`), forwarded to `Chat.Prompt`; omit or pass empty list to disable slash palette. */
|
|
53
48
|
slashCommandItems?: SlashCommandItem[];
|
|
49
|
+
/** Custom slash pick handler; forwarded to `Chat.Prompt`. */
|
|
50
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
54
51
|
/** Composer placeholder forwarded to `Chat.Prompt`. */
|
|
55
52
|
promptPlaceholder?: string;
|
|
56
53
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ChatPromptProps } from '../Chat.types';
|
|
2
|
-
export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, slashCommandItems, attachments, onRemoveAttachment, disabled, attachmentAccept, onAttachmentFiles, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ChatPrompt({ onSubmit, placeholder, className, footer, prefillMessage, slashCommandItems, onSlashItemCommand, attachments, onRemoveAttachment, disabled, attachmentAccept, onAttachmentFiles, }: ChatPromptProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { type KeyboardEvent as ReactKeyboardEvent } from 'react';
|
|
2
|
-
import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
|
|
2
|
+
import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
|
|
3
3
|
import type { Editor } from '@tiptap/core';
|
|
4
4
|
export type UseChatPromptEditorOptions = {
|
|
5
5
|
disabled: boolean;
|
|
6
6
|
placeholder?: string;
|
|
7
7
|
slashCommandItems?: SlashCommandItem[];
|
|
8
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
8
9
|
prefillMessage?: string | null;
|
|
9
10
|
/** Staged attachment count — Enter-to-send when text empty but files present. */
|
|
10
11
|
attachmentsCount?: number;
|
|
@@ -17,4 +18,4 @@ export type UseChatPromptEditorResult = {
|
|
|
17
18
|
resetAfterSend: () => void;
|
|
18
19
|
handleComposerKeyDown: (event: ReactKeyboardEvent) => void;
|
|
19
20
|
};
|
|
20
|
-
export declare function useChatPromptEditor({ disabled, placeholder, slashCommandItems, prefillMessage, attachmentsCount, onEnterSubmit, }: UseChatPromptEditorOptions): UseChatPromptEditorResult;
|
|
21
|
+
export declare function useChatPromptEditor({ disabled, placeholder, slashCommandItems, onSlashItemCommand, prefillMessage, attachmentsCount, onEnterSubmit, }: UseChatPromptEditorOptions): UseChatPromptEditorResult;
|
|
@@ -19,4 +19,4 @@ export interface ChatSheetProps extends Omit<UseChatPanelChromeModelInput, 'embe
|
|
|
19
19
|
*/
|
|
20
20
|
inline?: boolean;
|
|
21
21
|
}
|
|
22
|
-
export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete,
|
|
22
|
+
export declare function ChatSheet({ triggerLabel, triggerAriaLabel, actionsRef, renderTrigger, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, inline, }: ChatSheetProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ChatPreset, type ScriptCompletePayload } from '#uilib/components/ui/Chat/Chat.types';
|
|
2
|
-
import type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
|
|
2
|
+
import type { SlashCommandItem, SlashOnItemCommand } from '#uilib/tiptap/slash-mention/types';
|
|
3
3
|
import type { ChatChromeProps } from '../ChatChrome';
|
|
4
4
|
import type { ChatAttachmentDropItem } from '../ChatChrome/ChatChrome.types';
|
|
5
5
|
import type { ChatEmptyStateConfig } from '../ChatEmptyState/ChatEmptyState.types';
|
|
@@ -12,8 +12,6 @@ export type UseChatPanelChromeModelInput = {
|
|
|
12
12
|
onMessage?: (message: string) => void;
|
|
13
13
|
/** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
|
|
14
14
|
onScriptComplete?: (payload: ScriptCompletePayload) => void;
|
|
15
|
-
/** After intake script completes, user can generate a dashboard from the full transcript (e.g. Decision board). */
|
|
16
|
-
onGenerateDashboard?: (transcript: string) => void | Promise<void>;
|
|
17
15
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
18
16
|
renderMessageChart?: () => React.ReactNode;
|
|
19
17
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
@@ -25,6 +23,8 @@ export type UseChatPanelChromeModelInput = {
|
|
|
25
23
|
onAttachmentsDropped?: (items: ChatAttachmentDropItem[]) => void | Promise<void>;
|
|
26
24
|
/** Slash menu (`/`) in the composer; omit or pass empty to disable. */
|
|
27
25
|
slashCommandItems?: SlashCommandItem[];
|
|
26
|
+
/** Custom slash command handler (palette pick or Enter on `/id`). */
|
|
27
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
28
28
|
};
|
|
29
29
|
export type UseChatPanelChromeModelResult = {
|
|
30
30
|
chromeProps: ChatChromeProps;
|
|
@@ -34,4 +34,4 @@ export type UseChatPanelChromeModelResult = {
|
|
|
34
34
|
newChat: () => void;
|
|
35
35
|
chatPanelContainer: HTMLElement | null;
|
|
36
36
|
};
|
|
37
|
-
export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete,
|
|
37
|
+
export declare function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onScriptComplete, renderMessageChart, emptyState, allowedAttachments, allowPdfAttachments, onAttachmentsDropped, slashCommandItems, onSlashItemCommand, }: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { Chat } from './Chat';
|
|
2
|
-
export { usedPresetIdsFromMessages } from './chat-preset-utils';
|
|
2
|
+
export { formatChatTranscript, usedPresetIdsFromMessages, } from './chat-preset-utils';
|
|
3
3
|
export { ChatChrome } from './ChatChrome';
|
|
4
4
|
export type { ChatChromeProps, ChatChromeResizeHandleConfig, } from './ChatChrome';
|
|
5
5
|
export { TEXT_ATTACHMENT_ACCEPT_PARTS, filterToTextAttachments, } from './chatAttachmentAccept';
|
|
@@ -15,5 +15,5 @@ export { ChatPresets } from './ChatPresets';
|
|
|
15
15
|
export type { ChatEmptyStateConfig, ChatEmptyStateContext, ChatEmptyStateProps, } from './ChatEmptyState/ChatEmptyState.types';
|
|
16
16
|
export type { Chat as ChatType, ChatAttachmentDropItem, ChatSendMessagePayload, ChatProps, ChatPreset as ChatPresetType, Message, UserTextFileAttachment, } from './Chat.types';
|
|
17
17
|
export { MessageRole } from './Chat.types';
|
|
18
|
-
export type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
|
|
18
|
+
export type { SlashCommandItem, SlashItemCommandContext, SlashOnItemCommand, } from '#uilib/tiptap/slash-mention/types';
|
|
19
19
|
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
|
@@ -5,12 +5,14 @@ export type SlashCommandItem = {
|
|
|
5
5
|
description?: string;
|
|
6
6
|
};
|
|
7
7
|
export type SlashItemCommandContext = {
|
|
8
|
-
editor: Editor;
|
|
9
|
-
range: Range;
|
|
10
8
|
item: SlashCommandItem;
|
|
9
|
+
/** Present when picked from the slash palette; omitted on Enter submit of `/id`. */
|
|
10
|
+
editor?: Editor;
|
|
11
|
+
range?: Range;
|
|
11
12
|
};
|
|
12
13
|
/**
|
|
13
|
-
* If provided, run
|
|
14
|
+
* If provided, run when a slash item is picked. Return true to skip mention insert
|
|
15
|
+
* (extension clears the trigger text from the composer).
|
|
14
16
|
*/
|
|
15
17
|
export type SlashOnItemCommand = (ctx: SlashItemCommandContext) => boolean;
|
|
16
18
|
/** Where the slash palette opens relative to the caret. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
SlashCommandItem,
|
|
5
|
+
SlashOnItemCommand,
|
|
6
|
+
} from '#uilib/tiptap/slash-mention/types';
|
|
4
7
|
|
|
5
8
|
import type { PresetScriptGraph } from './ChatMessage/presetScript';
|
|
6
9
|
|
|
@@ -10,9 +13,6 @@ export enum MessageRole {
|
|
|
10
13
|
SYSTEM = 'system',
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
/** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
|
|
14
|
-
export const GENERATING_DASHBOARD_SYSTEM_TEXT = 'Generating dashboard…';
|
|
15
|
-
|
|
16
16
|
/** USER-only: text file attached to a message; shown as downloadable file row(s). */
|
|
17
17
|
export type UserTextFileAttachment = {
|
|
18
18
|
displayName: string;
|
|
@@ -96,6 +96,8 @@ export interface ChatPromptProps {
|
|
|
96
96
|
onAttachmentFiles?: (files: File[]) => void;
|
|
97
97
|
/** Slash menu (`/`); omit or pass empty to disable Mention trigger (no default items). */
|
|
98
98
|
slashCommandItems?: SlashCommandItem[];
|
|
99
|
+
/** Custom slash pick handler; return true to skip default mention insert. */
|
|
100
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
export interface ChatMessageProps {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
} from '#uilib/components/ui/Chat/ChatMessage/presetScript';
|
|
8
8
|
import { TextShimmer } from '#uilib/components/ui/TextShimmer';
|
|
9
9
|
import { Scroll } from '@homecode/ui';
|
|
10
|
-
import {
|
|
10
|
+
import { PaperPlaneRightIcon, X } from '@phosphor-icons/react';
|
|
11
11
|
|
|
12
12
|
import { Button } from '../../Button';
|
|
13
13
|
import { DropZone } from '../../DropZone/DropZone';
|
|
@@ -37,13 +37,8 @@ export function ChatChrome({
|
|
|
37
37
|
scriptContinueLabel,
|
|
38
38
|
onScriptContinue,
|
|
39
39
|
renderMessageChart,
|
|
40
|
-
showBranchActionsRow,
|
|
41
40
|
showSyntheticBranchButtons,
|
|
42
41
|
unusedBranchKeys,
|
|
43
|
-
isScriptComplete,
|
|
44
|
-
onGenerateDashboard,
|
|
45
|
-
generatingDashboard,
|
|
46
|
-
onGenerateDashboardClick,
|
|
47
42
|
showInlinePresets,
|
|
48
43
|
isLastMessageFromUser,
|
|
49
44
|
scrollRef,
|
|
@@ -57,6 +52,7 @@ export function ChatChrome({
|
|
|
57
52
|
allowPdfAttachments = false,
|
|
58
53
|
onAttachmentsDropped,
|
|
59
54
|
slashCommandItems,
|
|
55
|
+
onSlashItemCommand,
|
|
60
56
|
promptPlaceholder,
|
|
61
57
|
}: ChatChromeProps) {
|
|
62
58
|
const filteredAllowedAttachments = useMemo(
|
|
@@ -223,48 +219,29 @@ export function ChatChrome({
|
|
|
223
219
|
);
|
|
224
220
|
})}
|
|
225
221
|
|
|
226
|
-
{
|
|
222
|
+
{showSyntheticBranchButtons ? (
|
|
227
223
|
<div className={S.branchRow}>
|
|
228
|
-
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
</span>
|
|
248
|
-
);
|
|
249
|
-
})
|
|
250
|
-
: null}
|
|
251
|
-
|
|
252
|
-
{isScriptComplete &&
|
|
253
|
-
onGenerateDashboard &&
|
|
254
|
-
!generatingDashboard ? (
|
|
255
|
-
<Button
|
|
256
|
-
type="button"
|
|
257
|
-
variant="default"
|
|
258
|
-
size="lg"
|
|
259
|
-
disabled={isLoading}
|
|
260
|
-
onClick={onGenerateDashboardClick}
|
|
261
|
-
>
|
|
262
|
-
<ChartLineIcon />
|
|
263
|
-
Generate Dashboard
|
|
264
|
-
</Button>
|
|
265
|
-
) : null}
|
|
224
|
+
{unusedBranchKeys.map(key => {
|
|
225
|
+
const label =
|
|
226
|
+
displayLabelForBranchKeyFromMessages(key, messages) ??
|
|
227
|
+
humanizeBranchKey(key);
|
|
228
|
+
return (
|
|
229
|
+
<span key={key} className={S.branchBtnWrap}>
|
|
230
|
+
<Button
|
|
231
|
+
type="button"
|
|
232
|
+
variant="outline"
|
|
233
|
+
size="sm"
|
|
234
|
+
disabled={isLoading}
|
|
235
|
+
onClick={() => onQuickReply(key, label)}
|
|
236
|
+
>
|
|
237
|
+
<PaperPlaneRightIcon />
|
|
238
|
+
{label}
|
|
239
|
+
</Button>
|
|
240
|
+
</span>
|
|
241
|
+
);
|
|
242
|
+
})}
|
|
266
243
|
</div>
|
|
267
|
-
)}
|
|
244
|
+
) : null}
|
|
268
245
|
|
|
269
246
|
{showInlinePresets && renderPresets('inline')}
|
|
270
247
|
|
|
@@ -291,6 +268,7 @@ export function ChatChrome({
|
|
|
291
268
|
prefillMessage={promptPrefill ?? undefined}
|
|
292
269
|
placeholder={promptPlaceholder}
|
|
293
270
|
slashCommandItems={slashCommandItems}
|
|
271
|
+
onSlashItemCommand={onSlashItemCommand}
|
|
294
272
|
attachmentAccept={
|
|
295
273
|
attachmentsDropzoneEnabled ? attachmentAccept : undefined
|
|
296
274
|
}
|
|
@@ -6,7 +6,10 @@ import type {
|
|
|
6
6
|
} from '#uilib/components/ui/Chat/Chat.types';
|
|
7
7
|
import type { ChatEmptyStateProps } from '#uilib/components/ui/Chat/ChatEmptyState/ChatEmptyState.types';
|
|
8
8
|
import type { ChatPresetsLayout } from '#uilib/components/ui/Chat/ChatPresets';
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
SlashCommandItem,
|
|
11
|
+
SlashOnItemCommand,
|
|
12
|
+
} from '#uilib/tiptap/slash-mention/types';
|
|
10
13
|
import type { ScrollRef } from '@homecode/ui';
|
|
11
14
|
|
|
12
15
|
export type ChatChromeResizeHandleConfig = {
|
|
@@ -33,15 +36,8 @@ export interface ChatChromeProps {
|
|
|
33
36
|
scriptContinueLabel: string | undefined;
|
|
34
37
|
onScriptContinue: (() => void) | undefined;
|
|
35
38
|
renderMessageChart?: () => React.ReactNode;
|
|
36
|
-
showBranchActionsRow: boolean;
|
|
37
39
|
showSyntheticBranchButtons: boolean;
|
|
38
40
|
unusedBranchKeys: string[];
|
|
39
|
-
isScriptComplete: boolean;
|
|
40
|
-
onGenerateDashboard:
|
|
41
|
-
| ((transcript: string) => void | Promise<void>)
|
|
42
|
-
| undefined;
|
|
43
|
-
generatingDashboard: boolean;
|
|
44
|
-
onGenerateDashboardClick: () => void;
|
|
45
41
|
showInlinePresets: boolean;
|
|
46
42
|
isLastMessageFromUser: boolean;
|
|
47
43
|
scrollRef: RefObject<ScrollRef | null>;
|
|
@@ -65,6 +61,8 @@ export interface ChatChromeProps {
|
|
|
65
61
|
) => void | Promise<void>;
|
|
66
62
|
/** Slash menu (`/`), forwarded to `Chat.Prompt`; omit or pass empty list to disable slash palette. */
|
|
67
63
|
slashCommandItems?: SlashCommandItem[];
|
|
64
|
+
/** Custom slash pick handler; forwarded to `Chat.Prompt`. */
|
|
65
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
68
66
|
/** Composer placeholder forwarded to `Chat.Prompt`. */
|
|
69
67
|
promptPlaceholder?: string;
|
|
70
68
|
}
|
|
@@ -2,12 +2,7 @@ import cn from 'classnames';
|
|
|
2
2
|
|
|
3
3
|
import { InteractiveContent } from '#uilib/components/ui/InteractiveContent';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
type ChatMessageProps,
|
|
8
|
-
GENERATING_DASHBOARD_SYSTEM_TEXT,
|
|
9
|
-
MessageRole,
|
|
10
|
-
} from '../Chat.types';
|
|
5
|
+
import { type ChatMessageProps, MessageRole } from '../Chat.types';
|
|
11
6
|
import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments';
|
|
12
7
|
import { AgentMessageContent } from './AgentMessageContent';
|
|
13
8
|
import S from './ChatMessage.styl';
|
|
@@ -34,13 +29,7 @@ export function ChatMessage({
|
|
|
34
29
|
return (
|
|
35
30
|
<div className={cn(S.root, S[`role-${role}`])}>
|
|
36
31
|
{isSystem ? (
|
|
37
|
-
<div className={S.text}>
|
|
38
|
-
{text === GENERATING_DASHBOARD_SYSTEM_TEXT ? (
|
|
39
|
-
<TextShimmer as="span">{text}</TextShimmer>
|
|
40
|
-
) : (
|
|
41
|
-
text
|
|
42
|
-
)}
|
|
43
|
-
</div>
|
|
32
|
+
<div className={S.text}>{text}</div>
|
|
44
33
|
) : isAssistant ? (
|
|
45
34
|
<AgentMessageContent
|
|
46
35
|
text={text}
|
|
@@ -14,6 +14,7 @@ export function ChatPrompt({
|
|
|
14
14
|
footer,
|
|
15
15
|
prefillMessage,
|
|
16
16
|
slashCommandItems,
|
|
17
|
+
onSlashItemCommand,
|
|
17
18
|
attachments = [],
|
|
18
19
|
onRemoveAttachment,
|
|
19
20
|
disabled = false,
|
|
@@ -28,6 +29,7 @@ export function ChatPrompt({
|
|
|
28
29
|
disabled,
|
|
29
30
|
placeholder,
|
|
30
31
|
slashCommandItems,
|
|
32
|
+
onSlashItemCommand,
|
|
31
33
|
prefillMessage,
|
|
32
34
|
attachmentsCount,
|
|
33
35
|
onEnterSubmit: () => emitSubmitRef.current(),
|
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
DEFAULT_CHAT_SLASH_ITEMS,
|
|
13
13
|
createSlashMentionExtension,
|
|
14
14
|
} from '#uilib/tiptap/slash-mention';
|
|
15
|
-
import type {
|
|
15
|
+
import type {
|
|
16
|
+
SlashCommandItem,
|
|
17
|
+
SlashOnItemCommand,
|
|
18
|
+
} from '#uilib/tiptap/slash-mention/types';
|
|
16
19
|
import type { AnyExtension, Editor } from '@tiptap/core';
|
|
17
20
|
import { Placeholder } from '@tiptap/extensions';
|
|
18
21
|
import { useEditor } from '@tiptap/react';
|
|
@@ -28,6 +31,7 @@ export type UseChatPromptEditorOptions = {
|
|
|
28
31
|
disabled: boolean;
|
|
29
32
|
placeholder?: string;
|
|
30
33
|
slashCommandItems?: SlashCommandItem[];
|
|
34
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
31
35
|
prefillMessage?: string | null;
|
|
32
36
|
/** Staged attachment count — Enter-to-send when text empty but files present. */
|
|
33
37
|
attachmentsCount?: number;
|
|
@@ -46,11 +50,14 @@ export function useChatPromptEditor({
|
|
|
46
50
|
disabled,
|
|
47
51
|
placeholder,
|
|
48
52
|
slashCommandItems,
|
|
53
|
+
onSlashItemCommand,
|
|
49
54
|
prefillMessage,
|
|
50
55
|
attachmentsCount = 0,
|
|
51
56
|
onEnterSubmit,
|
|
52
57
|
}: UseChatPromptEditorOptions): UseChatPromptEditorResult {
|
|
53
58
|
const slashOpenRef = useRef(false);
|
|
59
|
+
const onSlashItemCommandRef = useRef(onSlashItemCommand);
|
|
60
|
+
onSlashItemCommandRef.current = onSlashItemCommand;
|
|
54
61
|
const suggestionActiveUpdater = useCallback((active: boolean) => {
|
|
55
62
|
slashOpenRef.current = active;
|
|
56
63
|
}, []);
|
|
@@ -96,6 +103,7 @@ export function useChatPromptEditor({
|
|
|
96
103
|
items: slashItemsStable,
|
|
97
104
|
suggestionPlacement: 'above',
|
|
98
105
|
onSuggestionUiActiveChange: suggestionActiveUpdater,
|
|
106
|
+
onItemCommand: ctx => onSlashItemCommandRef.current?.(ctx) === true,
|
|
99
107
|
}),
|
|
100
108
|
);
|
|
101
109
|
}
|
|
@@ -139,7 +147,7 @@ export function useChatPromptEditor({
|
|
|
139
147
|
},
|
|
140
148
|
onCreate: bindEditorDom,
|
|
141
149
|
},
|
|
142
|
-
[extensions,
|
|
150
|
+
[extensions, bindEditorDom, ariaLabelComposer],
|
|
143
151
|
);
|
|
144
152
|
|
|
145
153
|
useEffect(() => {
|
|
@@ -41,13 +41,13 @@ export function ChatSheet({
|
|
|
41
41
|
scopeId,
|
|
42
42
|
onMessage,
|
|
43
43
|
onScriptComplete,
|
|
44
|
-
onGenerateDashboard,
|
|
45
44
|
renderMessageChart,
|
|
46
45
|
emptyState,
|
|
47
46
|
allowedAttachments,
|
|
48
47
|
allowPdfAttachments,
|
|
49
48
|
onAttachmentsDropped,
|
|
50
49
|
slashCommandItems,
|
|
50
|
+
onSlashItemCommand,
|
|
51
51
|
inline = false,
|
|
52
52
|
}: ChatSheetProps) {
|
|
53
53
|
const model = useChatPanelChromeModel({
|
|
@@ -56,13 +56,13 @@ export function ChatSheet({
|
|
|
56
56
|
scopeId,
|
|
57
57
|
onMessage,
|
|
58
58
|
onScriptComplete,
|
|
59
|
-
onGenerateDashboard,
|
|
60
59
|
renderMessageChart,
|
|
61
60
|
emptyState,
|
|
62
61
|
allowedAttachments,
|
|
63
62
|
allowPdfAttachments,
|
|
64
63
|
onAttachmentsDropped,
|
|
65
64
|
slashCommandItems,
|
|
65
|
+
onSlashItemCommand,
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
if (actionsRef) {
|
|
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ChatPreset,
|
|
5
|
-
GENERATING_DASHBOARD_SYSTEM_TEXT,
|
|
6
5
|
MessageRole,
|
|
7
6
|
type ScriptCompletePayload,
|
|
8
7
|
} from '#uilib/components/ui/Chat/Chat.types';
|
|
@@ -23,10 +22,7 @@ import {
|
|
|
23
22
|
buildChatSendMessagePayload,
|
|
24
23
|
displayTextFromSendPayload,
|
|
25
24
|
} from '#uilib/components/ui/Chat/buildChatSendMessagePayload';
|
|
26
|
-
import {
|
|
27
|
-
formatChatTranscript,
|
|
28
|
-
usedPresetIdsFromMessages,
|
|
29
|
-
} from '#uilib/components/ui/Chat/chat-preset-utils';
|
|
25
|
+
import { usedPresetIdsFromMessages } from '#uilib/components/ui/Chat/chat-preset-utils';
|
|
30
26
|
import {
|
|
31
27
|
isChatEmpty,
|
|
32
28
|
useChat,
|
|
@@ -38,7 +34,10 @@ import useEvent from '#uilib/hooks/useEvent';
|
|
|
38
34
|
import { useIsMobile } from '#uilib/hooks/useIsMobile';
|
|
39
35
|
import { useQueryParams } from '#uilib/hooks/useQueryParams';
|
|
40
36
|
import logger from '#uilib/lib/logger';
|
|
41
|
-
import type {
|
|
37
|
+
import type {
|
|
38
|
+
SlashCommandItem,
|
|
39
|
+
SlashOnItemCommand,
|
|
40
|
+
} from '#uilib/tiptap/slash-mention/types';
|
|
42
41
|
import { mergePresetFreeformDefaults } from '#uilib/utils/chatPresetMerge';
|
|
43
42
|
import { ScrollRef } from '@homecode/ui';
|
|
44
43
|
|
|
@@ -58,8 +57,6 @@ export type UseChatPanelChromeModelInput = {
|
|
|
58
57
|
onMessage?: (message: string) => void;
|
|
59
58
|
/** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
|
|
60
59
|
onScriptComplete?: (payload: ScriptCompletePayload) => void;
|
|
61
|
-
/** After intake script completes, user can generate a dashboard from the full transcript (e.g. Decision board). */
|
|
62
|
-
onGenerateDashboard?: (transcript: string) => void | Promise<void>;
|
|
63
60
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
64
61
|
renderMessageChart?: () => React.ReactNode;
|
|
65
62
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
@@ -73,6 +70,8 @@ export type UseChatPanelChromeModelInput = {
|
|
|
73
70
|
) => void | Promise<void>;
|
|
74
71
|
/** Slash menu (`/`) in the composer; omit or pass empty to disable. */
|
|
75
72
|
slashCommandItems?: SlashCommandItem[];
|
|
73
|
+
/** Custom slash command handler (palette pick or Enter on `/id`). */
|
|
74
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
76
75
|
};
|
|
77
76
|
|
|
78
77
|
export type UseChatPanelChromeModelResult = {
|
|
@@ -115,13 +114,13 @@ export function useChatPanelChromeModel({
|
|
|
115
114
|
scopeId,
|
|
116
115
|
onMessage,
|
|
117
116
|
onScriptComplete,
|
|
118
|
-
onGenerateDashboard,
|
|
119
117
|
renderMessageChart,
|
|
120
118
|
emptyState,
|
|
121
119
|
allowedAttachments,
|
|
122
120
|
allowPdfAttachments,
|
|
123
121
|
onAttachmentsDropped,
|
|
124
122
|
slashCommandItems,
|
|
123
|
+
onSlashItemCommand,
|
|
125
124
|
}: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
|
|
126
125
|
const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
|
|
127
126
|
const isMobile = useIsMobile();
|
|
@@ -193,11 +192,10 @@ export function useChatPanelChromeModel({
|
|
|
193
192
|
const [intakeByChatId, setIntakeByChatId] = useState<
|
|
194
193
|
Record<string, IntakeScriptState | undefined>
|
|
195
194
|
>({});
|
|
196
|
-
/** Preset intake finished for this session
|
|
195
|
+
/** Preset intake finished for this session (e.g. `onScriptComplete` callback). */
|
|
197
196
|
const [scriptCompleteByChatId, setScriptCompleteByChatId] = useState<
|
|
198
197
|
Record<string, boolean>
|
|
199
198
|
>({});
|
|
200
|
-
const [generatingDashboard, setGeneratingDashboard] = useState(false);
|
|
201
199
|
const scriptAdvanceLockRef = useRef(false);
|
|
202
200
|
const quickReplyLockRef = useRef(false);
|
|
203
201
|
const scrollRef = useRef<ScrollRef>(null);
|
|
@@ -956,46 +954,19 @@ export function useChatPanelChromeModel({
|
|
|
956
954
|
!linearScriptActive &&
|
|
957
955
|
(!graphActive || (!lastHasQuickMarkers && unusedBranchKeys.length === 0));
|
|
958
956
|
|
|
959
|
-
const
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
const handleGenerateDashboard = useCallback(async () => {
|
|
969
|
-
if (!currentChatId || !onGenerateDashboard) return;
|
|
970
|
-
const transcript = formatChatTranscript(
|
|
971
|
-
(chat?.messages ?? []).filter(m => m.role !== MessageRole.SYSTEM),
|
|
972
|
-
);
|
|
973
|
-
setGeneratingDashboard(true);
|
|
974
|
-
const systemPlaceholderId = addMessage(
|
|
975
|
-
currentChatId,
|
|
976
|
-
MessageRole.SYSTEM,
|
|
977
|
-
GENERATING_DASHBOARD_SYSTEM_TEXT,
|
|
978
|
-
);
|
|
979
|
-
try {
|
|
980
|
-
await Promise.resolve(onGenerateDashboard(transcript));
|
|
981
|
-
setScriptCompleteByChatId(prev => {
|
|
982
|
-
const next = { ...prev };
|
|
983
|
-
delete next[currentChatId];
|
|
984
|
-
return next;
|
|
985
|
-
});
|
|
986
|
-
} finally {
|
|
987
|
-
if (systemPlaceholderId) {
|
|
988
|
-
removeMessageById(currentChatId, systemPlaceholderId);
|
|
957
|
+
const onPromptSubmitWithSlashCommands = useCallback(
|
|
958
|
+
async (message: string, attachments?: ChatAttachmentDropItem[]) => {
|
|
959
|
+
const trimmed = message.trim();
|
|
960
|
+
const slashItem = slashCommandItems?.find(
|
|
961
|
+
item => trimmed === `/${item.id}`,
|
|
962
|
+
);
|
|
963
|
+
if (slashItem && onSlashItemCommand?.({ item: slashItem }) === true) {
|
|
964
|
+
return;
|
|
989
965
|
}
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
onGenerateDashboard,
|
|
995
|
-
chat?.messages,
|
|
996
|
-
addMessage,
|
|
997
|
-
removeMessageById,
|
|
998
|
-
]);
|
|
966
|
+
return handlePromptSubmit(message, attachments);
|
|
967
|
+
},
|
|
968
|
+
[handlePromptSubmit, slashCommandItems, onSlashItemCommand],
|
|
969
|
+
);
|
|
999
970
|
|
|
1000
971
|
const onDragChatWidth = useCallback(
|
|
1001
972
|
(rawPx: number) => setChatWidthPx(rawPx, { persist: false }),
|
|
@@ -1028,20 +999,13 @@ export function useChatPanelChromeModel({
|
|
|
1028
999
|
scriptContinueLabel,
|
|
1029
1000
|
onScriptContinue,
|
|
1030
1001
|
renderMessageChart,
|
|
1031
|
-
showBranchActionsRow,
|
|
1032
1002
|
showSyntheticBranchButtons,
|
|
1033
1003
|
unusedBranchKeys,
|
|
1034
|
-
isScriptComplete,
|
|
1035
|
-
onGenerateDashboard,
|
|
1036
|
-
generatingDashboard,
|
|
1037
|
-
onGenerateDashboardClick: () => {
|
|
1038
|
-
void handleGenerateDashboard();
|
|
1039
|
-
},
|
|
1040
1004
|
showInlinePresets,
|
|
1041
1005
|
isLastMessageFromUser,
|
|
1042
1006
|
scrollRef,
|
|
1043
1007
|
effectiveScopeId,
|
|
1044
|
-
onPromptSubmit:
|
|
1008
|
+
onPromptSubmit: onPromptSubmitWithSlashCommands,
|
|
1045
1009
|
onChatDeleted: endLocalDemoFlow,
|
|
1046
1010
|
promptPrefill: promptLinkPrefill,
|
|
1047
1011
|
emptyState: resolvedEmptyState,
|
|
@@ -1049,6 +1013,7 @@ export function useChatPanelChromeModel({
|
|
|
1049
1013
|
allowPdfAttachments,
|
|
1050
1014
|
onAttachmentsDropped,
|
|
1051
1015
|
slashCommandItems,
|
|
1016
|
+
onSlashItemCommand,
|
|
1052
1017
|
};
|
|
1053
1018
|
|
|
1054
1019
|
const toggleOpen = () => onOpenChange(!isOpen);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export { Chat } from './Chat';
|
|
2
|
-
export {
|
|
2
|
+
export {
|
|
3
|
+
formatChatTranscript,
|
|
4
|
+
usedPresetIdsFromMessages,
|
|
5
|
+
} from './chat-preset-utils';
|
|
3
6
|
export { ChatChrome } from './ChatChrome';
|
|
4
7
|
export type {
|
|
5
8
|
ChatChromeProps,
|
|
@@ -40,5 +43,9 @@ export type {
|
|
|
40
43
|
UserTextFileAttachment,
|
|
41
44
|
} from './Chat.types';
|
|
42
45
|
export { MessageRole } from './Chat.types';
|
|
43
|
-
export type {
|
|
46
|
+
export type {
|
|
47
|
+
SlashCommandItem,
|
|
48
|
+
SlashItemCommandContext,
|
|
49
|
+
SlashOnItemCommand,
|
|
50
|
+
} from '#uilib/tiptap/slash-mention/types';
|
|
44
51
|
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
|
@@ -120,13 +120,8 @@ export default function ChatAttachmentsDropzonePage() {
|
|
|
120
120
|
isLoading={isLoading}
|
|
121
121
|
scriptContinueLabel={undefined}
|
|
122
122
|
onScriptContinue={undefined}
|
|
123
|
-
showBranchActionsRow={false}
|
|
124
123
|
showSyntheticBranchButtons={false}
|
|
125
124
|
unusedBranchKeys={[]}
|
|
126
|
-
isScriptComplete={false}
|
|
127
|
-
onGenerateDashboard={undefined}
|
|
128
|
-
generatingDashboard={false}
|
|
129
|
-
onGenerateDashboardClick={() => {}}
|
|
130
125
|
showInlinePresets={false}
|
|
131
126
|
isLastMessageFromUser={isLastMessageFromUser}
|
|
132
127
|
scrollRef={scrollRef}
|
|
@@ -84,13 +84,8 @@ export default function ChatPage() {
|
|
|
84
84
|
isLoading={isLoading}
|
|
85
85
|
scriptContinueLabel={undefined}
|
|
86
86
|
onScriptContinue={undefined}
|
|
87
|
-
showBranchActionsRow={false}
|
|
88
87
|
showSyntheticBranchButtons={false}
|
|
89
88
|
unusedBranchKeys={[]}
|
|
90
|
-
isScriptComplete={false}
|
|
91
|
-
onGenerateDashboard={undefined}
|
|
92
|
-
generatingDashboard={false}
|
|
93
|
-
onGenerateDashboardClick={() => {}}
|
|
94
89
|
showInlinePresets={false}
|
|
95
90
|
isLastMessageFromUser={isLastMessageFromUser}
|
|
96
91
|
scrollRef={scrollRef}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type SlashCommandItem,
|
|
9
9
|
} from '#uilib/components/ui/Chat';
|
|
10
10
|
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
11
|
+
import type { SlashItemCommandContext } from '#uilib/tiptap/slash-mention/types';
|
|
11
12
|
import { ScrollRef } from '@homecode/ui';
|
|
12
13
|
|
|
13
14
|
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
@@ -15,18 +16,23 @@ import { DocsHeaderActions } from '../docsHeaderActions';
|
|
|
15
16
|
|
|
16
17
|
const NO_QUICK_REPLY_KEYS: ReadonlySet<string> = new Set();
|
|
17
18
|
|
|
19
|
+
const DOCS_SAMPLE_COMMAND_ID = 'sample-command';
|
|
20
|
+
|
|
18
21
|
/** Sample items so the docs demo still shows a `/` palette (`DEFAULT_CHAT_SLASH_ITEMS` is empty). */
|
|
19
22
|
const DOCS_SAMPLE_SLASH_ITEMS: SlashCommandItem[] = [
|
|
20
23
|
{
|
|
21
|
-
id:
|
|
22
|
-
label:
|
|
24
|
+
id: DOCS_SAMPLE_COMMAND_ID,
|
|
25
|
+
label: DOCS_SAMPLE_COMMAND_ID,
|
|
23
26
|
description:
|
|
24
|
-
'Demo
|
|
27
|
+
'Demo handler — clears composer and runs `onSlashItemCommand` (no mention insert).',
|
|
25
28
|
},
|
|
26
29
|
];
|
|
27
30
|
|
|
31
|
+
const SAMPLE_COMMAND_REPLY_TEXT =
|
|
32
|
+
'Sample command ran via `onSlashItemCommand` — composer cleared, no mention inserted.';
|
|
33
|
+
|
|
28
34
|
const ASSISTANT_REPLY_TEXT =
|
|
29
|
-
'Demo reply
|
|
35
|
+
'Demo reply for a normal message. Slash picks with a custom handler skip mention insert.';
|
|
30
36
|
|
|
31
37
|
function makeMessage(role: MessageRole, text: string): Message {
|
|
32
38
|
return {
|
|
@@ -56,11 +62,34 @@ export default function ChatSlashCommandsPage() {
|
|
|
56
62
|
messages.length > 0 &&
|
|
57
63
|
messages[messages.length - 1]?.role === MessageRole.USER;
|
|
58
64
|
|
|
65
|
+
const runSampleCommand = useCallback(() => {
|
|
66
|
+
setMessages(prev => [
|
|
67
|
+
...prev,
|
|
68
|
+
makeMessage(MessageRole.ASSISTANT, SAMPLE_COMMAND_REPLY_TEXT),
|
|
69
|
+
]);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const onSlashItemCommand = useCallback(
|
|
73
|
+
({ item }: SlashItemCommandContext) => {
|
|
74
|
+
if (item.id !== DOCS_SAMPLE_COMMAND_ID) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
queueMicrotask(() => runSampleCommand());
|
|
78
|
+
return true;
|
|
79
|
+
},
|
|
80
|
+
[runSampleCommand],
|
|
81
|
+
);
|
|
82
|
+
|
|
59
83
|
const onSubmit = useCallback(
|
|
60
84
|
(raw: string) => {
|
|
61
85
|
const text = raw.trim();
|
|
62
86
|
if (!text || isLoading) return;
|
|
63
87
|
|
|
88
|
+
if (text === `/${DOCS_SAMPLE_COMMAND_ID}`) {
|
|
89
|
+
runSampleCommand();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
64
93
|
setMessages(prev => [...prev, makeMessage(MessageRole.USER, text)]);
|
|
65
94
|
setIsLoading(true);
|
|
66
95
|
|
|
@@ -76,7 +105,7 @@ export default function ChatSlashCommandsPage() {
|
|
|
76
105
|
setIsLoading(false);
|
|
77
106
|
}, 900);
|
|
78
107
|
},
|
|
79
|
-
[isLoading],
|
|
108
|
+
[isLoading, runSampleCommand],
|
|
80
109
|
);
|
|
81
110
|
|
|
82
111
|
return (
|
|
@@ -84,7 +113,7 @@ export default function ChatSlashCommandsPage() {
|
|
|
84
113
|
<AppPageHeader
|
|
85
114
|
breadcrumbs={[{ label: 'Chat' }, { label: 'Chat slash commands' }]}
|
|
86
115
|
title="Chat slash commands"
|
|
87
|
-
subheader={`Slash palette uses TipTap Mention with "/" as the trigger.
|
|
116
|
+
subheader={`Slash palette uses TipTap Mention with "/" as the trigger. Pass slashCommandItems from the app; optional onSlashItemCommand can clear the composer and run an action instead of inserting a mention.`}
|
|
88
117
|
actions={
|
|
89
118
|
<DocsHeaderActions slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS} />
|
|
90
119
|
}
|
|
@@ -97,7 +126,9 @@ export default function ChatSlashCommandsPage() {
|
|
|
97
126
|
</Link>{' '}
|
|
98
127
|
shell below: scrolling history, empty state, disclaimer, composer.
|
|
99
128
|
Type <kbd className="font-mono">/</kbd> at line start or after a
|
|
100
|
-
space; pick
|
|
129
|
+
space; pick <kbd className="font-mono">sample-command</kbd> to run the
|
|
130
|
+
custom handler, or send{' '}
|
|
131
|
+
<kbd className="font-mono">/sample-command</kbd> with Enter.
|
|
101
132
|
</p>
|
|
102
133
|
<ChatChrome
|
|
103
134
|
showResizeHandle={false}
|
|
@@ -111,13 +142,8 @@ export default function ChatSlashCommandsPage() {
|
|
|
111
142
|
isLoading={isLoading}
|
|
112
143
|
scriptContinueLabel={undefined}
|
|
113
144
|
onScriptContinue={undefined}
|
|
114
|
-
showBranchActionsRow={false}
|
|
115
145
|
showSyntheticBranchButtons={false}
|
|
116
146
|
unusedBranchKeys={[]}
|
|
117
|
-
isScriptComplete={false}
|
|
118
|
-
onGenerateDashboard={undefined}
|
|
119
|
-
generatingDashboard={false}
|
|
120
|
-
onGenerateDashboardClick={() => {}}
|
|
121
147
|
showInlinePresets={false}
|
|
122
148
|
isLastMessageFromUser={isLastMessageFromUser}
|
|
123
149
|
scrollRef={scrollRef}
|
|
@@ -125,14 +151,12 @@ export default function ChatSlashCommandsPage() {
|
|
|
125
151
|
onPromptSubmit={onSubmit}
|
|
126
152
|
onChatDeleted={() => {}}
|
|
127
153
|
slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS}
|
|
154
|
+
onSlashItemCommand={onSlashItemCommand}
|
|
128
155
|
promptPlaceholder='Ask something or type "/" for demo commands…'
|
|
129
156
|
emptyState={{
|
|
130
157
|
title: 'Try a slash command',
|
|
131
158
|
description:
|
|
132
|
-
'
|
|
133
|
-
additionalContent: (
|
|
134
|
-
<p>Optional empty-state slot via additionalContent.</p>
|
|
135
|
-
),
|
|
159
|
+
'Pick sample-command from the palette or send /sample-command — onSlashItemCommand clears the composer and runs the demo action.',
|
|
136
160
|
}}
|
|
137
161
|
/>
|
|
138
162
|
</PageContentSection>
|
|
@@ -148,13 +148,8 @@ export default function ChatUserCsvAttachmentPage() {
|
|
|
148
148
|
isLoading={isLoading}
|
|
149
149
|
scriptContinueLabel={undefined}
|
|
150
150
|
onScriptContinue={undefined}
|
|
151
|
-
showBranchActionsRow={false}
|
|
152
151
|
showSyntheticBranchButtons={false}
|
|
153
152
|
unusedBranchKeys={[]}
|
|
154
|
-
isScriptComplete={false}
|
|
155
|
-
onGenerateDashboard={undefined}
|
|
156
|
-
generatingDashboard={false}
|
|
157
|
-
onGenerateDashboardClick={() => {}}
|
|
158
153
|
showInlinePresets={false}
|
|
159
154
|
isLastMessageFromUser={isLastMessageFromUser}
|
|
160
155
|
scrollRef={scrollRef}
|
|
@@ -124,6 +124,28 @@ export function slashMentionSuggestionRender(
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
function clearSlashTriggerEditor(
|
|
128
|
+
editor: Editor,
|
|
129
|
+
range: { from: number; to: number },
|
|
130
|
+
): void {
|
|
131
|
+
if (editor.isDestroyed) return;
|
|
132
|
+
try {
|
|
133
|
+
editor.chain().focus().deleteRange(range).clearContent().run();
|
|
134
|
+
} catch {
|
|
135
|
+
// Editor view may be tearing down during suggestion exit.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function collapseEditorSelectionEnd(editor: Editor): void {
|
|
140
|
+
if (editor.isDestroyed) return;
|
|
141
|
+
try {
|
|
142
|
+
editor.view?.dom?.ownerDocument?.defaultView
|
|
143
|
+
?.getSelection?.()
|
|
144
|
+
?.collapseToEnd();
|
|
145
|
+
} catch {
|
|
146
|
+
// view.dom throws when editor is not mounted
|
|
147
|
+
}
|
|
148
|
+
}
|
|
127
149
|
function insertDefaultMention(
|
|
128
150
|
editor: Editor,
|
|
129
151
|
range: { from: number; to: number },
|
|
@@ -223,11 +245,8 @@ export function createSlashMentionExtension({
|
|
|
223
245
|
command: ({ editor, range, props }) => {
|
|
224
246
|
const item = props as SlashCommandItem;
|
|
225
247
|
if (onItemCommand?.({ editor, range, item }) === true) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
?.getSelection?.()
|
|
229
|
-
?.collapseToEnd();
|
|
230
|
-
});
|
|
248
|
+
clearSlashTriggerEditor(editor, range);
|
|
249
|
+
queueMicrotask(() => collapseEditorSelectionEnd(editor));
|
|
231
250
|
return null;
|
|
232
251
|
}
|
|
233
252
|
insertDefaultMention(editor, range, item, slashChar);
|
|
@@ -7,13 +7,15 @@ export type SlashCommandItem = {
|
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
export type SlashItemCommandContext = {
|
|
10
|
-
editor: Editor;
|
|
11
|
-
range: Range;
|
|
12
10
|
item: SlashCommandItem;
|
|
11
|
+
/** Present when picked from the slash palette; omitted on Enter submit of `/id`. */
|
|
12
|
+
editor?: Editor;
|
|
13
|
+
range?: Range;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
|
-
* If provided, run
|
|
17
|
+
* If provided, run when a slash item is picked. Return true to skip mention insert
|
|
18
|
+
* (extension clears the trigger text from the composer).
|
|
17
19
|
*/
|
|
18
20
|
export type SlashOnItemCommand = (ctx: SlashItemCommandContext) => boolean;
|
|
19
21
|
|