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.
- package/CHANGELOG.md +46 -0
- package/README.md +1 -1
- package/bin/cli.ts +1 -1
- package/bin/generate-tokens.ts +13 -7
- package/components/assistant/xertica-assistant/index.ts +2 -0
- package/components/assistant/xertica-assistant/parts/AssistantCollapsedView.tsx +97 -0
- package/components/assistant/xertica-assistant/parts/AssistantConversationList.tsx +104 -0
- package/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.tsx +81 -0
- package/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.tsx +86 -0
- package/components/assistant/xertica-assistant/parts/AssistantHeader.tsx +77 -0
- package/components/assistant/xertica-assistant/parts/AssistantMessageBubble.tsx +573 -0
- package/components/assistant/xertica-assistant/parts/AssistantTabBar.tsx +65 -0
- package/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.tsx +41 -0
- package/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.tsx +98 -0
- package/components/assistant/xertica-assistant/parts/index.ts +16 -0
- package/components/assistant/xertica-assistant/types.ts +139 -0
- package/components/assistant/xertica-assistant/use-assistant.ts +559 -0
- package/components/assistant/xertica-assistant/xertica-assistant.stories.tsx +200 -0
- package/components/assistant/xertica-assistant/xertica-assistant.tsx +198 -1460
- package/components/brand/theme-toggle/ThemeToggle.tsx +8 -27
- package/components/hooks/index.ts +3 -0
- package/components/hooks/use-layout-shortcuts.ts +46 -0
- package/components/layout/sidebar/index.ts +2 -0
- package/components/layout/sidebar/sidebar.stories.tsx +160 -8
- package/components/layout/sidebar/sidebar.tsx +606 -497
- package/components/layout/sidebar/use-sidebar.ts +104 -0
- package/components/media/audio-player/AudioPlayer.tsx +131 -206
- package/components/media/audio-player/use-audio-player.ts +298 -0
- package/components/pages/home-page/HomePage.tsx +1 -1
- package/components/pages/template-content/TemplateContent.tsx +5 -5
- package/components/pages/template-page/TemplatePage.tsx +5 -5
- package/components/shared/CustomTooltipContent.tsx +52 -0
- package/components/shared/layout-constants.ts +1 -1
- package/components/ui/chart/chart.stories.tsx +966 -7
- package/components/ui/chart/chart.tsx +918 -45
- package/components/ui/file-upload/file-upload.stories.tsx +100 -0
- package/components/ui/file-upload/file-upload.tsx +14 -74
- package/components/ui/file-upload/index.ts +1 -0
- package/components/ui/file-upload/use-file-upload.ts +181 -0
- package/components/ui/pagination/index.ts +2 -0
- package/components/ui/pagination/pagination.stories.tsx +94 -0
- package/components/ui/pagination/use-pagination.ts +194 -0
- package/components/ui/rich-text-editor/index.ts +2 -0
- package/components/ui/rich-text-editor/rich-text-editor.stories.tsx +129 -1
- package/components/ui/rich-text-editor/rich-text-editor.tsx +86 -305
- package/components/ui/rich-text-editor/use-rich-text-editor.ts +439 -0
- package/components/ui/stepper/index.ts +3 -1
- package/components/ui/stepper/stepper.stories.tsx +116 -0
- package/components/ui/stepper/stepper.tsx +4 -4
- package/components/ui/stepper/use-stepper.ts +137 -0
- package/components/ui/tree-view/index.ts +4 -1
- package/components/ui/tree-view/tree-view.stories.tsx +110 -4
- package/components/ui/tree-view/tree-view.tsx +17 -125
- package/components/ui/tree-view/use-tree-view.ts +229 -0
- package/contexts/AssistenteContext.tsx +17 -54
- package/contexts/BrandColorsContext.tsx +6 -17
- package/contexts/LayoutContext.tsx +5 -31
- package/dist/AssistantChart-BAudAfne.cjs +3591 -0
- package/dist/AssistantChart-BP8upjMk.js +3565 -0
- package/dist/AudioPlayer-1ypwE2Wh.cjs +936 -0
- package/dist/AudioPlayer-DuKXrCfy.js +937 -0
- package/dist/CustomTooltipContent-DHjkY0ww.js +40 -0
- package/dist/CustomTooltipContent-c_K-DWRr.cjs +56 -0
- package/dist/LanguageContext-BwhwC3G2.js +657 -0
- package/dist/LanguageContext-DvUt5jBg.cjs +656 -0
- package/dist/LayoutContext-BDmcZfMH.cjs +84 -0
- package/dist/LayoutContext-dbQvdC4O.js +85 -0
- package/dist/ThemeContext-RTy1m2Uq.js +82 -0
- package/dist/ThemeContext-bSzuOit2.cjs +81 -0
- package/dist/VerifyEmailPage-C_ihbcth.js +2828 -0
- package/dist/VerifyEmailPage-Dt7zgA4w.cjs +2827 -0
- package/dist/XerticaProvider-CW9hpCdF.cjs +39 -0
- package/dist/XerticaProvider-siSt9uG2.js +40 -0
- package/dist/XerticaXLogo-D8jf0SNv.cjs +214 -0
- package/dist/XerticaXLogo-fAJMy3H4.js +215 -0
- package/dist/assistant.cjs.js +2 -1
- package/dist/assistant.es.js +3 -2
- package/dist/brand.cjs.js +2 -2
- package/dist/brand.es.js +2 -2
- package/dist/cli.js +14 -8
- package/dist/components/assistant/xertica-assistant/index.d.ts +2 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantCollapsedView.d.ts +13 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantConversationList.d.ts +16 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantDocumentEditor.d.ts +17 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantFeedbackDialog.d.ts +19 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantHeader.d.ts +11 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantMessageBubble.d.ts +29 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantTabBar.d.ts +13 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantTypingIndicator.d.ts +4 -0
- package/dist/components/assistant/xertica-assistant/parts/AssistantWelcomeScreen.d.ts +17 -0
- package/dist/components/assistant/xertica-assistant/parts/index.d.ts +16 -0
- package/dist/components/assistant/xertica-assistant/types.d.ts +106 -0
- package/dist/components/assistant/xertica-assistant/use-assistant.d.ts +125 -0
- package/dist/components/assistant/xertica-assistant/xertica-assistant.d.ts +8 -97
- package/dist/components/hooks/index.d.ts +3 -0
- package/dist/components/hooks/use-layout-shortcuts.d.ts +22 -0
- package/dist/components/layout/sidebar/index.d.ts +2 -0
- package/dist/components/layout/sidebar/sidebar.d.ts +80 -0
- package/dist/components/layout/sidebar/use-sidebar.d.ts +22 -0
- package/dist/components/media/audio-player/AudioPlayer.d.ts +4 -1
- package/dist/components/media/audio-player/use-audio-player.d.ts +72 -0
- package/dist/components/shared/CustomTooltipContent.d.ts +20 -0
- package/dist/components/shared/layout-constants.d.ts +1 -1
- package/dist/components/ui/alert/alert.d.ts +1 -1
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts +2 -2
- package/dist/components/ui/chart/chart.d.ts +162 -5
- package/dist/components/ui/file-upload/file-upload.d.ts +2 -0
- package/dist/components/ui/file-upload/index.d.ts +1 -0
- package/dist/components/ui/file-upload/use-file-upload.d.ts +49 -0
- package/dist/components/ui/pagination/index.d.ts +2 -0
- package/dist/components/ui/pagination/use-pagination.d.ts +78 -0
- package/dist/components/ui/rich-text-editor/index.d.ts +2 -0
- package/dist/components/ui/rich-text-editor/use-rich-text-editor.d.ts +107 -0
- package/dist/components/ui/stepper/index.d.ts +3 -1
- package/dist/components/ui/stepper/stepper.d.ts +2 -2
- package/dist/components/ui/stepper/use-stepper.d.ts +60 -0
- package/dist/components/ui/tree-view/index.d.ts +4 -1
- package/dist/components/ui/tree-view/tree-view.d.ts +4 -6
- package/dist/components/ui/tree-view/use-tree-view.d.ts +60 -0
- package/dist/contexts/AssistenteContext.d.ts +10 -49
- package/dist/hooks.cjs.js +30 -10
- package/dist/hooks.es.js +25 -4
- package/dist/index.cjs.js +20 -9
- package/dist/index.es.js +38 -27
- package/dist/layout.cjs.js +82 -1
- package/dist/layout.es.js +83 -2
- package/dist/media.cjs.js +1 -1
- package/dist/media.es.js +1 -1
- package/dist/pages.cjs.js +1 -1
- package/dist/pages.es.js +1 -1
- package/dist/rich-text-editor-BmsjY03B.js +2949 -0
- package/dist/rich-text-editor-GS2kpTAK.cjs +2966 -0
- package/dist/sidebar-CVUGHOS_.cjs +756 -0
- package/dist/sidebar-CmvwjnVb.js +757 -0
- package/dist/ui.cjs.js +12 -2
- package/dist/ui.es.js +24 -14
- package/dist/use-audio-player-Bkh23vQ3.js +177 -0
- package/dist/use-audio-player-Dn1NR9xN.cjs +176 -0
- package/dist/utils/color-utils.d.ts +51 -0
- package/dist/xertica-assistant-BMqdyRVi.js +2082 -0
- package/dist/xertica-assistant-Bj3vBCq_.cjs +2081 -0
- package/dist/xertica-ui.css +1 -1
- package/docs/ai-usage.md +28 -10
- package/docs/architecture-improvements.md +463 -0
- package/docs/architecture.md +77 -1
- package/docs/components/assistant-chart.md +1 -1
- package/docs/components/assistant.md +159 -0
- package/docs/components/audio-player.md +46 -0
- package/docs/components/branding.md +251 -0
- package/docs/components/chart.md +354 -39
- package/docs/components/code-block.md +108 -0
- package/docs/components/file-upload.md +119 -2
- package/docs/components/formatted-document.md +113 -0
- package/docs/components/hooks.md +430 -0
- package/docs/components/image-with-fallback.md +106 -0
- package/docs/components/map-layers.md +140 -0
- package/docs/components/modern-chat-input.md +163 -0
- package/docs/components/pages.md +351 -0
- package/docs/components/pagination.md +187 -0
- package/docs/components/rich-text-editor.md +164 -0
- package/docs/components/sidebar.md +153 -4
- package/docs/components/stepper.md +157 -12
- package/docs/components/tree-view.md +164 -6
- package/docs/doc-audit.md +223 -0
- package/docs/getting-started.md +155 -1
- package/docs/guidelines.md +14 -8
- package/docs/layout.md +2 -2
- package/docs/llms.md +29 -9
- package/docs/patterns/detail-page.md +276 -0
- package/docs/patterns/settings.md +346 -0
- package/docs/patterns/wizard.md +217 -0
- package/guidelines/Guidelines.md +5 -3
- package/llms.txt +1 -1
- package/package.json +10 -10
- package/styles/xertica/tokens.css +41 -12
- package/templates/CLAUDE.md +16 -6
- package/templates/guidelines/Guidelines.md +16 -4
- package/templates/package.json +3 -3
- package/templates/src/styles/xertica/tokens.css +39 -10
- 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
|
+
}
|