@sybilion/uilib 1.3.38 → 1.3.40
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 -8
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +1 -1
- package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +3 -3
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -2
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +7 -54
- package/dist/esm/contexts/chat-context.js +1 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +3 -5
- package/dist/esm/types/src/components/ui/Chat/ChatMessage/ChatMessage.d.ts +1 -1
- 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 +3 -3
- package/dist/esm/types/src/contexts/chat-context.d.ts +2 -0
- package/dist/esm/types/src/tiptap/slash-mention/types.d.ts +3 -2
- package/package.json +1 -1
- package/src/components/ui/Chat/Chat.types.ts +3 -10
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +1 -0
- package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +4 -11
- package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -2
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +11 -75
- package/src/components/ui/Chat/index.ts +10 -7
- package/src/contexts/chat-context.tsx +3 -0
- package/src/docs/pages/ChatSlashCommandsPage.tsx +37 -11
- package/src/tiptap/slash-mention/types.ts +3 -2
- package/dist/esm/types/src/components/ui/Chat/Chat.types.test.d.ts +0 -1
- package/src/components/ui/Chat/Chat.types.test.ts +0 -32
|
@@ -4,12 +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
|
-
/** Slash command id that triggers dashboard generation when `onGenerateDashboard` is set. */
|
|
10
|
-
const GENERATE_DASHBOARD_SLASH_COMMAND_ID = 'generate-dashboard';
|
|
11
|
-
function isGenerateDashboardSlashMessage(message) {
|
|
12
|
-
return message.trim() === `/${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`;
|
|
13
|
-
}
|
|
14
7
|
|
|
15
|
-
export {
|
|
8
|
+
export { MessageRole };
|
|
@@ -68,7 +68,7 @@ function ChatChrome({ showResizeHandle, resizeHandle, onClose, isEmpty, renderPr
|
|
|
68
68
|
}, [isEmpty, messages.length]);
|
|
69
69
|
return (jsxs("div", { className: S.root, children: [showResizeHandle && resizeHandle ? (jsx(PanelResizeHandle, { edge: "leading", isActive: resizeHandle.isActive, startWidthPx: resizeHandle.startWidthPx, getShellWidth: resizeHandle.getShellWidth, onDragWidth: resizeHandle.onDragWidth, onDragComplete: resizeHandle.onDragComplete, className: cn(SidebarStem.sidebarResizeHandle, S.chatResizeHandle) })) : null, jsx("div", { className: S.panelHeader, children: onClose ? (jsx(Button, { type: "button", variant: "ghost", icon: true, className: S.panelClose, "aria-label": "Close chat", onClick: onClose, children: jsx(X, { className: "size-4" }) })) : null }), jsxs("div", { className: S.content, children: [attachmentsDropzoneEnabled ? (jsx(DropZone, { accept: attachmentAccept, label: "Drop text files to attach", multiple: true, ghost: true, overlayScope: "container", disabled: promptBusy, className: S.attachmentDropzone, onFiles: handleAttachmentFiles })) : null, jsxs(Chat, { isEmpty: isEmpty, scopeId: effectiveScopeId, onChatDeleted: onChatDeleted, children: [isEmpty ? (jsxs(Fragment, { children: [jsx(Chat.EmptyState, { ...emptyState }), renderPresets('fixed')] })) : (jsx("div", { className: S.scrollWrapper, children: jsxs(Scroll, { y: true, yScrollbarClassName: S.scrollbar, className: S.scroll, innerClassName: S.scrollInner, offset: { y: { before: 56, after: 180 } }, fadeSize: "m", autoHide: true, ref: scrollRef, children: [messages.map((msg, index, arr) => {
|
|
70
70
|
const isLast = index === arr.length - 1;
|
|
71
|
-
return (jsx(Chat.Message, { role: msg.role, text: msg.text, userTextFileAttachments: msg.userTextFileAttachments, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: isLoading, isLastMessage: isLast, scriptContinue: isLast && scriptContinueLabel
|
|
71
|
+
return (jsx(Chat.Message, { role: msg.role, text: msg.text, inProgress: msg.inProgress, userTextFileAttachments: msg.userTextFileAttachments, onQuickReply: onQuickReply, suppressedQuickReplyKeys: suppressedQuickReplyKeys, quickReplyDisabled: isLoading, isLastMessage: isLast, scriptContinue: isLast && scriptContinueLabel
|
|
72
72
|
? { label: scriptContinueLabel }
|
|
73
73
|
: undefined, onScriptContinue: isLast && scriptContinueLabel
|
|
74
74
|
? onScriptContinue
|
|
@@ -4,19 +4,19 @@ import { InteractiveContent } from '../../InteractiveContent/InteractiveContent.
|
|
|
4
4
|
import 'lucide-react';
|
|
5
5
|
import '../../InteractiveContent/InteractiveContent.styl.js';
|
|
6
6
|
import { TextShimmer } from '../../TextShimmer/TextShimmer.js';
|
|
7
|
-
import { MessageRole
|
|
7
|
+
import { MessageRole } from '../Chat.types.js';
|
|
8
8
|
import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments.js';
|
|
9
9
|
import { AgentMessageContent } from './AgentMessageContent.js';
|
|
10
10
|
import S from './ChatMessage.styl.js';
|
|
11
11
|
import { UserTextFileAttachmentBubble } from './UserTextFileAttachmentBubble.js';
|
|
12
12
|
|
|
13
|
-
function ChatMessage({ role, text, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, }) {
|
|
13
|
+
function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage = true, scriptContinue, onScriptContinue, renderMessageChart, }) {
|
|
14
14
|
const fileAttachments = userTextFileAttachmentsFromMessage({
|
|
15
15
|
userTextFileAttachments,
|
|
16
16
|
});
|
|
17
17
|
const isAssistant = role === MessageRole.ASSISTANT;
|
|
18
18
|
const isSystem = role === MessageRole.SYSTEM;
|
|
19
|
-
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children:
|
|
19
|
+
return (jsx("div", { className: cn(S.root, S[`role-${role}`]), children: isSystem ? (jsx("div", { className: S.text, children: inProgress ? jsx(TextShimmer, { as: "span", children: text }) : 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
20
|
}
|
|
21
21
|
|
|
22
22
|
export { ChatMessage };
|
|
@@ -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();
|
|
@@ -60,7 +60,6 @@ function useChatPanelChromeModel({ embedAsPage, presets, scopeId, onMessage, onS
|
|
|
60
60
|
const [intakeByChatId, setIntakeByChatId] = useState({});
|
|
61
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,60 +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 handleGenerateDashboard = useCallback(async () => {
|
|
716
|
-
if (!currentChatId || !onGenerateDashboard)
|
|
717
|
-
return;
|
|
718
|
-
const transcript = formatChatTranscript((chat?.messages ?? []).filter(m => m.role !== MessageRole.SYSTEM));
|
|
719
|
-
setGeneratingDashboard(true);
|
|
720
|
-
const systemPlaceholderId = addMessage(currentChatId, MessageRole.SYSTEM, GENERATING_DASHBOARD_SYSTEM_TEXT);
|
|
721
|
-
try {
|
|
722
|
-
await Promise.resolve(onGenerateDashboard(transcript));
|
|
723
|
-
setScriptCompleteByChatId(prev => {
|
|
724
|
-
const next = { ...prev };
|
|
725
|
-
delete next[currentChatId];
|
|
726
|
-
return next;
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
finally {
|
|
730
|
-
if (systemPlaceholderId) {
|
|
731
|
-
removeMessageById(currentChatId, systemPlaceholderId);
|
|
732
|
-
}
|
|
733
|
-
setGeneratingDashboard(false);
|
|
734
|
-
}
|
|
735
|
-
}, [
|
|
736
|
-
currentChatId,
|
|
737
|
-
onGenerateDashboard,
|
|
738
|
-
chat?.messages,
|
|
739
|
-
addMessage,
|
|
740
|
-
removeMessageById,
|
|
741
|
-
]);
|
|
742
|
-
const onSlashItemCommand = useCallback(({ item }) => {
|
|
743
|
-
if (item.id !== GENERATE_DASHBOARD_SLASH_COMMAND_ID ||
|
|
744
|
-
!onGenerateDashboard) {
|
|
745
|
-
return false;
|
|
746
|
-
}
|
|
747
|
-
if (generatingDashboard) {
|
|
748
|
-
return true;
|
|
749
|
-
}
|
|
750
|
-
queueMicrotask(() => {
|
|
751
|
-
void handleGenerateDashboard();
|
|
752
|
-
});
|
|
753
|
-
return true;
|
|
754
|
-
}, [onGenerateDashboard, generatingDashboard, handleGenerateDashboard]);
|
|
755
714
|
const onPromptSubmitWithSlashCommands = useCallback(async (message, attachments) => {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
void handleGenerateDashboard();
|
|
715
|
+
const trimmed = message.trim();
|
|
716
|
+
const slashItem = slashCommandItems?.find(item => trimmed === `/${item.id}`);
|
|
717
|
+
if (slashItem && onSlashItemCommand?.({ item: slashItem }) === true) {
|
|
760
718
|
return;
|
|
761
719
|
}
|
|
762
720
|
return handlePromptSubmit(message, attachments);
|
|
763
|
-
}, [
|
|
764
|
-
handlePromptSubmit,
|
|
765
|
-
onGenerateDashboard,
|
|
766
|
-
generatingDashboard,
|
|
767
|
-
handleGenerateDashboard,
|
|
768
|
-
]);
|
|
721
|
+
}, [handlePromptSubmit, slashCommandItems, onSlashItemCommand]);
|
|
769
722
|
const onDragChatWidth = useCallback((rawPx) => setChatWidthPx(rawPx, { persist: false }), [setChatWidthPx]);
|
|
770
723
|
const onDragChatComplete = useCallback((finalRawPx) => setChatWidthPx(finalRawPx, { persist: true }), [setChatWidthPx]);
|
|
771
724
|
const chromeProps = {
|
|
@@ -200,6 +200,7 @@ function ChatProvider({ children, userSwitchKey, sendChatMessage: sendChatMessag
|
|
|
200
200
|
...(attachments?.length
|
|
201
201
|
? { userTextFileAttachments: attachments }
|
|
202
202
|
: {}),
|
|
203
|
+
...(options?.inProgress ? { inProgress: true } : {}),
|
|
203
204
|
};
|
|
204
205
|
setChats(prev => {
|
|
205
206
|
const scopeChats = prev[scopeId] ?? [];
|
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';
|
|
@@ -32,7 +32,7 @@ export { useChatPanelChromeModel } from './components/ui/Chat/ChatSheet/useChatP
|
|
|
32
32
|
export { ChatMessage } from './components/ui/Chat/ChatMessage/ChatMessage.js';
|
|
33
33
|
export { ChatPrompt } from './components/ui/Chat/ChatPrompt/ChatPrompt.js';
|
|
34
34
|
export { ChatPresets } from './components/ui/Chat/ChatPresets/ChatPresets.js';
|
|
35
|
-
export {
|
|
35
|
+
export { MessageRole } from './components/ui/Chat/Chat.types.js';
|
|
36
36
|
export { CsvIcon } from './components/icons/CsvIcon/CsvIcon.js';
|
|
37
37
|
export { Checkbox } from './components/ui/Checkbox/Checkbox.js';
|
|
38
38
|
export { Confirm } from './components/ui/Confirm/Confirm.js';
|
|
@@ -6,11 +6,6 @@ export declare enum MessageRole {
|
|
|
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
|
-
/** Slash command id that triggers dashboard generation when `onGenerateDashboard` is set. */
|
|
12
|
-
export declare const GENERATE_DASHBOARD_SLASH_COMMAND_ID = "generate-dashboard";
|
|
13
|
-
export declare function isGenerateDashboardSlashMessage(message: string): boolean;
|
|
14
9
|
/** USER-only: text file attached to a message; shown as downloadable file row(s). */
|
|
15
10
|
export type UserTextFileAttachment = {
|
|
16
11
|
displayName: string;
|
|
@@ -29,6 +24,8 @@ export interface Message {
|
|
|
29
24
|
text: string;
|
|
30
25
|
timestamp: number;
|
|
31
26
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
27
|
+
/** SYSTEM-only: transient progress placeholder while work is in flight. */
|
|
28
|
+
inProgress?: boolean;
|
|
32
29
|
}
|
|
33
30
|
export interface Chat {
|
|
34
31
|
session_id: string;
|
|
@@ -93,6 +90,7 @@ export interface ChatPromptProps {
|
|
|
93
90
|
export interface ChatMessageProps {
|
|
94
91
|
role: MessageRole;
|
|
95
92
|
text: string;
|
|
93
|
+
inProgress?: boolean;
|
|
96
94
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
97
95
|
onQuickReply?: (branchKey: string, displayLabel: string) => void;
|
|
98
96
|
/** Branch keys already taken (e.g. from chat history); hide quick-reply buttons for these. */
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type ChatMessageProps } from '../Chat.types';
|
|
2
|
-
export declare function ChatMessage({ role, text, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage, scriptContinue, onScriptContinue, renderMessageChart, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function ChatMessage({ role, text, inProgress, userTextFileAttachments, onQuickReply, suppressedQuickReplyKeys, quickReplyDisabled, isLastMessage, scriptContinue, onScriptContinue, renderMessageChart, }: ChatMessageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -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
|
-
/** Generate dashboard from chat transcript (e.g. via `/generate-dashboard` slash command). */
|
|
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';
|
|
@@ -14,6 +14,6 @@ export { ChatPrompt } from './ChatPrompt';
|
|
|
14
14
|
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
|
-
export {
|
|
18
|
-
export type { SlashCommandItem } from '#uilib/tiptap/slash-mention/types';
|
|
17
|
+
export { MessageRole } from './Chat.types';
|
|
18
|
+
export type { SlashCommandItem, SlashItemCommandContext, SlashOnItemCommand, } from '#uilib/tiptap/slash-mention/types';
|
|
19
19
|
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
|
@@ -5,6 +5,8 @@ export type SendChatMessageFn = (message: string, targetChatId: string) => Promi
|
|
|
5
5
|
export type { ChatSendMessagePayload, UserTextFileAttachment, } from '#uilib/components/ui/Chat/Chat.types';
|
|
6
6
|
export type AddChatMessageOptions = {
|
|
7
7
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
8
|
+
/** SYSTEM-only: mark as transient progress placeholder (shimmer until removed). */
|
|
9
|
+
inProgress?: boolean;
|
|
8
10
|
};
|
|
9
11
|
export interface ChatContextType {
|
|
10
12
|
/** Returns the new session id, or undefined if no user / not created. */
|
|
@@ -5,9 +5,10 @@ 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
14
|
* If provided, run when a slash item is picked. Return true to skip mention insert
|
package/package.json
CHANGED
|
@@ -13,16 +13,6 @@ export enum MessageRole {
|
|
|
13
13
|
SYSTEM = 'system',
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/** System placeholder while dashboard generation runs (must match ChatSheet `addMessage` text). */
|
|
17
|
-
export const GENERATING_DASHBOARD_SYSTEM_TEXT = 'Generating dashboard…';
|
|
18
|
-
|
|
19
|
-
/** Slash command id that triggers dashboard generation when `onGenerateDashboard` is set. */
|
|
20
|
-
export const GENERATE_DASHBOARD_SLASH_COMMAND_ID = 'generate-dashboard';
|
|
21
|
-
|
|
22
|
-
export function isGenerateDashboardSlashMessage(message: string): boolean {
|
|
23
|
-
return message.trim() === `/${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
16
|
/** USER-only: text file attached to a message; shown as downloadable file row(s). */
|
|
27
17
|
export type UserTextFileAttachment = {
|
|
28
18
|
displayName: string;
|
|
@@ -43,6 +33,8 @@ export interface Message {
|
|
|
43
33
|
text: string;
|
|
44
34
|
timestamp: number;
|
|
45
35
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
36
|
+
/** SYSTEM-only: transient progress placeholder while work is in flight. */
|
|
37
|
+
inProgress?: boolean;
|
|
46
38
|
}
|
|
47
39
|
|
|
48
40
|
export interface Chat {
|
|
@@ -113,6 +105,7 @@ export interface ChatPromptProps {
|
|
|
113
105
|
export interface ChatMessageProps {
|
|
114
106
|
role: MessageRole;
|
|
115
107
|
text: string;
|
|
108
|
+
inProgress?: boolean;
|
|
116
109
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
117
110
|
onQuickReply?: (branchKey: string, displayLabel: string) => void;
|
|
118
111
|
/** Branch keys already taken (e.g. from chat history); hide quick-reply buttons for these. */
|
|
@@ -199,6 +199,7 @@ export function ChatChrome({
|
|
|
199
199
|
key={msg.id}
|
|
200
200
|
role={msg.role}
|
|
201
201
|
text={msg.text}
|
|
202
|
+
inProgress={msg.inProgress}
|
|
202
203
|
userTextFileAttachments={msg.userTextFileAttachments}
|
|
203
204
|
onQuickReply={onQuickReply}
|
|
204
205
|
suppressedQuickReplyKeys={suppressedQuickReplyKeys}
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import cn from 'classnames';
|
|
2
2
|
|
|
3
3
|
import { InteractiveContent } from '#uilib/components/ui/InteractiveContent';
|
|
4
|
+
import { TextShimmer } from '#uilib/components/ui/TextShimmer';
|
|
4
5
|
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
type ChatMessageProps,
|
|
8
|
-
GENERATING_DASHBOARD_SYSTEM_TEXT,
|
|
9
|
-
MessageRole,
|
|
10
|
-
} from '../Chat.types';
|
|
6
|
+
import { type ChatMessageProps, MessageRole } from '../Chat.types';
|
|
11
7
|
import { userTextFileAttachmentsFromMessage } from '../userTextFileAttachments';
|
|
12
8
|
import { AgentMessageContent } from './AgentMessageContent';
|
|
13
9
|
import S from './ChatMessage.styl';
|
|
@@ -16,6 +12,7 @@ import { UserTextFileAttachmentBubble } from './UserTextFileAttachmentBubble';
|
|
|
16
12
|
export function ChatMessage({
|
|
17
13
|
role,
|
|
18
14
|
text,
|
|
15
|
+
inProgress,
|
|
19
16
|
userTextFileAttachments,
|
|
20
17
|
onQuickReply,
|
|
21
18
|
suppressedQuickReplyKeys,
|
|
@@ -35,11 +32,7 @@ export function ChatMessage({
|
|
|
35
32
|
<div className={cn(S.root, S[`role-${role}`])}>
|
|
36
33
|
{isSystem ? (
|
|
37
34
|
<div className={S.text}>
|
|
38
|
-
{
|
|
39
|
-
<TextShimmer as="span">{text}</TextShimmer>
|
|
40
|
-
) : (
|
|
41
|
-
text
|
|
42
|
-
)}
|
|
35
|
+
{inProgress ? <TextShimmer as="span">{text}</TextShimmer> : text}
|
|
43
36
|
</div>
|
|
44
37
|
) : isAssistant ? (
|
|
45
38
|
<AgentMessageContent
|
|
@@ -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,11 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
ChatPreset,
|
|
5
|
-
GENERATE_DASHBOARD_SLASH_COMMAND_ID,
|
|
6
|
-
GENERATING_DASHBOARD_SYSTEM_TEXT,
|
|
7
5
|
MessageRole,
|
|
8
6
|
type ScriptCompletePayload,
|
|
9
|
-
isGenerateDashboardSlashMessage,
|
|
10
7
|
} from '#uilib/components/ui/Chat/Chat.types';
|
|
11
8
|
import {
|
|
12
9
|
branchKeysUsedByUserMessages,
|
|
@@ -25,10 +22,7 @@ import {
|
|
|
25
22
|
buildChatSendMessagePayload,
|
|
26
23
|
displayTextFromSendPayload,
|
|
27
24
|
} from '#uilib/components/ui/Chat/buildChatSendMessagePayload';
|
|
28
|
-
import {
|
|
29
|
-
formatChatTranscript,
|
|
30
|
-
usedPresetIdsFromMessages,
|
|
31
|
-
} from '#uilib/components/ui/Chat/chat-preset-utils';
|
|
25
|
+
import { usedPresetIdsFromMessages } from '#uilib/components/ui/Chat/chat-preset-utils';
|
|
32
26
|
import {
|
|
33
27
|
isChatEmpty,
|
|
34
28
|
useChat,
|
|
@@ -42,7 +36,7 @@ import { useQueryParams } from '#uilib/hooks/useQueryParams';
|
|
|
42
36
|
import logger from '#uilib/lib/logger';
|
|
43
37
|
import type {
|
|
44
38
|
SlashCommandItem,
|
|
45
|
-
|
|
39
|
+
SlashOnItemCommand,
|
|
46
40
|
} from '#uilib/tiptap/slash-mention/types';
|
|
47
41
|
import { mergePresetFreeformDefaults } from '#uilib/utils/chatPresetMerge';
|
|
48
42
|
import { ScrollRef } from '@homecode/ui';
|
|
@@ -63,8 +57,6 @@ export type UseChatPanelChromeModelInput = {
|
|
|
63
57
|
onMessage?: (message: string) => void;
|
|
64
58
|
/** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
|
|
65
59
|
onScriptComplete?: (payload: ScriptCompletePayload) => void;
|
|
66
|
-
/** Generate dashboard from chat transcript (e.g. via `/generate-dashboard` slash command). */
|
|
67
|
-
onGenerateDashboard?: (transcript: string) => void | Promise<void>;
|
|
68
60
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
69
61
|
renderMessageChart?: () => React.ReactNode;
|
|
70
62
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
@@ -78,6 +70,8 @@ export type UseChatPanelChromeModelInput = {
|
|
|
78
70
|
) => void | Promise<void>;
|
|
79
71
|
/** Slash menu (`/`) in the composer; omit or pass empty to disable. */
|
|
80
72
|
slashCommandItems?: SlashCommandItem[];
|
|
73
|
+
/** Custom slash command handler (palette pick or Enter on `/id`). */
|
|
74
|
+
onSlashItemCommand?: SlashOnItemCommand;
|
|
81
75
|
};
|
|
82
76
|
|
|
83
77
|
export type UseChatPanelChromeModelResult = {
|
|
@@ -120,13 +114,13 @@ export function useChatPanelChromeModel({
|
|
|
120
114
|
scopeId,
|
|
121
115
|
onMessage,
|
|
122
116
|
onScriptComplete,
|
|
123
|
-
onGenerateDashboard,
|
|
124
117
|
renderMessageChart,
|
|
125
118
|
emptyState,
|
|
126
119
|
allowedAttachments,
|
|
127
120
|
allowPdfAttachments,
|
|
128
121
|
onAttachmentsDropped,
|
|
129
122
|
slashCommandItems,
|
|
123
|
+
onSlashItemCommand,
|
|
130
124
|
}: UseChatPanelChromeModelInput): UseChatPanelChromeModelResult {
|
|
131
125
|
const effectiveScopeId = scopeId ?? NO_SCOPE_FALLBACK;
|
|
132
126
|
const isMobile = useIsMobile();
|
|
@@ -202,7 +196,6 @@ export function useChatPanelChromeModel({
|
|
|
202
196
|
const [scriptCompleteByChatId, setScriptCompleteByChatId] = useState<
|
|
203
197
|
Record<string, boolean>
|
|
204
198
|
>({});
|
|
205
|
-
const [generatingDashboard, setGeneratingDashboard] = useState(false);
|
|
206
199
|
const scriptAdvanceLockRef = useRef(false);
|
|
207
200
|
const quickReplyLockRef = useRef(false);
|
|
208
201
|
const scrollRef = useRef<ScrollRef>(null);
|
|
@@ -961,75 +954,18 @@ export function useChatPanelChromeModel({
|
|
|
961
954
|
!linearScriptActive &&
|
|
962
955
|
(!graphActive || (!lastHasQuickMarkers && unusedBranchKeys.length === 0));
|
|
963
956
|
|
|
964
|
-
const handleGenerateDashboard = useCallback(async () => {
|
|
965
|
-
if (!currentChatId || !onGenerateDashboard) return;
|
|
966
|
-
const transcript = formatChatTranscript(
|
|
967
|
-
(chat?.messages ?? []).filter(m => m.role !== MessageRole.SYSTEM),
|
|
968
|
-
);
|
|
969
|
-
setGeneratingDashboard(true);
|
|
970
|
-
const systemPlaceholderId = addMessage(
|
|
971
|
-
currentChatId,
|
|
972
|
-
MessageRole.SYSTEM,
|
|
973
|
-
GENERATING_DASHBOARD_SYSTEM_TEXT,
|
|
974
|
-
);
|
|
975
|
-
try {
|
|
976
|
-
await Promise.resolve(onGenerateDashboard(transcript));
|
|
977
|
-
setScriptCompleteByChatId(prev => {
|
|
978
|
-
const next = { ...prev };
|
|
979
|
-
delete next[currentChatId];
|
|
980
|
-
return next;
|
|
981
|
-
});
|
|
982
|
-
} finally {
|
|
983
|
-
if (systemPlaceholderId) {
|
|
984
|
-
removeMessageById(currentChatId, systemPlaceholderId);
|
|
985
|
-
}
|
|
986
|
-
setGeneratingDashboard(false);
|
|
987
|
-
}
|
|
988
|
-
}, [
|
|
989
|
-
currentChatId,
|
|
990
|
-
onGenerateDashboard,
|
|
991
|
-
chat?.messages,
|
|
992
|
-
addMessage,
|
|
993
|
-
removeMessageById,
|
|
994
|
-
]);
|
|
995
|
-
|
|
996
|
-
const onSlashItemCommand = useCallback(
|
|
997
|
-
({ item }: SlashItemCommandContext) => {
|
|
998
|
-
if (
|
|
999
|
-
item.id !== GENERATE_DASHBOARD_SLASH_COMMAND_ID ||
|
|
1000
|
-
!onGenerateDashboard
|
|
1001
|
-
) {
|
|
1002
|
-
return false;
|
|
1003
|
-
}
|
|
1004
|
-
if (generatingDashboard) {
|
|
1005
|
-
return true;
|
|
1006
|
-
}
|
|
1007
|
-
queueMicrotask(() => {
|
|
1008
|
-
void handleGenerateDashboard();
|
|
1009
|
-
});
|
|
1010
|
-
return true;
|
|
1011
|
-
},
|
|
1012
|
-
[onGenerateDashboard, generatingDashboard, handleGenerateDashboard],
|
|
1013
|
-
);
|
|
1014
|
-
|
|
1015
957
|
const onPromptSubmitWithSlashCommands = useCallback(
|
|
1016
958
|
async (message: string, attachments?: ChatAttachmentDropItem[]) => {
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
) {
|
|
1022
|
-
void handleGenerateDashboard();
|
|
959
|
+
const trimmed = message.trim();
|
|
960
|
+
const slashItem = slashCommandItems?.find(
|
|
961
|
+
item => trimmed === `/${item.id}`,
|
|
962
|
+
);
|
|
963
|
+
if (slashItem && onSlashItemCommand?.({ item: slashItem }) === true) {
|
|
1023
964
|
return;
|
|
1024
965
|
}
|
|
1025
966
|
return handlePromptSubmit(message, attachments);
|
|
1026
967
|
},
|
|
1027
|
-
[
|
|
1028
|
-
handlePromptSubmit,
|
|
1029
|
-
onGenerateDashboard,
|
|
1030
|
-
generatingDashboard,
|
|
1031
|
-
handleGenerateDashboard,
|
|
1032
|
-
],
|
|
968
|
+
[handlePromptSubmit, slashCommandItems, onSlashItemCommand],
|
|
1033
969
|
);
|
|
1034
970
|
|
|
1035
971
|
const onDragChatWidth = useCallback(
|
|
@@ -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,
|
|
@@ -39,10 +42,10 @@ export type {
|
|
|
39
42
|
Message,
|
|
40
43
|
UserTextFileAttachment,
|
|
41
44
|
} from './Chat.types';
|
|
42
|
-
export {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
export { MessageRole } from './Chat.types';
|
|
46
|
+
export type {
|
|
47
|
+
SlashCommandItem,
|
|
48
|
+
SlashItemCommandContext,
|
|
49
|
+
SlashOnItemCommand,
|
|
50
|
+
} from '#uilib/tiptap/slash-mention/types';
|
|
48
51
|
export { CsvIcon } from '../../icons/CsvIcon/CsvIcon';
|
|
@@ -35,6 +35,8 @@ const CHAT_SCOPE_IDS_REGISTRY_KEY = 'chat-scope-ids';
|
|
|
35
35
|
|
|
36
36
|
export type AddChatMessageOptions = {
|
|
37
37
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
38
|
+
/** SYSTEM-only: mark as transient progress placeholder (shimmer until removed). */
|
|
39
|
+
inProgress?: boolean;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
export interface ChatContextType {
|
|
@@ -326,6 +328,7 @@ export function ChatProvider({
|
|
|
326
328
|
...(attachments?.length
|
|
327
329
|
? { userTextFileAttachments: attachments }
|
|
328
330
|
: {}),
|
|
331
|
+
...(options?.inProgress ? { inProgress: true } : {}),
|
|
329
332
|
};
|
|
330
333
|
|
|
331
334
|
setChats(prev => {
|
|
@@ -31,18 +31,25 @@ const DOCS_SAMPLE_SLASH_ITEMS: SlashCommandItem[] = [
|
|
|
31
31
|
const SAMPLE_COMMAND_REPLY_TEXT =
|
|
32
32
|
'Sample command ran via `onSlashItemCommand` — composer cleared, no mention inserted.';
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
'Demo reply for a normal message. Slash picks with a custom handler skip mention insert.';
|
|
34
|
+
const SAMPLE_COMMAND_PROGRESS_TEXT = 'Running sample command…';
|
|
36
35
|
|
|
37
|
-
function makeMessage(
|
|
36
|
+
function makeMessage(
|
|
37
|
+
role: MessageRole,
|
|
38
|
+
text: string,
|
|
39
|
+
options?: { inProgress?: boolean },
|
|
40
|
+
): Message {
|
|
38
41
|
return {
|
|
39
42
|
id: crypto.randomUUID(),
|
|
40
43
|
role,
|
|
41
44
|
text,
|
|
42
45
|
timestamp: Date.now(),
|
|
46
|
+
...(options?.inProgress ? { inProgress: true } : {}),
|
|
43
47
|
};
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
const ASSISTANT_REPLY_TEXT =
|
|
51
|
+
'Demo reply for a normal message. Slash picks with a custom handler skip mention insert.';
|
|
52
|
+
|
|
46
53
|
export default function ChatSlashCommandsPage() {
|
|
47
54
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
48
55
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -63,10 +70,30 @@ export default function ChatSlashCommandsPage() {
|
|
|
63
70
|
messages[messages.length - 1]?.role === MessageRole.USER;
|
|
64
71
|
|
|
65
72
|
const runSampleCommand = useCallback(() => {
|
|
73
|
+
const progressId = crypto.randomUUID();
|
|
74
|
+
setIsLoading(true);
|
|
66
75
|
setMessages(prev => [
|
|
67
76
|
...prev,
|
|
68
|
-
|
|
77
|
+
{
|
|
78
|
+
id: progressId,
|
|
79
|
+
role: MessageRole.SYSTEM,
|
|
80
|
+
text: SAMPLE_COMMAND_PROGRESS_TEXT,
|
|
81
|
+
timestamp: Date.now(),
|
|
82
|
+
inProgress: true,
|
|
83
|
+
},
|
|
69
84
|
]);
|
|
85
|
+
|
|
86
|
+
if (replyTimeoutRef.current != null) {
|
|
87
|
+
clearTimeout(replyTimeoutRef.current);
|
|
88
|
+
}
|
|
89
|
+
replyTimeoutRef.current = setTimeout(() => {
|
|
90
|
+
replyTimeoutRef.current = null;
|
|
91
|
+
setMessages(prev => [
|
|
92
|
+
...prev.filter(m => m.id !== progressId),
|
|
93
|
+
makeMessage(MessageRole.ASSISTANT, SAMPLE_COMMAND_REPLY_TEXT),
|
|
94
|
+
]);
|
|
95
|
+
setIsLoading(false);
|
|
96
|
+
}, 1200);
|
|
70
97
|
}, []);
|
|
71
98
|
|
|
72
99
|
const onSlashItemCommand = useCallback(
|
|
@@ -113,7 +140,7 @@ export default function ChatSlashCommandsPage() {
|
|
|
113
140
|
<AppPageHeader
|
|
114
141
|
breadcrumbs={[{ label: 'Chat' }, { label: 'Chat slash commands' }]}
|
|
115
142
|
title="Chat slash commands"
|
|
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.`}
|
|
143
|
+
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. Long-running handlers can append a SYSTEM message with inProgress while work is in flight, then remove it when done.`}
|
|
117
144
|
actions={
|
|
118
145
|
<DocsHeaderActions slashCommandItems={DOCS_SAMPLE_SLASH_ITEMS} />
|
|
119
146
|
}
|
|
@@ -127,8 +154,10 @@ export default function ChatSlashCommandsPage() {
|
|
|
127
154
|
shell below: scrolling history, empty state, disclaimer, composer.
|
|
128
155
|
Type <kbd className="font-mono">/</kbd> at line start or after a
|
|
129
156
|
space; pick <kbd className="font-mono">sample-command</kbd> to run the
|
|
130
|
-
custom handler
|
|
131
|
-
<kbd className="font-mono"
|
|
157
|
+
custom handler (shows a transient{' '}
|
|
158
|
+
<kbd className="font-mono">inProgress</kbd> shimmer while the demo
|
|
159
|
+
action runs), or send <kbd className="font-mono">/sample-command</kbd>{' '}
|
|
160
|
+
with Enter.
|
|
132
161
|
</p>
|
|
133
162
|
<ChatChrome
|
|
134
163
|
showResizeHandle={false}
|
|
@@ -156,10 +185,7 @@ export default function ChatSlashCommandsPage() {
|
|
|
156
185
|
emptyState={{
|
|
157
186
|
title: 'Try a slash command',
|
|
158
187
|
description:
|
|
159
|
-
'Pick sample-command from the palette or send /sample-command — onSlashItemCommand clears the composer
|
|
160
|
-
additionalContent: (
|
|
161
|
-
<p>Optional empty-state slot via additionalContent.</p>
|
|
162
|
-
),
|
|
188
|
+
'Pick sample-command from the palette or send /sample-command — onSlashItemCommand clears the composer, shows an inProgress placeholder, then posts the demo reply.',
|
|
163
189
|
}}
|
|
164
190
|
/>
|
|
165
191
|
</PageContentSection>
|
|
@@ -7,9 +7,10 @@ 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
|
/**
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
GENERATE_DASHBOARD_SLASH_COMMAND_ID,
|
|
3
|
-
isGenerateDashboardSlashMessage,
|
|
4
|
-
} from './Chat.types';
|
|
5
|
-
|
|
6
|
-
describe('isGenerateDashboardSlashMessage', () => {
|
|
7
|
-
it('matches exact slash command', () => {
|
|
8
|
-
expect(
|
|
9
|
-
isGenerateDashboardSlashMessage(
|
|
10
|
-
`/${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`,
|
|
11
|
-
),
|
|
12
|
-
).toBe(true);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it('trims surrounding whitespace', () => {
|
|
16
|
-
expect(
|
|
17
|
-
isGenerateDashboardSlashMessage(
|
|
18
|
-
` /${GENERATE_DASHBOARD_SLASH_COMMAND_ID} `,
|
|
19
|
-
),
|
|
20
|
-
).toBe(true);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('rejects plain text and partial commands', () => {
|
|
24
|
-
expect(isGenerateDashboardSlashMessage('generate-dashboard')).toBe(false);
|
|
25
|
-
expect(isGenerateDashboardSlashMessage('/generate')).toBe(false);
|
|
26
|
-
expect(
|
|
27
|
-
isGenerateDashboardSlashMessage(
|
|
28
|
-
`/ ${GENERATE_DASHBOARD_SLASH_COMMAND_ID}`,
|
|
29
|
-
),
|
|
30
|
-
).toBe(false);
|
|
31
|
-
});
|
|
32
|
-
});
|