@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.
Files changed (48) hide show
  1. package/dist/esm/components/ui/Chat/Chat.js +2 -2
  2. package/dist/esm/components/ui/Chat/Chat.styl.js +1 -1
  3. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.js +6 -6
  4. package/dist/esm/components/ui/Chat/ChatChrome/ChatChrome.styl.js +2 -2
  5. package/dist/esm/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl.js +1 -1
  6. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.js +2 -2
  7. package/dist/esm/components/ui/Chat/ChatMessage/ChatMessage.styl.js +1 -1
  8. package/dist/esm/components/ui/Chat/ChatPresets/ChatPresets.styl.js +1 -1
  9. package/dist/esm/components/ui/Chat/ChatSheet/ChatSelector.js +2 -2
  10. package/dist/esm/components/ui/Chat/ChatSheet/ChatSheet.js +2 -1
  11. package/dist/esm/components/ui/Chat/ChatSheet/useChatPanelChromeModel.js +6 -5
  12. package/dist/esm/components/ui/FileChip/FileChip.js +4 -0
  13. package/dist/esm/contexts/chat-context.js +36 -4
  14. package/dist/esm/index.js +1 -0
  15. package/dist/esm/types/src/components/ui/Chat/Chat.d.ts +1 -1
  16. package/dist/esm/types/src/components/ui/Chat/Chat.types.d.ts +10 -0
  17. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.d.ts +1 -1
  18. package/dist/esm/types/src/components/ui/Chat/ChatChrome/ChatChrome.types.d.ts +4 -0
  19. package/dist/esm/types/src/components/ui/Chat/ChatMessage/ChatMessage.d.ts +1 -1
  20. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSelector.d.ts +2 -1
  21. package/dist/esm/types/src/components/ui/Chat/ChatSheet/ChatSheet.d.ts +1 -1
  22. package/dist/esm/types/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.d.ts +5 -3
  23. package/dist/esm/types/src/components/ui/Chat/index.d.ts +3 -1
  24. package/dist/esm/types/src/components/ui/FileChip/FileChip.types.d.ts +1 -1
  25. package/dist/esm/types/src/contexts/chat-context.d.ts +16 -4
  26. package/package.json +1 -1
  27. package/src/components/ui/Chat/Chat.styl +4 -1
  28. package/src/components/ui/Chat/Chat.tsx +2 -1
  29. package/src/components/ui/Chat/Chat.types.ts +12 -0
  30. package/src/components/ui/Chat/ChatChrome/ChatChrome.styl +20 -10
  31. package/src/components/ui/Chat/ChatChrome/ChatChrome.styl.d.ts +2 -0
  32. package/src/components/ui/Chat/ChatChrome/ChatChrome.tsx +12 -4
  33. package/src/components/ui/Chat/ChatChrome/ChatChrome.types.ts +4 -0
  34. package/src/components/ui/Chat/ChatEmptyState/ChatEmptyState.styl +1 -2
  35. package/src/components/ui/Chat/ChatMessage/ChatMessage.styl +1 -0
  36. package/src/components/ui/Chat/ChatMessage/ChatMessage.tsx +9 -1
  37. package/src/components/ui/Chat/ChatPresets/ChatPresets.styl +5 -4
  38. package/src/components/ui/Chat/ChatSheet/ChatSelector.tsx +3 -1
  39. package/src/components/ui/Chat/ChatSheet/ChatSheet.tsx +2 -0
  40. package/src/components/ui/Chat/ChatSheet/useChatPanelChromeModel.tsx +24 -5
  41. package/src/components/ui/Chat/index.ts +4 -0
  42. package/src/components/ui/FileChip/FileChip.tsx +11 -0
  43. package/src/components/ui/FileChip/FileChip.types.ts +1 -1
  44. package/src/contexts/chat-context.tsx +57 -5
  45. package/src/docs/pages/ChatPage.styl +6 -0
  46. package/src/docs/pages/ChatPage.styl.d.ts +7 -0
  47. package/src/docs/pages/ChatPage.tsx +30 -87
  48. package/src/docs/pages/ChatSlashCommandsPage.tsx +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sybilion/uilib",
3
- "version": "1.3.77",
3
+ "version": "1.3.79",
4
4
  "description": "Sybilion Design System — React UI components (Webpack + Stylus)",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -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
- padding-bottom 170px // goes under prompt
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
- &::after
89
- content ''
90
- display block
91
- height 260px
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
- position absolute
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 0 20px 16px var(--background)
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
@@ -6,6 +6,8 @@ interface CssExports {
6
6
  'branchRow': string;
7
7
  'chatResizeHandle': string;
8
8
  'content': string;
9
+ 'emptyBody': string;
10
+ 'fixedPresets': string;
9
11
  'footer': string;
10
12
  'loader': string;
11
13
  'notice': string;
@@ -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
- {renderPresets('fixed')}
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: 180 } }}
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
  }
@@ -4,9 +4,8 @@
4
4
  align-items center
5
5
  justify-content center
6
6
  gap var(--p-10)
7
-
8
- // height 450px
9
7
  flex 1
8
+ min-height 0
10
9
  padding var(--p-6)
11
10
 
12
11
  text-align center
@@ -142,6 +142,7 @@
142
142
  white-space nowrap
143
143
 
144
144
  .quickReplyWrap
145
+ width 100%
145
146
  display inline-block
146
147
  vertical-align middle
147
148
  margin var(--p-1) var(--p-1) var(--p-1) 0
@@ -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 ? <TextShimmer as="span">{text}</TextShimmer> : text}
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
- // position fixed
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-shrink 0
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?: (displayText: string, assistantResponse?: string) => void;
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 = await sendMessage(payload);
561
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
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 = await sendMessage(payload);
724
- onMessage?.(displayTextFromSendPayload(payload), assistantResponse);
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}
@@ -1,4 +1,4 @@
1
- export type FileChipFormat = 'csv' | 'pdf' | 'text';
1
+ export type FileChipFormat = 'csv' | 'pdf' | 'text' | 'dashboard';
2
2
 
3
3
  export type FileChipProps = {
4
4
  name: string;
@@ -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<string>;
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 => ({ ...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<string> => {
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
- data.session_id ? data.session_id : pendingChatSessionId,
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
  }
@@ -0,0 +1,6 @@
1
+ .chatDemoHost
2
+ display flex
3
+ flex-direction column
4
+ flex 1
5
+ // Bounded height so ChatChrome flex column can place presets above the prompt
6
+ min-height 480px
@@ -0,0 +1,7 @@
1
+ // This file is automatically generated.
2
+ // Please do not change this file!
3
+ interface CssExports {
4
+ 'chatDemoHost': string;
5
+ }
6
+ export const cssExports: CssExports;
7
+ export default cssExports;