@xcelsior/ui-chat 1.0.7 → 2.0.0

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/CHANGELOG.md +10 -0
  2. package/dist/index.d.mts +69 -69
  3. package/dist/index.d.ts +69 -69
  4. package/dist/index.js +2458 -627
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +2457 -628
  7. package/dist/index.mjs.map +1 -1
  8. package/package.json +6 -5
  9. package/src/components/BrandIcons.stories.tsx +95 -0
  10. package/src/components/BrandIcons.tsx +84 -0
  11. package/src/components/Chat.stories.tsx +149 -16
  12. package/src/components/Chat.tsx +116 -96
  13. package/src/components/ChatHeader.tsx +124 -69
  14. package/src/components/ChatInput.tsx +253 -104
  15. package/src/components/ChatWidget.tsx +209 -63
  16. package/src/components/ConversationRating.stories.tsx +33 -0
  17. package/src/components/ConversationRating.tsx +156 -0
  18. package/src/components/MarkdownMessage.tsx +202 -0
  19. package/src/components/MessageItem.stories.tsx +253 -55
  20. package/src/components/MessageItem.tsx +222 -59
  21. package/src/components/MessageList.tsx +164 -35
  22. package/src/components/PreChatForm.tsx +236 -96
  23. package/src/components/ThinkingIndicator.tsx +370 -0
  24. package/src/components/TypingIndicator.tsx +27 -11
  25. package/src/hooks/useDraggablePosition.ts +91 -0
  26. package/src/hooks/useMessages.ts +12 -13
  27. package/src/hooks/useResizableWidget.ts +324 -0
  28. package/src/index.tsx +5 -0
  29. package/src/types.ts +51 -5
  30. package/src/utils/markdown-styles.ts +140 -0
  31. package/storybook-static/assets/BrandIcons-Cjy5INAp.js +4 -0
  32. package/storybook-static/assets/BrandIcons.stories-BeVC6svr.js +64 -0
  33. package/storybook-static/assets/Chat.stories-J_Yp51wU.js +803 -0
  34. package/storybook-static/assets/Color-YHDXOIA2-BMnd3YrF.js +1 -0
  35. package/storybook-static/assets/ConversationRating.stories-B5_QddHN.js +12 -0
  36. package/storybook-static/assets/DocsRenderer-CFRXHY34-i_W8iCu9.js +575 -0
  37. package/storybook-static/assets/MessageItem-DAaKZ9s9.js +14 -0
  38. package/storybook-static/assets/MessageItem.stories-Ckr1_scc.js +255 -0
  39. package/storybook-static/assets/ToastContext-Bty1K7ya.js +1 -0
  40. package/storybook-static/assets/chunk-XP5HYGXS-BpfKkqn7.js +1 -0
  41. package/storybook-static/assets/en-US-BukEqXxE.js +1 -0
  42. package/storybook-static/assets/entry-preview-docs-DHohToDm.js +46 -0
  43. package/storybook-static/assets/entry-preview-oDnntGcx.js +2 -0
  44. package/storybook-static/assets/iframe-CGBtu2Se.js +211 -0
  45. package/storybook-static/assets/index--qcDGAq6.js +1 -0
  46. package/storybook-static/assets/index-BLHw34Di.js +24 -0
  47. package/storybook-static/assets/index-B_4m48Mv.js +1 -0
  48. package/storybook-static/assets/index-DgH-xKnr.js +11 -0
  49. package/storybook-static/assets/index-DrFu-skq.js +6 -0
  50. package/storybook-static/assets/index-DrdPSA1J.js +240 -0
  51. package/storybook-static/assets/index-jvNEZhzf.js +1 -0
  52. package/storybook-static/assets/index-yBjzXJbu.js +9 -0
  53. package/storybook-static/assets/jsx-runtime-Cf8x2fCZ.js +9 -0
  54. package/storybook-static/assets/preview-B8lJiyuQ.js +34 -0
  55. package/storybook-static/assets/preview-BBWR9nbA.js +1 -0
  56. package/storybook-static/assets/preview-BRpahs9B.js +2 -0
  57. package/storybook-static/assets/preview-BWzBA1C2.js +396 -0
  58. package/storybook-static/assets/preview-CvbIS5ZJ.js +1 -0
  59. package/storybook-static/assets/preview-DD_OYowb.js +1 -0
  60. package/storybook-static/assets/preview-DGUiP6tS.js +7 -0
  61. package/storybook-static/assets/preview-DHQbi4pV.js +1 -0
  62. package/storybook-static/assets/preview-DUOvJmsz.js +1 -0
  63. package/storybook-static/assets/preview-DcGwT3kv.css +1 -0
  64. package/storybook-static/assets/preview-DwI0w3cI.js +1 -0
  65. package/storybook-static/assets/react-18-CALspjOX.js +1 -0
  66. package/storybook-static/assets/test-utils-BE0XkMtV.js +9 -0
  67. package/storybook-static/favicon.svg +1 -0
  68. package/storybook-static/iframe.html +666 -0
  69. package/storybook-static/index.html +177 -0
  70. package/storybook-static/index.json +1 -0
  71. package/storybook-static/nunito-sans-bold-italic.woff2 +0 -0
  72. package/storybook-static/nunito-sans-bold.woff2 +0 -0
  73. package/storybook-static/nunito-sans-italic.woff2 +0 -0
  74. package/storybook-static/nunito-sans-regular.woff2 +0 -0
  75. package/storybook-static/project.json +1 -0
  76. package/storybook-static/sb-addons/essentials-actions-3/manager-bundle.js +3 -0
  77. package/storybook-static/sb-addons/essentials-backgrounds-5/manager-bundle.js +12 -0
  78. package/storybook-static/sb-addons/essentials-controls-2/manager-bundle.js +405 -0
  79. package/storybook-static/sb-addons/essentials-docs-4/manager-bundle.js +245 -0
  80. package/storybook-static/sb-addons/essentials-measure-8/manager-bundle.js +3 -0
  81. package/storybook-static/sb-addons/essentials-outline-9/manager-bundle.js +3 -0
  82. package/storybook-static/sb-addons/essentials-toolbars-7/manager-bundle.js +3 -0
  83. package/storybook-static/sb-addons/essentials-viewport-6/manager-bundle.js +3 -0
  84. package/storybook-static/sb-addons/interactions-10/manager-bundle.js +222 -0
  85. package/storybook-static/sb-addons/links-1/manager-bundle.js +3 -0
  86. package/storybook-static/sb-addons/storybook-core-core-server-presets-0/common-manager-bundle.js +3 -0
  87. package/storybook-static/sb-common-assets/favicon.svg +1 -0
  88. package/storybook-static/sb-common-assets/nunito-sans-bold-italic.woff2 +0 -0
  89. package/storybook-static/sb-common-assets/nunito-sans-bold.woff2 +0 -0
  90. package/storybook-static/sb-common-assets/nunito-sans-italic.woff2 +0 -0
  91. package/storybook-static/sb-common-assets/nunito-sans-regular.woff2 +0 -0
  92. package/storybook-static/sb-manager/globals-module-info.js +1052 -0
  93. package/storybook-static/sb-manager/globals-runtime.js +42127 -0
  94. package/storybook-static/sb-manager/globals.js +48 -0
  95. package/storybook-static/sb-manager/runtime.js +12048 -0
  96. package/.turbo/turbo-lint.log +0 -5
@@ -0,0 +1,202 @@
1
+ import type { CSSProperties } from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import type { Components } from 'react-markdown';
4
+ import type { IChatTheme } from '../types';
5
+ import { getMarkdownStyles } from '../utils/markdown-styles';
6
+
7
+ interface MarkdownMessageProps {
8
+ content: string;
9
+ theme?: IChatTheme;
10
+ }
11
+
12
+ function computeIsLightTheme(theme?: IChatTheme): boolean {
13
+ const bgColor = theme?.background || '#00001a';
14
+ if (!bgColor.startsWith('#')) return false;
15
+ const hex = bgColor.replace('#', '');
16
+ if (hex.length < 6) return false;
17
+ const r = parseInt(hex.substring(0, 2), 16);
18
+ const g = parseInt(hex.substring(2, 4), 16);
19
+ const b = parseInt(hex.substring(4, 6), 16);
20
+ return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.5;
21
+ }
22
+
23
+ export function MarkdownMessage({ content, theme }: MarkdownMessageProps) {
24
+ const isLightTheme = computeIsLightTheme(theme);
25
+ const styles = getMarkdownStyles(theme, isLightTheme);
26
+
27
+ // Last paragraph in a message should have no bottom margin to avoid
28
+ // extra space at the bottom of the bubble. We track last child via
29
+ // a wrapper + CSS last-child, but since we use inline styles we
30
+ // strip the margin in the paragraph renderer if the node is the last
31
+ // element. react-markdown does not expose "is last sibling" easily,
32
+ // so we use a simple wrapper approach: strip bottom margin on the
33
+ // outermost container's last child by keeping the container style tight
34
+ // and letting natural flow handle it.
35
+
36
+ const wrapperStyle: CSSProperties = {
37
+ // Collapse bottom margin of the last child to avoid extra padding
38
+ // inside the bubble.
39
+ display: 'block',
40
+ };
41
+
42
+ return (
43
+ <div style={wrapperStyle}>
44
+ <ReactMarkdown
45
+ components={{
46
+ // Paragraphs
47
+ p: ({ children }) => (
48
+ <p style={styles.paragraph}>{children}</p>
49
+ ),
50
+
51
+ // Bold
52
+ strong: ({ children }) => (
53
+ <strong style={styles.strong}>{children}</strong>
54
+ ),
55
+
56
+ // Italic
57
+ em: ({ children }) => (
58
+ <em style={styles.emphasis}>{children}</em>
59
+ ),
60
+
61
+ // Links — open in new tab
62
+ a: ({ href, children }) => (
63
+ <a
64
+ href={href}
65
+ target="_blank"
66
+ rel="noopener noreferrer"
67
+ style={styles.link}
68
+ >
69
+ {children}
70
+ </a>
71
+ ),
72
+
73
+ // Unordered list
74
+ ul: ({ children }) => (
75
+ <ul style={{ ...styles.list, listStyleType: 'disc' }}>
76
+ {children}
77
+ </ul>
78
+ ),
79
+
80
+ // Ordered list
81
+ ol: ({ children }) => (
82
+ <ol
83
+ style={{ ...styles.list, listStyleType: 'decimal' }}
84
+ >
85
+ {children}
86
+ </ol>
87
+ ),
88
+
89
+ // List item
90
+ li: ({ children }) => (
91
+ <li style={styles.listItem}>{children}</li>
92
+ ),
93
+
94
+ // Inline code vs code block — react-markdown wraps fenced
95
+ // code blocks as <pre><code className="language-*">. The
96
+ // cleanest heuristic: if the className starts with
97
+ // "language-" it is a fenced block; otherwise it's inline.
98
+ code: (({ children, className }) => {
99
+ const isBlock = Boolean(
100
+ className?.startsWith('language-'),
101
+ );
102
+ if (isBlock) {
103
+ return (
104
+ <code
105
+ className={className}
106
+ style={styles.codeBlock}
107
+ >
108
+ {children}
109
+ </code>
110
+ );
111
+ }
112
+ return <code style={styles.code}>{children}</code>;
113
+ }) as Components['code'],
114
+
115
+ // Pre wrapper for code blocks
116
+ pre: ({ children }) => (
117
+ <pre
118
+ style={{
119
+ margin: '8px 0',
120
+ padding: 0,
121
+ backgroundColor: 'transparent',
122
+ border: 'none',
123
+ overflow: 'visible',
124
+ }}
125
+ >
126
+ {children}
127
+ </pre>
128
+ ),
129
+
130
+ // Headings — h1 through h6 with progressive size reduction
131
+ h1: ({ children }) => (
132
+ <h1
133
+ style={{
134
+ ...styles.heading,
135
+ fontSize: '1.15em',
136
+ }}
137
+ >
138
+ {children}
139
+ </h1>
140
+ ),
141
+ h2: ({ children }) => (
142
+ <h2
143
+ style={{
144
+ ...styles.heading,
145
+ fontSize: '1.1em',
146
+ }}
147
+ >
148
+ {children}
149
+ </h2>
150
+ ),
151
+ h3: ({ children }) => (
152
+ <h3
153
+ style={{
154
+ ...styles.heading,
155
+ fontSize: '1.05em',
156
+ }}
157
+ >
158
+ {children}
159
+ </h3>
160
+ ),
161
+ h4: ({ children }) => (
162
+ <h4 style={{ ...styles.heading, fontSize: '1em' }}>
163
+ {children}
164
+ </h4>
165
+ ),
166
+ h5: ({ children }) => (
167
+ <h5
168
+ style={{
169
+ ...styles.heading,
170
+ fontSize: '0.95em',
171
+ }}
172
+ >
173
+ {children}
174
+ </h5>
175
+ ),
176
+ h6: ({ children }) => (
177
+ <h6
178
+ style={{
179
+ ...styles.heading,
180
+ fontSize: '0.9em',
181
+ }}
182
+ >
183
+ {children}
184
+ </h6>
185
+ ),
186
+
187
+ // Blockquote
188
+ blockquote: ({ children }) => (
189
+ <blockquote style={styles.blockquote}>
190
+ {children}
191
+ </blockquote>
192
+ ),
193
+
194
+ // Horizontal rule
195
+ hr: () => <hr style={styles.hr} />,
196
+ }}
197
+ >
198
+ {content}
199
+ </ReactMarkdown>
200
+ </div>
201
+ );
202
+ }
@@ -14,7 +14,6 @@ const meta: Meta<typeof MessageItem> = {
14
14
  export default meta;
15
15
  type Story = StoryObj<typeof MessageItem>;
16
16
 
17
- // Mock current user
18
17
  const currentUser: IUser = {
19
18
  name: 'John Doe',
20
19
  email: 'john@example.com',
@@ -22,7 +21,6 @@ const currentUser: IUser = {
22
21
  status: 'online',
23
22
  };
24
23
 
25
- // Base message template
26
24
  const baseMessage: IMessage = {
27
25
  id: '1',
28
26
  conversationId: 'conv-1',
@@ -34,9 +32,7 @@ const baseMessage: IMessage = {
34
32
  status: 'sent',
35
33
  };
36
34
 
37
- /**
38
- * Customer message (own message)
39
- */
35
+ /** Customer message (own message) */
40
36
  export const CustomerMessage: Story = {
41
37
  args: {
42
38
  message: {
@@ -50,17 +46,14 @@ export const CustomerMessage: Story = {
50
46
  },
51
47
  };
52
48
 
53
- /**
54
- * Human agent message
55
- */
49
+ /** Human agent message */
56
50
  export const AgentMessage: Story = {
57
51
  args: {
58
52
  message: {
59
53
  ...baseMessage,
60
54
  senderId: 'agent@example.com',
61
55
  senderType: 'agent',
62
- content:
63
- "Hi! I'd be happy to help you with your account. What specific issue are you experiencing?",
56
+ content: "Hi! I'd be happy to help you with your account. What issue are you experiencing?",
64
57
  },
65
58
  currentUser,
66
59
  showAvatar: true,
@@ -68,20 +61,16 @@ export const AgentMessage: Story = {
68
61
  },
69
62
  };
70
63
 
71
- /**
72
- * AI-generated message with robot icon
73
- */
74
- export const AIMessage: Story = {
64
+ /** Bot message (AI assistant) — new senderType */
65
+ export const BotMessage: Story = {
75
66
  args: {
76
67
  message: {
77
68
  ...baseMessage,
78
69
  senderId: 'ai-assistant',
79
- senderType: 'agent',
80
- content:
81
- "Hello! I'm an AI assistant. Based on our knowledge base, here are some common solutions for account issues:\n\n1. Reset your password\n2. Verify your email\n3. Contact support",
82
- metadata: {
83
- isAI: true,
84
- },
70
+ senderType: 'bot',
71
+ content: "Hello! I'm the Xcelsior assistant. Based on our knowledge base, here are some common solutions:\n\n1. Reset your password\n2. Verify your email\n3. Contact support\n\nWhich would you like help with?",
72
+ confidence: 0.85,
73
+ metadata: { isAI: true, kbUsed: true },
85
74
  },
86
75
  currentUser,
87
76
  showAvatar: true,
@@ -89,15 +78,66 @@ export const AIMessage: Story = {
89
78
  },
90
79
  };
91
80
 
92
- /**
93
- * AI message with markdown formatting
94
- */
95
- export const AIMessageWithMarkdown: Story = {
81
+ /** Bot message with low confidence */
82
+ export const BotMessageLowConfidence: Story = {
96
83
  args: {
97
84
  message: {
98
85
  ...baseMessage,
99
86
  senderId: 'ai-assistant',
100
- senderType: 'agent',
87
+ senderType: 'bot',
88
+ content: "I'm not entirely sure about this, but let me check with my team. In the meantime, you might want to visit our help center.",
89
+ confidence: 0.3,
90
+ metadata: { isAI: true, kbUsed: false },
91
+ },
92
+ currentUser,
93
+ showAvatar: true,
94
+ showTimestamp: true,
95
+ },
96
+ };
97
+
98
+ /** Bot message with feedback (rated good) */
99
+ export const BotMessageFeedbackGood: Story = {
100
+ args: {
101
+ message: {
102
+ ...baseMessage,
103
+ senderId: 'ai-assistant',
104
+ senderType: 'bot',
105
+ content: "You can reset your password by going to Settings > Security > Change Password.",
106
+ feedback: 'good',
107
+ confidence: 0.9,
108
+ metadata: { isAI: true, kbUsed: true },
109
+ },
110
+ currentUser,
111
+ showAvatar: true,
112
+ showTimestamp: true,
113
+ },
114
+ };
115
+
116
+ /** Bot message with feedback (rated wrong) */
117
+ export const BotMessageFeedbackWrong: Story = {
118
+ args: {
119
+ message: {
120
+ ...baseMessage,
121
+ senderId: 'ai-assistant',
122
+ senderType: 'bot',
123
+ content: "Your subscription expires on March 15th.",
124
+ feedback: 'wrong',
125
+ confidence: 0.6,
126
+ metadata: { isAI: true, kbUsed: true },
127
+ },
128
+ currentUser,
129
+ showAvatar: true,
130
+ showTimestamp: true,
131
+ },
132
+ };
133
+
134
+ /** AI message with markdown formatting */
135
+ export const BotMessageWithMarkdown: Story = {
136
+ args: {
137
+ message: {
138
+ ...baseMessage,
139
+ senderId: 'ai-assistant',
140
+ senderType: 'bot',
101
141
  content: `## Getting Started
102
142
 
103
143
  Here's how you can reset your password:
@@ -109,10 +149,8 @@ Here's how you can reset your password:
109
149
 
110
150
  *Note: The link expires in 24 hours.*
111
151
 
112
- For more help, visit our [support page](https://example.com/support).`,
113
- metadata: {
114
- isAI: true,
115
- },
152
+ For more help, visit our [support page](https://xcelsior.co/support).`,
153
+ metadata: { isAI: true, kbUsed: true },
116
154
  },
117
155
  currentUser,
118
156
  showAvatar: true,
@@ -120,9 +158,7 @@ For more help, visit our [support page](https://example.com/support).`,
120
158
  },
121
159
  };
122
160
 
123
- /**
124
- * System message
125
- */
161
+ /** System message — generic */
126
162
  export const SystemMessage: Story = {
127
163
  args: {
128
164
  message: {
@@ -130,6 +166,23 @@ export const SystemMessage: Story = {
130
166
  senderId: 'system',
131
167
  senderType: 'system',
132
168
  content: 'Agent John joined the conversation',
169
+ messageType: 'system',
170
+ },
171
+ currentUser,
172
+ showAvatar: true,
173
+ showTimestamp: true,
174
+ },
175
+ };
176
+
177
+ /** System message — escalation notice */
178
+ export const EscalationNotice: Story = {
179
+ args: {
180
+ message: {
181
+ ...baseMessage,
182
+ senderId: 'system',
183
+ senderType: 'system',
184
+ content: 'Connecting you with a team member who can help further...',
185
+ messageType: 'system',
133
186
  },
134
187
  currentUser,
135
188
  showAvatar: true,
@@ -137,9 +190,23 @@ export const SystemMessage: Story = {
137
190
  },
138
191
  };
139
192
 
140
- /**
141
- * Message with image
142
- */
193
+ /** System message — agent took over */
194
+ export const AgentTookOver: Story = {
195
+ args: {
196
+ message: {
197
+ ...baseMessage,
198
+ senderId: 'system',
199
+ senderType: 'system',
200
+ content: 'Sarah from our team is here to help!',
201
+ messageType: 'system',
202
+ },
203
+ currentUser,
204
+ showAvatar: true,
205
+ showTimestamp: true,
206
+ },
207
+ };
208
+
209
+ /** Image message */
143
210
  export const ImageMessage: Story = {
144
211
  args: {
145
212
  message: {
@@ -153,18 +220,14 @@ export const ImageMessage: Story = {
153
220
  },
154
221
  };
155
222
 
156
- /**
157
- * Message with file attachment
158
- */
223
+ /** File message */
159
224
  export const FileMessage: Story = {
160
225
  args: {
161
226
  message: {
162
227
  ...baseMessage,
163
228
  content: 'https://example.com/document.pdf',
164
229
  messageType: 'file',
165
- metadata: {
166
- fileName: 'Technical Documentation.pdf',
167
- },
230
+ metadata: { fileName: 'Technical Documentation.pdf' },
168
231
  },
169
232
  currentUser,
170
233
  showAvatar: true,
@@ -172,19 +235,142 @@ export const FileMessage: Story = {
172
235
  },
173
236
  };
174
237
 
175
- /**
176
- * Conversation showing different message types
177
- */
178
- export const ConversationExample: Story = {
238
+ /** Bot message with rich markdown — bullet list, bold, pricing options */
239
+ export const BotMarkdownMessage: Story = {
240
+ args: {
241
+ message: {
242
+ id: 'md-1',
243
+ conversationId: 'conv-1',
244
+ senderId: 'ai-assistant',
245
+ senderType: 'bot',
246
+ content: `Hi there! I'd be happy to help you get a quote for your project.
247
+
248
+ To provide you with the most accurate quote, I'll need to understand more about what you're looking for. Could you tell me:
249
+
250
+ - **What type of project** are you considering? (mobile app, website, custom software, etc.)
251
+ - **What's the main purpose** or functionality you need?
252
+ - **Any specific requirements** or features you have in mind?
253
+
254
+ At Xcelsior, we offer flexible pricing models to fit your needs:
255
+
256
+ 1. Hour-based pricing
257
+ 2. Feature-based pricing
258
+ 3. Fixed-sum pricing
259
+
260
+ Once I understand your project better, I can connect you with our team for a detailed quote and consultation.`,
261
+ messageType: 'text',
262
+ createdAt: new Date().toISOString(),
263
+ status: 'sent',
264
+ },
265
+ currentUser: {
266
+ name: 'Visitor',
267
+ email: 'visitor@example.com',
268
+ type: 'customer',
269
+ status: 'online',
270
+ },
271
+ showAvatar: true,
272
+ showTimestamp: true,
273
+ },
274
+ };
275
+
276
+ /** Bot message with a fenced code block and inline code */
277
+ export const BotCodeBlock: Story = {
278
+ args: {
279
+ message: {
280
+ id: 'md-2',
281
+ conversationId: 'conv-1',
282
+ senderId: 'ai-assistant',
283
+ senderType: 'bot',
284
+ content: `Here's a quick example of how to integrate our SDK:
285
+
286
+ \`\`\`javascript
287
+ import { XcelsiorChat } from '@xcelsior/ui-chat';
288
+
289
+ const config = {
290
+ apiKey: 'your-api-key',
291
+ theme: { primary: '#337eff' }
292
+ };
293
+
294
+ <XcelsiorChat config={config} />
295
+ \`\`\`
296
+
297
+ You can also use inline code like \`npm install @xcelsior/ui-chat\` to get started.
298
+
299
+ For more details, check our **documentation** or reach out to our team!`,
300
+ messageType: 'text',
301
+ createdAt: new Date().toISOString(),
302
+ status: 'sent',
303
+ },
304
+ currentUser: {
305
+ name: 'Visitor',
306
+ email: 'visitor@example.com',
307
+ type: 'customer',
308
+ status: 'online',
309
+ },
310
+ showAvatar: true,
311
+ showTimestamp: true,
312
+ },
313
+ };
314
+
315
+ /** Bot message with headings, nested lists, and a blockquote */
316
+ export const BotLongResponse: Story = {
317
+ args: {
318
+ message: {
319
+ id: 'md-3',
320
+ conversationId: 'conv-1',
321
+ senderId: 'ai-assistant',
322
+ senderType: 'bot',
323
+ content: `Great question! Here's a comprehensive overview of our services:
324
+
325
+ ## Web Development
326
+ We build modern, responsive web applications using cutting-edge technologies like **Next.js**, **React**, and **TypeScript**. Our team focuses on performance, accessibility, and SEO.
327
+
328
+ ## Mobile Development
329
+ We develop cross-platform mobile apps using **React Native** and **Flutter**, ensuring your app runs smoothly on both iOS and Android.
330
+
331
+ ## Digital Solutions
332
+ - Custom CMS development
333
+ - E-commerce platforms
334
+ - API integrations
335
+ - Cloud infrastructure (AWS, GCP)
336
+
337
+ ## IT Consulting
338
+ Our experienced consultants help you:
339
+ 1. Plan your technology roadmap
340
+ 2. Optimize existing systems
341
+ 3. Scale your infrastructure
342
+ 4. Implement security best practices
343
+
344
+ > "Xcelsior delivered our project on time and exceeded our expectations." — Client Testimonial
345
+
346
+ Would you like to know more about any specific service?`,
347
+ messageType: 'text',
348
+ createdAt: new Date().toISOString(),
349
+ status: 'sent',
350
+ },
351
+ currentUser: {
352
+ name: 'Visitor',
353
+ email: 'visitor@example.com',
354
+ type: 'customer',
355
+ status: 'online',
356
+ },
357
+ showAvatar: true,
358
+ showTimestamp: true,
359
+ },
360
+ };
361
+
362
+ /** Full conversation flow: customer → bot → escalation → agent */
363
+ export const FullConversationFlow: Story = {
179
364
  render: (args) => (
180
- <div className="flex flex-col gap-2 max-w-2xl">
365
+ <div className="flex flex-col gap-2 max-w-2xl p-4 bg-gray-950 rounded-lg">
181
366
  <MessageItem
182
367
  {...args}
183
368
  message={{
184
369
  ...baseMessage,
185
370
  id: '1',
186
371
  senderId: currentUser.email,
187
- content: 'Hi, I need help with resetting my password.',
372
+ senderType: 'customer',
373
+ content: 'Hi, I need help with pricing for a custom web app.',
188
374
  }}
189
375
  />
190
376
  <MessageItem
@@ -193,10 +379,10 @@ export const ConversationExample: Story = {
193
379
  ...baseMessage,
194
380
  id: '2',
195
381
  senderId: 'ai-assistant',
196
- senderType: 'agent',
197
- content:
198
- 'I can help you with that! Here are the steps to reset your password:\n\n1. Click on "Forgot Password" on the login page\n2. Enter your email address\n3. Check your inbox for a reset link\n\nThe link will expire in 24 hours. Would you like me to send you the reset link now?',
199
- metadata: { isAI: true },
382
+ senderType: 'bot',
383
+ content: "Hello! I'd be happy to help with pricing. At Xcelsior, we offer custom quotes based on your project requirements. Could you tell me more about what you're looking for?",
384
+ confidence: 0.8,
385
+ metadata: { isAI: true, kbUsed: true },
200
386
  createdAt: new Date(Date.now() - 60000).toISOString(),
201
387
  }}
202
388
  />
@@ -206,7 +392,8 @@ export const ConversationExample: Story = {
206
392
  ...baseMessage,
207
393
  id: '3',
208
394
  senderId: currentUser.email,
209
- content: 'Yes please, send me the reset link.',
395
+ senderType: 'customer',
396
+ content: 'I want to talk to a real person about this. Can you connect me with your sales team?',
210
397
  createdAt: new Date(Date.now() - 30000).toISOString(),
211
398
  }}
212
399
  />
@@ -215,10 +402,21 @@ export const ConversationExample: Story = {
215
402
  message={{
216
403
  ...baseMessage,
217
404
  id: '4',
218
- senderId: 'agent@example.com',
405
+ senderId: 'system',
406
+ senderType: 'system',
407
+ content: 'Connecting you with a team member who can help further...',
408
+ messageType: 'system',
409
+ createdAt: new Date(Date.now() - 25000).toISOString(),
410
+ }}
411
+ />
412
+ <MessageItem
413
+ {...args}
414
+ message={{
415
+ ...baseMessage,
416
+ id: '5',
417
+ senderId: 'sarah@xcelsior.co',
219
418
  senderType: 'agent',
220
- content:
221
- "I've sent the password reset link to your registered email address. Please check your inbox and spam folder. Let me know if you need any further assistance!",
419
+ content: "Hi there! I'm Sarah from our sales team. I'd love to discuss your custom web app project. Can you share a bit about the features you need and your timeline?",
222
420
  createdAt: new Date().toISOString(),
223
421
  }}
224
422
  />