@xcelsior/ui-chat 1.0.8 → 2.0.1

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 (96) hide show
  1. package/dist/index.d.mts +69 -69
  2. package/dist/index.d.ts +69 -69
  3. package/dist/index.js +2458 -627
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2457 -628
  6. package/dist/index.mjs.map +1 -1
  7. package/package.json +6 -5
  8. package/src/components/BrandIcons.stories.tsx +95 -0
  9. package/src/components/BrandIcons.tsx +84 -0
  10. package/src/components/Chat.stories.tsx +149 -16
  11. package/src/components/Chat.tsx +116 -96
  12. package/src/components/ChatHeader.tsx +124 -69
  13. package/src/components/ChatInput.tsx +253 -104
  14. package/src/components/ChatWidget.tsx +209 -63
  15. package/src/components/ConversationRating.stories.tsx +33 -0
  16. package/src/components/ConversationRating.tsx +156 -0
  17. package/src/components/MarkdownMessage.tsx +202 -0
  18. package/src/components/MessageItem.stories.tsx +253 -55
  19. package/src/components/MessageItem.tsx +223 -60
  20. package/src/components/MessageList.tsx +164 -35
  21. package/src/components/PreChatForm.tsx +236 -96
  22. package/src/components/ThinkingIndicator.tsx +370 -0
  23. package/src/components/TypingIndicator.tsx +27 -11
  24. package/src/hooks/useDraggablePosition.ts +91 -0
  25. package/src/hooks/useMessages.ts +12 -13
  26. package/src/hooks/useResizableWidget.ts +324 -0
  27. package/src/index.tsx +5 -0
  28. package/src/types.ts +51 -5
  29. package/src/utils/markdown-styles.ts +140 -0
  30. package/storybook-static/assets/BrandIcons-Cjy5INAp.js +4 -0
  31. package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +64 -0
  32. package/storybook-static/assets/Chat.stories-J_Yp51wU.js +803 -0
  33. package/storybook-static/assets/Color-YHDXOIA2-BMnd3YrF.js +1 -0
  34. package/storybook-static/assets/ConversationRating.stories-B5_QddHN.js +12 -0
  35. package/storybook-static/assets/DocsRenderer-CFRXHY34-i_W8iCu9.js +575 -0
  36. package/storybook-static/assets/MessageItem-DAaKZ9s9.js +14 -0
  37. package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +255 -0
  38. package/storybook-static/assets/ToastContext-Bty1K7ya.js +1 -0
  39. package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
  40. package/storybook-static/assets/en-US-BukEqXxE.js +1 -0
  41. package/storybook-static/assets/entry-preview-docs-DHohToDm.js +46 -0
  42. package/storybook-static/assets/entry-preview-oDnntGcx.js +2 -0
  43. package/storybook-static/assets/iframe-CGBtu2Se.js +211 -0
  44. package/storybook-static/assets/index--qcDGAq6.js +1 -0
  45. package/storybook-static/assets/index-BLHw34Di.js +24 -0
  46. package/storybook-static/assets/index-B_4m48Mv.js +1 -0
  47. package/storybook-static/assets/index-DgH-xKnr.js +11 -0
  48. package/storybook-static/assets/index-DrFu-skq.js +6 -0
  49. package/storybook-static/assets/index-DrdPSA1J.js +240 -0
  50. package/storybook-static/assets/index-jvNEZhzf.js +1 -0
  51. package/storybook-static/assets/index-yBjzXJbu.js +9 -0
  52. package/storybook-static/assets/jsx-runtime-Cf8x2fCZ.js +9 -0
  53. package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
  54. package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
  55. package/storybook-static/assets/preview-BRpahs9B.js +2 -0
  56. package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
  57. package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
  58. package/storybook-static/assets/preview-DD_OYowb.js +1 -0
  59. package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
  60. package/storybook-static/assets/preview-DHQbi4pV.js +1 -0
  61. package/storybook-static/assets/preview-DUOvJmsz.js +1 -0
  62. package/storybook-static/assets/preview-DcGwT3kv.css +1 -0
  63. package/storybook-static/assets/preview-DwI0w3cI.js +1 -0
  64. package/storybook-static/assets/react-18-CALspjOX.js +1 -0
  65. package/storybook-static/assets/test-utils-BE0XkMtV.js +9 -0
  66. package/storybook-static/favicon.svg +1 -0
  67. package/storybook-static/iframe.html +666 -0
  68. package/storybook-static/index.html +177 -0
  69. package/storybook-static/index.json +1 -0
  70. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  71. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  72. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  73. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  74. package/storybook-static/project.json +1 -0
  75. package/storybook-static/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  76. package/storybook-static/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  77. package/storybook-static/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  78. package/storybook-static/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  79. package/storybook-static/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  80. package/storybook-static/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  81. package/storybook-static/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  82. package/storybook-static/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  83. package/storybook-static/sb-addons/interactions-10/manager-bundle.js +222 -0
  84. package/storybook-static/sb-addons/links-1/manager-bundle.js +3 -0
  85. package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  86. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  87. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  88. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  89. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  90. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  91. package/storybook-static/sb-manager/globals-module-info.js +1052 -0
  92. package/storybook-static/sb-manager/globals-runtime.js +42127 -0
  93. package/storybook-static/sb-manager/globals.js +48 -0
  94. package/storybook-static/sb-manager/runtime.js +12048 -0
  95. package/.turbo/turbo-build.log +0 -22
  96. package/.turbo/turbo-lint.log +0 -5
@@ -0,0 +1,324 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+
3
+ export type ResizeEdge = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw';
4
+
5
+ interface UseResizableWidgetOptions {
6
+ initialWidth?: number;
7
+ initialHeight?: number;
8
+ minWidth?: number;
9
+ minHeight?: number;
10
+ maxWidth?: number;
11
+ maxHeight?: number;
12
+ storageKey?: string;
13
+ enabled?: boolean;
14
+ }
15
+
16
+ export interface UseResizableWidgetReturn {
17
+ width: number;
18
+ height: number;
19
+ isResizing: boolean;
20
+ /** Attach to the widget container for edge/corner resize zones */
21
+ containerResizeProps: {
22
+ onMouseMove: (e: React.MouseEvent) => void;
23
+ onMouseDown: (e: React.MouseEvent) => void;
24
+ onMouseLeave: (e: React.MouseEvent) => void;
25
+ onTouchStart: (e: React.TouchEvent) => void;
26
+ };
27
+ /** True when user hovers near a resize edge — use for visual hint */
28
+ isNearEdge: boolean;
29
+ /** Which edge/corner the user is near or dragging */
30
+ activeEdge: ResizeEdge | null;
31
+ }
32
+
33
+ const STORAGE_KEY = 'xcelsior-chat-size';
34
+ const EDGE_ZONE = 8; // px from edge to trigger resize cursor
35
+
36
+ const CURSOR_MAP: Record<ResizeEdge, string> = {
37
+ n: 'ns-resize',
38
+ s: 'ns-resize',
39
+ e: 'ew-resize',
40
+ w: 'ew-resize',
41
+ ne: 'nesw-resize',
42
+ nw: 'nwse-resize',
43
+ se: 'nwse-resize',
44
+ sw: 'nesw-resize',
45
+ };
46
+
47
+ function readStoredSize(
48
+ key: string,
49
+ fallbackWidth: number,
50
+ fallbackHeight: number,
51
+ ): { width: number; height: number } {
52
+ try {
53
+ const stored = localStorage.getItem(key);
54
+ if (stored) {
55
+ const parsed = JSON.parse(stored);
56
+ if (typeof parsed.width === 'number' && typeof parsed.height === 'number') {
57
+ return { width: parsed.width, height: parsed.height };
58
+ }
59
+ }
60
+ } catch {
61
+ // localStorage unavailable
62
+ }
63
+ return { width: fallbackWidth, height: fallbackHeight };
64
+ }
65
+
66
+ function persistSize(key: string, width: number, height: number) {
67
+ try {
68
+ localStorage.setItem(key, JSON.stringify({ width, height }));
69
+ } catch {
70
+ // Storage unavailable
71
+ }
72
+ }
73
+
74
+ function isMobile(): boolean {
75
+ return typeof window !== 'undefined' && window.innerWidth < 768;
76
+ }
77
+
78
+ /** Detect which edge/corner the mouse is near */
79
+ function detectEdge(
80
+ e: { clientX: number; clientY: number },
81
+ rect: DOMRect,
82
+ ): ResizeEdge | null {
83
+ const { clientX: x, clientY: y } = e;
84
+ const nearTop = y - rect.top < EDGE_ZONE;
85
+ const nearBottom = rect.bottom - y < EDGE_ZONE;
86
+ const nearLeft = x - rect.left < EDGE_ZONE;
87
+ const nearRight = rect.right - x < EDGE_ZONE;
88
+
89
+ if (nearTop && nearLeft) return 'nw';
90
+ if (nearTop && nearRight) return 'ne';
91
+ if (nearBottom && nearLeft) return 'sw';
92
+ if (nearBottom && nearRight) return 'se';
93
+ if (nearTop) return 'n';
94
+ if (nearBottom) return 's';
95
+ if (nearLeft) return 'w';
96
+ if (nearRight) return 'e';
97
+ return null;
98
+ }
99
+
100
+ export function useResizableWidget({
101
+ initialWidth = 380,
102
+ initialHeight = 580,
103
+ minWidth = 320,
104
+ minHeight = 400,
105
+ maxWidth = 800,
106
+ maxHeight = 900,
107
+ storageKey = STORAGE_KEY,
108
+ enabled = true,
109
+ }: UseResizableWidgetOptions = {}): UseResizableWidgetReturn {
110
+ const [size, setSize] = useState<{ width: number; height: number }>(() => {
111
+ if (typeof window === 'undefined') return { width: initialWidth, height: initialHeight };
112
+ return readStoredSize(storageKey, initialWidth, initialHeight);
113
+ });
114
+ const [isResizing, setIsResizing] = useState(false);
115
+ const [isNearEdge, setIsNearEdge] = useState(false);
116
+ const [activeEdge, setActiveEdge] = useState<ResizeEdge | null>(null);
117
+
118
+ const sizeRef = useRef(size);
119
+ sizeRef.current = size;
120
+
121
+ const dragRef = useRef<{
122
+ edge: ResizeEdge;
123
+ startX: number;
124
+ startY: number;
125
+ startWidth: number;
126
+ startHeight: number;
127
+ } | null>(null);
128
+
129
+ const containerRef = useRef<HTMLElement | null>(null);
130
+
131
+ const clamp = useCallback(
132
+ (w: number, h: number) => {
133
+ const mxW = Math.min(maxWidth, window.innerWidth - 24);
134
+ const mxH = Math.min(maxHeight, window.innerHeight - 100);
135
+ return {
136
+ width: Math.round(Math.max(minWidth, Math.min(mxW, w))),
137
+ height: Math.round(Math.max(minHeight, Math.min(mxH, h))),
138
+ };
139
+ },
140
+ [minWidth, minHeight, maxWidth, maxHeight],
141
+ );
142
+
143
+ /** Calculate new size from drag delta based on which edge is being dragged */
144
+ const calcSize = useCallback(
145
+ (dx: number, dy: number) => {
146
+ if (!dragRef.current) return sizeRef.current;
147
+ const { edge, startWidth, startHeight } = dragRef.current;
148
+
149
+ let w = startWidth;
150
+ let h = startHeight;
151
+
152
+ // Horizontal: east edges expand right, west edges expand left (invert delta)
153
+ if (edge.includes('e')) w = startWidth + dx;
154
+ if (edge.includes('w')) w = startWidth - dx;
155
+
156
+ // Vertical: south edges expand down, north edges expand up (invert delta)
157
+ if (edge.includes('s')) h = startHeight + dy;
158
+ if (edge.includes('n')) h = startHeight - dy;
159
+
160
+ return clamp(w, h);
161
+ },
162
+ [clamp],
163
+ );
164
+
165
+ // ─── Mouse handlers ──────────────────────────────────────────────────
166
+
167
+ const handleDocMouseMove = useCallback(
168
+ (e: MouseEvent) => {
169
+ if (!dragRef.current) return;
170
+ const dx = e.clientX - dragRef.current.startX;
171
+ const dy = e.clientY - dragRef.current.startY;
172
+ setSize(calcSize(dx, dy));
173
+ },
174
+ [calcSize],
175
+ );
176
+
177
+ const handleDocMouseUp = useCallback(
178
+ (e: MouseEvent) => {
179
+ if (!dragRef.current) return;
180
+ const dx = e.clientX - dragRef.current.startX;
181
+ const dy = e.clientY - dragRef.current.startY;
182
+ const final = calcSize(dx, dy);
183
+ persistSize(storageKey, final.width, final.height);
184
+ dragRef.current = null;
185
+ setIsResizing(false);
186
+ setActiveEdge(null);
187
+ document.body.style.cursor = '';
188
+ document.removeEventListener('mousemove', handleDocMouseMove);
189
+ document.removeEventListener('mouseup', handleDocMouseUp);
190
+ },
191
+ [calcSize, storageKey, handleDocMouseMove],
192
+ );
193
+
194
+ // ─── Touch handlers ──────────────────────────────────────────────────
195
+
196
+ const handleDocTouchMove = useCallback(
197
+ (e: TouchEvent) => {
198
+ if (!dragRef.current || e.touches.length === 0) return;
199
+ e.preventDefault();
200
+ const t = e.touches[0];
201
+ const dx = t.clientX - dragRef.current.startX;
202
+ const dy = t.clientY - dragRef.current.startY;
203
+ setSize(calcSize(dx, dy));
204
+ },
205
+ [calcSize],
206
+ );
207
+
208
+ const handleDocTouchEnd = useCallback(
209
+ (e: TouchEvent) => {
210
+ if (!dragRef.current) return;
211
+ const t = e.changedTouches[0];
212
+ if (t) {
213
+ const dx = t.clientX - dragRef.current.startX;
214
+ const dy = t.clientY - dragRef.current.startY;
215
+ const final = calcSize(dx, dy);
216
+ persistSize(storageKey, final.width, final.height);
217
+ }
218
+ dragRef.current = null;
219
+ setIsResizing(false);
220
+ setActiveEdge(null);
221
+ document.removeEventListener('touchmove', handleDocTouchMove);
222
+ document.removeEventListener('touchend', handleDocTouchEnd);
223
+ },
224
+ [calcSize, storageKey, handleDocTouchMove],
225
+ );
226
+
227
+ // ─── Cleanup ─────────────────────────────────────────────────────────
228
+
229
+ useEffect(() => {
230
+ return () => {
231
+ document.removeEventListener('mousemove', handleDocMouseMove);
232
+ document.removeEventListener('mouseup', handleDocMouseUp);
233
+ document.removeEventListener('touchmove', handleDocTouchMove);
234
+ document.removeEventListener('touchend', handleDocTouchEnd);
235
+ document.body.style.cursor = '';
236
+ };
237
+ }, [handleDocMouseMove, handleDocMouseUp, handleDocTouchMove, handleDocTouchEnd]);
238
+
239
+ // ─── Container event props ───────────────────────────────────────────
240
+
241
+ const onContainerMouseMove = useCallback(
242
+ (e: React.MouseEvent) => {
243
+ if (!enabled || isMobile() || isResizing) return;
244
+ const el = e.currentTarget as HTMLElement;
245
+ containerRef.current = el;
246
+ const rect = el.getBoundingClientRect();
247
+ const edge = detectEdge(e, rect);
248
+ setIsNearEdge(!!edge);
249
+ setActiveEdge(edge);
250
+ el.style.cursor = edge ? CURSOR_MAP[edge] : '';
251
+ },
252
+ [enabled, isResizing],
253
+ );
254
+
255
+ const onContainerMouseDown = useCallback(
256
+ (e: React.MouseEvent) => {
257
+ if (!enabled || isMobile() || !activeEdge) return;
258
+ // Only start resize if near an edge
259
+ e.preventDefault();
260
+ e.stopPropagation();
261
+ dragRef.current = {
262
+ edge: activeEdge,
263
+ startX: e.clientX,
264
+ startY: e.clientY,
265
+ startWidth: sizeRef.current.width,
266
+ startHeight: sizeRef.current.height,
267
+ };
268
+ setIsResizing(true);
269
+ document.body.style.cursor = CURSOR_MAP[activeEdge];
270
+ document.addEventListener('mousemove', handleDocMouseMove);
271
+ document.addEventListener('mouseup', handleDocMouseUp);
272
+ },
273
+ [enabled, activeEdge, handleDocMouseMove, handleDocMouseUp],
274
+ );
275
+
276
+ const onContainerMouseLeave = useCallback(
277
+ (_e: React.MouseEvent) => {
278
+ if (!isResizing) {
279
+ setIsNearEdge(false);
280
+ setActiveEdge(null);
281
+ if (containerRef.current) containerRef.current.style.cursor = '';
282
+ }
283
+ },
284
+ [isResizing],
285
+ );
286
+
287
+ const onContainerTouchStart = useCallback(
288
+ (e: React.TouchEvent) => {
289
+ if (!enabled || isMobile() || e.touches.length === 0) return;
290
+ const el = e.currentTarget as HTMLElement;
291
+ const rect = el.getBoundingClientRect();
292
+ const t = e.touches[0];
293
+ const edge = detectEdge(t, rect);
294
+ if (!edge) return;
295
+
296
+ dragRef.current = {
297
+ edge,
298
+ startX: t.clientX,
299
+ startY: t.clientY,
300
+ startWidth: sizeRef.current.width,
301
+ startHeight: sizeRef.current.height,
302
+ };
303
+ setIsResizing(true);
304
+ setActiveEdge(edge);
305
+ document.addEventListener('touchmove', handleDocTouchMove, { passive: false });
306
+ document.addEventListener('touchend', handleDocTouchEnd);
307
+ },
308
+ [enabled, handleDocTouchMove, handleDocTouchEnd],
309
+ );
310
+
311
+ return {
312
+ width: size.width,
313
+ height: size.height,
314
+ isResizing,
315
+ isNearEdge,
316
+ activeEdge,
317
+ containerResizeProps: {
318
+ onMouseMove: onContainerMouseMove,
319
+ onMouseDown: onContainerMouseDown,
320
+ onMouseLeave: onContainerMouseLeave,
321
+ onTouchStart: onContainerTouchStart,
322
+ },
323
+ };
324
+ }
package/src/index.tsx CHANGED
@@ -6,7 +6,10 @@ export { Chat } from './components/Chat';
6
6
  // Individual components (for custom implementations)
7
7
  export { ChatHeader } from './components/ChatHeader';
8
8
  export { ChatInput } from './components/ChatInput';
9
+ export { MarkdownMessage } from './components/MarkdownMessage';
9
10
  export { MessageItem } from './components/MessageItem';
11
+ export { ThinkingIndicator } from './components/ThinkingIndicator';
12
+ export type { ThinkingIndicatorProps } from './components/ThinkingIndicator';
10
13
  export { MessageList } from './components/MessageList';
11
14
  export { TypingIndicator } from './components/TypingIndicator';
12
15
  export { PreChatForm } from './components/PreChatForm';
@@ -44,6 +47,7 @@ import type {
44
47
  ConversationStatus,
45
48
  ConversationPriority,
46
49
  ConversationChannel,
50
+ UseMessagesReturn,
47
51
  } from './types';
48
52
 
49
53
  export type {
@@ -63,4 +67,5 @@ export type {
63
67
  ConversationStatus,
64
68
  ConversationPriority,
65
69
  ConversationChannel,
70
+ UseMessagesReturn,
66
71
  };
package/src/types.ts CHANGED
@@ -15,16 +15,18 @@ export interface IMessage {
15
15
  id: string;
16
16
  conversationId: string;
17
17
  senderId: string;
18
- senderType: 'customer' | 'agent' | 'system';
18
+ senderType: 'customer' | 'agent' | 'bot' | 'system';
19
19
  content: string;
20
20
  messageType: MessageType;
21
21
  createdAt: string;
22
22
  status: MessageStatus;
23
+ feedback?: 'good' | 'wrong' | 'corrected' | null;
24
+ confidence?: number | null;
23
25
  metadata?: Record<string, unknown>;
24
26
  }
25
27
 
26
28
  // Conversation types
27
- export type ConversationStatus = 'open' | 'pending' | 'closed' | 'archived';
29
+ export type ConversationStatus = 'open' | 'pending' | 'closed' | 'archived' | 'bot_handling' | 'pending_agent' | 'agent_active';
28
30
  export type ConversationPriority = 'low' | 'medium' | 'high' | 'urgent';
29
31
  export type ConversationChannel = 'web' | 'mobile' | 'email';
30
32
 
@@ -49,7 +51,7 @@ export interface IConversation {
49
51
 
50
52
  // WebSocket message types
51
53
  export interface IWebSocketMessage {
52
- type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system';
54
+ type: 'message' | 'typing' | 'read' | 'connected' | 'error' | 'system' | 'escalation_requested' | 'bot_response' | 'conversation_updated';
53
55
  data: any;
54
56
  }
55
57
 
@@ -85,6 +87,26 @@ export interface IUploadedFile {
85
87
  markdown?: string;
86
88
  }
87
89
 
90
+ // Theme configuration
91
+ export interface IChatTheme {
92
+ primary?: string;
93
+ primaryStrong?: string;
94
+ background?: string;
95
+ backgroundAlt?: string;
96
+ text?: string;
97
+ textMuted?: string;
98
+ statusPositive?: string;
99
+ statusCaution?: string;
100
+ statusNegative?: string;
101
+ borderColor?: string;
102
+ }
103
+
104
+ // Widget position
105
+ export type WidgetPosition = 'left' | 'right' | 'auto';
106
+
107
+ // Identity collection mode
108
+ export type IdentityCollectionMode = 'progressive' | 'form' | 'none';
109
+
88
110
  // Chat widget configuration
89
111
  export interface IChatConfig {
90
112
  // WebSocket connection
@@ -92,8 +114,8 @@ export interface IChatConfig {
92
114
  conversationId?: string;
93
115
  apiKey: string;
94
116
 
95
- // User information
96
- currentUser: IUser;
117
+ // User information (optional for anonymous mode)
118
+ currentUser?: IUser;
97
119
 
98
120
  // File upload
99
121
  fileUpload?: IFileUploadConfig;
@@ -116,12 +138,22 @@ export interface IChatConfig {
116
138
  */
117
139
  disableWebSocket?: boolean;
118
140
 
141
+ // Theme
142
+ theme?: IChatTheme;
143
+
144
+ // Widget position (default: 'auto' — reads localStorage, falls back to 'right')
145
+ position?: WidgetPosition;
146
+
147
+ // Identity collection (default: 'progressive' — no pre-chat form)
148
+ identityCollection?: IdentityCollectionMode;
149
+
119
150
  // Callbacks
120
151
  onMessageSent?: (message: IMessage) => void;
121
152
  onMessageReceived?: (message: IMessage) => void;
122
153
  onConversationChange?: (conversation: IConversation) => void;
123
154
  onConnectionChange?: (connected: boolean) => void;
124
155
  onError?: (error: Error) => void;
156
+ onConversationRated?: (rating: 'positive' | 'negative') => void;
125
157
 
126
158
  // Toast notifications
127
159
  toast?: {
@@ -131,6 +163,20 @@ export interface IChatConfig {
131
163
  };
132
164
  }
133
165
 
166
+ // UseMessages return type
167
+ export interface UseMessagesReturn {
168
+ messages: IMessage[];
169
+ addMessage: (message: IMessage) => void;
170
+ updateMessageStatus: (messageId: string, status: IMessage['status']) => void;
171
+ clearMessages: () => void;
172
+ isLoading: boolean;
173
+ error: Error | null;
174
+ loadMore: () => Promise<void>;
175
+ hasMore: boolean;
176
+ isLoadingMore: boolean;
177
+ isBotThinking: boolean;
178
+ }
179
+
134
180
  // API Response types
135
181
  export interface IApiResponse<T> {
136
182
  success: boolean;
@@ -0,0 +1,140 @@
1
+ import type { CSSProperties } from 'react';
2
+ import type { IChatTheme } from '../types';
3
+
4
+ export interface MarkdownStyles {
5
+ paragraph: CSSProperties;
6
+ strong: CSSProperties;
7
+ emphasis: CSSProperties;
8
+ link: CSSProperties;
9
+ list: CSSProperties;
10
+ listItem: CSSProperties;
11
+ code: CSSProperties;
12
+ codeBlock: CSSProperties;
13
+ heading: CSSProperties;
14
+ blockquote: CSSProperties;
15
+ hr: CSSProperties;
16
+ }
17
+
18
+ export function getMarkdownStyles(
19
+ theme?: IChatTheme,
20
+ isLightTheme?: boolean,
21
+ ): MarkdownStyles {
22
+ const primaryColor = theme?.primary || '#337eff';
23
+ const textColor = theme?.text || (isLightTheme ? '#1a1a2e' : '#f7f7f8');
24
+
25
+ // Slightly brightened text for strong elements
26
+ const strongColor = isLightTheme
27
+ ? 'rgba(0,0,0,0.9)'
28
+ : 'rgba(255,255,255,0.95)';
29
+
30
+ // Subtle background for inline code
31
+ const inlineCodeBg = isLightTheme
32
+ ? 'rgba(0,0,0,0.06)'
33
+ : 'rgba(255,255,255,0.08)';
34
+
35
+ // Darker background for code blocks
36
+ const codeBlockBg = isLightTheme
37
+ ? 'rgba(0,0,0,0.04)'
38
+ : 'rgba(0,0,0,0.25)';
39
+
40
+ const codeBlockBorder = isLightTheme
41
+ ? '1px solid rgba(0,0,0,0.08)'
42
+ : '1px solid rgba(255,255,255,0.06)';
43
+
44
+ const blockquoteBorder = isLightTheme
45
+ ? `3px solid ${primaryColor}60`
46
+ : `3px solid ${primaryColor}80`;
47
+
48
+ const blockquoteBg = isLightTheme
49
+ ? 'rgba(0,0,0,0.02)'
50
+ : 'rgba(255,255,255,0.02)';
51
+
52
+ return {
53
+ paragraph: {
54
+ margin: '0 0 8px 0',
55
+ lineHeight: '1.6',
56
+ color: textColor,
57
+ },
58
+
59
+ strong: {
60
+ fontWeight: 600,
61
+ color: strongColor,
62
+ },
63
+
64
+ emphasis: {
65
+ fontStyle: 'italic',
66
+ color: textColor,
67
+ },
68
+
69
+ link: {
70
+ color: primaryColor,
71
+ textDecoration: 'underline',
72
+ textUnderlineOffset: '2px',
73
+ cursor: 'pointer',
74
+ },
75
+
76
+ list: {
77
+ paddingLeft: '20px',
78
+ margin: '8px 0',
79
+ color: textColor,
80
+ },
81
+
82
+ listItem: {
83
+ margin: '4px 0',
84
+ lineHeight: '1.5',
85
+ color: textColor,
86
+ },
87
+
88
+ code: {
89
+ fontFamily:
90
+ 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
91
+ fontSize: '0.875em',
92
+ backgroundColor: inlineCodeBg,
93
+ padding: '2px 6px',
94
+ borderRadius: '4px',
95
+ color: textColor,
96
+ },
97
+
98
+ codeBlock: {
99
+ fontFamily:
100
+ 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace',
101
+ fontSize: '0.8125em',
102
+ backgroundColor: codeBlockBg,
103
+ border: codeBlockBorder,
104
+ padding: '12px',
105
+ borderRadius: '8px',
106
+ overflowX: 'auto',
107
+ margin: '8px 0',
108
+ color: textColor,
109
+ lineHeight: '1.5',
110
+ display: 'block',
111
+ whiteSpace: 'pre',
112
+ },
113
+
114
+ heading: {
115
+ fontWeight: 600,
116
+ marginTop: '12px',
117
+ marginBottom: '6px',
118
+ color: strongColor,
119
+ lineHeight: '1.3',
120
+ },
121
+
122
+ blockquote: {
123
+ borderLeft: blockquoteBorder,
124
+ backgroundColor: blockquoteBg,
125
+ margin: '8px 0',
126
+ padding: '6px 12px',
127
+ borderRadius: '0 4px 4px 0',
128
+ color: textColor,
129
+ fontStyle: 'italic',
130
+ },
131
+
132
+ hr: {
133
+ border: 'none',
134
+ borderTop: isLightTheme
135
+ ? '1px solid rgba(0,0,0,0.1)'
136
+ : '1px solid rgba(255,255,255,0.08)',
137
+ margin: '12px 0',
138
+ },
139
+ };
140
+ }
@@ -0,0 +1,4 @@
1
+ import{j as a}from"./jsx-runtime-Cf8x2fCZ.js";function i({size:e=24,color:r="white",className:t="",style:n}){return a.jsxs("svg",{width:e,height:e,viewBox:"0 0 32 32",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:t,style:n,"aria-hidden":"true",children:[a.jsx("path",{d:"M20.582 15.027L31.849 0.036H24.808L17.039 10.303L20.582 15.027ZM24.808 31.837H31.849L20.582 16.846L17.039 21.57L24.808 31.837Z",fill:r}),a.jsx("path",{d:"M14.313 15.027H18.402L7.135 0.036H0.185L9.406 12.392C10.587 13.983 12.359 15.027 14.313 15.027Z",fill:r}),a.jsx("path",{d:"M0.185 31.837H7.135L18.402 16.846H14.313C12.359 16.846 10.588 17.891 9.406 19.481L0.185 31.837Z",fill:r})]})}function s({size:e=28,color:r="white",className:t="",style:n}){return a.jsx("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:r,strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round",className:t,style:n,"aria-hidden":"true",children:a.jsx("path",{d:"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"})})}function l({size:e=40,className:r=""}){const t=Math.round(e*.55);return a.jsx("div",{className:`flex items-center justify-center rounded-full ${r}`,style:{width:e,height:e,background:"linear-gradient(135deg, #337eff, #005eff)"},children:a.jsx(i,{size:t,color:"white"})})}try{i.displayName="XcelsiorSymbol",i.__docgenInfo={description:`Xcelsior "X" symbol mark (from favicon-brand.svg)
2
+ Reusable across the chat widget — FAB button, avatar, header, etc.`,displayName:"XcelsiorSymbol",props:{size:{defaultValue:{value:"24"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},color:{defaultValue:{value:"white"},description:"",name:"color",required:!1,type:{name:"string | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}},style:{defaultValue:null,description:"",name:"style",required:!1,type:{name:"CSSProperties | undefined"}}}}}catch{}try{s.displayName="ChatBubbleIcon",s.__docgenInfo={description:`Chat bubble icon for the FAB launcher button.
3
+ Instantly recognizable as a chat trigger.`,displayName:"ChatBubbleIcon",props:{size:{defaultValue:{value:"28"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},color:{defaultValue:{value:"white"},description:"",name:"color",required:!1,type:{name:"string | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}},style:{defaultValue:null,description:"",name:"style",required:!1,type:{name:"CSSProperties | undefined"}}}}}catch{}try{l.displayName="XcelsiorAvatar",l.__docgenInfo={description:`Xcelsior "X" avatar — symbol inside a gradient circle
4
+ Used as bot avatar in messages and chat header`,displayName:"XcelsiorAvatar",props:{size:{defaultValue:{value:"40"},description:"",name:"size",required:!1,type:{name:"number | undefined"}},className:{defaultValue:{value:""},description:"",name:"className",required:!1,type:{name:"string | undefined"}}}}}catch{}export{s as C,i as X,l as a};
@@ -0,0 +1,64 @@
1
+ import{j as e}from"./jsx-runtime-Cf8x2fCZ.js";import{X as s,a as i}from"./BrandIcons-Cjy5INAp.js";import"./index-yBjzXJbu.js";const H={title:"Brand/Icons",parameters:{layout:"padded"},tags:["autodocs"]},a={render:()=>e.jsxs("div",{className:"flex items-center gap-6 p-4",children:[e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(s,{size:16,color:"#337eff"}),e.jsx("span",{className:"text-xs text-gray-500",children:"16px"})]}),e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(s,{size:24,color:"#337eff"}),e.jsx("span",{className:"text-xs text-gray-500",children:"24px"})]}),e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(s,{size:32,color:"#337eff"}),e.jsx("span",{className:"text-xs text-gray-500",children:"32px"})]}),e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(s,{size:48,color:"#337eff"}),e.jsx("span",{className:"text-xs text-gray-500",children:"48px"})]})]})},t={render:()=>e.jsxs("div",{className:"flex items-center gap-6 p-6 bg-gray-900 rounded-lg",children:[e.jsx(s,{size:24,color:"white"}),e.jsx(s,{size:32,color:"white"}),e.jsx(s,{size:48,color:"white"})]})},r={render:()=>e.jsxs("div",{className:"flex items-center gap-6 p-4",children:[e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(i,{size:24}),e.jsx("span",{className:"text-xs text-gray-500",children:"24px"})]}),e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(i,{size:32}),e.jsx("span",{className:"text-xs text-gray-500",children:"32px"})]}),e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(i,{size:40}),e.jsx("span",{className:"text-xs text-gray-500",children:"40px (default)"})]}),e.jsxs("div",{className:"flex flex-col items-center gap-2",children:[e.jsx(i,{size:56}),e.jsx("span",{className:"text-xs text-gray-500",children:"56px (FAB)"})]})]})},l={render:()=>e.jsxs("div",{className:"flex flex-col gap-3 max-w-md p-4 bg-gray-900 rounded-lg",children:[e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(i,{size:32}),e.jsxs("div",{className:"bg-gray-800 rounded-lg px-3 py-2 text-sm text-white",children:[e.jsx("p",{className:"text-xs text-blue-400 mb-1",children:"AI Assistant"}),"Hello! How can I help you today?"]})]}),e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx("div",{className:"w-8 h-8 rounded-full bg-green-600 flex items-center justify-center text-white text-sm font-medium",children:"S"}),e.jsxs("div",{className:"bg-gray-800 rounded-lg px-3 py-2 text-sm text-white",children:[e.jsx("p",{className:"text-xs text-green-400 mb-1",children:"Sarah (Agent)"}),"I'm here to help with your inquiry!"]})]})]})};var c,n,x,o,d;a.parameters={...a.parameters,docs:{...(c=a.parameters)==null?void 0:c.docs,source:{originalSource:`{
2
+ render: () => <div className="flex items-center gap-6 p-4">
3
+ <div className="flex flex-col items-center gap-2">
4
+ <XcelsiorSymbol size={16} color="#337eff" />
5
+ <span className="text-xs text-gray-500">16px</span>
6
+ </div>
7
+ <div className="flex flex-col items-center gap-2">
8
+ <XcelsiorSymbol size={24} color="#337eff" />
9
+ <span className="text-xs text-gray-500">24px</span>
10
+ </div>
11
+ <div className="flex flex-col items-center gap-2">
12
+ <XcelsiorSymbol size={32} color="#337eff" />
13
+ <span className="text-xs text-gray-500">32px</span>
14
+ </div>
15
+ <div className="flex flex-col items-center gap-2">
16
+ <XcelsiorSymbol size={48} color="#337eff" />
17
+ <span className="text-xs text-gray-500">48px</span>
18
+ </div>
19
+ </div>
20
+ }`,...(x=(n=a.parameters)==null?void 0:n.docs)==null?void 0:x.source},description:{story:"Xcelsior X symbol at various sizes",...(d=(o=a.parameters)==null?void 0:o.docs)==null?void 0:d.description}}};var m,p,g,f,v;t.parameters={...t.parameters,docs:{...(m=t.parameters)==null?void 0:m.docs,source:{originalSource:`{
21
+ render: () => <div className="flex items-center gap-6 p-6 bg-gray-900 rounded-lg">
22
+ <XcelsiorSymbol size={24} color="white" />
23
+ <XcelsiorSymbol size={32} color="white" />
24
+ <XcelsiorSymbol size={48} color="white" />
25
+ </div>
26
+ }`,...(g=(p=t.parameters)==null?void 0:p.docs)==null?void 0:g.source},description:{story:"Symbol on dark backgrounds",...(v=(f=t.parameters)==null?void 0:f.docs)==null?void 0:v.description}}};var y,N,h,u,j;r.parameters={...r.parameters,docs:{...(y=r.parameters)==null?void 0:y.docs,source:{originalSource:`{
27
+ render: () => <div className="flex items-center gap-6 p-4">
28
+ <div className="flex flex-col items-center gap-2">
29
+ <XcelsiorAvatar size={24} />
30
+ <span className="text-xs text-gray-500">24px</span>
31
+ </div>
32
+ <div className="flex flex-col items-center gap-2">
33
+ <XcelsiorAvatar size={32} />
34
+ <span className="text-xs text-gray-500">32px</span>
35
+ </div>
36
+ <div className="flex flex-col items-center gap-2">
37
+ <XcelsiorAvatar size={40} />
38
+ <span className="text-xs text-gray-500">40px (default)</span>
39
+ </div>
40
+ <div className="flex flex-col items-center gap-2">
41
+ <XcelsiorAvatar size={56} />
42
+ <span className="text-xs text-gray-500">56px (FAB)</span>
43
+ </div>
44
+ </div>
45
+ }`,...(h=(N=r.parameters)==null?void 0:N.docs)==null?void 0:h.source},description:{story:"Xcelsior avatar (gradient circle with symbol) at various sizes",...(j=(u=r.parameters)==null?void 0:u.docs)==null?void 0:j.description}}};var b,z,S,w,A;l.parameters={...l.parameters,docs:{...(b=l.parameters)==null?void 0:b.docs,source:{originalSource:`{
46
+ render: () => <div className="flex flex-col gap-3 max-w-md p-4 bg-gray-900 rounded-lg">
47
+ <div className="flex items-start gap-3">
48
+ <XcelsiorAvatar size={32} />
49
+ <div className="bg-gray-800 rounded-lg px-3 py-2 text-sm text-white">
50
+ <p className="text-xs text-blue-400 mb-1">AI Assistant</p>
51
+ Hello! How can I help you today?
52
+ </div>
53
+ </div>
54
+ <div className="flex items-start gap-3">
55
+ <div className="w-8 h-8 rounded-full bg-green-600 flex items-center justify-center text-white text-sm font-medium">
56
+ S
57
+ </div>
58
+ <div className="bg-gray-800 rounded-lg px-3 py-2 text-sm text-white">
59
+ <p className="text-xs text-green-400 mb-1">Sarah (Agent)</p>
60
+ I&apos;m here to help with your inquiry!
61
+ </div>
62
+ </div>
63
+ </div>
64
+ }`,...(S=(z=l.parameters)==null?void 0:z.docs)==null?void 0:S.source},description:{story:"Avatar in context — message list style",...(A=(w=l.parameters)==null?void 0:w.docs)==null?void 0:A.description}}};const B=["SymbolSizes","SymbolOnDark","AvatarSizes","AvatarInContext"];export{l as AvatarInContext,r as AvatarSizes,t as SymbolOnDark,a as SymbolSizes,B as __namedExportsOrder,H as default};