xertica-ui 2.1.2 → 2.1.4

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 (181) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +1 -1
  3. package/bin/cli.ts +1 -1
  4. package/bin/generate-tokens.ts +13 -7
  5. package/components/assistant/xertica-assistant/index.ts +2 -0
  6. package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +97 -0
  7. package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -0
  8. package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -0
  9. package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +86 -0
  10. package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +77 -0
  11. package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +573 -0
  12. package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +65 -0
  13. package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -0
  14. package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +98 -0
  15. package/components/assistant/xertica-assistant/parts/index.ts +16 -0
  16. package/components/assistant/xertica-assistant/types.ts +139 -0
  17. package/components/assistant/xertica-assistant/use-assistant.ts +559 -0
  18. package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +200 -0
  19. package/components/assistant/xertica-assistant/xertica-assistant.tsx +198 -1460
  20. package/components/brand/theme-toggle/ThemeToggle.tsx +8 -27
  21. package/components/hooks/index.ts +3 -0
  22. package/components/hooks/use-layout-shortcuts.ts +46 -0
  23. package/components/layout/sidebar/index.ts +2 -0
  24. package/components/layout/sidebar/sidebar.stories.tsx +160 -8
  25. package/components/layout/sidebar/sidebar.tsx +606 -497
  26. package/components/layout/sidebar/use-sidebar.ts +104 -0
  27. package/components/media/audio-player/AudioPlayer.tsx +131 -206
  28. package/components/media/audio-player/use-audio-player.ts +298 -0
  29. package/components/pages/home-page/HomePage.tsx +1 -1
  30. package/components/pages/template-content/TemplateContent.tsx +5 -5
  31. package/components/pages/template-page/TemplatePage.tsx +5 -5
  32. package/components/shared/CustomTooltipContent.tsx +52 -0
  33. package/components/shared/layout-constants.ts +1 -1
  34. package/components/ui/chart/chart.stories.tsx +966 -7
  35. package/components/ui/chart/chart.tsx +918 -45
  36. package/components/ui/file-upload/file-upload.stories.tsx +100 -0
  37. package/components/ui/file-upload/file-upload.tsx +14 -74
  38. package/components/ui/file-upload/index.ts +1 -0
  39. package/components/ui/file-upload/use-file-upload.ts +181 -0
  40. package/components/ui/pagination/index.ts +2 -0
  41. package/components/ui/pagination/pagination.stories.tsx +94 -0
  42. package/components/ui/pagination/use-pagination.ts +194 -0
  43. package/components/ui/rich-text-editor/index.ts +2 -0
  44. package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +129 -1
  45. package/components/ui/rich-text-editor/rich-text-editor.tsx +86 -305
  46. package/components/ui/rich-text-editor/use-rich-text-editor.ts +439 -0
  47. package/components/ui/stepper/index.ts +3 -1
  48. package/components/ui/stepper/stepper.stories.tsx +116 -0
  49. package/components/ui/stepper/stepper.tsx +4 -4
  50. package/components/ui/stepper/use-stepper.ts +137 -0
  51. package/components/ui/tree-view/index.ts +4 -1
  52. package/components/ui/tree-view/tree-view.stories.tsx +110 -4
  53. package/components/ui/tree-view/tree-view.tsx +17 -125
  54. package/components/ui/tree-view/use-tree-view.ts +229 -0
  55. package/contexts/AssistenteContext.tsx +17 -54
  56. package/contexts/BrandColorsContext.tsx +6 -17
  57. package/contexts/LayoutContext.tsx +5 -31
  58. package/dist/AssistantChart-BAudAfne.cjs +3591 -0
  59. package/dist/AssistantChart-BP8upjMk.js +3565 -0
  60. package/dist/AudioPlayer-1ypwE2Wh.cjs +936 -0
  61. package/dist/AudioPlayer-DuKXrCfy.js +937 -0
  62. package/dist/CustomTooltipContent-DHjkY0ww.js +40 -0
  63. package/dist/CustomTooltipContent-c_K-DWRr.cjs +56 -0
  64. package/dist/LanguageContext-BwhwC3G2.js +657 -0
  65. package/dist/LanguageContext-DvUt5jBg.cjs +656 -0
  66. package/dist/LayoutContext-BDmcZfMH.cjs +84 -0
  67. package/dist/LayoutContext-dbQvdC4O.js +85 -0
  68. package/dist/ThemeContext-RTy1m2Uq.js +82 -0
  69. package/dist/ThemeContext-bSzuOit2.cjs +81 -0
  70. package/dist/VerifyEmailPage-C_ihbcth.js +2828 -0
  71. package/dist/VerifyEmailPage-Dt7zgA4w.cjs +2827 -0
  72. package/dist/XerticaProvider-CW9hpCdF.cjs +39 -0
  73. package/dist/XerticaProvider-siSt9uG2.js +40 -0
  74. package/dist/XerticaXLogo-D8jf0SNv.cjs +214 -0
  75. package/dist/XerticaXLogo-fAJMy3H4.js +215 -0
  76. package/dist/assistant.cjs.js +2 -1
  77. package/dist/assistant.es.js +3 -2
  78. package/dist/brand.cjs.js +2 -2
  79. package/dist/brand.es.js +2 -2
  80. package/dist/cli.js +14 -8
  81. package/dist/components/assistant/xertica-assistant/index.d.ts +2 -0
  82. package/dist/components/assistant/xertica-assistant/parts/AssistantCollapsedView.d.ts +13 -0
  83. package/dist/components/assistant/xertica-assistant/parts/AssistantConversationList.d.ts +16 -0
  84. package/dist/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.d.ts +17 -0
  85. package/dist/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.d.ts +19 -0
  86. package/dist/components/assistant/xertica-assistant/parts/AssistantHeader.d.ts +11 -0
  87. package/dist/components/assistant/xertica-assistant/parts/AssistantMessageBubble.d.ts +29 -0
  88. package/dist/components/assistant/xertica-assistant/parts/AssistantTabBar.d.ts +13 -0
  89. package/dist/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.d.ts +4 -0
  90. package/dist/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.d.ts +17 -0
  91. package/dist/components/assistant/xertica-assistant/parts/index.d.ts +16 -0
  92. package/dist/components/assistant/xertica-assistant/types.d.ts +106 -0
  93. package/dist/components/assistant/xertica-assistant/use-assistant.d.ts +125 -0
  94. package/dist/components/assistant/xertica-assistant/xertica-assistant.d.ts +8 -97
  95. package/dist/components/hooks/index.d.ts +3 -0
  96. package/dist/components/hooks/use-layout-shortcuts.d.ts +22 -0
  97. package/dist/components/layout/sidebar/index.d.ts +2 -0
  98. package/dist/components/layout/sidebar/sidebar.d.ts +80 -0
  99. package/dist/components/layout/sidebar/use-sidebar.d.ts +22 -0
  100. package/dist/components/media/audio-player/AudioPlayer.d.ts +4 -1
  101. package/dist/components/media/audio-player/use-audio-player.d.ts +72 -0
  102. package/dist/components/shared/CustomTooltipContent.d.ts +20 -0
  103. package/dist/components/shared/layout-constants.d.ts +1 -1
  104. package/dist/components/ui/alert/alert.d.ts +1 -1
  105. package/dist/components/ui/badge/badge.d.ts +1 -1
  106. package/dist/components/ui/button/button.d.ts +2 -2
  107. package/dist/components/ui/chart/chart.d.ts +162 -5
  108. package/dist/components/ui/file-upload/file-upload.d.ts +2 -0
  109. package/dist/components/ui/file-upload/index.d.ts +1 -0
  110. package/dist/components/ui/file-upload/use-file-upload.d.ts +49 -0
  111. package/dist/components/ui/pagination/index.d.ts +2 -0
  112. package/dist/components/ui/pagination/use-pagination.d.ts +78 -0
  113. package/dist/components/ui/rich-text-editor/index.d.ts +2 -0
  114. package/dist/components/ui/rich-text-editor/use-rich-text-editor.d.ts +107 -0
  115. package/dist/components/ui/stepper/index.d.ts +3 -1
  116. package/dist/components/ui/stepper/stepper.d.ts +2 -2
  117. package/dist/components/ui/stepper/use-stepper.d.ts +60 -0
  118. package/dist/components/ui/tree-view/index.d.ts +4 -1
  119. package/dist/components/ui/tree-view/tree-view.d.ts +4 -6
  120. package/dist/components/ui/tree-view/use-tree-view.d.ts +60 -0
  121. package/dist/contexts/AssistenteContext.d.ts +10 -49
  122. package/dist/hooks.cjs.js +30 -10
  123. package/dist/hooks.es.js +25 -4
  124. package/dist/index.cjs.js +20 -9
  125. package/dist/index.es.js +38 -27
  126. package/dist/layout.cjs.js +82 -1
  127. package/dist/layout.es.js +83 -2
  128. package/dist/media.cjs.js +1 -1
  129. package/dist/media.es.js +1 -1
  130. package/dist/pages.cjs.js +1 -1
  131. package/dist/pages.es.js +1 -1
  132. package/dist/rich-text-editor-BmsjY03B.js +2949 -0
  133. package/dist/rich-text-editor-GS2kpTAK.cjs +2966 -0
  134. package/dist/sidebar-CVUGHOS_.cjs +756 -0
  135. package/dist/sidebar-CmvwjnVb.js +757 -0
  136. package/dist/ui.cjs.js +12 -2
  137. package/dist/ui.es.js +24 -14
  138. package/dist/use-audio-player-Bkh23vQ3.js +177 -0
  139. package/dist/use-audio-player-Dn1NR9xN.cjs +176 -0
  140. package/dist/utils/color-utils.d.ts +51 -0
  141. package/dist/xertica-assistant-BMqdyRVi.js +2082 -0
  142. package/dist/xertica-assistant-Bj3vBCq_.cjs +2081 -0
  143. package/dist/xertica-ui.css +1 -1
  144. package/docs/ai-usage.md +28 -10
  145. package/docs/architecture-improvements.md +463 -0
  146. package/docs/architecture.md +77 -1
  147. package/docs/components/assistant-chart.md +1 -1
  148. package/docs/components/assistant.md +159 -0
  149. package/docs/components/audio-player.md +46 -0
  150. package/docs/components/branding.md +251 -0
  151. package/docs/components/chart.md +354 -39
  152. package/docs/components/code-block.md +108 -0
  153. package/docs/components/file-upload.md +119 -2
  154. package/docs/components/formatted-document.md +113 -0
  155. package/docs/components/hooks.md +430 -0
  156. package/docs/components/image-with-fallback.md +106 -0
  157. package/docs/components/map-layers.md +140 -0
  158. package/docs/components/modern-chat-input.md +163 -0
  159. package/docs/components/pages.md +351 -0
  160. package/docs/components/pagination.md +187 -0
  161. package/docs/components/rich-text-editor.md +164 -0
  162. package/docs/components/sidebar.md +153 -4
  163. package/docs/components/stepper.md +157 -12
  164. package/docs/components/tree-view.md +164 -6
  165. package/docs/doc-audit.md +223 -0
  166. package/docs/getting-started.md +155 -1
  167. package/docs/guidelines.md +14 -8
  168. package/docs/layout.md +2 -2
  169. package/docs/llms.md +29 -9
  170. package/docs/patterns/detail-page.md +276 -0
  171. package/docs/patterns/settings.md +346 -0
  172. package/docs/patterns/wizard.md +217 -0
  173. package/guidelines/Guidelines.md +5 -3
  174. package/llms.txt +1 -1
  175. package/package.json +10 -10
  176. package/styles/xertica/tokens.css +41 -12
  177. package/templates/CLAUDE.md +16 -6
  178. package/templates/guidelines/Guidelines.md +16 -4
  179. package/templates/package.json +3 -3
  180. package/templates/src/styles/xertica/tokens.css +39 -10
  181. package/utils/color-utils.ts +72 -0
@@ -0,0 +1,573 @@
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import {
4
+ FileText,
5
+ Music,
6
+ Image as ImageIcon,
7
+ Radio,
8
+ Loader2,
9
+ Download,
10
+ Edit,
11
+ Search,
12
+ Folder,
13
+ Users,
14
+ ExternalLink,
15
+ Clock,
16
+ FolderOpen,
17
+ MessageSquare,
18
+ AlertCircle,
19
+ ThumbsUp,
20
+ ThumbsDown,
21
+ Copy,
22
+ Check,
23
+ } from 'lucide-react';
24
+ import {
25
+ Bar,
26
+ BarChart,
27
+ CartesianGrid,
28
+ XAxis,
29
+ } from 'recharts';
30
+ import {
31
+ ChartContainer,
32
+ ChartTooltip,
33
+ ChartTooltipContent,
34
+ ChartLegend,
35
+ ChartLegendContent,
36
+ } from '../../../ui/chart';
37
+ import {
38
+ Table,
39
+ TableBody,
40
+ TableCaption,
41
+ TableCell,
42
+ TableHead,
43
+ TableHeader,
44
+ TableRow,
45
+ } from '../../../ui/table';
46
+ import {
47
+ DropdownMenu,
48
+ DropdownMenuContent,
49
+ DropdownMenuItem,
50
+ DropdownMenuTrigger,
51
+ } from '../../../ui/dropdown-menu';
52
+ import { Button } from '../../../ui/button';
53
+ import { MarkdownMessage } from '../../markdown-message';
54
+ import { FormattedDocument } from '../../formatted-document';
55
+ import { XerticaOrbe } from '../../../brand/xertica-orbe';
56
+ import { cn } from '../../../shared/utils';
57
+ import type { Message, SearchResult } from '../types';
58
+
59
+ interface AssistantMessageBubbleProps {
60
+ msg: Message;
61
+ copiedId: string | null;
62
+ generatingPodcastId: string | null;
63
+ executingCommand: string | null;
64
+ savedSearches: string[];
65
+ enablePodcastGeneration: boolean;
66
+ feedbackOptions?: string[];
67
+ onCopyMessage: (content: string, id: string) => void;
68
+ onToggleFavorite: (id: string) => void;
69
+ onGeneratePodcast: (id: string, content: string) => void;
70
+ onDownloadDocument: (content: string, fileName: string) => void;
71
+ onDownloadPodcast: (audioUrl: string, fileName: string) => void;
72
+ onEditDocument: (content: string, title: string) => void;
73
+ onOpenSearchResult: (result: SearchResult) => void;
74
+ onExecuteSearchCommand: (
75
+ commandId: string,
76
+ query: string,
77
+ results: SearchResult[],
78
+ messageId: string,
79
+ ) => void;
80
+ onEvaluationClick: (id: string, type: 'like' | 'dislike') => void;
81
+ onOpenFeedbackDialog: (id: string, category: string | null) => void;
82
+ }
83
+
84
+ /**
85
+ * AssistantMessageBubble — Renders a single chat message with all supported content types:
86
+ * plain text, Markdown, charts, tables, documents, podcasts, and search results.
87
+ *
88
+ * Handles both user and assistant message styles, plus per-message action buttons
89
+ * (copy, like/dislike, podcast generation).
90
+ */
91
+ export function AssistantMessageBubble({
92
+ msg,
93
+ copiedId,
94
+ generatingPodcastId,
95
+ executingCommand,
96
+ savedSearches,
97
+ enablePodcastGeneration,
98
+ feedbackOptions,
99
+ onCopyMessage,
100
+ onToggleFavorite,
101
+ onGeneratePodcast,
102
+ onDownloadDocument,
103
+ onDownloadPodcast,
104
+ onEditDocument,
105
+ onOpenSearchResult,
106
+ onExecuteSearchCommand,
107
+ onEvaluationClick,
108
+ onOpenFeedbackDialog,
109
+ }: AssistantMessageBubbleProps) {
110
+ return (
111
+ <motion.div
112
+ key={msg.id}
113
+ initial={{ opacity: 0, y: 10 }}
114
+ animate={{ opacity: 1, y: 0 }}
115
+ className={`flex gap-2 w-full min-w-0 ${msg.type === 'user' ? 'justify-end' : 'justify-start'}`}
116
+ >
117
+ {/* Avatar do Assistente */}
118
+ {msg.type === 'assistant' && (
119
+ <div className="flex-shrink-0 pt-1">
120
+ <XerticaOrbe size={32} />
121
+ </div>
122
+ )}
123
+
124
+ <div
125
+ className={cn(
126
+ 'flex flex-col min-w-0 transition-all duration-300',
127
+ msg.type === 'user'
128
+ ? 'max-w-[85%] md:max-w-[70%] items-end w-fit'
129
+ : cn(
130
+ 'items-start',
131
+ msg.tableData || msg.chartData || msg.searchResults || msg.attachmentType === 'document'
132
+ ? 'w-full max-w-full'
133
+ : 'w-fit max-w-[95%] md:max-w-[90%]',
134
+ ),
135
+ )}
136
+ >
137
+ <div
138
+ className={cn(
139
+ 'px-4 py-2 break-words overflow-hidden overflow-x-hidden w-full min-w-0 rounded-2xl',
140
+ msg.type === 'user'
141
+ ? 'bg-primary text-primary-foreground shadow-sm'
142
+ : 'bg-muted text-foreground',
143
+ )}
144
+ >
145
+ {/* Document Header with Edit and Download Buttons */}
146
+ {msg.attachmentType === 'document' && (
147
+ <div className="flex items-center justify-between mb-2 pb-2 border-b border-border min-w-0 overflow-hidden">
148
+ <div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
149
+ <FileText className="w-4 h-4 flex-shrink-0" />
150
+ <span className="text-sm font-medium break-words">{msg.attachmentName}</span>
151
+ </div>
152
+ <div className="flex items-center gap-1 flex-shrink-0">
153
+ <Button
154
+ variant="ghost"
155
+ size="sm"
156
+ onClick={() =>
157
+ msg.documentContent &&
158
+ msg.attachmentName &&
159
+ onDownloadDocument(msg.documentContent, msg.attachmentName)
160
+ }
161
+ className="h-6 px-2"
162
+ title="Baixar"
163
+ >
164
+ <Download className="w-3 h-3" />
165
+ </Button>
166
+ <Button
167
+ variant="ghost"
168
+ size="sm"
169
+ onClick={() =>
170
+ msg.documentContent &&
171
+ msg.documentTitle &&
172
+ onEditDocument(msg.documentContent, msg.documentTitle)
173
+ }
174
+ className="h-6 px-2"
175
+ >
176
+ <Edit className="w-3 h-3 mr-1" />
177
+ Editar
178
+ </Button>
179
+ </div>
180
+ </div>
181
+ )}
182
+
183
+ {/* Attachments (file, audio, image) */}
184
+ {msg.attachmentType &&
185
+ msg.attachmentType !== 'podcast' &&
186
+ msg.attachmentType !== 'document' && (
187
+ <div className="flex items-center gap-2 mb-2 pb-2 border-b border-border min-w-0 overflow-hidden">
188
+ {msg.attachmentType === 'file' && <FileText className="w-4 h-4 flex-shrink-0" />}
189
+ {msg.attachmentType === 'audio' && <Music className="w-4 h-4 flex-shrink-0" />}
190
+ {msg.attachmentType === 'image' && <ImageIcon className="w-4 h-4 flex-shrink-0" />}
191
+ <span className="text-small break-words">{msg.attachmentName}</span>
192
+ </div>
193
+ )}
194
+
195
+ {/* Message Content */}
196
+ {msg.type === 'user' ? (
197
+ <p className="whitespace-pre-wrap break-words">{msg.content}</p>
198
+ ) : (
199
+ <>
200
+ {(msg.content.includes('🔐') || msg.content.includes('❌')) && (
201
+ <div className="mb-3 p-3 border rounded-[var(--radius)] overflow-hidden bg-[var(--toast-error-bg)]/20 border-[var(--toast-error-border)]/30">
202
+ <div className="flex items-start gap-2 min-w-0">
203
+ <AlertCircle className="w-5 h-5 flex-shrink-0 mt-0.5 text-[var(--toast-error-icon)]" />
204
+ <div className="flex-1 min-w-0 overflow-hidden">
205
+ <MarkdownMessage content={msg.content} className="text-foreground" />
206
+ </div>
207
+ </div>
208
+ </div>
209
+ )}
210
+ {!(msg.content.includes('🔐') || msg.content.includes('❌')) && (
211
+ <MarkdownMessage content={msg.content} className="text-foreground" />
212
+ )}
213
+ </>
214
+ )}
215
+
216
+ {/* Chart Rendering */}
217
+ {msg.chartData && msg.chartConfig && (
218
+ <div className="mt-4 w-full h-[300px] min-w-[300px]">
219
+ <ChartContainer config={msg.chartConfig} className="h-full w-full">
220
+ <BarChart accessibilityLayer data={msg.chartData}>
221
+ <CartesianGrid vertical={false} />
222
+ <XAxis
223
+ dataKey="month"
224
+ tickLine={false}
225
+ tickMargin={10}
226
+ axisLine={false}
227
+ tickFormatter={(value) => value.slice(0, 3)}
228
+ />
229
+ <ChartTooltip content={<ChartTooltipContent />} />
230
+ <ChartLegend content={<ChartLegendContent />} />
231
+ {Object.keys(msg.chartConfig).map((key) => (
232
+ <Bar key={key} dataKey={key} fill={`var(--color-${key})`} radius={4} />
233
+ ))}
234
+ </BarChart>
235
+ </ChartContainer>
236
+ </div>
237
+ )}
238
+
239
+ {/* Table Rendering */}
240
+ {msg.tableData && (
241
+ <div className="mt-4 w-full max-w-full border rounded-[var(--radius)] overflow-x-auto custom-scrollbar relative">
242
+ <Table>
243
+ {msg.tableData.caption && <TableCaption>{msg.tableData.caption}</TableCaption>}
244
+ <TableHeader>
245
+ <TableRow>
246
+ {msg.tableData.headers.map((header, i) => (
247
+ <TableHead key={i}>{header}</TableHead>
248
+ ))}
249
+ </TableRow>
250
+ </TableHeader>
251
+ <TableBody>
252
+ {msg.tableData.rows.map((row, i) => (
253
+ <TableRow key={i}>
254
+ {row.map((cell, j) => (
255
+ <TableCell key={j}>{cell}</TableCell>
256
+ ))}
257
+ </TableRow>
258
+ ))}
259
+ </TableBody>
260
+ </Table>
261
+ </div>
262
+ )}
263
+
264
+ {/* Document Preview */}
265
+ {msg.attachmentType === 'document' && msg.documentContent && (
266
+ <div className="mt-3 pt-3 border-t border-border overflow-hidden">
267
+ <FormattedDocument content={msg.documentContent} maxPreviewLength={250} />
268
+ </div>
269
+ )}
270
+
271
+ {/* Podcast Player */}
272
+ {msg.attachmentType === 'podcast' && msg.audioUrl && (
273
+ <div className="mt-3 pt-3 border-t border-border overflow-hidden">
274
+ <div className="flex items-center justify-between mb-3 min-w-0 overflow-hidden">
275
+ <div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
276
+ <div className="w-6 h-6 flex items-center justify-center flex-shrink-0 rounded-[var(--radius-button)] bg-gradient-to-br from-[var(--chart-1)] via-[var(--chart-4)] to-[var(--chart-1)]">
277
+ <Radio className="w-3 h-3 text-white" />
278
+ </div>
279
+ <span className="text-sm font-medium break-words">{msg.attachmentName}</span>
280
+ </div>
281
+ <Button
282
+ variant="ghost"
283
+ size="sm"
284
+ onClick={() =>
285
+ msg.audioUrl &&
286
+ msg.attachmentName &&
287
+ onDownloadPodcast(msg.audioUrl, msg.attachmentName)
288
+ }
289
+ className="h-6 px-2 flex-shrink-0"
290
+ title="Baixar"
291
+ >
292
+ <Download className="w-3 h-3" />
293
+ </Button>
294
+ </div>
295
+ <div className="rounded-[var(--radius)] p-2 overflow-hidden bg-gradient-to-r from-[var(--chart-1)]/10 to-[var(--chart-4)]/10">
296
+ <audio controls className="w-full max-w-full h-10 outline-none">
297
+ <source src={msg.audioUrl} type="audio/mpeg" />
298
+ Seu navegador não suporta o elemento de áudio.
299
+ </audio>
300
+ </div>
301
+ </div>
302
+ )}
303
+
304
+ {/* Search Results */}
305
+ {msg.attachmentType === 'search' && msg.searchResults && (
306
+ <div className="mt-3 pt-3 border-t border-border space-y-4 overflow-hidden">
307
+ <div className="overflow-hidden">
308
+ <div className="flex items-center gap-2 mb-3">
309
+ <Search className="w-4 h-4 text-[var(--chart-4)]" />
310
+ <h4 className="text-foreground">
311
+ Resultados Encontrados ({msg.searchResults.length})
312
+ </h4>
313
+ </div>
314
+ <div className="space-y-2 overflow-hidden">
315
+ {msg.searchResults.map((result) => (
316
+ <div
317
+ key={result.id}
318
+ onClick={() => onOpenSearchResult(result)}
319
+ className="p-3 rounded-[var(--radius)] border border-border transition-all cursor-pointer group overflow-hidden hover:bg-muted/50"
320
+ >
321
+ <div className="flex items-start justify-between gap-2 min-w-0">
322
+ <div className="flex items-start gap-2 flex-1 min-w-0 overflow-hidden">
323
+ <div className="mt-0.5 flex-shrink-0">
324
+ {result.type === 'document' && (
325
+ <FileText className="w-4 h-4 text-[var(--chart-4)]" />
326
+ )}
327
+ {result.type === 'project' && (
328
+ <FolderOpen className="w-4 h-4 text-[var(--chart-1)]" />
329
+ )}
330
+ {result.type === 'conversation' && (
331
+ <MessageSquare className="w-4 h-4 text-[var(--chart-2)]" />
332
+ )}
333
+ {result.type === 'file' && (
334
+ <Folder className="w-4 h-4 text-[var(--chart-3)]" />
335
+ )}
336
+ {result.type === 'contact' && (
337
+ <Users className="w-4 h-4 text-[var(--chart-5)]" />
338
+ )}
339
+ </div>
340
+ <div className="flex-1 min-w-0 overflow-hidden">
341
+ <div className="flex items-start gap-2 min-w-0">
342
+ <h5 className="text-sm font-medium break-words flex-1 min-w-0 text-foreground">
343
+ {result.title}
344
+ </h5>
345
+ <span className="px-1.5 py-0.5 rounded-sm flex-shrink-0 self-start bg-[var(--chart-4)]/10 text-[var(--chart-4)]">
346
+ {result.relevance}%
347
+ </span>
348
+ </div>
349
+ <p className="text-sm text-muted-foreground mt-1 line-clamp-2 break-words">
350
+ {result.description}
351
+ </p>
352
+ <div className="flex items-center gap-3 mt-2 flex-wrap min-w-0">
353
+ <span className="text-sm text-muted-foreground flex items-center gap-1 min-w-0 max-w-full">
354
+ <ExternalLink className="w-3 h-3 flex-shrink-0" />
355
+ <span className="truncate">{result.path}</span>
356
+ </span>
357
+ {result.lastModified && (
358
+ <span className="text-sm text-muted-foreground flex items-center gap-1 flex-shrink-0">
359
+ <Clock className="w-3 h-3" />
360
+ {result.lastModified}
361
+ </span>
362
+ )}
363
+ </div>
364
+ </div>
365
+ </div>
366
+ </div>
367
+ </div>
368
+ ))}
369
+ </div>
370
+ </div>
371
+
372
+ {/* Search Sources */}
373
+ {msg.searchSources && (
374
+ <div>
375
+ <h4 className="mb-2 text-foreground">Fontes Consultadas</h4>
376
+ <div className="flex flex-wrap gap-2 overflow-hidden">
377
+ {msg.searchSources.map((source, index) => (
378
+ <div
379
+ key={index}
380
+ className="px-3 py-1.5 border max-w-full rounded-[var(--radius-button)] bg-muted border-border"
381
+ >
382
+ <span className="text-sm font-medium text-foreground break-words">
383
+ {source.name}
384
+ </span>
385
+ <span className="text-sm text-muted-foreground ml-1 whitespace-nowrap">
386
+ ({source.count})
387
+ </span>
388
+ </div>
389
+ ))}
390
+ </div>
391
+ </div>
392
+ )}
393
+
394
+ {/* Search Commands */}
395
+ {msg.searchCommands && msg.searchResults && (
396
+ <div>
397
+ <h4 className="mb-2 text-foreground">
398
+ O que você pode fazer com estes resultados
399
+ </h4>
400
+ <div className="grid grid-cols-1 gap-2">
401
+ {msg.searchCommands.map((command) => (
402
+ <button
403
+ key={command.id}
404
+ onClick={() =>
405
+ onExecuteSearchCommand(
406
+ command.id,
407
+ msg.content
408
+ .replace('🔍 Pesquisa realizada com sucesso!', '')
409
+ .split('"')[1] || 'pesquisa',
410
+ msg.searchResults!,
411
+ msg.id,
412
+ )
413
+ }
414
+ disabled={executingCommand === command.id}
415
+ className={cn(
416
+ 'flex items-start gap-2 p-2 rounded-[var(--radius)] border transition-all text-left disabled:opacity-50 disabled:cursor-not-allowed',
417
+ savedSearches.includes(msg.id) && command.id === '5'
418
+ ? 'border-[var(--toast-warning-border)] bg-[var(--toast-warning-bg)]/20'
419
+ : 'border-border bg-transparent hover:bg-muted/50',
420
+ )}
421
+ >
422
+ {executingCommand === command.id ? (
423
+ <Loader2 className="w-5 h-5 animate-spin flex-shrink-0 text-primary" />
424
+ ) : (
425
+ <span className="flex-shrink-0">{command.icon}</span>
426
+ )}
427
+ <div className="flex-1 min-w-0 overflow-hidden">
428
+ <div className="text-sm font-medium text-foreground break-words">
429
+ {command.id === '5' && savedSearches.includes(msg.id)
430
+ ? '⭐ Pesquisa salva'
431
+ : command.label}
432
+ </div>
433
+ <div className="text-sm text-muted-foreground break-words">
434
+ {executingCommand === command.id
435
+ ? 'Processando...'
436
+ : command.description}
437
+ </div>
438
+ </div>
439
+ </button>
440
+ ))}
441
+ </div>
442
+ </div>
443
+ )}
444
+ </div>
445
+ )}
446
+ </div>
447
+
448
+ {/* Message Actions */}
449
+ <div className="flex items-center gap-2 mt-1 px-2">
450
+ <span className="text-sm text-muted-foreground">
451
+ {msg.timestamp.toLocaleTimeString('pt-BR', {
452
+ hour: '2-digit',
453
+ minute: '2-digit',
454
+ })}
455
+ </span>
456
+
457
+ {enablePodcastGeneration &&
458
+ msg.type === 'assistant' &&
459
+ msg.attachmentType !== 'podcast' && (
460
+ <Button
461
+ variant="ghost"
462
+ size="sm"
463
+ onClick={() => onGeneratePodcast(msg.id, msg.content)}
464
+ disabled={generatingPodcastId === msg.id}
465
+ className="h-6 w-6 p-0 disabled:opacity-50 text-muted-foreground"
466
+ title="Gerar Podcast"
467
+ aria-label="Gerar Podcast"
468
+ >
469
+ {generatingPodcastId === msg.id ? (
470
+ <Loader2 className="w-3 h-3 animate-spin" />
471
+ ) : (
472
+ <Radio className="w-3 h-3" />
473
+ )}
474
+ </Button>
475
+ )}
476
+
477
+ {msg.type === 'assistant' && (
478
+ <div className="flex items-center gap-1 border-l border-border pl-2 ml-1">
479
+ <Button
480
+ variant="ghost"
481
+ size="icon"
482
+ className={cn(
483
+ 'h-6 w-6 rounded-full hover:bg-green-100 dark:hover:bg-green-900/20 hover:text-green-600',
484
+ msg.evaluation === 'like' &&
485
+ 'text-green-600 bg-green-100 dark:bg-green-900/20',
486
+ )}
487
+ onClick={() => onEvaluationClick(msg.id, 'like')}
488
+ title="Gostei"
489
+ aria-label="Gostei"
490
+ >
491
+ <ThumbsUp className="h-3.5 w-3.5" />
492
+ </Button>
493
+
494
+ <DropdownMenu>
495
+ <DropdownMenuTrigger asChild>
496
+ <Button
497
+ variant="ghost"
498
+ size="icon"
499
+ className={cn(
500
+ 'h-6 w-6 rounded-full hover:bg-red-100 dark:hover:bg-red-900/20 hover:text-red-600',
501
+ msg.evaluation === 'dislike' &&
502
+ 'text-red-600 bg-red-100 dark:bg-red-900/20',
503
+ )}
504
+ title="Não gostei"
505
+ aria-label="Não gostei"
506
+ >
507
+ <ThumbsDown className="h-3.5 w-3.5" />
508
+ </Button>
509
+ </DropdownMenuTrigger>
510
+ <DropdownMenuContent align="start">
511
+ {feedbackOptions && feedbackOptions.length > 0 ? (
512
+ feedbackOptions.map((option, idx) => (
513
+ <DropdownMenuItem
514
+ key={idx}
515
+ onClick={() => onOpenFeedbackDialog(msg.id, option)}
516
+ >
517
+ {option}
518
+ </DropdownMenuItem>
519
+ ))
520
+ ) : (
521
+ <>
522
+ <DropdownMenuItem
523
+ onClick={() =>
524
+ onOpenFeedbackDialog(msg.id, 'Não era o que eu procurava')
525
+ }
526
+ >
527
+ Não era o que eu procurava
528
+ </DropdownMenuItem>
529
+ <DropdownMenuItem
530
+ onClick={() =>
531
+ onOpenFeedbackDialog(msg.id, 'Informação incorreta')
532
+ }
533
+ >
534
+ Informação incorreta
535
+ </DropdownMenuItem>
536
+ <DropdownMenuItem
537
+ onClick={() =>
538
+ onOpenFeedbackDialog(msg.id, 'Resposta incompleta')
539
+ }
540
+ >
541
+ Resposta incompleta
542
+ </DropdownMenuItem>
543
+ </>
544
+ )}
545
+ <DropdownMenuItem onClick={() => onOpenFeedbackDialog(msg.id, null)}>
546
+ Outros...
547
+ </DropdownMenuItem>
548
+ </DropdownMenuContent>
549
+ </DropdownMenu>
550
+ </div>
551
+ )}
552
+
553
+ <Button
554
+ variant="ghost"
555
+ size="sm"
556
+ onClick={() => onCopyMessage(msg.content, msg.id)}
557
+ className={`h-6 w-6 p-0 ${copiedId === msg.id
558
+ ? 'text-[var(--toast-success-icon)]'
559
+ : 'text-muted-foreground'
560
+ }`}
561
+ aria-label={copiedId === msg.id ? 'Copiado' : 'Copiar mensagem'}
562
+ >
563
+ {copiedId === msg.id ? (
564
+ <Check className="w-3 h-3" />
565
+ ) : (
566
+ <Copy className="w-3 h-3" />
567
+ )}
568
+ </Button>
569
+ </div>
570
+ </div>
571
+ </motion.div>
572
+ );
573
+ }
@@ -0,0 +1,65 @@
1
+ import React from 'react';
2
+ import { MessageSquare, Heart, History } from 'lucide-react';
3
+ import { Button } from '../../../ui/button';
4
+ import type { AssistantTab } from '../types';
5
+
6
+ interface AssistantTabBarProps {
7
+ activeTab: AssistantTab;
8
+ showHistory: boolean;
9
+ showFavorites: boolean;
10
+ onTabChange: (tab: AssistantTab) => void;
11
+ }
12
+
13
+ /**
14
+ * AssistantTabBar — Navigation tabs for Chat, History, and Favorites.
15
+ * Only rendered in expanded (non-fullPage) mode when at least one secondary tab is enabled.
16
+ */
17
+ export function AssistantTabBar({
18
+ activeTab,
19
+ showHistory,
20
+ showFavorites,
21
+ onTabChange,
22
+ }: AssistantTabBarProps) {
23
+ return (
24
+ <div className="px-4 py-2 border-b border-border">
25
+ <div className="flex gap-1">
26
+ <Button
27
+ variant={activeTab === 'chat' ? 'default' : 'ghost'}
28
+ size="sm"
29
+ onClick={() => onTabChange('chat')}
30
+ className="flex-1 h-8"
31
+ aria-label="Ver chat"
32
+ >
33
+ <MessageSquare className="w-3 h-3 mr-1" />
34
+ Chat
35
+ </Button>
36
+
37
+ {showHistory && (
38
+ <Button
39
+ variant={activeTab === 'historico' ? 'default' : 'ghost'}
40
+ size="sm"
41
+ onClick={() => onTabChange('historico')}
42
+ className="flex-1 h-8"
43
+ aria-label="Ver histórico de conversas"
44
+ >
45
+ <History className="w-3 h-3 mr-1" />
46
+ Histórico
47
+ </Button>
48
+ )}
49
+
50
+ {showFavorites && (
51
+ <Button
52
+ variant={activeTab === 'favoritos' ? 'default' : 'ghost'}
53
+ size="sm"
54
+ onClick={() => onTabChange('favoritos')}
55
+ className="flex-1 h-8"
56
+ aria-label="Ver mensagens favoritas"
57
+ >
58
+ <Heart className="w-3 h-3 mr-1" />
59
+ Favoritos
60
+ </Button>
61
+ )}
62
+ </div>
63
+ </div>
64
+ );
65
+ }