blixify-ui-web-mcp 0.1.2 → 0.1.18

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 (135) hide show
  1. package/lib/components/action/button/README.md +12 -11
  2. package/lib/components/action/button/button.stories.tsx +4 -10
  3. package/lib/components/action/buttonGroup/buttonGroup.stories.tsx +4 -10
  4. package/lib/components/action/iconButton/iconButton.stories.tsx +4 -10
  5. package/lib/components/action/radioList/radioList.stories.tsx +4 -10
  6. package/lib/components/action/slider/slider.stories.tsx +4 -10
  7. package/lib/components/action/textButton/textButton.stories.tsx +4 -10
  8. package/lib/components/action/toggle/toggle.stories.tsx +4 -10
  9. package/lib/components/advanced/collapse/collapse.stories.tsx +4 -10
  10. package/lib/components/advanced/infoWindow/infoWindow.stories.tsx +4 -10
  11. package/lib/components/advanced/popUp/README.md +21 -7
  12. package/lib/components/advanced/popUp/popUp.stories.tsx +4 -10
  13. package/lib/components/auth/bareSignIn/bareSignIn.stories.tsx +30 -10
  14. package/lib/components/auth/bareSignUp/bareSignUp.stories.tsx +4 -10
  15. package/lib/components/auth/signIn/signIn.stories.tsx +4 -10
  16. package/lib/components/auth/signUp/signUp.stories.tsx +4 -10
  17. package/lib/components/chart/areaChart/areaChart.stories.tsx +7 -13
  18. package/lib/components/chart/barChart/barChart.stories.tsx +7 -13
  19. package/lib/components/chart/boxPlotChart/boxPlotChart.stories.tsx +8 -14
  20. package/lib/components/chart/bubbleChart/bubbleChart.stories.tsx +8 -14
  21. package/lib/components/chart/card/card.stories.tsx +4 -10
  22. package/lib/components/chart/ganttChart/README.md +96 -110
  23. package/lib/components/chart/ganttChart/ganttChart.stories.tsx +135 -125
  24. package/lib/components/chart/heatmapChart/README.md +93 -82
  25. package/lib/components/chart/heatmapChart/heatmapChart.stories.tsx +122 -84
  26. package/lib/components/chart/lineChart/lineChart.stories.tsx +4 -10
  27. package/lib/components/chart/pieChart/pieChart.stories.tsx +4 -10
  28. package/lib/components/chart/polarAreaChart/polarAreaChart.stories.tsx +8 -14
  29. package/lib/components/chart/radarChart/radarChart.stories.tsx +4 -10
  30. package/lib/components/chart/radialBar/radialChart.stories.tsx +4 -10
  31. package/lib/components/chart/rangeBarChart/rangeBarChart.stories.tsx +4 -10
  32. package/lib/components/chart/statsCard/statsCard.stories.tsx +4 -10
  33. package/lib/components/chart/treemapChart/treemapChart.stories.tsx +8 -14
  34. package/lib/components/chat/chatAnchor/README.md +61 -79
  35. package/lib/components/chat/chatAnchor/chatAnchor.stories.tsx +13 -19
  36. package/lib/components/chat/chatBase/README.md +244 -196
  37. package/lib/components/chat/chatBase/chatBase.stories.tsx +828 -250
  38. package/lib/components/chat/chatDashboard/README.md +89 -129
  39. package/lib/components/chat/chatDashboard/chatDashboard.stories.tsx +406 -117
  40. package/lib/components/data/dataGraph/README.md +135 -1
  41. package/lib/components/data/dataGraph/dataGraph.stories.tsx +90 -77
  42. package/lib/components/data/dataReference/README.md +141 -43
  43. package/lib/components/data/dataReference/dataReference.stories.tsx +111 -45
  44. package/lib/components/data/dataTemplate/README.md +88 -61
  45. package/lib/components/data/dataTemplate/dataModule.stories.tsx +4 -10
  46. package/lib/components/data/dataTemplate/dataTemplate.stories.tsx +4 -10
  47. package/lib/components/data/dataWrapper/README.md +48 -46
  48. package/lib/components/data/dataWrapper/dataWrapper.stories.tsx +258 -489
  49. package/lib/components/data/queryForm/queryForm.stories.tsx +4 -10
  50. package/lib/components/data/readQuery/readQuery.stories.tsx +4 -9
  51. package/lib/components/data/readQueryHoc/readQueryHoc.stories.tsx +4 -10
  52. package/lib/components/data/writeQuery/writeQuery.stories.tsx +4 -8
  53. package/lib/components/design/actionSection/actionSection.stories.tsx +6 -10
  54. package/lib/components/design/banner/banner.stories.tsx +4 -10
  55. package/lib/components/design/bannerAction/bannerAction.stories.tsx +4 -10
  56. package/lib/components/design/blogSection/blogSection.stories.tsx +4 -10
  57. package/lib/components/design/carouselSection/carouselSection.stories.tsx +4 -10
  58. package/lib/components/design/designList/designList.stories.tsx +4 -10
  59. package/lib/components/design/faq/faq.stories.tsx +4 -10
  60. package/lib/components/design/fileGrid/fileGrid.stories.tsx +4 -10
  61. package/lib/components/design/fileViewer/fileViewer.stories.tsx +4 -10
  62. package/lib/components/design/footerSection/footerSection.stories.tsx +4 -10
  63. package/lib/components/design/googleReview/googleReview.stories.tsx +4 -10
  64. package/lib/components/design/heroSection/heroSection.stories.tsx +4 -10
  65. package/lib/components/design/iconList/iconList.stories.tsx +4 -10
  66. package/lib/components/design/imageGallery/imageGallery.stories.tsx +4 -10
  67. package/lib/components/design/imageZoom/imageZoom.stories.tsx +4 -10
  68. package/lib/components/design/leftContent/leftContent.stories.tsx +4 -10
  69. package/lib/components/design/logoCloud/logoCloud.stories.tsx +4 -10
  70. package/lib/components/design/onboarding/onboarding.stories.tsx +4 -10
  71. package/lib/components/design/pricing/pricing.stories.tsx +4 -10
  72. package/lib/components/design/rightContent/rightContent.stories.tsx +4 -10
  73. package/lib/components/design/sectionList/sectionList.stories.tsx +4 -10
  74. package/lib/components/design/teams/teams.stories.tsx +4 -10
  75. package/lib/components/display/alert/alert.stories.tsx +4 -10
  76. package/lib/components/display/calendar/README.md +49 -0
  77. package/lib/components/display/calendar/calendar.stories.tsx +71 -28
  78. package/lib/components/display/clipboard/clipboard.stories.tsx +4 -10
  79. package/lib/components/display/detailList/detailList.stories.tsx +5 -10
  80. package/lib/components/display/drawer/drawer.stories.tsx +5 -10
  81. package/lib/components/display/emptyState/emptyState.stories.tsx +4 -10
  82. package/lib/components/display/feeds/feeds.stories.tsx +4 -10
  83. package/lib/components/display/flexTable/README.md +11 -7
  84. package/lib/components/display/flexTable/flexTable.stories.tsx +114 -11
  85. package/lib/components/display/grid/README.md +28 -27
  86. package/lib/components/display/grid/grid.stories.tsx +4 -10
  87. package/lib/components/display/kanban/kanban.stories.tsx +14 -20
  88. package/lib/components/display/list/README.md +30 -29
  89. package/lib/components/display/list/list.stories.tsx +4 -10
  90. package/lib/components/display/loading/loading.stories.tsx +5 -10
  91. package/lib/components/display/mapView/mapView.stories.tsx +4 -10
  92. package/lib/components/display/modal/modal.stories.tsx +4 -11
  93. package/lib/components/display/notification/notification.stories.tsx +4 -10
  94. package/lib/components/display/progress/progress.stories.tsx +5 -10
  95. package/lib/components/display/stepper/stepper.stories.tsx +5 -10
  96. package/lib/components/display/table/README.md +38 -37
  97. package/lib/components/display/table/table.stories.tsx +8 -14
  98. package/lib/components/display/tag/tag.stories.tsx +4 -10
  99. package/lib/components/input/address/address.stories.tsx +4 -10
  100. package/lib/components/input/canvas/canvas.stories.tsx +4 -10
  101. package/lib/components/input/checkbox/checkbox.stories.tsx +4 -10
  102. package/lib/components/input/datePicker/datePicker.stories.tsx +4 -10
  103. package/lib/components/input/form/form.stories.tsx +4 -10
  104. package/lib/components/input/markdown/markdown.stories.tsx +5 -10
  105. package/lib/components/input/monthYearPicker/monthYearPicker.stories.tsx +4 -10
  106. package/lib/components/input/netPromoterScore/netPromoterScore.stories.tsx +4 -10
  107. package/lib/components/input/rating/rating.stories.tsx +4 -10
  108. package/lib/components/input/select/README.md +30 -29
  109. package/lib/components/input/select/select.stories.tsx +4 -10
  110. package/lib/components/input/signature/signature.stories.tsx +4 -10
  111. package/lib/components/input/textArea/textArea.stories.tsx +4 -10
  112. package/lib/components/input/textInput/textinput.stories.tsx +4 -10
  113. package/lib/components/input/uploadInput/uploadInput.stories.tsx +135 -11
  114. package/lib/components/navigation/bars/README.md +90 -17
  115. package/lib/components/navigation/bars/bars.stories.tsx +128 -60
  116. package/lib/components/navigation/bottomTab/bottomTab.stories.tsx +5 -10
  117. package/lib/components/navigation/breadcrumb/README.md +40 -20
  118. package/lib/components/navigation/breadcrumb/breadcrumb.stories.tsx +34 -14
  119. package/lib/components/navigation/header/README.md +67 -108
  120. package/lib/components/navigation/header/header.stories.tsx +165 -49
  121. package/lib/components/navigation/leftNav/README.md +121 -66
  122. package/lib/components/navigation/leftNav/leftNav.stories.tsx +178 -91
  123. package/lib/components/navigation/tabs/tabs.stories.tsx +57 -39
  124. package/lib/components/structure/column/column.stories.tsx +5 -10
  125. package/lib/components/structure/container/container.stories.tsx +5 -10
  126. package/lib/components/structure/link/link.stories.tsx +4 -10
  127. package/lib/components/structure/text/text.stories.tsx +5 -10
  128. package/lib/components/template/authPage/authPage.stories.tsx +6 -5
  129. package/lib/components/template/landingPage/landingPage.stories.tsx +8 -6
  130. package/lib/components/template/productListing/productListing.stories.tsx +7 -13
  131. package/lib/components/template/productOverview/productOverview.stories.tsx +5 -11
  132. package/lib/components/template/shoppingCart/shoppingCart.stories.tsx +6 -12
  133. package/lib/components/template/userHomePage/userHomePage.stories.tsx +7 -6
  134. package/lib/components/template/userRankingDashboard/userRankingDashboard.stories.tsx +5 -11
  135. package/package.json +1 -1
@@ -1,306 +1,884 @@
1
1
  import { TrashIcon } from "@heroicons/react/24/outline";
2
- import { storiesOf } from "@storybook/react";
3
- import moment from "moment";
4
- import React, { useEffect, useState } from "react";
5
- import { v4 as uuidv4 } from "uuid";
6
- import { ChatBase, ChatBaseAction, Conversation } from ".";
2
+ import { MicrophoneIcon, CameraIcon } from "@heroicons/react/24/outline";
3
+ import { DocumentIcon, XMarkIcon } from "@heroicons/react/24/solid";
4
+ import dayjs from "../../dayjs";
5
+ import React, { useState } from "react";
6
+ import { ChatBase, ChatBaseAction, Conversation, Message, ToolCall } from ".";
7
7
  import Logo from "../../../assets/logo.png";
8
+ import { StorySection } from "../../storyUtils";
8
9
  import { TextButton } from "../../action/textButton";
9
- import { Loading } from "../../display/loading";
10
- import README from "./README.md";
10
+ import { IconButton } from "../../action/iconButton";
11
11
 
12
- const stories = storiesOf("Chat", module);
12
+ const meta = { title: "Chat/Chat Base" };
13
+ export default meta;
13
14
 
14
- stories.addParameters({
15
- readme: {
16
- content: README,
15
+ const botUser = {
16
+ id: "bot",
17
+ name: "Bot",
18
+ imageUrl:
19
+ "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
20
+ };
21
+ const currentUser = { id: "me", name: "You" };
22
+
23
+ const seedMessages: Message[] = [
24
+ {
25
+ sender: botUser,
26
+ messageBody: "Hi! How can I help you? 🤖",
27
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
28
+ },
29
+ {
30
+ sender: currentUser,
31
+ messageBody: "My computer keeps crashing.",
32
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
33
+ },
34
+ {
35
+ sender: botUser,
36
+ messageBody:
37
+ "**Quick Tips**\n1. Restart your machine.\n2. Check for updates.\n3. Free up disk space.",
38
+ createdAt: dayjs("2023-11-03 12:02").toDate(),
39
+ },
40
+ {
41
+ sender: currentUser,
42
+ messageBody: "Thanks, I'll try that!",
43
+ createdAt: dayjs("2023-11-03 12:03").toDate(),
17
44
  },
45
+ {
46
+ sender: botUser,
47
+ messageBody:
48
+ "You can also check the [official troubleshooting guide](https://google.com) or visit [our FAQ page](https://google.com) for more help.",
49
+ createdAt: dayjs("2023-11-03 12:04").toDate(),
50
+ },
51
+ ];
52
+
53
+ const makeConversation = (
54
+ messages: Message[] = seedMessages
55
+ ): Conversation => ({
56
+ id: "1",
57
+ members: [currentUser, botUser],
58
+ messageList: messages,
18
59
  });
19
60
 
20
- stories.add("Chat Base", () => {
21
- const botUser = {
22
- id: "1",
23
- name: "Bot",
24
- imageUrl:
25
- "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80",
26
- };
27
- const currentUser = { id: "2", name: "Guest" };
61
+ const toolCallSequence: {
62
+ content: string;
63
+ request: string;
64
+ delayMs: number;
65
+ }[] = [
66
+ {
67
+ content: "search_knowledge_base",
68
+ request: "query: order status #4821",
69
+ delayMs: 800,
70
+ },
71
+ {
72
+ content: "fetch_user_context",
73
+ request: "userId: current_user",
74
+ delayMs: 1400,
75
+ },
76
+ {
77
+ content: "generate_summary",
78
+ request: "summarize order + delivery info",
79
+ delayMs: 2200,
80
+ },
81
+ ];
82
+
83
+ const seedToolMessages: Message[] = [
84
+ {
85
+ sender: botUser,
86
+ messageBody:
87
+ "Hi! I'm an AI assistant. Ask me anything and I'll show you which tools I use to answer.",
88
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
89
+ },
90
+ {
91
+ sender: { id: "me", name: "You" },
92
+ messageBody: "What's the status of my latest order?",
93
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
94
+ },
95
+ {
96
+ sender: botUser,
97
+ messageBody:
98
+ "Your latest order #4821 is currently **out for delivery** and should arrive today by 6 PM.",
99
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
100
+ toolReport: {
101
+ model: "gemini-3-flash-preview",
102
+ tools: [
103
+ {
104
+ content: "search_orders",
105
+ request: "query: latest order for user",
106
+ response: { success: true, output: "Order #4821 found" },
107
+ status: "done",
108
+ },
109
+ {
110
+ content: "fetch_delivery_status",
111
+ request: "orderId: 4821",
112
+ response: { success: true, output: "Out for delivery, ETA 6 PM" },
113
+ status: "done",
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ {
119
+ sender: { id: "me", name: "You" },
120
+ messageBody: "Can you summarise my account activity?",
121
+ createdAt: dayjs("2023-11-03 12:02").toDate(),
122
+ },
123
+ {
124
+ sender: botUser,
125
+ messageBody:
126
+ "Sorry, I couldn't retrieve your account activity right now. Please try again later.",
127
+ createdAt: dayjs("2023-11-03 12:02").toDate(),
128
+ toolReport: {
129
+ model: "gemini-3-flash-preview",
130
+ tools: [
131
+ {
132
+ content: "fetch_account_activity",
133
+ request: "userId: current_user",
134
+ response: { success: false, output: "Service unavailable" },
135
+ status: "error",
136
+ },
137
+ ],
138
+ },
139
+ },
140
+ ];
28
141
 
29
- const [message, setMessage] = useState("");
30
- const [messageLimit] = useState(20);
142
+ const toolCallConversation: Conversation = {
143
+ id: "tool-call-demo",
144
+ members: [currentUser, botUser],
145
+ messageList: [
146
+ {
147
+ sender: currentUser,
148
+ messageBody: "What's my policy renewal date?",
149
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
150
+ },
151
+ {
152
+ sender: botUser,
153
+ messageBody: "lookup_policy_by_user",
154
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
155
+ isToolCall: true,
156
+ },
157
+ {
158
+ sender: botUser,
159
+ messageBody: "fetch_renewal_schedule",
160
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
161
+ isToolCall: true,
162
+ },
163
+ {
164
+ sender: botUser,
165
+ messageBody:
166
+ "Your policy **POL-00421** is due for renewal on **15 Jan 2025**. Would you like me to send a reminder?",
167
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
168
+ },
169
+ {
170
+ sender: currentUser,
171
+ messageBody:
172
+ "Yes please, and also check if there are any pending claims.",
173
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
174
+ },
175
+ {
176
+ sender: botUser,
177
+ messageBody: "schedule_renewal_reminder",
178
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
179
+ isToolCall: true,
180
+ },
181
+ {
182
+ sender: botUser,
183
+ messageBody: "fetch_claims_by_policy",
184
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
185
+ isToolCall: true,
186
+ },
187
+ {
188
+ sender: botUser,
189
+ messageBody:
190
+ "Done! Reminder set for 8 Jan 2025. You have **1 pending claim** (REF-8821) currently under review.",
191
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
192
+ },
193
+ ],
194
+ };
195
+
196
+ // INFO: generates older messages for scroll-up pagination demo
197
+ const generateOlderMessages = (page: number): Message[] =>
198
+ Array.from({ length: 10 }, (_, i) => ({
199
+ sender: i % 2 === 0 ? botUser : currentUser,
200
+ messageBody: `Older message ${i + 1} from page ${page}`,
201
+ createdAt: dayjs("2023-11-03 12:00")
202
+ .subtract(page * 10 + (10 - i), "minutes")
203
+ .toDate(),
204
+ }));
205
+
206
+ export const ChatBaseStory = () => {
207
+ // --- Default ---
208
+ const [msg1, setMsg1] = useState("");
209
+ const [conv1, setConv1] = useState(makeConversation());
210
+
211
+ // --- Custom header/footer ---
212
+ const [msg2, setMsg2] = useState("");
213
+ const [conv2, setConv2] = useState(makeConversation());
214
+ const messageLimit = 8;
215
+
216
+ // --- Search ---
217
+ const [msg3, setMsg3] = useState("");
31
218
  const [search, setSearch] = useState("");
32
- const [filteredConversation, setFilteredConversation] =
33
- useState<Conversation | null>();
34
- const [file, setFile] = useState<File | any>(undefined);
35
- const [loading, setLoading] = useState(false);
219
+ const [conv3, setConv3] = useState(makeConversation());
220
+ const [filteredConv, setFilteredConv] = useState<Conversation | null>(null);
36
221
 
37
- const [conversation, setConversation] = useState<Conversation>({
38
- id: "1",
39
- members: [currentUser, botUser],
40
- messageList: [
222
+ // --- File upload ---
223
+ const [msg4, setMsg4] = useState("");
224
+ const [conv4, setConv4] = useState(
225
+ makeConversation([
41
226
  {
42
227
  sender: botUser,
43
- messageBody: "Hi! How can I help you? 🤖",
44
- createdAt: moment(
45
- "Fri Nov 03 2023 12:21:14 GMT+0800 (Malaysia Time)"
46
- ).toDate(),
47
- },
48
- {
49
- sender: currentUser,
50
- messageBody: "Yo, my computer's acting up.",
51
- createdAt: moment(
52
- "Fri Nov 03 2023 13:21:14 GMT+0800 (Malaysia Time)"
53
- ).toDate(),
54
- },
55
- {
56
- sender: currentUser,
57
- messageBody:
58
- "It keeps saying 'Error 404: Coffee not found' when I start it up.",
59
- createdAt: moment(
60
- "Fri Nov 03 2023 14:21:14 GMT+0800 (Malaysia Time)"
61
- ).toDate(),
228
+ messageBody: {
229
+ fileName:
230
+ "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400",
231
+ token: "sample-image.jpg",
232
+ type: "image/jpeg",
233
+ } as any,
234
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
62
235
  },
63
236
  {
64
237
  sender: botUser,
65
- messageBody:
66
- "Sounds like a caffeine emergency!\nHave you tried offering it coffee?",
67
- createdAt: moment(
68
- "Fri Nov 03 2023 15:21:14 GMT+0800 (Malaysia Time)"
69
- ).toDate(),
70
- },
71
- {
72
- sender: currentUser,
73
- messageBody: "Seriously?",
74
- createdAt: moment(
75
- "Fri Nov 03 2023 16:21:14 GMT+0800 (Malaysia Time)"
76
- ).toDate(),
238
+ messageBody: {
239
+ fileName:
240
+ "https://firebasestorage.googleapis.com/v0/b/verdant-solar-dev.appspot.com/o/preCheckList%2F0184b7ee-8bb0-4a48-992c-ac8d37f8e833%2FPre-Checklist.pdf?alt=media&token=1de8eafb-27c4-4ff7-91a3-e640e4ef07cf",
241
+ token: "sample-document.pdf",
242
+ type: "application/pdf",
243
+ } as any,
244
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
77
245
  },
78
246
  {
79
247
  sender: botUser,
80
- messageBody:
81
- "Well, computers do like a jolt of energy, but don't spill, that could be 'mocha-nical' trouble!",
82
- createdAt: moment(
83
- "Fri Nov 03 2023 17:21:14 GMT+0800 (Malaysia Time)"
84
- ).toDate(),
248
+ messageBody: {
249
+ fileName:
250
+ "https://docs.google.com/document/d/18eW31QU-UY_Tw2zJBzwaWTiijbuRKR7x/edit?usp=drive_link&ouid=106313527006787712257&rtpof=true&sd=true",
251
+ token: "sample-report.docx",
252
+ type: "",
253
+ } as any,
254
+ createdAt: dayjs("2023-11-03 12:02").toDate(),
85
255
  },
256
+ ])
257
+ );
258
+ const [files4, setFiles4] = useState<File[]>([]);
259
+
260
+ // --- Multi-line input ---
261
+ const [msg11, setMsg11] = useState("");
262
+ const [conv11, setConv11] = useState(makeConversation());
263
+
264
+ // --- Media rendering (audio / attachments[]) ---
265
+ const [msg12, setMsg12] = useState("");
266
+ const [conv12, setConv12] = useState(
267
+ makeConversation([
86
268
  {
87
269
  sender: currentUser,
88
- messageBody: "Cringe. I'll give it a shot.Thanks.",
89
- createdAt: moment(
90
- "Fri Nov 03 2023 18:21:14 GMT+0800 (Malaysia Time)"
91
- ).toDate(),
270
+ messageBody: "Here are the photos from the site visit.",
271
+ attachments: [
272
+ {
273
+ fileName:
274
+ "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400",
275
+ token: "site-photo-1.jpg",
276
+ type: "image/jpeg",
277
+ },
278
+ {
279
+ fileName:
280
+ "https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?w=400",
281
+ token: "site-photo-2.jpg",
282
+ type: "image/jpeg",
283
+ },
284
+ ] as any,
285
+ createdAt: dayjs("2023-11-03 12:00").toDate(),
92
286
  },
93
287
  {
94
288
  sender: botUser,
95
289
  messageBody:
96
- "**Quick Tips for Productivity**\n1. Set timers.\n2. Prioritize tasks.\n3. Take breaks.\n\n**Healthy Habits**\n 1. Stay hydrated.\n2. Eat well.\n3. Exercise daily.",
97
- createdAt: moment(
98
- "Fri Nov 03 2023 19:21:14 GMT+0800 (Malaysia Time)"
99
- ).toDate(),
100
- },
101
- {
102
- sender: botUser,
103
- messageBody: "Hey check out my dog!",
104
- createdAt: moment(
105
- "Fri Nov 03 2023 19:21:10 GMT+0800 (Malaysia Time)"
106
- ).toDate(),
107
- },
108
- {
109
- sender: botUser,
110
- messageBody: {
111
- fileName:
112
- "https://player.vimeo.com/external/364708250.sd.mp4?s=7382fdedda817a193bcffb6ff74624fa245ee666&profile_id=164&oauth2_token_id=57447761",
113
- token: "A",
114
- type: "video/mp4",
115
- },
116
- createdAt: moment(
117
- "Fri Nov 03 2023 19:21:14 GMT+0800 (Malaysia Time)"
118
- ).toDate(),
119
- },
120
- {
121
- sender: currentUser,
122
- messageBody: {
123
- fileName:
124
- "https://images.pexels.com/photos/3726314/pexels-photo-3726314.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1",
125
- token: "B",
126
- type: "image/png",
127
- },
128
- createdAt: moment(
129
- "Fri Nov 03 2023 19:21:18 GMT+0800 (Malaysia Time)"
130
- ).toDate(),
290
+ "Got them, thanks! I've also attached the inspection report and a voice note summary.",
291
+ attachments: [
292
+ {
293
+ fileName:
294
+ "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3",
295
+ token: "voice-summary.mp3",
296
+ type: "audio/mpeg",
297
+ },
298
+ ] as any,
299
+ createdAt: dayjs("2023-11-03 12:01").toDate(),
131
300
  },
132
301
  {
133
302
  sender: currentUser,
134
- messageBody: "Here's mine! He says hello!",
135
- createdAt: moment(
136
- "Fri Nov 03 2023 19:22:14 GMT+0800 (Malaysia Time)"
137
- ).toDate(),
303
+ messageBody: "Perfect. Can you also send the diagram?",
304
+ attachments: [
305
+ {
306
+ fileName:
307
+ "https://images.unsplash.com/photo-1519389950473-47ba0277781c?w=400",
308
+ token: "diagram.jpg",
309
+ type: "image/jpeg",
310
+ },
311
+ ] as any,
312
+ createdAt: dayjs("2023-11-03 12:02").toDate(),
138
313
  },
139
- ],
314
+ ])
315
+ );
316
+
317
+ // --- Loading reply ---
318
+ const [msg5, setMsg5] = useState("Hello, any updates?");
319
+ const [conv5, setConv5] = useState(makeConversation());
320
+ const [isLoading, setIsLoading] = useState(false);
321
+
322
+ // --- Tool report ---
323
+ const [msg6, setMsg6] = useState("");
324
+ const [isLoading6, setIsLoading6] = useState(false);
325
+ const [conv6, setConv6] = useState<Conversation>({
326
+ id: "tool-demo",
327
+ members: [currentUser, botUser],
328
+ messageList: seedToolMessages,
140
329
  });
141
330
 
142
- const handleOnSendMessage = (conversation: Conversation) => {
143
- setConversation(conversation);
144
- setMessage("");
145
- setFile(undefined);
146
- };
331
+ // --- Scroll-up pagination ---
332
+ const [msg7, setMsg7] = useState("");
333
+ const [msgPage, setMsgPage] = useState(1);
334
+ const [loadingOlder, setLoadingOlder] = useState(false);
335
+ const [conv7, setConv7] = useState<Conversation>(
336
+ makeConversation(generateOlderMessages(0))
337
+ );
338
+
339
+ // --- Quick replies (footerElement) ---
340
+ const quickReplies = [
341
+ "Track my order",
342
+ "Speak to an agent",
343
+ "View FAQs",
344
+ "Reset password",
345
+ ];
346
+ const [msg8, setMsg8] = useState("");
347
+ const [conv8, setConv8] = useState(makeConversation());
348
+
349
+ // --- Input actions ---
350
+ const [msg9, setMsg9] = useState("");
351
+ const [conv9, setConv9] = useState(makeConversation());
147
352
 
148
- const handleOnSearch = (searchMessage: string) => {
149
- if (searchMessage.length === 0) {
353
+ // --- File attach with URI footer ---
354
+ const [msg10, setMsg10] = useState("");
355
+ const [conv10, setConv10] = useState(makeConversation());
356
+ const [files10, setFiles10] = useState<File[]>([]);
357
+
358
+ const sendMessage =
359
+ (
360
+ conv: Conversation,
361
+ setConv: (c: Conversation) => void,
362
+ setMsg: (s: string) => void
363
+ ) =>
364
+ (updated: Conversation) => {
365
+ setConv(updated);
366
+ setMsg("");
367
+ };
368
+
369
+ const handleSearch = (searchMessage: string) => {
370
+ if (!searchMessage) {
150
371
  setSearch("");
151
- setFilteredConversation(null);
372
+ setFilteredConv(null);
152
373
  } else {
153
- const dupMessageList = conversation.messageList.filter((eachMessage) => {
154
- if (typeof eachMessage.messageBody === "string") {
155
- return eachMessage.messageBody
156
- .toLowerCase()
157
- .includes(searchMessage.toLowerCase());
158
- }
159
- return null;
374
+ setFilteredConv({
375
+ ...conv3,
376
+ messageList: conv3.messageList.filter(
377
+ (m) =>
378
+ typeof m.messageBody === "string" &&
379
+ m.messageBody.toLowerCase().includes(searchMessage.toLowerCase())
380
+ ),
160
381
  });
161
- const dupConversation = {
162
- id: conversation.id,
163
- members: [...conversation.members],
164
- messageList: [...dupMessageList],
165
- };
166
- setFilteredConversation(dupConversation);
167
382
  }
168
383
  };
169
384
 
170
- const handleOnClearMessage = () => {
171
- setFilteredConversation(null);
172
- setConversation({
173
- id: "1",
174
- members: [currentUser, botUser],
175
- messageList: [],
385
+ const handleSendToolReport = (updated: Conversation) => {
386
+ setMsg6("");
387
+ setIsLoading6(true);
388
+
389
+ const pendingTools: ToolCall[] = toolCallSequence.map((t) => ({
390
+ content: t.content,
391
+ request: t.request,
392
+ response: null,
393
+ status: "pending" as const,
394
+ }));
395
+
396
+ const placeholderMsg: Message = {
397
+ sender: botUser,
398
+ messageBody: "",
399
+ createdAt: dayjs().toDate(),
400
+ toolReport: { model: "gemini-3-flash-preview", tools: pendingTools },
401
+ };
402
+
403
+ setConv6({
404
+ ...updated,
405
+ messageList: [...updated.messageList, placeholderMsg],
176
406
  });
177
- };
178
407
 
179
- const handleOnChangeUpload = (e: any) => {
180
- if (e && e.target.files.length > 0) {
181
- setFile(e.target.files[0]);
182
- } else {
183
- setFile(undefined);
184
- }
185
- };
408
+ toolCallSequence.forEach(({ content, delayMs }) => {
409
+ setTimeout(() => {
410
+ setConv6((prev) => {
411
+ const msgs = [...prev.messageList];
412
+ const last = { ...msgs[msgs.length - 1] };
413
+ last.toolReport = {
414
+ ...last.toolReport!,
415
+ tools: last.toolReport!.tools.map((t) =>
416
+ t.content === content
417
+ ? {
418
+ ...t,
419
+ status: "done" as const,
420
+ response: { success: true, output: "OK" },
421
+ }
422
+ : t
423
+ ),
424
+ };
425
+ msgs[msgs.length - 1] = last;
426
+ return { ...prev, messageList: msgs };
427
+ });
428
+ }, delayMs);
429
+ });
186
430
 
187
- const handleOnSendUpload = (conversation: Conversation) => {
188
- if (file && conversation) {
189
- setLoading(true);
190
- const dupConversation = {
191
- id: conversation.id,
192
- members: [...conversation.members],
193
- messageList: [...conversation.messageList],
194
- };
195
- const fileUrl = URL.createObjectURL(file);
196
- const fileObj = {
197
- fileName: fileUrl,
198
- token: uuidv4(),
199
- type: file.type,
200
- };
201
- dupConversation.messageList.push({
202
- sender: currentUser,
203
- messageBody: fileObj,
204
- createdAt: moment().toDate(),
431
+ const totalDelay =
432
+ toolCallSequence[toolCallSequence.length - 1].delayMs + 400;
433
+ setTimeout(() => {
434
+ setConv6((prev) => {
435
+ const msgs = [...prev.messageList];
436
+ const last = { ...msgs[msgs.length - 1] };
437
+ last.messageBody =
438
+ "I've checked across all relevant sources. Here's a summary based on your query — everything looks good on our end!";
439
+ msgs[msgs.length - 1] = last;
440
+ return { ...prev, messageList: msgs };
205
441
  });
206
- setTimeout(() => {
207
- setConversation(dupConversation);
208
- setMessage("");
209
- setFile(undefined);
210
- setLoading(false);
211
- }, 5000);
212
- }
442
+ setIsLoading6(false);
443
+ }, totalDelay);
213
444
  };
214
445
 
215
- const headerElement = (
216
- <div className="p-2 bg-primary-500 rounded-t-md">
217
- <img src={Logo} alt="" className="h-12" />
218
- </div>
219
- );
446
+ const handleSendWithReply = (updated: Conversation) => {
447
+ setConv5(updated);
448
+ setMsg5("");
449
+ setIsLoading(true);
450
+ setTimeout(() => {
451
+ setConv5((prev) => ({
452
+ ...prev,
453
+ messageList: [
454
+ ...prev.messageList,
455
+ {
456
+ sender: botUser,
457
+ messageBody: "Got it! Let me look into that for you.",
458
+ createdAt: dayjs().toDate(),
459
+ },
460
+ ],
461
+ }));
462
+ setIsLoading(false);
463
+ }, 2000);
464
+ };
220
465
 
221
- const renderFooterElement = () => {
222
- if (conversation.messageList.length >= messageLimit)
223
- return (
224
- <div className="p-2">
225
- <div className="p-2 text-xs border rounded-md shadow-md bg-primary-50 border-primary-300">
226
- <p>
227
- You have reached the message limit of: {messageLimit}
228
- <br />
229
- Clear previous messages to send a new one.
230
- </p>
231
- <TextButton
232
- text="Clear now"
233
- onClick={handleOnClearMessage}
234
- className="mt-2"
235
- />
236
- </div>
237
- </div>
238
- );
239
- else return undefined;
466
+ // INFO: scroll-up handler — prepends older messages after a simulated delay
467
+ const handleLoadOlderMessages = () => {
468
+ setLoadingOlder(true);
469
+ const nextPage = msgPage + 1;
470
+ setTimeout(() => {
471
+ setConv7((prev) => ({
472
+ ...prev,
473
+ messageList: [...generateOlderMessages(nextPage), ...prev.messageList],
474
+ }));
475
+ setMsgPage(nextPage);
476
+ setLoadingOlder(false);
477
+ }, 1000);
240
478
  };
241
- const footerElement = renderFooterElement();
242
479
 
243
- const additionalAction: ChatBaseAction[] = [
480
+ const clearAction = (
481
+ setConv: (c: Conversation) => void
482
+ ): ChatBaseAction[] => [
244
483
  {
245
484
  text: "Clear",
246
- onClick: handleOnClearMessage,
247
- icon: <TrashIcon className="w-3 h-3" aria-hidden="true" />,
485
+ onClick: () => setConv(makeConversation([])),
486
+ icon: <TrashIcon className="w-3 h-3" />,
248
487
  },
249
488
  ];
250
489
 
251
- const loadingElement = (
252
- <div className="w-full h-full flex flex-col justify-center items-center">
253
- <Loading />
254
- <p className="mt-2">Uploading File</p>
255
- </div>
256
- );
490
+ return (
491
+ <>
492
+ <StorySection
493
+ title="Default"
494
+ description="Basic chat with send and receive. Sent messages appear on the right; received messages on the left with the sender's avatar. Received messages support markdown rendering."
495
+ >
496
+ <ChatBase
497
+ open
498
+ value={msg1}
499
+ currentUser={currentUser}
500
+ conversation={conv1}
501
+ onSendMessage={sendMessage(conv1, setConv1, setMsg1)}
502
+ onChangeMessage={(e) => setMsg1(e.target.value)}
503
+ additionalAction={clearAction(setConv1)}
504
+ sizeClassName="h-[500px] w-96"
505
+ disableMobileResize
506
+ />
507
+ </StorySection>
257
508
 
258
- useEffect(() => {
259
- setTimeout(() => {
260
- const dupConversation: Conversation = {
261
- id: conversation.id,
262
- members: [...conversation.members],
263
- messageList: [...conversation.messageList],
264
- };
265
- dupConversation.messageList.push({
266
- sender: botUser,
267
- messageBody: "Hello to you too",
268
- createdAt: moment().toDate(),
269
- });
270
- setConversation(dupConversation);
271
- }, 2000);
272
- }, []);
509
+ <StorySection
510
+ title="Custom Header & Footer"
511
+ description="headerElement replaces the default blue header bar. footerElement renders above the message input — useful for limit warnings, disclaimers, or action prompts."
512
+ >
513
+ <ChatBase
514
+ open
515
+ value={msg2}
516
+ currentUser={currentUser}
517
+ conversation={conv2}
518
+ onSendMessage={sendMessage(conv2, setConv2, setMsg2)}
519
+ onChangeMessage={(e) => setMsg2(e.target.value)}
520
+ headerElement={
521
+ <div className="p-3 bg-primary-500 rounded-t-md flex items-center">
522
+ <img src={Logo} alt="Logo" className="h-8" />
523
+ <span className="ml-3 text-white text-sm font-medium">
524
+ Support Chat
525
+ </span>
526
+ </div>
527
+ }
528
+ footerElement={
529
+ conv2.messageList.length >= messageLimit ? (
530
+ <div className="p-2">
531
+ <div className="p-2 text-xs border rounded-md bg-primary-50 border-primary-300">
532
+ Message limit reached ({messageLimit}).{" "}
533
+ <TextButton
534
+ text="Clear"
535
+ onClick={() => setConv2(makeConversation([]))}
536
+ className="inline"
537
+ />
538
+ </div>
539
+ </div>
540
+ ) : undefined
541
+ }
542
+ disableMessageInput={conv2.messageList.length >= messageLimit}
543
+ sizeClassName="h-[500px] w-96"
544
+ disableMobileResize
545
+ />
546
+ </StorySection>
273
547
 
274
- return (
275
- <ChatBase
276
- open
277
- value={message}
278
- currentUser={currentUser}
279
- conversation={filteredConversation ?? conversation}
280
- fileUpload={{
281
- allowFileUpload: true,
282
- file: file,
283
- acceptFileType: ".jpg,.jpeg,.png,.svg,.tiff,.mp4,.webm,.ogg",
284
- onChangeUpload: (e) => handleOnChangeUpload(e),
285
- onSendUpload: (conversation) => {
286
- handleOnSendUpload(conversation);
287
- },
288
- }}
289
- headerElement={headerElement}
290
- footerElement={footerElement}
291
- bodyElement={loading ? loadingElement : undefined}
292
- additionalAction={additionalAction}
293
- searchMessage={search}
294
- onChangeSearch={(e) => setSearch(e.target.value)}
295
- onSendMessage={handleOnSendMessage}
296
- onChangeMessage={(e) => setMessage(e.target.value)}
297
- onSearch={handleOnSearch}
298
- className="w-full border border-gray-200 shadow-lg sm:w-96"
299
- disableMobileResize
300
- disableMessageInput={
301
- conversation.messageList.length >= messageLimit || (file ? true : false)
302
- }
303
- onHiddenChatText={true}
304
- />
548
+ <StorySection
549
+ title="Message Search"
550
+ description="onSearch filters the conversation to matching messages. The search box is opened via the ⋮ actions menu. onChangeSearch updates the search input value."
551
+ >
552
+ <ChatBase
553
+ open
554
+ value={msg3}
555
+ currentUser={currentUser}
556
+ conversation={filteredConv ?? conv3}
557
+ searchMessage={search}
558
+ onSendMessage={sendMessage(conv3, setConv3, setMsg3)}
559
+ onChangeMessage={(e) => setMsg3(e.target.value)}
560
+ onSearch={handleSearch}
561
+ onChangeSearch={(e) => setSearch(e.target.value)}
562
+ additionalAction={clearAction(setConv3)}
563
+ sizeClassName="h-[500px] w-96"
564
+ disableMobileResize
565
+ />
566
+ </StorySection>
567
+
568
+ <StorySection
569
+ title="File Upload"
570
+ description="fileUpload.allowFileUpload=true shows a paperclip button. Selected files appear as chips in footerElement. onSendMessage fires for both text and file sends — the consumer handles upload logic there."
571
+ >
572
+ <ChatBase
573
+ open
574
+ value={msg4}
575
+ currentUser={currentUser}
576
+ conversation={conv4}
577
+ fileUpload={{
578
+ allowFileUpload: true,
579
+ hasFiles: files4.length > 0,
580
+ multiple: true,
581
+ acceptFileType: ".jpg,.jpeg,.png,.mp4,.webm,.pdf,.docx",
582
+ onChangeUpload: (e) => {
583
+ if (e?.target?.files?.length > 0)
584
+ setFiles4((prev) => [
585
+ ...prev,
586
+ ...Array.from(e.target.files as FileList),
587
+ ]);
588
+ },
589
+ }}
590
+ onSendMessage={(updated) => {
591
+ const newMessages = files4.map((f) => ({
592
+ sender: currentUser,
593
+ messageBody: {
594
+ fileName: URL.createObjectURL(f),
595
+ token: f.name,
596
+ type: f.type,
597
+ },
598
+ createdAt: dayjs().toDate(),
599
+ }));
600
+ setConv4({
601
+ ...updated,
602
+ messageList: [...updated.messageList, ...newMessages],
603
+ });
604
+ setFiles4([]);
605
+ setMsg4("");
606
+ }}
607
+ onChangeMessage={(e) => setMsg4(e.target.value)}
608
+ footerElement={
609
+ files4.length > 0 ? (
610
+ <div className="flex flex-wrap gap-2 px-2 pb-1 pt-1">
611
+ {files4.map((f, i) => (
612
+ <span
613
+ key={i}
614
+ className="inline-flex items-center gap-1.5 rounded-full border border-primary-300 bg-primary-50 py-1 pl-2.5 pr-1.5 text-xs text-primary-700"
615
+ >
616
+ <DocumentIcon className="h-3.5 w-3.5 flex-shrink-0 text-primary-500" />
617
+ <span className="max-w-[140px] truncate">{f.name}</span>
618
+ <button
619
+ onClick={() =>
620
+ setFiles4((prev) => prev.filter((_, idx) => idx !== i))
621
+ }
622
+ className="ml-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full hover:bg-primary-200 transition-colors"
623
+ >
624
+ <XMarkIcon className="h-3 w-3 text-primary-500" />
625
+ </button>
626
+ </span>
627
+ ))}
628
+ </div>
629
+ ) : undefined
630
+ }
631
+ sizeClassName="h-[500px] w-96"
632
+ disableMobileResize
633
+ />
634
+ </StorySection>
635
+
636
+ <StorySection
637
+ title="Loading Reply"
638
+ description="isLoadingReply=true shows an animated typing indicator above the input and disables the message input while waiting for a response. Send a message to see the effect."
639
+ >
640
+ <ChatBase
641
+ open
642
+ value={msg5}
643
+ currentUser={currentUser}
644
+ conversation={conv5}
645
+ isLoadingReply={isLoading}
646
+ onSendMessage={handleSendWithReply}
647
+ onChangeMessage={(e) => setMsg5(e.target.value)}
648
+ sizeClassName="h-[500px] w-96"
649
+ disableMobileResize
650
+ />
651
+ </StorySection>
652
+
653
+ <StorySection
654
+ title="Tool Report"
655
+ description="When a message has a toolReport, a compact log panel is shown above the message body. It displays the model used and each tool call with a status dot: yellow (pending), green (done), red (error). Send a message to see the simulated tool-call flow."
656
+ >
657
+ <ChatBase
658
+ open
659
+ value={msg6}
660
+ currentUser={currentUser}
661
+ conversation={conv6}
662
+ isLoadingReply={isLoading6}
663
+ onSendMessage={handleSendToolReport}
664
+ onChangeMessage={(e) => setMsg6(e.target.value)}
665
+ sizeClassName="h-[500px] w-96"
666
+ disableMobileResize
667
+ />
668
+ </StorySection>
669
+
670
+ <StorySection
671
+ title="Tool Call Messages"
672
+ description="Messages with isToolCall=true are rendered as centered inline chips instead of chat bubbles. This is used when the chat history includes tool invocation records (e.g. author: 'tool' from an AI pipeline). They sit between user and assistant messages to show what happened behind the scenes."
673
+ >
674
+ <ChatBase
675
+ open
676
+ value=""
677
+ currentUser={currentUser}
678
+ conversation={toolCallConversation}
679
+ sizeClassName="h-[500px] w-96"
680
+ disableMobileResize
681
+ hideAdditionalAction
682
+ />
683
+ </StorySection>
684
+
685
+ <StorySection
686
+ title="Scroll-Up Pagination"
687
+ description="onMessageListScrollTop fires when scrolled near the top. isLoadingMessages shows a spinner at the top while older messages are being fetched. Scroll up to see the effect."
688
+ >
689
+ <ChatBase
690
+ open
691
+ value={msg7}
692
+ currentUser={currentUser}
693
+ conversation={conv7}
694
+ isLoadingMessages={loadingOlder}
695
+ onMessageListScrollTop={handleLoadOlderMessages}
696
+ onSendMessage={(updated) => {
697
+ setConv7(updated);
698
+ setMsg7("");
699
+ }}
700
+ onChangeMessage={(e) => setMsg7(e.target.value)}
701
+ sizeClassName="h-[500px] w-96"
702
+ disableMobileResize
703
+ />
704
+ </StorySection>
705
+
706
+ <StorySection
707
+ title="Quick Replies (footerElement)"
708
+ description="footerElement renders above the message input. Pass quick-reply chips here so users can tap a preset prompt instead of typing. Clicking a chip fills the input — the user still hits send."
709
+ >
710
+ <ChatBase
711
+ open
712
+ value={msg8}
713
+ currentUser={currentUser}
714
+ conversation={conv8}
715
+ onSendMessage={(updated) => {
716
+ setConv8(updated);
717
+ setMsg8("");
718
+ }}
719
+ onChangeMessage={(e) => setMsg8(e.target.value)}
720
+ footerElement={
721
+ <div className="flex flex-wrap gap-2 px-2 pb-1 pt-1">
722
+ {quickReplies.map((reply) => (
723
+ <button
724
+ key={reply}
725
+ onClick={() => setMsg8(reply)}
726
+ className="text-xs px-3 py-1 rounded-full border border-primary-300 text-primary-600 bg-primary-50 hover:bg-primary-100 transition-colors"
727
+ >
728
+ {reply}
729
+ </button>
730
+ ))}
731
+ </div>
732
+ }
733
+ sizeClassName="h-[500px] w-96"
734
+ disableMobileResize
735
+ />
736
+ </StorySection>
737
+
738
+ <StorySection
739
+ title="File Attach with Chips Footer"
740
+ description="Attach multiple files via the paperclip. Each file appears as a dismissible chip in footerElement — click × to remove individually. onSendMessage handles both text and files together."
741
+ >
742
+ <ChatBase
743
+ open
744
+ value={msg10}
745
+ currentUser={currentUser}
746
+ conversation={conv10}
747
+ fileUpload={{
748
+ allowFileUpload: true,
749
+ hasFiles: files10.length > 0,
750
+ multiple: true,
751
+ acceptFileType: ".jpg,.jpeg,.png,.mp4,.webm,.pdf,.docx",
752
+ onChangeUpload: (e) => {
753
+ if (e?.target?.files?.length > 0)
754
+ setFiles10((prev) => [
755
+ ...prev,
756
+ ...Array.from(e.target.files as FileList),
757
+ ]);
758
+ },
759
+ }}
760
+ onSendMessage={(updated) => {
761
+ const newMessages = files10.map((f) => ({
762
+ sender: currentUser,
763
+ messageBody: {
764
+ fileName: URL.createObjectURL(f),
765
+ token: f.name,
766
+ type: f.type,
767
+ },
768
+ createdAt: dayjs().toDate(),
769
+ }));
770
+ setConv10({
771
+ ...updated,
772
+ messageList: [...updated.messageList, ...newMessages],
773
+ });
774
+ setFiles10([]);
775
+ setMsg10("");
776
+ }}
777
+ onChangeMessage={(e) => setMsg10(e.target.value)}
778
+ footerElement={
779
+ files10.length > 0 ? (
780
+ <div className="flex flex-wrap gap-2 px-2 pb-1 pt-1">
781
+ {files10.map((f, i) => (
782
+ <span
783
+ key={i}
784
+ className="inline-flex items-center gap-1.5 rounded-full border border-primary-300 bg-primary-50 py-1 pl-2.5 pr-1.5 text-xs text-primary-700"
785
+ >
786
+ <DocumentIcon className="h-3.5 w-3.5 flex-shrink-0 text-primary-500" />
787
+ <span className="max-w-[180px] truncate">{f.name}</span>
788
+ <button
789
+ onClick={() =>
790
+ setFiles10((prev) => prev.filter((_, idx) => idx !== i))
791
+ }
792
+ className="ml-0.5 flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full hover:bg-primary-200 transition-colors"
793
+ >
794
+ <XMarkIcon className="h-3 w-3 text-primary-500" />
795
+ </button>
796
+ </span>
797
+ ))}
798
+ </div>
799
+ ) : undefined
800
+ }
801
+ sizeClassName="h-[500px] w-96"
802
+ disableMobileResize
803
+ />
804
+ </StorySection>
805
+
806
+ <StorySection
807
+ title="Custom Input Actions (inputActions)"
808
+ description="inputActions renders extra icon buttons in the input row between the paperclip and the send button. Useful for voice input, camera, or any custom LLM action."
809
+ >
810
+ <ChatBase
811
+ open
812
+ value={msg9}
813
+ currentUser={currentUser}
814
+ conversation={conv9}
815
+ onSendMessage={(updated) => {
816
+ setConv9(updated);
817
+ setMsg9("");
818
+ }}
819
+ onChangeMessage={(e) => setMsg9(e.target.value)}
820
+ fileUpload={{
821
+ allowFileUpload: true,
822
+ hasFiles: false,
823
+ onChangeUpload: () => {},
824
+ }}
825
+ inputActions={[
826
+ <IconButton
827
+ key="mic"
828
+ onClick={() => alert("Voice input triggered")}
829
+ className="focus:ring-transparent flex flex-col justify-center"
830
+ icon={<MicrophoneIcon className="w-5 h-5 text-primary-500" />}
831
+ />,
832
+ <IconButton
833
+ key="camera"
834
+ onClick={() => alert("Camera triggered")}
835
+ className="focus:ring-transparent flex flex-col justify-center"
836
+ icon={<CameraIcon className="w-5 h-5 text-primary-500" />}
837
+ />,
838
+ ]}
839
+ sizeClassName="h-[500px] w-96"
840
+ disableMobileResize
841
+ />
842
+ </StorySection>
843
+
844
+ <StorySection
845
+ title="Multi-line Input (Email Composer)"
846
+ description="multiLine=true expands the textarea to 5 rows and moves action buttons to a bottom-right row. Enter adds a new line instead of sending — use the send button to submit. Ideal for composing emails."
847
+ >
848
+ <ChatBase
849
+ open
850
+ value={msg11}
851
+ currentUser={currentUser}
852
+ conversation={conv11}
853
+ multiLine={5}
854
+ onSendMessage={(updated) => {
855
+ setConv11(updated);
856
+ setMsg11("");
857
+ }}
858
+ onChangeMessage={(e) => setMsg11(e.target.value)}
859
+ sizeClassName="h-[500px] w-96"
860
+ disableMobileResize
861
+ />
862
+ </StorySection>
863
+
864
+ <StorySection
865
+ title="Media Rendering (Text + Attachments)"
866
+ description="Messages with both a text messageBody and an attachments[] array — images, PDFs, and audio files rendered inline beneath the message text. Simulates real-world scenarios like sending a caption with photos or a bot reply with supporting files."
867
+ >
868
+ <ChatBase
869
+ open
870
+ value={msg12}
871
+ currentUser={currentUser}
872
+ conversation={conv12}
873
+ onSendMessage={(updated) => {
874
+ setConv12(updated);
875
+ setMsg12("");
876
+ }}
877
+ onChangeMessage={(e) => setMsg12(e.target.value)}
878
+ sizeClassName="h-[500px] w-96"
879
+ disableMobileResize
880
+ />
881
+ </StorySection>
882
+ </>
305
883
  );
306
- });
884
+ };