@sybilion/uilib 1.3.77 → 1.3.79
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 +6 -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/ChatMessage/ChatMessage.styl.js +1 -1
- 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/FileChip/FileChip.js +4 -0
- package/dist/esm/contexts/chat-context.js +36 -4
- 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 +10 -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 +12 -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 +12 -4
- 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.styl +1 -0
- 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/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/contexts/chat-context.tsx +57 -5
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
.root
|
|
2
2
|
display flex
|
|
3
3
|
flex-direction column
|
|
4
|
+
flex 1
|
|
4
5
|
height 100%
|
|
5
6
|
min-height 0
|
|
6
7
|
// max-height 100vh
|
|
@@ -15,4 +16,6 @@
|
|
|
15
16
|
padding-right var(--p-12) // to not overlap with ChatSheet close button
|
|
16
17
|
|
|
17
18
|
.isEmpty
|
|
18
|
-
|
|
19
|
+
flex 1
|
|
20
|
+
min-height 0
|
|
21
|
+
overflow hidden
|
|
@@ -16,11 +16,12 @@ export function Chat({
|
|
|
16
16
|
scopeId,
|
|
17
17
|
onChatDeleted,
|
|
18
18
|
onNewChat,
|
|
19
|
+
hideChatSelector = false,
|
|
19
20
|
...props
|
|
20
21
|
}: ChatProps) {
|
|
21
22
|
return (
|
|
22
23
|
<div className={cn(S.root, className, isEmpty && S.isEmpty)} {...props}>
|
|
23
|
-
{scopeId ? (
|
|
24
|
+
{scopeId && !hideChatSelector ? (
|
|
24
25
|
<div className={S.header}>
|
|
25
26
|
<ChatSelector
|
|
26
27
|
id={scopeId}
|
|
@@ -31,6 +31,10 @@ export type ChatSendMessagePayload = {
|
|
|
31
31
|
loadingLabel?: string;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
export type ChatMetaValue = string | number | boolean | null;
|
|
35
|
+
|
|
36
|
+
export type ChatMeta = Record<string, ChatMetaValue>;
|
|
37
|
+
|
|
34
38
|
export interface Message {
|
|
35
39
|
id: string;
|
|
36
40
|
role: MessageRole;
|
|
@@ -39,12 +43,14 @@ export interface Message {
|
|
|
39
43
|
userTextFileAttachments?: UserTextFileAttachment[];
|
|
40
44
|
/** SYSTEM-only: transient progress placeholder while work is in flight. */
|
|
41
45
|
inProgress?: boolean;
|
|
46
|
+
meta?: ChatMeta;
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
export interface Chat {
|
|
45
50
|
session_id: string;
|
|
46
51
|
name: string;
|
|
47
52
|
messages: Message[];
|
|
53
|
+
meta?: ChatMeta;
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
export interface ChatPreset {
|
|
@@ -124,6 +130,10 @@ export interface ChatMessageProps {
|
|
|
124
130
|
onScriptContinue?: () => void;
|
|
125
131
|
/** Renders `[CHART]` placeholder in assistant messages (app supplies dataset chart). */
|
|
126
132
|
renderMessageChart?: () => ReactNode;
|
|
133
|
+
/** Full message for system-role render delegation. */
|
|
134
|
+
message?: Message;
|
|
135
|
+
/** When set, SYSTEM messages render via this callback instead of plain text. */
|
|
136
|
+
renderSystemMessage?: (message: Message) => ReactNode;
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
|
@@ -135,4 +145,6 @@ export interface ChatProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
135
145
|
onChatDeleted?: (sessionId: string) => void;
|
|
136
146
|
/** "+ New Chat" in the selector; when omitted, starts an empty session. */
|
|
137
147
|
onNewChat?: () => void;
|
|
148
|
+
/** When true, skip built-in header ChatSelector (e.g. external page-header slot). */
|
|
149
|
+
hideChatSelector?: boolean;
|
|
138
150
|
}
|
|
@@ -83,29 +83,39 @@
|
|
|
83
83
|
.scrollInner
|
|
84
84
|
padding-top var(--p-10)
|
|
85
85
|
min-height 100%
|
|
86
|
-
// padding-bottom 320px // goes under prompt
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
.emptyBody
|
|
88
|
+
flex 1
|
|
89
|
+
min-height 0
|
|
90
|
+
display flex
|
|
91
|
+
flex-direction column
|
|
92
|
+
overflow hidden
|
|
92
93
|
|
|
94
|
+
& > *
|
|
95
|
+
flex 1
|
|
96
|
+
min-height 0
|
|
97
|
+
overflow auto
|
|
93
98
|
|
|
94
99
|
// ---------------
|
|
95
100
|
|
|
96
101
|
.footer
|
|
102
|
+
position relative
|
|
97
103
|
z-index 50
|
|
98
104
|
display flex
|
|
99
105
|
flex-direction column
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
bottom 0
|
|
103
|
-
width 100%;
|
|
106
|
+
flex-shrink 0
|
|
107
|
+
width 100%
|
|
104
108
|
|
|
105
109
|
backdrop-filter blur(30px)
|
|
106
110
|
background-color var(--background-alpha-800)
|
|
107
111
|
border-top 1px solid var(--border)
|
|
108
|
-
box-shadow 0
|
|
112
|
+
box-shadow 0 8px 24px 0 var(--background)
|
|
113
|
+
|
|
114
|
+
.fixedPresets
|
|
115
|
+
position relative
|
|
116
|
+
z-index 10
|
|
117
|
+
flex-shrink 0
|
|
118
|
+
width 100%
|
|
109
119
|
|
|
110
120
|
.notice
|
|
111
121
|
position absolute
|
|
@@ -38,6 +38,7 @@ export function ChatChrome({
|
|
|
38
38
|
scriptContinueLabel,
|
|
39
39
|
onScriptContinue,
|
|
40
40
|
renderMessageChart,
|
|
41
|
+
renderSystemMessage,
|
|
41
42
|
showSyntheticBranchButtons,
|
|
42
43
|
unusedBranchKeys,
|
|
43
44
|
showInlinePresets,
|
|
@@ -56,6 +57,7 @@ export function ChatChrome({
|
|
|
56
57
|
slashCommandItems,
|
|
57
58
|
onSlashItemCommand,
|
|
58
59
|
promptPlaceholder,
|
|
60
|
+
hideChatSelector = false,
|
|
59
61
|
}: ChatChromeProps) {
|
|
60
62
|
const filteredAllowedAttachments = useMemo(
|
|
61
63
|
() => filterToTextAttachments(allowedAttachments),
|
|
@@ -186,12 +188,12 @@ export function ChatChrome({
|
|
|
186
188
|
scopeId={effectiveScopeId}
|
|
187
189
|
onChatDeleted={onChatDeleted}
|
|
188
190
|
onNewChat={onNewChat}
|
|
191
|
+
hideChatSelector={hideChatSelector}
|
|
189
192
|
>
|
|
190
193
|
{isEmpty ? (
|
|
191
|
-
|
|
194
|
+
<div className={S.emptyBody}>
|
|
192
195
|
<Chat.EmptyState {...emptyState} />
|
|
193
|
-
|
|
194
|
-
</>
|
|
196
|
+
</div>
|
|
195
197
|
) : (
|
|
196
198
|
<div className={S.scrollWrapper}>
|
|
197
199
|
<Scroll
|
|
@@ -199,7 +201,7 @@ export function ChatChrome({
|
|
|
199
201
|
yScrollbarClassName={S.scrollbar}
|
|
200
202
|
className={S.scroll}
|
|
201
203
|
innerClassName={S.scrollInner}
|
|
202
|
-
offset={{ y: { before: 56, after:
|
|
204
|
+
offset={{ y: { before: 56, after: 24 } }}
|
|
203
205
|
fadeSize="m"
|
|
204
206
|
autoHide
|
|
205
207
|
ref={scrollRef}
|
|
@@ -209,6 +211,7 @@ export function ChatChrome({
|
|
|
209
211
|
return (
|
|
210
212
|
<Chat.Message
|
|
211
213
|
key={msg.id}
|
|
214
|
+
message={msg}
|
|
212
215
|
role={msg.role}
|
|
213
216
|
text={msg.text}
|
|
214
217
|
inProgress={msg.inProgress}
|
|
@@ -229,6 +232,7 @@ export function ChatChrome({
|
|
|
229
232
|
: undefined
|
|
230
233
|
}
|
|
231
234
|
renderMessageChart={renderMessageChart}
|
|
235
|
+
renderSystemMessage={renderSystemMessage}
|
|
232
236
|
/>
|
|
233
237
|
);
|
|
234
238
|
})}
|
|
@@ -268,6 +272,10 @@ export function ChatChrome({
|
|
|
268
272
|
</div>
|
|
269
273
|
)}
|
|
270
274
|
|
|
275
|
+
{isEmpty ? (
|
|
276
|
+
<div className={S.fixedPresets}>{renderPresets('fixed')}</div>
|
|
277
|
+
) : null}
|
|
278
|
+
|
|
271
279
|
<div className={cn(S.footer, footerClassName)}>
|
|
272
280
|
{isEmpty ? (
|
|
273
281
|
<div className={S.notice}>
|
|
@@ -38,6 +38,8 @@ export interface ChatChromeProps {
|
|
|
38
38
|
scriptContinueLabel: string | undefined;
|
|
39
39
|
onScriptContinue: (() => void) | undefined;
|
|
40
40
|
renderMessageChart?: () => React.ReactNode;
|
|
41
|
+
/** When set, SYSTEM messages render via this callback instead of plain text. */
|
|
42
|
+
renderSystemMessage?: (message: Message) => React.ReactNode;
|
|
41
43
|
showSyntheticBranchButtons: boolean;
|
|
42
44
|
unusedBranchKeys: string[];
|
|
43
45
|
showInlinePresets: boolean;
|
|
@@ -69,4 +71,6 @@ export interface ChatChromeProps {
|
|
|
69
71
|
onSlashItemCommand?: SlashOnItemCommand;
|
|
70
72
|
/** Composer placeholder forwarded to `Chat.Prompt`. */
|
|
71
73
|
promptPlaceholder?: string;
|
|
74
|
+
/** When true, skip built-in header ChatSelector (e.g. external page-header slot). */
|
|
75
|
+
hideChatSelector?: boolean;
|
|
72
76
|
}
|
|
@@ -22,6 +22,8 @@ export function ChatMessage({
|
|
|
22
22
|
scriptContinue,
|
|
23
23
|
onScriptContinue,
|
|
24
24
|
renderMessageChart,
|
|
25
|
+
message,
|
|
26
|
+
renderSystemMessage,
|
|
25
27
|
}: ChatMessageProps) {
|
|
26
28
|
const fileAttachments = userTextFileAttachmentsFromMessage({
|
|
27
29
|
userTextFileAttachments,
|
|
@@ -33,7 +35,13 @@ export function ChatMessage({
|
|
|
33
35
|
<div className={cn(S.root, S[`role-${role}`])}>
|
|
34
36
|
{isSystem ? (
|
|
35
37
|
<div className={S.text}>
|
|
36
|
-
{inProgress ?
|
|
38
|
+
{inProgress ? (
|
|
39
|
+
<TextShimmer as="span">{text}</TextShimmer>
|
|
40
|
+
) : renderSystemMessage && message ? (
|
|
41
|
+
renderSystemMessage(message)
|
|
42
|
+
) : (
|
|
43
|
+
text
|
|
44
|
+
)}
|
|
37
45
|
</div>
|
|
38
46
|
) : isAssistant ? (
|
|
39
47
|
<AgentMessageContent
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.root
|
|
2
|
-
|
|
2
|
+
position relative
|
|
3
|
+
flex-shrink 0
|
|
3
4
|
width 100%
|
|
4
|
-
bottom 160px
|
|
5
5
|
|
|
6
6
|
.inlineRoot
|
|
7
7
|
position relative
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
display flex
|
|
13
13
|
flex-wrap wrap
|
|
14
14
|
gap 8px
|
|
15
|
+
min-width 0
|
|
15
16
|
padding var(--p-2) var(--p-6) var(--p-3)
|
|
16
17
|
background-color var(--background)
|
|
17
18
|
|
|
@@ -19,9 +20,9 @@
|
|
|
19
20
|
background-color transparent
|
|
20
21
|
|
|
21
22
|
.item
|
|
22
|
-
flex
|
|
23
|
+
flex 0 1 auto
|
|
23
24
|
min-width 0
|
|
24
|
-
max-width 300px
|
|
25
|
+
max-width unquote('min(300px, 100%)')
|
|
25
26
|
height auto
|
|
26
27
|
min-height auto
|
|
27
28
|
padding var(--p-3)
|
|
@@ -16,6 +16,7 @@ import S from './ChatSelector.styl';
|
|
|
16
16
|
|
|
17
17
|
export interface ChatSelectorProps {
|
|
18
18
|
id: string;
|
|
19
|
+
wrapperClassName?: string;
|
|
19
20
|
className?: string;
|
|
20
21
|
onChatDeleted?: (sessionId: string) => void;
|
|
21
22
|
/** When set, used for "+ New Chat" instead of the default empty `newChat()`. */
|
|
@@ -24,6 +25,7 @@ export interface ChatSelectorProps {
|
|
|
24
25
|
|
|
25
26
|
export function ChatSelector({
|
|
26
27
|
id,
|
|
28
|
+
wrapperClassName,
|
|
27
29
|
className,
|
|
28
30
|
onChatDeleted,
|
|
29
31
|
onNewChat,
|
|
@@ -65,7 +67,7 @@ export function ChatSelector({
|
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
return (
|
|
68
|
-
<div className={S.wrapper}>
|
|
70
|
+
<div className={cn(S.wrapper, wrapperClassName)}>
|
|
69
71
|
<div className={S.selectGrow}>
|
|
70
72
|
<Select
|
|
71
73
|
variant="clear"
|
|
@@ -44,6 +44,7 @@ export function ChatSheet({
|
|
|
44
44
|
onMessage,
|
|
45
45
|
onScriptComplete,
|
|
46
46
|
renderMessageChart,
|
|
47
|
+
renderSystemMessage,
|
|
47
48
|
emptyState,
|
|
48
49
|
allowedAttachments,
|
|
49
50
|
allowPdfAttachments,
|
|
@@ -60,6 +61,7 @@ export function ChatSheet({
|
|
|
60
61
|
onMessage,
|
|
61
62
|
onScriptComplete,
|
|
62
63
|
renderMessageChart,
|
|
64
|
+
renderSystemMessage,
|
|
63
65
|
emptyState,
|
|
64
66
|
allowedAttachments,
|
|
65
67
|
allowPdfAttachments,
|
|
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import {
|
|
4
4
|
ChatPreset,
|
|
5
5
|
type ChatSendMessagePayload,
|
|
6
|
+
type Message,
|
|
6
7
|
MessageRole,
|
|
7
8
|
type ScriptCompletePayload,
|
|
8
9
|
} from '#uilib/components/ui/Chat/Chat.types';
|
|
@@ -69,11 +70,17 @@ export type UseChatPanelChromeModelInput = {
|
|
|
69
70
|
/** Composite chat scope (e.g. `${userId}-${datasetId}`, `${userId}-dashboard`, `${userId}-report-${reportId}`). */
|
|
70
71
|
scopeId?: string | null;
|
|
71
72
|
/** Fires after send; second arg is the assistant reply when the API call succeeded. */
|
|
72
|
-
onMessage?: (
|
|
73
|
+
onMessage?: (
|
|
74
|
+
displayText: string,
|
|
75
|
+
assistantResponse?: string,
|
|
76
|
+
chatSessionId?: string,
|
|
77
|
+
) => void;
|
|
73
78
|
/** Fires when a preset script has no further `[Label|branchKey]` steps (graph leaf or linear script end). */
|
|
74
79
|
onScriptComplete?: (payload: ScriptCompletePayload) => void;
|
|
75
80
|
/** Renders `[CHART]` tokens in assistant messages. */
|
|
76
81
|
renderMessageChart?: () => React.ReactNode;
|
|
82
|
+
/** When set, SYSTEM messages render via this callback instead of plain text. */
|
|
83
|
+
renderSystemMessage?: (message: Message) => React.ReactNode;
|
|
77
84
|
/** Forwarded to `ChatChrome` when the thread is empty. */
|
|
78
85
|
emptyState?: ChatEmptyStateConfig;
|
|
79
86
|
/** MIME types / extensions for text-only chat attachments (filtered by uilib allowlist). */
|
|
@@ -144,6 +151,7 @@ export function useChatPanelChromeModel({
|
|
|
144
151
|
onMessage,
|
|
145
152
|
onScriptComplete,
|
|
146
153
|
renderMessageChart,
|
|
154
|
+
renderSystemMessage,
|
|
147
155
|
emptyState,
|
|
148
156
|
allowedAttachments,
|
|
149
157
|
allowPdfAttachments,
|
|
@@ -557,8 +565,13 @@ export function useChatPanelChromeModel({
|
|
|
557
565
|
}
|
|
558
566
|
setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
|
|
559
567
|
try {
|
|
560
|
-
const assistantResponse
|
|
561
|
-
|
|
568
|
+
const { response: assistantResponse, sessionId } =
|
|
569
|
+
await sendMessage(payload);
|
|
570
|
+
onMessage?.(
|
|
571
|
+
displayTextFromSendPayload(payload),
|
|
572
|
+
assistantResponse,
|
|
573
|
+
sessionId,
|
|
574
|
+
);
|
|
562
575
|
} finally {
|
|
563
576
|
setOutboundLoadingLabel(undefined);
|
|
564
577
|
}
|
|
@@ -720,8 +733,13 @@ export function useChatPanelChromeModel({
|
|
|
720
733
|
}
|
|
721
734
|
setOutboundLoadingLabel(loadingLabelFromSendPayload(payload));
|
|
722
735
|
try {
|
|
723
|
-
const assistantResponse
|
|
724
|
-
|
|
736
|
+
const { response: assistantResponse, sessionId } =
|
|
737
|
+
await sendMessage(payload);
|
|
738
|
+
onMessage?.(
|
|
739
|
+
displayTextFromSendPayload(payload),
|
|
740
|
+
assistantResponse,
|
|
741
|
+
sessionId,
|
|
742
|
+
);
|
|
725
743
|
} finally {
|
|
726
744
|
setOutboundLoadingLabel(undefined);
|
|
727
745
|
}
|
|
@@ -1245,6 +1263,7 @@ export function useChatPanelChromeModel({
|
|
|
1245
1263
|
scriptContinueLabel,
|
|
1246
1264
|
onScriptContinue,
|
|
1247
1265
|
renderMessageChart,
|
|
1266
|
+
renderSystemMessage,
|
|
1248
1267
|
showSyntheticBranchButtons,
|
|
1249
1268
|
unusedBranchKeys,
|
|
1250
1269
|
showInlinePresets,
|
|
@@ -18,6 +18,8 @@ export {
|
|
|
18
18
|
normalizeUserTextFileAttachments,
|
|
19
19
|
} from './buildChatSendMessagePayload';
|
|
20
20
|
export { sanitizeAttachmentFilename } from './sanitizeAttachmentFilename';
|
|
21
|
+
export { ChatSelector } from './ChatSheet/ChatSelector';
|
|
22
|
+
export type { ChatSelectorProps } from './ChatSheet/ChatSelector';
|
|
21
23
|
export { ChatSheet } from './ChatSheet/ChatSheet';
|
|
22
24
|
export { useChatPanelChromeModel } from './ChatSheet/useChatPanelChromeModel';
|
|
23
25
|
export type { ChatSheetActions, ChatSheetProps } from './ChatSheet/ChatSheet';
|
|
@@ -45,6 +47,8 @@ export type {
|
|
|
45
47
|
export type {
|
|
46
48
|
Chat as ChatType,
|
|
47
49
|
ChatAttachmentDropItem,
|
|
50
|
+
ChatMeta,
|
|
51
|
+
ChatMetaValue,
|
|
48
52
|
ChatSendMessagePayload,
|
|
49
53
|
ChatProps,
|
|
50
54
|
ChatPreset as ChatPresetType,
|
|
@@ -3,6 +3,7 @@ import cn from 'classnames';
|
|
|
3
3
|
import { CsvIcon } from '#uilib/components/icons/CsvIcon/CsvIcon';
|
|
4
4
|
import { CardAction, CardHeader } from '#uilib/components/ui/Card';
|
|
5
5
|
import { FileTextIcon, XIcon } from '@phosphor-icons/react';
|
|
6
|
+
import { LayoutDashboard } from 'lucide-react';
|
|
6
7
|
|
|
7
8
|
import S from './FileChip.styl';
|
|
8
9
|
import type { FileChipFormat, FileChipProps } from './FileChip.types';
|
|
@@ -14,6 +15,16 @@ function FormatIcon({ format }: { format: FileChipFormat }) {
|
|
|
14
15
|
return <CsvIcon size={FORMAT_ICON_SIZE} />;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
if (format === 'dashboard') {
|
|
19
|
+
return (
|
|
20
|
+
<LayoutDashboard
|
|
21
|
+
size={FORMAT_ICON_SIZE}
|
|
22
|
+
aria-hidden
|
|
23
|
+
style={{ color: 'var(--muted-foreground)' }}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
17
28
|
return (
|
|
18
29
|
<FileTextIcon
|
|
19
30
|
size={FORMAT_ICON_SIZE}
|
|
@@ -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');
|
|
@@ -511,6 +551,11 @@ export function ChatProvider({
|
|
|
511
551
|
try {
|
|
512
552
|
const data = await sendChatMessageFn(apiPayload, pendingChatSessionId);
|
|
513
553
|
|
|
554
|
+
const effectiveSessionId =
|
|
555
|
+
data.session_id && data.session_id !== pendingChatSessionId
|
|
556
|
+
? data.session_id
|
|
557
|
+
: pendingChatSessionId;
|
|
558
|
+
|
|
514
559
|
if (data.session_id && data.session_id !== pendingChatSessionId) {
|
|
515
560
|
setChats(prev => {
|
|
516
561
|
const scopeChats = prev[scopeId] ?? [];
|
|
@@ -530,12 +575,12 @@ export function ChatProvider({
|
|
|
530
575
|
|
|
531
576
|
addMessage(
|
|
532
577
|
scopeId,
|
|
533
|
-
|
|
578
|
+
effectiveSessionId,
|
|
534
579
|
MessageRole.ASSISTANT,
|
|
535
580
|
data.response,
|
|
536
581
|
);
|
|
537
582
|
|
|
538
|
-
return data.response;
|
|
583
|
+
return { response: data.response, sessionId: effectiveSessionId };
|
|
539
584
|
} catch (error) {
|
|
540
585
|
const errorMessage =
|
|
541
586
|
error instanceof Error
|
|
@@ -597,6 +642,7 @@ export function ChatProvider({
|
|
|
597
642
|
removeMessageById,
|
|
598
643
|
updateMessageById,
|
|
599
644
|
setChatMessages,
|
|
645
|
+
updateChatMeta,
|
|
600
646
|
sendMessage,
|
|
601
647
|
getChatsForScopeId,
|
|
602
648
|
getCurrentChatId,
|
|
@@ -683,6 +729,8 @@ export function useChatsForScopeId(scopeId: string) {
|
|
|
683
729
|
addMessage,
|
|
684
730
|
removeMessageById,
|
|
685
731
|
updateMessageById,
|
|
732
|
+
updateChatMeta,
|
|
733
|
+
setChatMessages,
|
|
686
734
|
sendMessage,
|
|
687
735
|
deleteChat,
|
|
688
736
|
} = useChats();
|
|
@@ -713,6 +761,10 @@ export function useChatsForScopeId(scopeId: string) {
|
|
|
713
761
|
) => updateMessageById(scopeId, chatId, messageId, patch),
|
|
714
762
|
sendMessage: (message: string | ChatSendMessagePayload, chatId?: string) =>
|
|
715
763
|
sendMessage(scopeId, message, chatId),
|
|
764
|
+
updateChatMeta: (chatId: string, patch: ChatMeta) =>
|
|
765
|
+
updateChatMeta(scopeId, chatId, patch),
|
|
766
|
+
setChatMessages: (chatId: string, messages: Message[]) =>
|
|
767
|
+
setChatMessages(scopeId, chatId, messages),
|
|
716
768
|
deleteChat: (sessionId: string) => deleteChat(scopeId, sessionId),
|
|
717
769
|
};
|
|
718
770
|
}
|