@sybilion/uilib 1.3.78 → 1.3.80
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.js +2 -2
- package/dist/esm/components/ui/Chat/Chat.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +10 -6
- package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.styl.js +2 -2
- package/dist/esm/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +2 -2
- package/dist/esm/components/ui/Chat/ChatPresets/ChatPresets.styl.js +1 -1
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +2 -2
- package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -1
- package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +6 -5
- package/dist/esm/components/ui/Chat/buildChatSendMessagePayload.js +3 -1
- package/dist/esm/components/ui/FileChip/FileChip.js +4 -0
- package/dist/esm/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.js +3 -1
- package/dist/esm/contexts/chat-context.js +51 -5
- package/dist/esm/index.js +1 -0
- package/dist/esm/types/src/components/ui/Chat/Chat.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +12 -0
- 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 +4 -0
- package/dist/esm/types/src/components/ui/Chat/ChatMessage/ChatMessage.d.ts +1 -1
- package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSelector.d.ts +2 -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 +5 -3
- package/dist/esm/types/src/components/ui/Chat/index.d.ts +3 -1
- package/dist/esm/types/src/components/ui/FileChip/FileChip.types.d.ts +1 -1
- package/dist/esm/types/src/contexts/chat-context.d.ts +16 -4
- package/package.json +1 -1
- package/src/components/ui/Chat/Chat.styl +4 -1
- package/src/components/ui/Chat/Chat.tsx +2 -1
- package/src/components/ui/Chat/Chat.types.ts +14 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.styl +20 -10
- package/src/components/ui/Chat/ChatChrome/ChatChrome.styl.d.ts +2 -0
- package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +27 -10
- package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +4 -0
- package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl +1 -2
- package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +9 -1
- package/src/components/ui/Chat/ChatPresets/ChatPresets.styl +5 -4
- package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +3 -1
- package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -0
- package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +24 -5
- package/src/components/ui/Chat/buildChatSendMessagePayload.ts +2 -1
- package/src/components/ui/Chat/index.ts +4 -0
- package/src/components/ui/FileChip/FileChip.tsx +11 -0
- package/src/components/ui/FileChip/FileChip.types.ts +1 -1
- package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.test.ts +13 -4
- package/src/components/widgets/DriversComparisonChart/driversComparisonChart.helpers.ts +2 -1
- package/src/contexts/chat-context.tsx +80 -6
- package/src/docs/pages/ChatPage.styl +6 -0
- package/src/docs/pages/ChatPage.styl.d.ts +7 -0
- package/src/docs/pages/ChatPage.tsx +30 -87
- package/src/docs/pages/ChatSlashCommandsPage.tsx +4 -4
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
type Chat,
|
|
13
|
+
type ChatMeta,
|
|
13
14
|
type ChatSendMessagePayload,
|
|
14
15
|
type Message,
|
|
15
16
|
MessageRole,
|
|
@@ -37,12 +38,14 @@ export type AddChatMessageOptions = {
|
|
|
37
38
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
38
39
|
/** SYSTEM-only: mark as transient progress placeholder (shimmer until removed). */
|
|
39
40
|
inProgress?: boolean;
|
|
41
|
+
meta?: ChatMeta;
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
export type UpdateChatMessagePatch = {
|
|
43
45
|
role?: MessageRole;
|
|
44
46
|
text?: string;
|
|
45
47
|
inProgress?: boolean;
|
|
48
|
+
meta?: ChatMeta;
|
|
46
49
|
};
|
|
47
50
|
|
|
48
51
|
export type NewChatOptions = {
|
|
@@ -50,6 +53,11 @@ export type NewChatOptions = {
|
|
|
50
53
|
seedMessages?: readonly Message[];
|
|
51
54
|
};
|
|
52
55
|
|
|
56
|
+
export type SendMessageResult = {
|
|
57
|
+
response: string;
|
|
58
|
+
sessionId: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
53
61
|
export interface ChatContextType {
|
|
54
62
|
/** Returns the new session id, or undefined if no user / not created. */
|
|
55
63
|
newChat: (scopeId: string, options?: NewChatOptions) => string | undefined;
|
|
@@ -78,11 +86,13 @@ export interface ChatContextType {
|
|
|
78
86
|
chatId: string,
|
|
79
87
|
messages: Message[],
|
|
80
88
|
) => void;
|
|
89
|
+
/** Shallow-merge keys into `chat.meta` and persist. */
|
|
90
|
+
updateChatMeta: (scopeId: string, chatId: string, patch: ChatMeta) => void;
|
|
81
91
|
sendMessage: (
|
|
82
92
|
scopeId: string,
|
|
83
93
|
message: string | ChatSendMessagePayload,
|
|
84
94
|
chatId?: string,
|
|
85
|
-
) => Promise<
|
|
95
|
+
) => Promise<SendMessageResult>;
|
|
86
96
|
getChatsForScopeId: (scopeId: string) => Chat[];
|
|
87
97
|
getCurrentChatId: (scopeId: string) => string | null;
|
|
88
98
|
deleteChat: (scopeId: string, sessionId: string) => void;
|
|
@@ -184,6 +194,7 @@ function cloneMessagesForNewSession(messages: readonly Message[]): Message[] {
|
|
|
184
194
|
.filter(message => !message.inProgress)
|
|
185
195
|
.map(message => ({
|
|
186
196
|
...message,
|
|
197
|
+
...(message.meta ? { meta: { ...message.meta } } : {}),
|
|
187
198
|
...(message.userTextFileAttachments
|
|
188
199
|
? {
|
|
189
200
|
userTextFileAttachments: message.userTextFileAttachments.map(
|
|
@@ -372,6 +383,7 @@ export function ChatProvider({
|
|
|
372
383
|
? { userTextFileAttachments: attachments }
|
|
373
384
|
: {}),
|
|
374
385
|
...(options?.inProgress ? { inProgress: true } : {}),
|
|
386
|
+
...(options?.meta ? { meta: { ...options.meta } } : {}),
|
|
375
387
|
};
|
|
376
388
|
|
|
377
389
|
setChats(prev => {
|
|
@@ -444,6 +456,9 @@ export function ChatProvider({
|
|
|
444
456
|
) {
|
|
445
457
|
delete next.inProgress;
|
|
446
458
|
}
|
|
459
|
+
if (patch.meta != null) {
|
|
460
|
+
next.meta = { ...next.meta, ...patch.meta };
|
|
461
|
+
}
|
|
447
462
|
return next;
|
|
448
463
|
}),
|
|
449
464
|
};
|
|
@@ -460,7 +475,10 @@ export function ChatProvider({
|
|
|
460
475
|
(scopeId: string, chatId: string, messages: Message[]) => {
|
|
461
476
|
if (userSwitchKey === null) return;
|
|
462
477
|
addScopeIdToRegistry(scopeId);
|
|
463
|
-
const cloned = messages.map(message => ({
|
|
478
|
+
const cloned = messages.map(message => ({
|
|
479
|
+
...message,
|
|
480
|
+
...(message.meta ? { meta: { ...message.meta } } : {}),
|
|
481
|
+
}));
|
|
464
482
|
|
|
465
483
|
setChats(prev => {
|
|
466
484
|
const scopeChats = prev[scopeId] ?? [];
|
|
@@ -478,12 +496,34 @@ export function ChatProvider({
|
|
|
478
496
|
[userSwitchKey],
|
|
479
497
|
);
|
|
480
498
|
|
|
499
|
+
const updateChatMeta = useCallback(
|
|
500
|
+
(scopeId: string, chatId: string, patch: ChatMeta) => {
|
|
501
|
+
if (userSwitchKey === null) return;
|
|
502
|
+
setChats(prev => {
|
|
503
|
+
const scopeChats = prev[scopeId] ?? [];
|
|
504
|
+
const updatedChats = scopeChats.map(chat => {
|
|
505
|
+
if (chat.session_id !== chatId) return chat;
|
|
506
|
+
return {
|
|
507
|
+
...chat,
|
|
508
|
+
meta: { ...chat.meta, ...patch },
|
|
509
|
+
};
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
const chatsKey = getChatsKey(scopeId);
|
|
513
|
+
LS.set(chatsKey, updatedChats);
|
|
514
|
+
|
|
515
|
+
return { ...prev, [scopeId]: updatedChats };
|
|
516
|
+
});
|
|
517
|
+
},
|
|
518
|
+
[userSwitchKey],
|
|
519
|
+
);
|
|
520
|
+
|
|
481
521
|
const sendMessage = useCallback(
|
|
482
522
|
async (
|
|
483
523
|
scopeId: string,
|
|
484
524
|
message: string | ChatSendMessagePayload,
|
|
485
525
|
chatId?: string,
|
|
486
|
-
): Promise<
|
|
526
|
+
): Promise<SendMessageResult> => {
|
|
487
527
|
const targetChatId = chatId ?? getCurrentChatId(scopeId);
|
|
488
528
|
if (targetChatId === null || targetChatId === '') {
|
|
489
529
|
throw new Error('No chat selected');
|
|
@@ -506,11 +546,28 @@ export function ChatProvider({
|
|
|
506
546
|
);
|
|
507
547
|
}
|
|
508
548
|
|
|
549
|
+
let progressMessageId: string | undefined;
|
|
550
|
+
if (typeof message !== 'string' && message.systemProgressLabel) {
|
|
551
|
+
progressMessageId = addMessage(
|
|
552
|
+
scopeId,
|
|
553
|
+
targetChatId,
|
|
554
|
+
MessageRole.SYSTEM,
|
|
555
|
+
message.systemProgressLabel,
|
|
556
|
+
{ inProgress: true },
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
509
560
|
const pendingChatSessionId = targetChatId;
|
|
510
561
|
beginOutboundPending(scopeId, pendingChatSessionId);
|
|
562
|
+
let effectiveSessionId = pendingChatSessionId;
|
|
511
563
|
try {
|
|
512
564
|
const data = await sendChatMessageFn(apiPayload, pendingChatSessionId);
|
|
513
565
|
|
|
566
|
+
effectiveSessionId =
|
|
567
|
+
data.session_id && data.session_id !== pendingChatSessionId
|
|
568
|
+
? data.session_id
|
|
569
|
+
: pendingChatSessionId;
|
|
570
|
+
|
|
514
571
|
if (data.session_id && data.session_id !== pendingChatSessionId) {
|
|
515
572
|
setChats(prev => {
|
|
516
573
|
const scopeChats = prev[scopeId] ?? [];
|
|
@@ -528,15 +585,24 @@ export function ChatProvider({
|
|
|
528
585
|
setCurrentChatId(scopeId, data.session_id);
|
|
529
586
|
}
|
|
530
587
|
|
|
588
|
+
if (progressMessageId) {
|
|
589
|
+
removeMessageById(scopeId, effectiveSessionId, progressMessageId);
|
|
590
|
+
progressMessageId = undefined;
|
|
591
|
+
}
|
|
592
|
+
|
|
531
593
|
addMessage(
|
|
532
594
|
scopeId,
|
|
533
|
-
|
|
595
|
+
effectiveSessionId,
|
|
534
596
|
MessageRole.ASSISTANT,
|
|
535
597
|
data.response,
|
|
536
598
|
);
|
|
537
599
|
|
|
538
|
-
return data.response;
|
|
600
|
+
return { response: data.response, sessionId: effectiveSessionId };
|
|
539
601
|
} catch (error) {
|
|
602
|
+
if (progressMessageId) {
|
|
603
|
+
removeMessageById(scopeId, effectiveSessionId, progressMessageId);
|
|
604
|
+
}
|
|
605
|
+
|
|
540
606
|
const errorMessage =
|
|
541
607
|
error instanceof Error
|
|
542
608
|
? error.message
|
|
@@ -544,7 +610,7 @@ export function ChatProvider({
|
|
|
544
610
|
|
|
545
611
|
addMessage(
|
|
546
612
|
scopeId,
|
|
547
|
-
|
|
613
|
+
effectiveSessionId,
|
|
548
614
|
MessageRole.ASSISTANT,
|
|
549
615
|
errorMessage,
|
|
550
616
|
);
|
|
@@ -555,6 +621,7 @@ export function ChatProvider({
|
|
|
555
621
|
},
|
|
556
622
|
[
|
|
557
623
|
addMessage,
|
|
624
|
+
removeMessageById,
|
|
558
625
|
beginOutboundPending,
|
|
559
626
|
endOutboundPending,
|
|
560
627
|
getCurrentChatId,
|
|
@@ -597,6 +664,7 @@ export function ChatProvider({
|
|
|
597
664
|
removeMessageById,
|
|
598
665
|
updateMessageById,
|
|
599
666
|
setChatMessages,
|
|
667
|
+
updateChatMeta,
|
|
600
668
|
sendMessage,
|
|
601
669
|
getChatsForScopeId,
|
|
602
670
|
getCurrentChatId,
|
|
@@ -683,6 +751,8 @@ export function useChatsForScopeId(scopeId: string) {
|
|
|
683
751
|
addMessage,
|
|
684
752
|
removeMessageById,
|
|
685
753
|
updateMessageById,
|
|
754
|
+
updateChatMeta,
|
|
755
|
+
setChatMessages,
|
|
686
756
|
sendMessage,
|
|
687
757
|
deleteChat,
|
|
688
758
|
} = useChats();
|
|
@@ -713,6 +783,10 @@ export function useChatsForScopeId(scopeId: string) {
|
|
|
713
783
|
) => updateMessageById(scopeId, chatId, messageId, patch),
|
|
714
784
|
sendMessage: (message: string | ChatSendMessagePayload, chatId?: string) =>
|
|
715
785
|
sendMessage(scopeId, message, chatId),
|
|
786
|
+
updateChatMeta: (chatId: string, patch: ChatMeta) =>
|
|
787
|
+
updateChatMeta(scopeId, chatId, patch),
|
|
788
|
+
setChatMessages: (chatId: string, messages: Message[]) =>
|
|
789
|
+
setChatMessages(scopeId, chatId, messages),
|
|
716
790
|
deleteChat: (sessionId: string) => deleteChat(scopeId, sessionId),
|
|
717
791
|
};
|
|
718
792
|
}
|
|
@@ -1,106 +1,49 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { ChatChrome, Message, MessageRole } from '#uilib/components/ui/Chat';
|
|
1
|
+
import { ChatChrome, useChatPanelChromeModel } from '#uilib/components/ui/Chat';
|
|
4
2
|
import { PageContentSection } from '#uilib/components/ui/Page';
|
|
5
|
-
import { ScrollRef } from '@homecode/ui';
|
|
6
3
|
|
|
4
|
+
import {
|
|
5
|
+
DOCS_DATASET_PRESETS,
|
|
6
|
+
DOCS_SAMPLE_DATASET_NAME,
|
|
7
|
+
} from '../chatPresets.sample';
|
|
7
8
|
import { AppPageHeader } from '../components/AppPageHeader/AppPageHeader';
|
|
9
|
+
import { DOCS_CHAT_USER_KEY } from '../docsConstants';
|
|
8
10
|
import { DocsHeaderActions } from '../docsHeaderActions';
|
|
11
|
+
import S from './ChatPage.styl';
|
|
9
12
|
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
const ASSISTANT_REPLY_TEXT =
|
|
13
|
-
'This is a generic assistant reply for the docs preview. Wire your app to a real model here.';
|
|
14
|
-
|
|
15
|
-
function makeMessage(role: MessageRole, text: string): Message {
|
|
16
|
-
return {
|
|
17
|
-
id: crypto.randomUUID(),
|
|
18
|
-
role,
|
|
19
|
-
text,
|
|
20
|
-
timestamp: Date.now(),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
13
|
+
const DOCS_CHAT_INLINE_SCOPE_ID = `${DOCS_CHAT_USER_KEY}-docs-chat-inline`;
|
|
23
14
|
|
|
24
15
|
export default function ChatPage() {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}, []);
|
|
37
|
-
|
|
38
|
-
const isEmpty = messages.length === 0 && !isLoading;
|
|
39
|
-
const isLastMessageFromUser =
|
|
40
|
-
messages.length > 0 &&
|
|
41
|
-
messages[messages.length - 1]?.role === MessageRole.USER;
|
|
42
|
-
|
|
43
|
-
const onSubmit = useCallback(
|
|
44
|
-
(raw: string) => {
|
|
45
|
-
const text = raw.trim();
|
|
46
|
-
if (!text || isLoading) return;
|
|
47
|
-
|
|
48
|
-
setMessages(prev => [...prev, makeMessage(MessageRole.USER, text)]);
|
|
49
|
-
setIsLoading(true);
|
|
50
|
-
|
|
51
|
-
if (replyTimeoutRef.current != null) {
|
|
52
|
-
clearTimeout(replyTimeoutRef.current);
|
|
53
|
-
}
|
|
54
|
-
replyTimeoutRef.current = setTimeout(() => {
|
|
55
|
-
replyTimeoutRef.current = null;
|
|
56
|
-
setMessages(prev => [
|
|
57
|
-
...prev,
|
|
58
|
-
makeMessage(MessageRole.ASSISTANT, ASSISTANT_REPLY_TEXT),
|
|
59
|
-
]);
|
|
60
|
-
setIsLoading(false);
|
|
61
|
-
}, 900);
|
|
16
|
+
const { chromeProps } = useChatPanelChromeModel({
|
|
17
|
+
embedAsPage: true,
|
|
18
|
+
scopeId: DOCS_CHAT_INLINE_SCOPE_ID,
|
|
19
|
+
presets: DOCS_DATASET_PRESETS,
|
|
20
|
+
emptyState: {
|
|
21
|
+
title: 'Start a conversation',
|
|
22
|
+
description:
|
|
23
|
+
'Send a message below or pick a preset chip. The demo echoes your text after a short delay.',
|
|
24
|
+
additionalContent: (
|
|
25
|
+
<p>Optional empty-state slot via additionalContent.</p>
|
|
26
|
+
),
|
|
62
27
|
},
|
|
63
|
-
|
|
64
|
-
);
|
|
28
|
+
});
|
|
65
29
|
|
|
66
30
|
return (
|
|
67
31
|
<>
|
|
68
32
|
<AppPageHeader
|
|
69
33
|
breadcrumbs={[{ label: 'Chat' }]}
|
|
70
34
|
title="Chat"
|
|
71
|
-
subheader=
|
|
72
|
-
actions={<DocsHeaderActions />}
|
|
35
|
+
subheader={`Chat shell with message list, prompt, and dataset-scoped preset bubbles for "${DOCS_SAMPLE_DATASET_NAME}".`}
|
|
36
|
+
actions={<DocsHeaderActions presets={DOCS_DATASET_PRESETS} />}
|
|
73
37
|
/>
|
|
74
38
|
<PageContentSection>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
suppressedQuickReplyKeys={NO_QUICK_REPLY_KEYS}
|
|
84
|
-
isLoading={isLoading}
|
|
85
|
-
scriptContinueLabel={undefined}
|
|
86
|
-
onScriptContinue={undefined}
|
|
87
|
-
showSyntheticBranchButtons={false}
|
|
88
|
-
unusedBranchKeys={[]}
|
|
89
|
-
showInlinePresets={false}
|
|
90
|
-
isLastMessageFromUser={isLastMessageFromUser}
|
|
91
|
-
scrollRef={scrollRef}
|
|
92
|
-
effectiveScopeId="docs-chat-inline"
|
|
93
|
-
onPromptSubmit={onSubmit}
|
|
94
|
-
onChatDeleted={() => {}}
|
|
95
|
-
emptyState={{
|
|
96
|
-
title: 'Start a conversation',
|
|
97
|
-
description:
|
|
98
|
-
'Send a message below. This demo appends a canned reply after a short delay.',
|
|
99
|
-
additionalContent: (
|
|
100
|
-
<p>Optional empty-state slot via additionalContent.</p>
|
|
101
|
-
),
|
|
102
|
-
}}
|
|
103
|
-
/>
|
|
39
|
+
<p style={{ marginBottom: 16, fontSize: 14, lineHeight: 1.5 }}>
|
|
40
|
+
Preset chips render above the prompt in the empty state (fixed layout)
|
|
41
|
+
and inline after the assistant reply. Click a chip to send its text;
|
|
42
|
+
used chips hide for the session.
|
|
43
|
+
</p>
|
|
44
|
+
<div className={S.chatDemoHost}>
|
|
45
|
+
<ChatChrome {...chromeProps} />
|
|
46
|
+
</div>
|
|
104
47
|
</PageContentSection>
|
|
105
48
|
</>
|
|
106
49
|
);
|
|
@@ -18,9 +18,9 @@ const NO_QUICK_REPLY_KEYS: ReadonlySet<string> = new Set();
|
|
|
18
18
|
/** Sample items — default TipTap mention insert; optional `className` / `color` style the chip. */
|
|
19
19
|
const DOCS_SLASH_ITEMS: SlashCommandItem[] = [
|
|
20
20
|
{
|
|
21
|
-
id: 'generate-
|
|
22
|
-
label: 'Generate
|
|
23
|
-
description: 'Insert /generate-
|
|
21
|
+
id: 'generate-report',
|
|
22
|
+
label: 'Generate report',
|
|
23
|
+
description: 'Insert /generate-report',
|
|
24
24
|
color: 'var(--brand-color-600)',
|
|
25
25
|
},
|
|
26
26
|
{
|
|
@@ -48,7 +48,7 @@ function makeMessage(role: MessageRole, text: string): Message {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const ASSISTANT_REPLY_TEXT =
|
|
51
|
-
'Demo reply. Slash picks insert inline mention chips; plain text on send uses each item id (e.g. /generate-
|
|
51
|
+
'Demo reply. Slash picks insert inline mention chips; plain text on send uses each item id (e.g. /generate-report).';
|
|
52
52
|
|
|
53
53
|
export default function ChatSlashCommandsPage() {
|
|
54
54
|
const [messages, setMessages] = useState<Message[]>([]);
|