xertica-ui 1.2.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/AssistenteXertica.tsx +1190 -829
- package/components/HomeContent.tsx +11 -3
- package/components/layout-constants.ts +4 -0
- package/contexts/AssistenteContext.tsx +20 -4
- package/dist/components/layout-constants.d.ts +4 -0
- package/dist/components/ui/alert.d.ts +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/contexts/AssistenteContext.d.ts +17 -4
- package/dist/index.es.js +39495 -21192
- package/dist/index.umd.js +39543 -21240
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { useNavigate } from 'react-router';
|
|
3
3
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
4
|
-
import {
|
|
5
|
-
MessageSquare,
|
|
6
|
-
Heart,
|
|
7
|
-
History,
|
|
4
|
+
import {
|
|
5
|
+
MessageSquare,
|
|
6
|
+
Heart,
|
|
7
|
+
History,
|
|
8
8
|
MoreHorizontal,
|
|
9
9
|
X,
|
|
10
10
|
ChevronLeft,
|
|
@@ -32,15 +32,36 @@ import {
|
|
|
32
32
|
BarChart3,
|
|
33
33
|
Bookmark,
|
|
34
34
|
Maximize2,
|
|
35
|
-
AlertCircle
|
|
35
|
+
AlertCircle,
|
|
36
|
+
ThumbsUp,
|
|
37
|
+
ThumbsDown,
|
|
38
|
+
BookOpen,
|
|
39
|
+
ArrowLeft,
|
|
40
|
+
Table as TableIcon
|
|
36
41
|
} from 'lucide-react';
|
|
37
42
|
import { Button } from './ui/button';
|
|
38
43
|
import { ScrollArea } from './ui/scroll-area';
|
|
39
|
-
import { ModernChatInput, ActionType } from './ModernChatInput';
|
|
40
44
|
import { Separator } from './ui/separator';
|
|
41
45
|
import { DocumentEditor } from './DocumentEditor';
|
|
42
|
-
import { FormattedDocument } from './FormattedDocument';
|
|
43
46
|
import { MarkdownMessage } from './MarkdownMessage';
|
|
47
|
+
import { FormattedDocument } from './FormattedDocument';
|
|
48
|
+
import { ModernChatInput, ActionType } from './ModernChatInput';
|
|
49
|
+
import {
|
|
50
|
+
DropdownMenu,
|
|
51
|
+
DropdownMenuContent,
|
|
52
|
+
DropdownMenuItem,
|
|
53
|
+
DropdownMenuTrigger,
|
|
54
|
+
} from "./ui/dropdown-menu";
|
|
55
|
+
import {
|
|
56
|
+
Dialog,
|
|
57
|
+
DialogContent,
|
|
58
|
+
DialogDescription,
|
|
59
|
+
DialogFooter,
|
|
60
|
+
DialogHeader,
|
|
61
|
+
DialogTitle,
|
|
62
|
+
} from "./ui/dialog";
|
|
63
|
+
import { Textarea } from "./ui/textarea";
|
|
64
|
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
|
44
65
|
import { XerticaOrbe } from './XerticaOrbe';
|
|
45
66
|
import { useApiKey } from '../contexts/ApiKeyContext';
|
|
46
67
|
import { useAssistente } from '../contexts/AssistenteContext';
|
|
@@ -49,6 +70,19 @@ import { toast } from 'sonner';
|
|
|
49
70
|
import { Tooltip, TooltipTrigger } from './ui/tooltip';
|
|
50
71
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
51
72
|
import { cn } from './ui/utils';
|
|
73
|
+
import { ASSISTANT_EXPANDED_WIDTH, ASSISTANT_COLLAPSED_WIDTH } from './layout-constants';
|
|
74
|
+
// Import Chart Components
|
|
75
|
+
import { Bar, BarChart, CartesianGrid, XAxis, Tooltip as RechartsTooltip, ResponsiveContainer } from "recharts";
|
|
76
|
+
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "./ui/chart";
|
|
77
|
+
import {
|
|
78
|
+
Table,
|
|
79
|
+
TableBody,
|
|
80
|
+
TableCaption,
|
|
81
|
+
TableCell,
|
|
82
|
+
TableHead,
|
|
83
|
+
TableHeader,
|
|
84
|
+
TableRow,
|
|
85
|
+
} from "./ui/table";
|
|
52
86
|
|
|
53
87
|
// Tooltip customizado estilo sidebar (branco)
|
|
54
88
|
function AssistantTooltipContent({
|
|
@@ -86,12 +120,7 @@ interface AssistenteXerticaProps {
|
|
|
86
120
|
isFullPage?: boolean;
|
|
87
121
|
}
|
|
88
122
|
|
|
89
|
-
import type { Message, Conversa, SearchResult, SearchSource, SearchCommand } from '../contexts/AssistenteContext';
|
|
90
|
-
|
|
91
|
-
interface Sugestao {
|
|
92
|
-
id: string;
|
|
93
|
-
texto: string;
|
|
94
|
-
}
|
|
123
|
+
import type { Message, Conversa, SearchResult, SearchSource, SearchCommand, Sugestao } from '../contexts/AssistenteContext';
|
|
95
124
|
|
|
96
125
|
const sugestoesPadrao: Sugestao[] = [
|
|
97
126
|
{ id: '1', texto: 'O que posso pedir para você fazer?' },
|
|
@@ -101,59 +130,66 @@ const sugestoesPadrao: Sugestao[] = [
|
|
|
101
130
|
{ id: '5', texto: 'Qual projetos está tendo o melhor desempenho?' }
|
|
102
131
|
];
|
|
103
132
|
|
|
133
|
+
const sugestoesRicas: Sugestao[] = [
|
|
134
|
+
{ id: 'rich-1', texto: 'Ver exemplo de Gráfico', icon: <BarChart3 className="w-4 h-4 mr-2" /> },
|
|
135
|
+
{ id: 'rich-2', texto: 'Ver exemplo de Imagem', icon: <ImageIcon className="w-4 h-4 mr-2" /> },
|
|
136
|
+
{ id: 'rich-3', texto: 'Ver exemplo de Tabela', icon: <TableIcon className="w-4 h-4 mr-2" /> },
|
|
137
|
+
{ id: 'rich-4', texto: 'Ver exemplo de Documento', icon: <FileText className="w-4 h-4 mr-2" /> }
|
|
138
|
+
];
|
|
139
|
+
|
|
104
140
|
// Respostas simuladas da IA
|
|
105
141
|
const gerarResposta = (mensagemUsuario: string): string => {
|
|
106
142
|
const mensagemLower = mensagemUsuario.toLowerCase();
|
|
107
143
|
const mensagemOriginal = mensagemUsuario;
|
|
108
|
-
|
|
144
|
+
|
|
109
145
|
if (mensagemLower.includes('o que') && (mensagemLower.includes('fazer') || mensagemLower.includes('pedir'))) {
|
|
110
146
|
return 'Posso ajudar você com diversas tarefas! Posso:\n\n• Analisar dados e métricas dos seus projetos\n• Responder perguntas sobre performance e resultados\n• Sugerir otimizações e melhorias\n• Gerar relatórios e documentação\n• Ajudar no planejamento de sprints\n• E muito mais!\n\nQual tarefa você gostaria de realizar primeiro?';
|
|
111
147
|
}
|
|
112
|
-
|
|
148
|
+
|
|
113
149
|
if (mensagemLower.includes('o que você faz') || mensagemLower.includes('quem é você')) {
|
|
114
150
|
return 'Olá! Sou o Assistente Xertica, uma IA desenvolvida para ajudar você a gerenciar projetos, analisar dados e otimizar processos. Estou aqui 24/7 para responder suas perguntas e auxiliar nas suas tarefas diárias.\n\nPosso processar documentos, analisar áudios e imagens, além de fornecer insights baseados nos dados da plataforma.';
|
|
115
151
|
}
|
|
116
|
-
|
|
152
|
+
|
|
117
153
|
if (mensagemLower.includes('projeto') && (mensagemLower.includes('preocupar') || mensagemLower.includes('atenção'))) {
|
|
118
154
|
return 'Com base na análise dos seus projetos ativos, recomendo focar nos seguintes:\n\n1. **Projeto Alpha** - 15% acima do prazo, requer atenção imediata\n2. **Sistema Beta** - Performance crítica, necessita otimização\n3. **Mobile Gamma** - Aguardando aprovações há 5 dias\n\nGostaria de mais detalhes sobre algum deles?';
|
|
119
155
|
}
|
|
120
|
-
|
|
156
|
+
|
|
121
157
|
if (mensagemLower.includes('próximo projeto') || mensagemLower.includes('próxima tarefa')) {
|
|
122
158
|
return 'Seu próximo projeto prioritário é o **Sistema de Analytics V2**.\n\n📅 Início previsto: Próxima segunda-feira\n👥 Time: 5 desenvolvedores\n⏱️ Duração estimada: 3 sprints\n\nJá preparei um roadmap inicial. Gostaria de revisar?';
|
|
123
159
|
}
|
|
124
|
-
|
|
160
|
+
|
|
125
161
|
if (mensagemLower.includes('desempenho') || mensagemLower.includes('performance') || mensagemLower.includes('melhor')) {
|
|
126
162
|
return 'Analisando os dados de performance dos últimos 30 dias:\n\n🏆 **Melhor Performance:**\n• Projeto Dashboard 2.0: +35% eficiência\n• Sistema CRM: -40% tempo de resposta\n• App Mobile: 4.8★ rating (+0.5)\n\n📊 Todos estão acima das metas estabelecidas. Parab��ns!\n\nQuer ver métricas detalhadas?';
|
|
127
163
|
}
|
|
128
|
-
|
|
164
|
+
|
|
129
165
|
if (mensagemLower.includes('olá') || mensagemLower.includes('oi') || mensagemLower.includes('bom dia') || mensagemLower.includes('boa tarde') || mensagemLower.includes('boa noite')) {
|
|
130
166
|
return 'Olá! 👋 Como posso ajudar você hoje? Estou pronto para auxiliar com análises, relatórios ou responder suas dúvidas sobre os projetos.';
|
|
131
167
|
}
|
|
132
|
-
|
|
168
|
+
|
|
133
169
|
if (mensagemLower.includes('obrigado') || mensagemLower.includes('obrigada')) {
|
|
134
170
|
return 'Por nada! Estou aqui sempre que precisar. 😊 Se tiver mais alguma dúvida ou precisar de ajuda, é só chamar!';
|
|
135
171
|
}
|
|
136
|
-
|
|
172
|
+
|
|
137
173
|
// Respostas para ações especiais
|
|
138
174
|
if (mensagemLower.includes('criar documento')) {
|
|
139
175
|
const tema = mensagemOriginal.replace(/📄 \[Criar documento\]/gi, '').trim();
|
|
140
176
|
return `📝 Documento criado com sucesso!\\n\\nGerei um documento completo sobre "${tema}" com as seguintes seções:\\n\\n• Introdução e contexto\\n• Análise detalhada\\n• Dados e métricas relevantes\\n• Conclusões e recomendações\\n• Próximos passos\\n\\nO documento está pronto para revisão e pode ser editado conforme necessário. Gostaria de adicionar ou modificar alguma seção?`;
|
|
141
177
|
}
|
|
142
|
-
|
|
178
|
+
|
|
143
179
|
if (mensagemLower.includes('gerar podcast')) {
|
|
144
180
|
const tema = mensagemOriginal.replace(/🎙️ \[Gerar podcast\]/gi, '').trim();
|
|
145
181
|
return `🎙️ Preparando podcast sobre "${tema}"...\\n\\nEstou processando o conteúdo e gerando um roteiro de podcast profissional com:\\n\\n• Introdução envolvente\\n• Desenvolvimento do tema\\n• Exemplos práticos\\n• Conclusão e insights\\n\\nO áudio será gerado em instantes. Aguarde...`;
|
|
146
182
|
}
|
|
147
|
-
|
|
183
|
+
|
|
148
184
|
if (mensagemLower.includes('pesquisar')) {
|
|
149
185
|
const termo = mensagemOriginal.replace(/🔍 \[Pesquisar\]/gi, '').trim();
|
|
150
186
|
return `🔍 Resultados da pesquisa sobre "${termo}"\\n\\n**Encontrei as seguintes informações relevantes:**\\n\\n1. **Documentação interna** - 12 resultados\\n Guias e manuais relacionados ao tema\\n\\n2. **Projetos relacionados** - 8 projetos\\n Incluindo Analytics v2 e Dashboard Pro\\n\\n3. **Discussões em equipe** - 15 menções\\n Últimas conversas sobre o assunto\\n\\nGostaria de ver mais detalhes sobre algum desses resultados?`;
|
|
151
187
|
}
|
|
152
|
-
|
|
188
|
+
|
|
153
189
|
if (mensagemLower.includes('arquivo') || mensagemLower.includes('documento')) {
|
|
154
190
|
return 'Entendi que você deseja trabalhar com arquivos. Posso analisar diversos tipos de documentos:\n\n📄 Documentos de texto (PDF, DOCX)\n📊 Planilhas (XLSX, CSV)\n📈 Relatórios e apresentações\n\nBasta enviá-los usando o botão de anexo (📎) e terei prazer em analisá-los para você!';
|
|
155
191
|
}
|
|
156
|
-
|
|
192
|
+
|
|
157
193
|
// Resposta genérica inteligente
|
|
158
194
|
const respostasGenericas = [
|
|
159
195
|
'Entendo sua questão. Com base nos dados disponíveis na plataforma Xertica, posso fornecer análises detalhadas sobre esse tema. Poderia me dar mais contexto para que eu possa ajudá-lo melhor?',
|
|
@@ -161,7 +197,7 @@ const gerarResposta = (mensagemUsuario: string): string => {
|
|
|
161
197
|
'Ótima pergunta! Para te dar a melhor resposta possível, preciso entender melhor o contexto. Você pode me fornecer mais informações sobre o que está buscando?',
|
|
162
198
|
'Estou analisando sua solicitação. Baseado nos dados do sistema Xertica, posso te ajudar com isso. Você gostaria de uma análise rápida ou um relatório completo?'
|
|
163
199
|
];
|
|
164
|
-
|
|
200
|
+
|
|
165
201
|
return respostasGenericas[Math.floor(Math.random() * respostasGenericas.length)];
|
|
166
202
|
};
|
|
167
203
|
|
|
@@ -184,16 +220,167 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
184
220
|
savedSearches,
|
|
185
221
|
setSavedSearches,
|
|
186
222
|
} = useAssistente();
|
|
187
|
-
|
|
223
|
+
|
|
188
224
|
const [mensagem, setMensagem] = useState('');
|
|
189
225
|
const [sugestoes] = useState<Sugestao[]>(sugestoesPadrao);
|
|
226
|
+
const [showMoreSuggestions, setShowMoreSuggestions] = useState(false);
|
|
190
227
|
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
191
228
|
const [generatingPodcastId, setGeneratingPodcastId] = useState<string | null>(null);
|
|
192
229
|
const [executingCommand, setExecutingCommand] = useState<string | null>(null);
|
|
230
|
+
const [feedbackDialogOpen, setFeedbackDialogOpen] = useState(false);
|
|
231
|
+
const [feedbackMessageId, setFeedbackMessageId] = useState<string | null>(null);
|
|
232
|
+
const [feedbackCategory, setFeedbackCategory] = useState<string | null>(null);
|
|
233
|
+
const [feedbackReason, setFeedbackReason] = useState("");
|
|
193
234
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
194
235
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
195
236
|
const audioInputRef = useRef<HTMLInputElement>(null);
|
|
196
237
|
|
|
238
|
+
const handleRichResponse = (suggestionId: string, suggestionText: string) => {
|
|
239
|
+
setIsTyping(true);
|
|
240
|
+
setMensagem(''); // Clear input if any
|
|
241
|
+
|
|
242
|
+
// Simulate network delay
|
|
243
|
+
setTimeout(() => {
|
|
244
|
+
let novaMensagem: Message = {
|
|
245
|
+
id: Date.now().toString() + '-ia-rich',
|
|
246
|
+
type: 'assistant',
|
|
247
|
+
content: '',
|
|
248
|
+
timestamp: new Date(),
|
|
249
|
+
isFavorite: false
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (suggestionId === 'rich-1') { // Gráfico
|
|
253
|
+
const chartData = [
|
|
254
|
+
{ month: "Jan", desktop: 186, mobile: 80 },
|
|
255
|
+
{ month: "Feb", desktop: 305, mobile: 200 },
|
|
256
|
+
{ month: "Mar", desktop: 237, mobile: 120 },
|
|
257
|
+
{ month: "Apr", desktop: 73, mobile: 190 },
|
|
258
|
+
{ month: "May", desktop: 209, mobile: 130 },
|
|
259
|
+
{ month: "Jun", desktop: 214, mobile: 140 },
|
|
260
|
+
];
|
|
261
|
+
const chartConfig = {
|
|
262
|
+
desktop: {
|
|
263
|
+
label: "Desktop",
|
|
264
|
+
color: "hsl(var(--chart-1))",
|
|
265
|
+
},
|
|
266
|
+
mobile: {
|
|
267
|
+
label: "Mobile",
|
|
268
|
+
color: "hsl(var(--chart-2))",
|
|
269
|
+
},
|
|
270
|
+
} satisfies ChartConfig;
|
|
271
|
+
|
|
272
|
+
novaMensagem = {
|
|
273
|
+
...novaMensagem,
|
|
274
|
+
content: 'Aqui está o gráfico de desempenho do projeto nos últimos 6 meses, comparando acessos Desktop vs Mobile.',
|
|
275
|
+
chartData: chartData,
|
|
276
|
+
chartConfig: chartConfig
|
|
277
|
+
};
|
|
278
|
+
} else if (suggestionId === 'rich-2') { // Imagem
|
|
279
|
+
novaMensagem = {
|
|
280
|
+
...novaMensagem,
|
|
281
|
+
content: 'Aqui está a imagem prévia do novo layout do dashboard que você solicitou.',
|
|
282
|
+
attachmentType: 'image',
|
|
283
|
+
attachmentName: 'dashboard-preview-v2.jpg',
|
|
284
|
+
// Placeholder image URL
|
|
285
|
+
documentContent: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?q=80&w=2070&auto=format&fit=crop'
|
|
286
|
+
};
|
|
287
|
+
} else if (suggestionId === 'rich-3') { // Tabela
|
|
288
|
+
novaMensagem = {
|
|
289
|
+
...novaMensagem,
|
|
290
|
+
content: 'Aqui está a tabela com os status dos servidores:',
|
|
291
|
+
tableData: {
|
|
292
|
+
caption: "Status dos Servidores em Tempo Real",
|
|
293
|
+
headers: ["Servidor", "Status", "Uptime", "Link"],
|
|
294
|
+
rows: [
|
|
295
|
+
["Alpha-1", "✅ Online", "99.9%", <a href="https://example.com/log1" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline flex items-center gap-1">Log <ExternalLink size={12} /></a>],
|
|
296
|
+
["Beta-2", "⚠️ Warning", "98.5%", <a href="https://example.com/log2" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline flex items-center gap-1">Log <ExternalLink size={12} /></a>],
|
|
297
|
+
["Gamma-3", "❌ Offline", "0%", <a href="https://example.com/log3" target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline flex items-center gap-1">Log <ExternalLink size={12} /></a>]
|
|
298
|
+
]
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
} else if (suggestionId === 'rich-4') { // Documento para Download
|
|
302
|
+
novaMensagem = {
|
|
303
|
+
...novaMensagem,
|
|
304
|
+
content: 'Gerei o relatório completo de auditoria de segurança. Você pode baixá-lo ou editá-lo abaixo.',
|
|
305
|
+
attachmentType: 'document',
|
|
306
|
+
attachmentName: 'auditoria-seguranca-2024.md',
|
|
307
|
+
documentTitle: 'Relatório de Auditoria de Segurança',
|
|
308
|
+
documentContent: '# Auditoria de Segurança 2024\n\n## Resumo Executivo\nForam encontradas 3 vulnerabilidades de nível baixo...'
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
setConversas(prev => prev.map(conv => {
|
|
313
|
+
if (conv.id === conversaAtual) {
|
|
314
|
+
return {
|
|
315
|
+
...conv,
|
|
316
|
+
mensagens: [...conv.mensagens,
|
|
317
|
+
{ // User message triggers the response
|
|
318
|
+
id: Date.now().toString() + '-user',
|
|
319
|
+
type: 'user',
|
|
320
|
+
content: suggestionText,
|
|
321
|
+
timestamp: new Date()
|
|
322
|
+
},
|
|
323
|
+
novaMensagem
|
|
324
|
+
],
|
|
325
|
+
ultimaMensagem: suggestionText,
|
|
326
|
+
timestamp: new Date().toLocaleString('pt-BR')
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
return conv;
|
|
330
|
+
}));
|
|
331
|
+
|
|
332
|
+
setIsTyping(false);
|
|
333
|
+
setShowMoreSuggestions(false); // Close suggestions
|
|
334
|
+
}, 1500);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const handleEvaluation = (messageId: string, evaluation: 'like' | 'dislike', reason?: string) => {
|
|
338
|
+
setConversas(prev => prev.map(conv => {
|
|
339
|
+
if (conv.id === conversaAtual) {
|
|
340
|
+
return {
|
|
341
|
+
...conv,
|
|
342
|
+
mensagens: conv.mensagens.map(msg =>
|
|
343
|
+
msg.id === messageId ? { ...msg, evaluation, evaluationReason: reason } : msg
|
|
344
|
+
)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return conv;
|
|
348
|
+
}));
|
|
349
|
+
|
|
350
|
+
if (evaluation === 'like') {
|
|
351
|
+
toast.success("Obrigado por avaliar positivamente!");
|
|
352
|
+
} else if (evaluation === 'dislike' && reason) {
|
|
353
|
+
toast.success("Obrigado pelo seu feedback. Vamos melhorar.");
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const openFeedbackDialog = (messageId: string, category: string | null = null) => {
|
|
358
|
+
setFeedbackMessageId(messageId);
|
|
359
|
+
setFeedbackCategory(category);
|
|
360
|
+
setFeedbackReason("");
|
|
361
|
+
setFeedbackDialogOpen(true);
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const submitFeedbackDialog = () => {
|
|
365
|
+
if (!feedbackMessageId) return;
|
|
366
|
+
|
|
367
|
+
let finalReason = "";
|
|
368
|
+
if (feedbackCategory) {
|
|
369
|
+
// Se tem categoria pré-definida, o comentário é opcional
|
|
370
|
+
finalReason = feedbackReason.trim() ? `${feedbackCategory}: ${feedbackReason}` : feedbackCategory;
|
|
371
|
+
} else {
|
|
372
|
+
// Se é "Outros", o motivo é obrigatório
|
|
373
|
+
if (!feedbackReason.trim()) return;
|
|
374
|
+
finalReason = feedbackReason;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
handleEvaluation(feedbackMessageId, 'dislike', finalReason);
|
|
378
|
+
setFeedbackDialogOpen(false);
|
|
379
|
+
setFeedbackMessageId(null);
|
|
380
|
+
setFeedbackReason("");
|
|
381
|
+
setFeedbackCategory(null);
|
|
382
|
+
};
|
|
383
|
+
|
|
197
384
|
const conversaAtiva = conversas.find(c => c.id === conversaAtual);
|
|
198
385
|
const mensagens = conversaAtiva?.mensagens || [];
|
|
199
386
|
|
|
@@ -209,19 +396,19 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
209
396
|
// Resposta da IA usando Gemini API
|
|
210
397
|
const simularRespostaIA = async (mensagemUsuario: string) => {
|
|
211
398
|
setIsTyping(true);
|
|
212
|
-
|
|
399
|
+
|
|
213
400
|
try {
|
|
214
401
|
let resposta: string;
|
|
215
|
-
|
|
402
|
+
|
|
216
403
|
// Se a chave da API for válida, usar Gemini
|
|
217
404
|
if (isApiKeyValid && geminiApiKey) {
|
|
218
405
|
// Construir histórico da conversa para o Gemini
|
|
219
406
|
const conversaAtiva = conversas.find(c => c.id === conversaAtual);
|
|
220
407
|
const historico: GeminiMessage[] = [];
|
|
221
|
-
|
|
408
|
+
|
|
222
409
|
// Adicionar prompt do sistema
|
|
223
410
|
const systemPrompt = buildSystemPrompt();
|
|
224
|
-
|
|
411
|
+
|
|
225
412
|
// Converter mensagens anteriores para formato Gemini (últimas 10 mensagens)
|
|
226
413
|
const mensagensRecentes = (conversaAtiva?.mensagens || []).slice(-10);
|
|
227
414
|
for (const msg of mensagensRecentes) {
|
|
@@ -231,7 +418,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
231
418
|
.replace(/^📄 \[Criar documento\] /, '')
|
|
232
419
|
.replace(/^🎙️ \[Gerar podcast\] /, '')
|
|
233
420
|
.replace(/^🔍 \[Pesquisar\] /, '');
|
|
234
|
-
|
|
421
|
+
|
|
235
422
|
historico.push({
|
|
236
423
|
role: 'user',
|
|
237
424
|
parts: [{ text: conteudoLimpo }]
|
|
@@ -243,7 +430,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
243
430
|
});
|
|
244
431
|
}
|
|
245
432
|
}
|
|
246
|
-
|
|
433
|
+
|
|
247
434
|
// Se não houver histórico, adicionar o prompt do sistema como primeira mensagem
|
|
248
435
|
if (historico.length === 0) {
|
|
249
436
|
historico.push({
|
|
@@ -255,19 +442,19 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
255
442
|
parts: [{ text: 'Entendido! Estou pronto para ajudar você com a plataforma Xertica.ai. Como posso ajudá-lo hoje?' }]
|
|
256
443
|
});
|
|
257
444
|
}
|
|
258
|
-
|
|
445
|
+
|
|
259
446
|
try {
|
|
260
447
|
resposta = await callGeminiAPI(geminiApiKey, mensagemUsuario, historico);
|
|
261
448
|
} catch (error) {
|
|
262
449
|
console.error('Erro ao chamar API Gemini:', error);
|
|
263
450
|
// Usar a mensagem de erro do utilitário se disponível
|
|
264
451
|
const errorMessage = error instanceof Error ? error.message : '❌ Erro desconhecido';
|
|
265
|
-
|
|
452
|
+
|
|
266
453
|
// Verificar se é erro de chave vazada ou 403
|
|
267
|
-
const isLeakedKeyError = errorMessage.toLowerCase().includes('leaked') ||
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
454
|
+
const isLeakedKeyError = errorMessage.toLowerCase().includes('leaked') ||
|
|
455
|
+
errorMessage.toLowerCase().includes('reported') ||
|
|
456
|
+
errorMessage.toLowerCase().includes('403');
|
|
457
|
+
|
|
271
458
|
if (isLeakedKeyError) {
|
|
272
459
|
toast.error('Chave de API desativada por segurança', {
|
|
273
460
|
description: 'Sua chave foi reportada como vazada. Gere uma nova em Google AI Studio.',
|
|
@@ -287,7 +474,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
287
474
|
await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000));
|
|
288
475
|
resposta = gerarResposta(mensagemUsuario);
|
|
289
476
|
}
|
|
290
|
-
|
|
477
|
+
|
|
291
478
|
const novaMensagem: Message = {
|
|
292
479
|
id: Date.now().toString() + '-ia',
|
|
293
480
|
type: 'assistant',
|
|
@@ -295,7 +482,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
295
482
|
timestamp: new Date(),
|
|
296
483
|
isFavorite: false
|
|
297
484
|
};
|
|
298
|
-
|
|
485
|
+
|
|
299
486
|
setConversas(prev => prev.map(conv => {
|
|
300
487
|
if (conv.id === conversaAtual) {
|
|
301
488
|
const novasMensagens = [...conv.mensagens, novaMensagem];
|
|
@@ -317,7 +504,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
317
504
|
timestamp: new Date(),
|
|
318
505
|
isFavorite: false
|
|
319
506
|
};
|
|
320
|
-
|
|
507
|
+
|
|
321
508
|
setConversas(prev => prev.map(conv => {
|
|
322
509
|
if (conv.id === conversaAtual) {
|
|
323
510
|
return {
|
|
@@ -334,7 +521,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
|
|
|
334
521
|
|
|
335
522
|
const simularRespostaDocumento = (tema: string) => {
|
|
336
523
|
setIsTyping(true);
|
|
337
|
-
|
|
524
|
+
|
|
338
525
|
setTimeout(() => {
|
|
339
526
|
const temaLimpo = tema.replace(/📄 \[Criar documento\]/gi, '').trim();
|
|
340
527
|
const documentTitle = `Documento: ${temaLimpo}`;
|
|
@@ -399,7 +586,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
399
586
|
documentContent: documentContent,
|
|
400
587
|
documentTitle: documentTitle
|
|
401
588
|
};
|
|
402
|
-
|
|
589
|
+
|
|
403
590
|
setConversas(prev => prev.map(conv => {
|
|
404
591
|
if (conv.id === conversaAtual) {
|
|
405
592
|
return {
|
|
@@ -411,21 +598,21 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
411
598
|
}
|
|
412
599
|
return conv;
|
|
413
600
|
}));
|
|
414
|
-
|
|
601
|
+
|
|
415
602
|
setIsTyping(false);
|
|
416
603
|
}, 2000);
|
|
417
604
|
};
|
|
418
605
|
|
|
419
606
|
const simularRespostaPodcast = (tema: string) => {
|
|
420
607
|
setIsTyping(true);
|
|
421
|
-
|
|
608
|
+
|
|
422
609
|
setTimeout(() => {
|
|
423
610
|
const temaLimpo = tema.replace(/🎙️ \[Gerar podcast\]/gi, '').trim();
|
|
424
|
-
|
|
611
|
+
|
|
425
612
|
// Gerar URL de áudio simulado (usando um data URL de áudio vazio)
|
|
426
613
|
// Em produção, isso seria substituído por uma chamada real à API de geração de áudio
|
|
427
614
|
const audioUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADhAC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7v///////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAAAAAAAAAAAA4TnTxAOAAAAAAD/+xBkAA';
|
|
428
|
-
|
|
615
|
+
|
|
429
616
|
const novaMensagemIA: Message = {
|
|
430
617
|
id: Date.now().toString(),
|
|
431
618
|
type: 'assistant',
|
|
@@ -436,7 +623,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
436
623
|
attachmentName: `Podcast - ${temaLimpo}.mp3`,
|
|
437
624
|
audioUrl: audioUrl
|
|
438
625
|
};
|
|
439
|
-
|
|
626
|
+
|
|
440
627
|
setConversas(prev => prev.map(conv => {
|
|
441
628
|
if (conv.id === conversaAtual) {
|
|
442
629
|
return {
|
|
@@ -448,17 +635,17 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
448
635
|
}
|
|
449
636
|
return conv;
|
|
450
637
|
}));
|
|
451
|
-
|
|
638
|
+
|
|
452
639
|
setIsTyping(false);
|
|
453
640
|
}, 2000);
|
|
454
641
|
};
|
|
455
642
|
|
|
456
643
|
const simularRespostaPesquisa = (query: string) => {
|
|
457
644
|
setIsTyping(true);
|
|
458
|
-
|
|
645
|
+
|
|
459
646
|
setTimeout(() => {
|
|
460
647
|
const queryLimpa = query.replace(/🔍 \[Pesquisar\]/gi, '').trim();
|
|
461
|
-
|
|
648
|
+
|
|
462
649
|
// Simular resultados de pesquisa baseados na query
|
|
463
650
|
const mockResults: SearchResult[] = [
|
|
464
651
|
{
|
|
@@ -548,7 +735,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
548
735
|
description: 'Salvar esta pesquisa nos favoritos'
|
|
549
736
|
}
|
|
550
737
|
];
|
|
551
|
-
|
|
738
|
+
|
|
552
739
|
const novaMensagemIA: Message = {
|
|
553
740
|
id: Date.now().toString(),
|
|
554
741
|
type: 'assistant',
|
|
@@ -560,7 +747,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
560
747
|
searchSources: mockSources,
|
|
561
748
|
searchCommands: mockCommands
|
|
562
749
|
};
|
|
563
|
-
|
|
750
|
+
|
|
564
751
|
setConversas(prev => prev.map(conv => {
|
|
565
752
|
if (conv.id === conversaAtual) {
|
|
566
753
|
return {
|
|
@@ -572,7 +759,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
572
759
|
}
|
|
573
760
|
return conv;
|
|
574
761
|
}));
|
|
575
|
-
|
|
762
|
+
|
|
576
763
|
setIsTyping(false);
|
|
577
764
|
}, 1500);
|
|
578
765
|
};
|
|
@@ -580,10 +767,10 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
580
767
|
// Detectar intenção do usuário baseado na mensagem com análise contextual avançada
|
|
581
768
|
const detectarIntencao = (texto: string): ActionType | null => {
|
|
582
769
|
const textoLower = texto.toLowerCase().trim();
|
|
583
|
-
|
|
770
|
+
|
|
584
771
|
// Normalizar texto removendo pontuação extra
|
|
585
772
|
const textoNormalizado = textoLower.replace(/[?.!,]/g, ' ').replace(/\s+/g, ' ');
|
|
586
|
-
|
|
773
|
+
|
|
587
774
|
// ====== VERBOS DE AÇÃO PARA DOCUMENTO ======
|
|
588
775
|
const verbosDocumento = [
|
|
589
776
|
'crie', 'criar', 'criação', 'cria',
|
|
@@ -599,7 +786,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
599
786
|
'desenvolva', 'desenvolver', 'desenvolve', 'desenvolvendo',
|
|
600
787
|
'formule', 'formular', 'formula', 'formulando'
|
|
601
788
|
];
|
|
602
|
-
|
|
789
|
+
|
|
603
790
|
const substantivosDocumento = [
|
|
604
791
|
'documento', 'documentos', 'doc',
|
|
605
792
|
'relatório', 'relatorio', 'relatórios', 'relatorios',
|
|
@@ -618,7 +805,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
618
805
|
'proposta', 'propostas',
|
|
619
806
|
'briefing', 'brief'
|
|
620
807
|
];
|
|
621
|
-
|
|
808
|
+
|
|
622
809
|
// ====== VERBOS DE AÇÃO PARA PODCAST ======
|
|
623
810
|
const verbosPodcast = [
|
|
624
811
|
'grave', 'gravar', 'grava', 'gravando', 'gravação', 'gravacao',
|
|
@@ -627,7 +814,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
627
814
|
'converta para áudio', 'converta para audio',
|
|
628
815
|
'sintetize', 'sintetizar'
|
|
629
816
|
];
|
|
630
|
-
|
|
817
|
+
|
|
631
818
|
const substantivosPodcast = [
|
|
632
819
|
'podcast', 'podcasts',
|
|
633
820
|
'áudio', 'audio', 'áudios', 'audios',
|
|
@@ -636,7 +823,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
636
823
|
'locução', 'locucao',
|
|
637
824
|
'gravação', 'gravacao', 'gravações', 'gravacoes'
|
|
638
825
|
];
|
|
639
|
-
|
|
826
|
+
|
|
640
827
|
// ====== VERBOS DE AÇÃO PARA PESQUISA ======
|
|
641
828
|
const verbosPesquisa = [
|
|
642
829
|
'pesquise', 'pesquisar', 'pesquisa', 'pesquisando',
|
|
@@ -653,7 +840,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
653
840
|
'traga', 'trazer', 'traz', 'trazendo',
|
|
654
841
|
'recupere', 'recuperar', 'recupera', 'recuperando'
|
|
655
842
|
];
|
|
656
|
-
|
|
843
|
+
|
|
657
844
|
const complementosPesquisa = [
|
|
658
845
|
'me mostre', 'mostre-me', 'me mostra',
|
|
659
846
|
'me traga', 'traga-me', 'me traz',
|
|
@@ -666,16 +853,16 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
666
853
|
'onde está', 'onde estão', 'onde fica', 'onde ficam',
|
|
667
854
|
'qual é', 'quais são'
|
|
668
855
|
];
|
|
669
|
-
|
|
856
|
+
|
|
670
857
|
// ====== DETECÇÃO CONTEXTUAL ======
|
|
671
|
-
|
|
858
|
+
|
|
672
859
|
// 1. Verificar PODCAST primeiro (mais específico)
|
|
673
860
|
for (const verbo of verbosPodcast) {
|
|
674
861
|
if (textoNormalizado.includes(verbo)) {
|
|
675
862
|
return 'podcast';
|
|
676
863
|
}
|
|
677
864
|
}
|
|
678
|
-
|
|
865
|
+
|
|
679
866
|
for (const substantivo of substantivosPodcast) {
|
|
680
867
|
for (const verbo of verbosDocumento) {
|
|
681
868
|
if (textoNormalizado.includes(verbo) && textoNormalizado.includes(substantivo)) {
|
|
@@ -683,14 +870,14 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
683
870
|
}
|
|
684
871
|
}
|
|
685
872
|
}
|
|
686
|
-
|
|
873
|
+
|
|
687
874
|
// Detecção específica: "transformar/converter em áudio"
|
|
688
875
|
if ((textoNormalizado.includes('transforme') || textoNormalizado.includes('transformar') ||
|
|
689
|
-
|
|
690
|
-
|
|
876
|
+
textoNormalizado.includes('converta') || textoNormalizado.includes('converter')) &&
|
|
877
|
+
(textoNormalizado.includes('áudio') || textoNormalizado.includes('audio'))) {
|
|
691
878
|
return 'podcast';
|
|
692
879
|
}
|
|
693
|
-
|
|
880
|
+
|
|
694
881
|
// 2. Verificar DOCUMENTO (combinação verbo + substantivo)
|
|
695
882
|
for (const verbo of verbosDocumento) {
|
|
696
883
|
for (const substantivo of substantivosDocumento) {
|
|
@@ -699,54 +886,54 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
699
886
|
}
|
|
700
887
|
}
|
|
701
888
|
}
|
|
702
|
-
|
|
889
|
+
|
|
703
890
|
// Detecção simples: apenas o verbo "documentar"
|
|
704
891
|
if (textoNormalizado.includes('documente') || textoNormalizado.includes('documentar')) {
|
|
705
892
|
return 'document';
|
|
706
893
|
}
|
|
707
|
-
|
|
894
|
+
|
|
708
895
|
// Detecção: "escrever/escreva sobre"
|
|
709
896
|
if ((textoNormalizado.includes('escreva sobre') || textoNormalizado.includes('escrever sobre') ||
|
|
710
|
-
|
|
897
|
+
textoNormalizado.includes('escreva um') || textoNormalizado.includes('escreva uma'))) {
|
|
711
898
|
return 'document';
|
|
712
899
|
}
|
|
713
|
-
|
|
900
|
+
|
|
714
901
|
// Padrões comuns de criação de documento
|
|
715
902
|
const padrõesDocumento = [
|
|
716
903
|
/^(crie|criar|faça|fazer|gere|gerar).*(documento|relatório|relatorio|resumo|nota|artigo|texto|análise|analise)/,
|
|
717
904
|
/^(preciso|quero|gostaria).*(criar|fazer|gerar).*(documento|relatório|relatorio|resumo)/,
|
|
718
905
|
/^me (ajude|ajuda) (a )?(criar|fazer|gerar).*(documento|relatório|relatorio)/
|
|
719
906
|
];
|
|
720
|
-
|
|
907
|
+
|
|
721
908
|
for (const padrao of padrõesDocumento) {
|
|
722
909
|
if (padrao.test(textoNormalizado)) {
|
|
723
910
|
return 'document';
|
|
724
911
|
}
|
|
725
912
|
}
|
|
726
|
-
|
|
913
|
+
|
|
727
914
|
// 3. Verificar PESQUISA
|
|
728
915
|
for (const verbo of verbosPesquisa) {
|
|
729
916
|
if (textoNormalizado.startsWith(verbo + ' ') || textoNormalizado.includes(' ' + verbo + ' ')) {
|
|
730
917
|
return 'search';
|
|
731
918
|
}
|
|
732
919
|
}
|
|
733
|
-
|
|
920
|
+
|
|
734
921
|
for (const complemento of complementosPesquisa) {
|
|
735
922
|
if (textoNormalizado.includes(complemento)) {
|
|
736
923
|
return 'search';
|
|
737
924
|
}
|
|
738
925
|
}
|
|
739
|
-
|
|
926
|
+
|
|
740
927
|
// Padrões interrogativos que indicam pesquisa
|
|
741
928
|
if ((textoNormalizado.startsWith('onde ') || textoNormalizado.startsWith('qual ') ||
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
929
|
+
textoNormalizado.startsWith('quais ') || textoNormalizado.startsWith('quem ') ||
|
|
930
|
+
textoNormalizado.startsWith('como encontrar') || textoNormalizado.startsWith('tem algum') ||
|
|
931
|
+
textoNormalizado.startsWith('tem alguma') || textoNormalizado.includes('você tem')) &&
|
|
932
|
+
!textoNormalizado.includes('documento') && !textoNormalizado.includes('relatório') &&
|
|
933
|
+
!textoNormalizado.includes('relatorio') && !textoNormalizado.includes('podcast')) {
|
|
747
934
|
return 'search';
|
|
748
935
|
}
|
|
749
|
-
|
|
936
|
+
|
|
750
937
|
return null;
|
|
751
938
|
};
|
|
752
939
|
|
|
@@ -757,9 +944,9 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
757
944
|
if (!acaoDetectada) {
|
|
758
945
|
acaoDetectada = detectarIntencao(mensagem) || undefined;
|
|
759
946
|
}
|
|
760
|
-
|
|
947
|
+
|
|
761
948
|
let mensagemComAcao = mensagem;
|
|
762
|
-
|
|
949
|
+
|
|
763
950
|
// Adicionar prefixo baseado na ação selecionada ou detectada
|
|
764
951
|
if (acaoDetectada === 'document') {
|
|
765
952
|
mensagemComAcao = `📄 [Criar documento] ${mensagem}`;
|
|
@@ -768,7 +955,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
768
955
|
} else if (acaoDetectada === 'search') {
|
|
769
956
|
mensagemComAcao = `🔍 [Pesquisar] ${mensagem}`;
|
|
770
957
|
}
|
|
771
|
-
|
|
958
|
+
|
|
772
959
|
const novaMensagem: Message = {
|
|
773
960
|
id: Date.now().toString(),
|
|
774
961
|
type: 'user',
|
|
@@ -776,15 +963,15 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
776
963
|
timestamp: new Date(),
|
|
777
964
|
isFavorite: false
|
|
778
965
|
};
|
|
779
|
-
|
|
966
|
+
|
|
780
967
|
// Atualizar título da conversa se for a primeira mensagem
|
|
781
968
|
setConversas(prev => prev.map(conv => {
|
|
782
969
|
if (conv.id === conversaAtual) {
|
|
783
970
|
const novasMensagens = [...conv.mensagens, novaMensagem];
|
|
784
|
-
const novoTitulo = conv.mensagens.length === 0
|
|
971
|
+
const novoTitulo = conv.mensagens.length === 0
|
|
785
972
|
? mensagem.substring(0, 30) + (mensagem.length > 30 ? '...' : '')
|
|
786
973
|
: conv.titulo;
|
|
787
|
-
|
|
974
|
+
|
|
788
975
|
return {
|
|
789
976
|
...conv,
|
|
790
977
|
titulo: novoTitulo,
|
|
@@ -795,9 +982,9 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
795
982
|
}
|
|
796
983
|
return conv;
|
|
797
984
|
}));
|
|
798
|
-
|
|
985
|
+
|
|
799
986
|
setMensagem('');
|
|
800
|
-
|
|
987
|
+
|
|
801
988
|
// Simular resposta da IA baseada na ação detectada ou explícita
|
|
802
989
|
if (acaoDetectada === 'document') {
|
|
803
990
|
simularRespostaDocumento(mensagem);
|
|
@@ -854,10 +1041,10 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
854
1041
|
document.body.appendChild(textArea);
|
|
855
1042
|
textArea.focus();
|
|
856
1043
|
textArea.select();
|
|
857
|
-
|
|
1044
|
+
|
|
858
1045
|
const successful = document.execCommand('copy');
|
|
859
1046
|
document.body.removeChild(textArea);
|
|
860
|
-
|
|
1047
|
+
|
|
861
1048
|
if (successful) {
|
|
862
1049
|
setCopiedId(messageId);
|
|
863
1050
|
setTimeout(() => setCopiedId(null), 2000);
|
|
@@ -885,7 +1072,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
885
1072
|
timestamp: new Date().toLocaleString('pt-BR'),
|
|
886
1073
|
favorita: false
|
|
887
1074
|
};
|
|
888
|
-
|
|
1075
|
+
|
|
889
1076
|
setConversas(prev => [novaConversa, ...prev]);
|
|
890
1077
|
setConversaAtual(novaConversa.id);
|
|
891
1078
|
setAbaSelecionada('chat');
|
|
@@ -908,7 +1095,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
908
1095
|
attachmentType: 'file',
|
|
909
1096
|
attachmentName: file.name
|
|
910
1097
|
};
|
|
911
|
-
|
|
1098
|
+
|
|
912
1099
|
setConversas(prev => prev.map(conv => {
|
|
913
1100
|
if (conv.id === conversaAtual) {
|
|
914
1101
|
return {
|
|
@@ -920,13 +1107,13 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
920
1107
|
}
|
|
921
1108
|
return conv;
|
|
922
1109
|
}));
|
|
923
|
-
|
|
1110
|
+
|
|
924
1111
|
// Simular resposta sobre o arquivo
|
|
925
1112
|
setTimeout(() => {
|
|
926
1113
|
simularRespostaIA(`Analisar arquivo ${file.name}`);
|
|
927
1114
|
}, 500);
|
|
928
1115
|
}
|
|
929
|
-
|
|
1116
|
+
|
|
930
1117
|
// Reset input
|
|
931
1118
|
if (e.target) e.target.value = '';
|
|
932
1119
|
};
|
|
@@ -943,7 +1130,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
943
1130
|
attachmentType: 'audio',
|
|
944
1131
|
attachmentName: file.name
|
|
945
1132
|
};
|
|
946
|
-
|
|
1133
|
+
|
|
947
1134
|
setConversas(prev => prev.map(conv => {
|
|
948
1135
|
if (conv.id === conversaAtual) {
|
|
949
1136
|
return {
|
|
@@ -955,11 +1142,11 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
955
1142
|
}
|
|
956
1143
|
return conv;
|
|
957
1144
|
}));
|
|
958
|
-
|
|
1145
|
+
|
|
959
1146
|
// Simular resposta sobre o áudio
|
|
960
1147
|
setTimeout(() => {
|
|
961
1148
|
const respostaAudio = 'Áudio recebido! Estou processando o conteúdo... 🎵\n\nIdentifiquei que você está falando sobre análise de projetos. Posso transcrever o áudio completo se desejar. Gostaria que eu faça isso?';
|
|
962
|
-
|
|
1149
|
+
|
|
963
1150
|
const novaMensagemIA: Message = {
|
|
964
1151
|
id: Date.now().toString() + '-ia',
|
|
965
1152
|
type: 'assistant',
|
|
@@ -967,7 +1154,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
967
1154
|
timestamp: new Date(),
|
|
968
1155
|
isFavorite: false
|
|
969
1156
|
};
|
|
970
|
-
|
|
1157
|
+
|
|
971
1158
|
setConversas(prev => prev.map(conv => {
|
|
972
1159
|
if (conv.id === conversaAtual) {
|
|
973
1160
|
return {
|
|
@@ -981,7 +1168,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
981
1168
|
}));
|
|
982
1169
|
}, 1500);
|
|
983
1170
|
}
|
|
984
|
-
|
|
1171
|
+
|
|
985
1172
|
// Reset input
|
|
986
1173
|
if (e.target) e.target.value = '';
|
|
987
1174
|
};
|
|
@@ -989,16 +1176,16 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
989
1176
|
const handleGeneratePodcast = async (messageId: string, messageContent: string) => {
|
|
990
1177
|
// Marcar que o podcast está sendo gerado
|
|
991
1178
|
setGeneratingPodcastId(messageId);
|
|
992
|
-
|
|
1179
|
+
|
|
993
1180
|
// Simular delay de geração (2-3 segundos)
|
|
994
1181
|
await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 1000));
|
|
995
|
-
|
|
1182
|
+
|
|
996
1183
|
// Gerar URL de áudio simulado (usando um data URL de áudio vazio)
|
|
997
1184
|
// Em produção, isso seria substituído por uma chamada real à API de geração de áudio
|
|
998
1185
|
const audioUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADhAC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7v///////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAAAAAAAAAAAA4TnTxAOAAAAAAD/+xBkAA';
|
|
999
|
-
|
|
1186
|
+
|
|
1000
1187
|
const temaExtraido = messageContent.substring(0, 50).replace(/[📝🎙️🔍]/g, '').trim();
|
|
1001
|
-
|
|
1188
|
+
|
|
1002
1189
|
const podcastMessage: Message = {
|
|
1003
1190
|
id: Date.now().toString() + '-podcast',
|
|
1004
1191
|
type: 'assistant',
|
|
@@ -1009,7 +1196,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1009
1196
|
attachmentName: `Podcast - ${temaExtraido}.mp3`,
|
|
1010
1197
|
audioUrl: audioUrl
|
|
1011
1198
|
};
|
|
1012
|
-
|
|
1199
|
+
|
|
1013
1200
|
setConversas(prev => prev.map(conv => {
|
|
1014
1201
|
if (conv.id === conversaAtual) {
|
|
1015
1202
|
return {
|
|
@@ -1021,11 +1208,11 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1021
1208
|
}
|
|
1022
1209
|
return conv;
|
|
1023
1210
|
}));
|
|
1024
|
-
|
|
1211
|
+
|
|
1025
1212
|
setGeneratingPodcastId(null);
|
|
1026
1213
|
};
|
|
1027
1214
|
|
|
1028
|
-
const conversasFiltradas = abaSelecionada === 'favoritos'
|
|
1215
|
+
const conversasFiltradas = abaSelecionada === 'favoritos'
|
|
1029
1216
|
? conversas.filter(c => c.favorita)
|
|
1030
1217
|
: conversas;
|
|
1031
1218
|
|
|
@@ -1077,7 +1264,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1077
1264
|
timestamp: new Date(),
|
|
1078
1265
|
isFavorite: false
|
|
1079
1266
|
};
|
|
1080
|
-
|
|
1267
|
+
|
|
1081
1268
|
setConversas(prev => prev.map(conv => {
|
|
1082
1269
|
if (conv.id === conversaAtual) {
|
|
1083
1270
|
return {
|
|
@@ -1089,12 +1276,12 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1089
1276
|
}
|
|
1090
1277
|
return conv;
|
|
1091
1278
|
}));
|
|
1092
|
-
|
|
1279
|
+
|
|
1093
1280
|
// Resposta do assistente com opções baseadas no tipo
|
|
1094
1281
|
setTimeout(() => {
|
|
1095
1282
|
let responseContent = '';
|
|
1096
1283
|
let actionButtons = '';
|
|
1097
|
-
|
|
1284
|
+
|
|
1098
1285
|
if (result.type === 'document') {
|
|
1099
1286
|
responseContent = `📄 **${result.title}**\n\n${result.description}\n\n**Localização:** ${result.path}\n${result.lastModified ? `**Última modificação:** ${result.lastModified}\n` : ''}\n**Relevância:** ${result.relevance}%\n\n**O que você gostaria de fazer?**\n\n• Visualizar conteúdo completo\n• Editar documento\n• Baixar arquivo\n• Compartilhar com equipe\n• Ver histórico de versões\n• Adicionar comentários`;
|
|
1100
1287
|
} else if (result.type === 'project') {
|
|
@@ -1106,7 +1293,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1106
1293
|
} else if (result.type === 'contact') {
|
|
1107
1294
|
responseContent = `👤 **${result.title}**\n\n${result.description}\n\n**Localização:** ${result.path}\n${result.lastModified ? `**Última interação:** ${result.lastModified}\n` : ''}\n**Relevância:** ${result.relevance}%\n\n**Opções de contato:**\n\n• Ver perfil completo\n• Enviar mensagem\n• Agendar reunião\n• Ver projetos em comum\n• Histórico de interações\n• Adicionar nota`;
|
|
1108
1295
|
}
|
|
1109
|
-
|
|
1296
|
+
|
|
1110
1297
|
const assistantMessage: Message = {
|
|
1111
1298
|
id: Date.now().toString() + '-response',
|
|
1112
1299
|
type: 'assistant',
|
|
@@ -1114,7 +1301,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1114
1301
|
timestamp: new Date(),
|
|
1115
1302
|
isFavorite: false
|
|
1116
1303
|
};
|
|
1117
|
-
|
|
1304
|
+
|
|
1118
1305
|
setConversas(prev => prev.map(conv => {
|
|
1119
1306
|
if (conv.id === conversaAtual) {
|
|
1120
1307
|
return {
|
|
@@ -1131,10 +1318,10 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1131
1318
|
|
|
1132
1319
|
const handleCreateDocumentFromSearch = (searchQuery: string, results: SearchResult[]) => {
|
|
1133
1320
|
setExecutingCommand('document');
|
|
1134
|
-
|
|
1321
|
+
|
|
1135
1322
|
// Criar conteúdo do documento baseado nos resultados
|
|
1136
1323
|
const documentContent = `# Relatório: ${searchQuery}\n\n## Resumo Executivo\n\nEsta pesquisa encontrou ${results.length} resultados relevantes sobre "${searchQuery}".\n\n## Resultados Encontrados\n\n${results.map((r, i) => `### ${i + 1}. ${r.title}\n- **Tipo:** ${r.type}\n- **Relevância:** ${r.relevance}%\n- **Descrição:** ${r.description}\n- **Localização:** ${r.path}\n${r.lastModified ? `- **Última modificação:** ${r.lastModified}` : ''}\n`).join('\n')}\n\n## Conclusão\n\nOs resultados acima representam as informações mais relevantes encontradas no sistema Xertica.ai.`;
|
|
1137
|
-
|
|
1324
|
+
|
|
1138
1325
|
setTimeout(() => {
|
|
1139
1326
|
setExecutingCommand(null);
|
|
1140
1327
|
simularRespostaDocumento(`Compilar resultados da pesquisa: ${searchQuery}`);
|
|
@@ -1143,7 +1330,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1143
1330
|
|
|
1144
1331
|
const handleGenerateReport = (searchQuery: string, results: SearchResult[]) => {
|
|
1145
1332
|
setExecutingCommand('report');
|
|
1146
|
-
|
|
1333
|
+
|
|
1147
1334
|
setTimeout(() => {
|
|
1148
1335
|
const reportMessage: Message = {
|
|
1149
1336
|
id: Date.now().toString(),
|
|
@@ -1152,7 +1339,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1152
1339
|
timestamp: new Date(),
|
|
1153
1340
|
isFavorite: false
|
|
1154
1341
|
};
|
|
1155
|
-
|
|
1342
|
+
|
|
1156
1343
|
setConversas(prev => prev.map(conv => {
|
|
1157
1344
|
if (conv.id === conversaAtual) {
|
|
1158
1345
|
return {
|
|
@@ -1164,14 +1351,14 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1164
1351
|
}
|
|
1165
1352
|
return conv;
|
|
1166
1353
|
}));
|
|
1167
|
-
|
|
1354
|
+
|
|
1168
1355
|
setExecutingCommand(null);
|
|
1169
1356
|
}, 2000);
|
|
1170
1357
|
};
|
|
1171
1358
|
|
|
1172
1359
|
const handleGeneratePodcastFromSearch = (searchQuery: string) => {
|
|
1173
1360
|
setExecutingCommand('podcast');
|
|
1174
|
-
|
|
1361
|
+
|
|
1175
1362
|
setTimeout(() => {
|
|
1176
1363
|
simularRespostaPodcast(`Criar podcast sobre os resultados da pesquisa: ${searchQuery}`);
|
|
1177
1364
|
setExecutingCommand(null);
|
|
@@ -1180,7 +1367,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1180
1367
|
|
|
1181
1368
|
const handleSendSummary = (searchQuery: string, results: SearchResult[]) => {
|
|
1182
1369
|
setExecutingCommand('email');
|
|
1183
|
-
|
|
1370
|
+
|
|
1184
1371
|
setTimeout(() => {
|
|
1185
1372
|
toast.success('Resumo enviado!', {
|
|
1186
1373
|
description: `Email enviado para sua equipe com ${results.length} resultados sobre "${searchQuery}"`
|
|
@@ -1226,778 +1413,952 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
|
|
|
1226
1413
|
return (
|
|
1227
1414
|
<>
|
|
1228
1415
|
{/* Assistente + Editor Combined Container */}
|
|
1229
|
-
<div
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1416
|
+
<div
|
|
1417
|
+
style={{
|
|
1418
|
+
width: editingDocument && isExpanded
|
|
1419
|
+
? '50%'
|
|
1420
|
+
: isExpanded
|
|
1421
|
+
? ASSISTANT_EXPANDED_WIDTH
|
|
1422
|
+
: ASSISTANT_COLLAPSED_WIDTH
|
|
1423
|
+
}}
|
|
1424
|
+
className={`fixed top-0 right-0 h-full z-40 transition-all duration-300 ease-in-out hidden md:block`}
|
|
1425
|
+
>
|
|
1236
1426
|
<div className={`flex h-full overflow-hidden ${isFullPage ? 'max-w-full' : ''}`}>
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
isFullPage
|
|
1427
|
+
{/* Hidden file inputs */}
|
|
1428
|
+
<input
|
|
1429
|
+
ref={fileInputRef}
|
|
1430
|
+
type="file"
|
|
1431
|
+
accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,.csv"
|
|
1432
|
+
onChange={handleFileUpload}
|
|
1433
|
+
className="hidden"
|
|
1434
|
+
/>
|
|
1435
|
+
<input
|
|
1436
|
+
ref={audioInputRef}
|
|
1437
|
+
type="file"
|
|
1438
|
+
accept="audio/*"
|
|
1439
|
+
onChange={handleAudioUpload}
|
|
1440
|
+
className="hidden"
|
|
1441
|
+
/>
|
|
1442
|
+
|
|
1443
|
+
{/* Assistente Sidebar */}
|
|
1444
|
+
<div className={`h-full flex flex-col ${isFullPage
|
|
1256
1445
|
? editingDocument ? 'w-1/2' : 'w-full'
|
|
1257
|
-
: `bg-card border-l border-border shadow-xl
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
<span className="text-foreground">Assistente Xertica</span>
|
|
1274
|
-
</motion.div>
|
|
1275
|
-
)}
|
|
1276
|
-
|
|
1277
|
-
<div className="flex items-center gap-1">
|
|
1278
|
-
{isExpanded && (
|
|
1279
|
-
<Button
|
|
1280
|
-
variant="ghost"
|
|
1281
|
-
size="sm"
|
|
1282
|
-
onClick={() => navigate('/assistente')}
|
|
1283
|
-
className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1284
|
-
title="Expandir assistente"
|
|
1285
|
-
>
|
|
1286
|
-
<Maximize2 className="w-4 h-4" />
|
|
1287
|
-
</Button>
|
|
1288
|
-
)}
|
|
1289
|
-
|
|
1290
|
-
<Button
|
|
1291
|
-
variant="ghost"
|
|
1292
|
-
size="sm"
|
|
1293
|
-
onClick={onToggle}
|
|
1294
|
-
className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1295
|
-
>
|
|
1296
|
-
{isExpanded ? (
|
|
1297
|
-
<ChevronRight className="w-4 h-4" />
|
|
1298
|
-
) : (
|
|
1299
|
-
<PanelRight className="w-4 h-4" />
|
|
1446
|
+
: `bg-card border-l border-border shadow-xl w-full`
|
|
1447
|
+
}`}>
|
|
1448
|
+
|
|
1449
|
+
{/* Header - Toggle Button - Apenas visível quando não está em fullPage */}
|
|
1450
|
+
{!isFullPage && (
|
|
1451
|
+
<div className="border-b border-border flex items-center justify-between px-[14px] px-[18px] py-[16px]">
|
|
1452
|
+
{isExpanded && (
|
|
1453
|
+
<motion.div
|
|
1454
|
+
initial={{ opacity: 0, x: 20 }}
|
|
1455
|
+
animate={{ opacity: 1, x: 0 }}
|
|
1456
|
+
exit={{ opacity: 0, x: 20 }}
|
|
1457
|
+
className="flex items-center gap-2"
|
|
1458
|
+
>
|
|
1459
|
+
<XerticaOrbe size={32} />
|
|
1460
|
+
<span className="text-foreground">Assistente Xertica</span>
|
|
1461
|
+
</motion.div>
|
|
1300
1462
|
)}
|
|
1301
|
-
</Button>
|
|
1302
|
-
</div>
|
|
1303
|
-
</div>
|
|
1304
|
-
)}
|
|
1305
1463
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
>
|
|
1335
|
-
<MessageSquare className="w-4 h-4" />
|
|
1336
|
-
</Button>
|
|
1337
|
-
</TooltipTrigger>
|
|
1338
|
-
<AssistantTooltipContent
|
|
1339
|
-
side="left"
|
|
1340
|
-
sideOffset={8}
|
|
1341
|
-
>
|
|
1342
|
-
<p>Chat</p>
|
|
1343
|
-
</AssistantTooltipContent>
|
|
1344
|
-
</Tooltip>
|
|
1345
|
-
|
|
1346
|
-
<Tooltip>
|
|
1347
|
-
<TooltipTrigger asChild>
|
|
1348
|
-
<Button
|
|
1349
|
-
variant="ghost"
|
|
1350
|
-
size="sm"
|
|
1351
|
-
onClick={() => handleExpandWithTab('favoritos')}
|
|
1352
|
-
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1353
|
-
>
|
|
1354
|
-
<Heart className="w-4 h-4" />
|
|
1355
|
-
</Button>
|
|
1356
|
-
</TooltipTrigger>
|
|
1357
|
-
<AssistantTooltipContent
|
|
1358
|
-
side="left"
|
|
1359
|
-
sideOffset={8}
|
|
1360
|
-
>
|
|
1361
|
-
<p>Favoritos</p>
|
|
1362
|
-
</AssistantTooltipContent>
|
|
1363
|
-
</Tooltip>
|
|
1364
|
-
|
|
1365
|
-
<Tooltip>
|
|
1366
|
-
<TooltipTrigger asChild>
|
|
1367
|
-
<Button
|
|
1368
|
-
variant="ghost"
|
|
1369
|
-
size="sm"
|
|
1370
|
-
onClick={() => handleExpandWithTab('historico')}
|
|
1371
|
-
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1372
|
-
>
|
|
1373
|
-
<History className="w-4 h-4" />
|
|
1374
|
-
</Button>
|
|
1375
|
-
</TooltipTrigger>
|
|
1376
|
-
<AssistantTooltipContent
|
|
1377
|
-
side="left"
|
|
1378
|
-
sideOffset={8}
|
|
1379
|
-
>
|
|
1380
|
-
<p>Histórico</p>
|
|
1381
|
-
</AssistantTooltipContent>
|
|
1382
|
-
</Tooltip>
|
|
1383
|
-
</div>
|
|
1384
|
-
)}
|
|
1464
|
+
<div className="flex items-center gap-1">
|
|
1465
|
+
{isExpanded && (
|
|
1466
|
+
<Button
|
|
1467
|
+
variant="ghost"
|
|
1468
|
+
size="sm"
|
|
1469
|
+
onClick={() => navigate('/assistente')}
|
|
1470
|
+
className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1471
|
+
title="Expandir assistente"
|
|
1472
|
+
>
|
|
1473
|
+
<Maximize2 className="w-4 h-4" />
|
|
1474
|
+
</Button>
|
|
1475
|
+
)}
|
|
1476
|
+
|
|
1477
|
+
<Button
|
|
1478
|
+
variant="ghost"
|
|
1479
|
+
size="sm"
|
|
1480
|
+
onClick={onToggle}
|
|
1481
|
+
className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1482
|
+
>
|
|
1483
|
+
{isExpanded ? (
|
|
1484
|
+
<ChevronRight className="w-4 h-4" />
|
|
1485
|
+
) : (
|
|
1486
|
+
<PanelRight className="w-4 h-4" />
|
|
1487
|
+
)}
|
|
1488
|
+
</Button>
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
)}
|
|
1385
1492
|
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
<
|
|
1493
|
+
{/* Collapsed State - Icons Only */}
|
|
1494
|
+
{!isExpanded && !isFullPage && (
|
|
1495
|
+
<div className="flex flex-col items-center p-4 space-y-4">
|
|
1496
|
+
{/* Ícone do Assistente - Xertica Orbe */}
|
|
1497
|
+
<Tooltip>
|
|
1498
|
+
<TooltipTrigger asChild>
|
|
1499
|
+
<button
|
|
1500
|
+
onClick={onToggle}
|
|
1501
|
+
className="flex items-center justify-center hover:opacity-90 transition-opacity duration-200 cursor-pointer"
|
|
1502
|
+
>
|
|
1503
|
+
<XerticaOrbe size={32} />
|
|
1504
|
+
</button>
|
|
1505
|
+
</TooltipTrigger>
|
|
1506
|
+
<AssistantTooltipContent
|
|
1507
|
+
side="left"
|
|
1508
|
+
sideOffset={8}
|
|
1509
|
+
>
|
|
1510
|
+
<p>Assistente Xertica</p>
|
|
1511
|
+
</AssistantTooltipContent>
|
|
1512
|
+
</Tooltip>
|
|
1513
|
+
|
|
1514
|
+
<Tooltip>
|
|
1515
|
+
<TooltipTrigger asChild>
|
|
1400
1516
|
<Button
|
|
1401
|
-
variant=
|
|
1517
|
+
variant="ghost"
|
|
1402
1518
|
size="sm"
|
|
1403
|
-
onClick={() =>
|
|
1404
|
-
className="
|
|
1519
|
+
onClick={() => handleExpandWithTab('chat')}
|
|
1520
|
+
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1405
1521
|
>
|
|
1406
|
-
<MessageSquare className="w-
|
|
1407
|
-
Chat
|
|
1522
|
+
<MessageSquare className="w-4 h-4" />
|
|
1408
1523
|
</Button>
|
|
1524
|
+
</TooltipTrigger>
|
|
1525
|
+
<AssistantTooltipContent
|
|
1526
|
+
side="left"
|
|
1527
|
+
sideOffset={8}
|
|
1528
|
+
>
|
|
1529
|
+
<p>Chat</p>
|
|
1530
|
+
</AssistantTooltipContent>
|
|
1531
|
+
</Tooltip>
|
|
1532
|
+
|
|
1533
|
+
<Tooltip>
|
|
1534
|
+
<TooltipTrigger asChild>
|
|
1409
1535
|
<Button
|
|
1410
|
-
variant=
|
|
1536
|
+
variant="ghost"
|
|
1411
1537
|
size="sm"
|
|
1412
|
-
onClick={() =>
|
|
1413
|
-
className="
|
|
1538
|
+
onClick={() => handleExpandWithTab('favoritos')}
|
|
1539
|
+
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1414
1540
|
>
|
|
1415
|
-
<
|
|
1416
|
-
Histórico
|
|
1541
|
+
<Heart className="w-4 h-4" />
|
|
1417
1542
|
</Button>
|
|
1543
|
+
</TooltipTrigger>
|
|
1544
|
+
<AssistantTooltipContent
|
|
1545
|
+
side="left"
|
|
1546
|
+
sideOffset={8}
|
|
1547
|
+
>
|
|
1548
|
+
<p>Favoritos</p>
|
|
1549
|
+
</AssistantTooltipContent>
|
|
1550
|
+
</Tooltip>
|
|
1551
|
+
|
|
1552
|
+
<Tooltip>
|
|
1553
|
+
<TooltipTrigger asChild>
|
|
1418
1554
|
<Button
|
|
1419
|
-
variant=
|
|
1555
|
+
variant="ghost"
|
|
1420
1556
|
size="sm"
|
|
1421
|
-
onClick={() =>
|
|
1422
|
-
className="
|
|
1557
|
+
onClick={() => handleExpandWithTab('historico')}
|
|
1558
|
+
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1423
1559
|
>
|
|
1424
|
-
<
|
|
1425
|
-
Favoritos
|
|
1560
|
+
<History className="w-4 h-4" />
|
|
1426
1561
|
</Button>
|
|
1427
|
-
</
|
|
1428
|
-
|
|
1429
|
-
|
|
1562
|
+
</TooltipTrigger>
|
|
1563
|
+
<AssistantTooltipContent
|
|
1564
|
+
side="left"
|
|
1565
|
+
sideOffset={8}
|
|
1566
|
+
>
|
|
1567
|
+
<p>Histórico</p>
|
|
1568
|
+
</AssistantTooltipContent>
|
|
1569
|
+
</Tooltip>
|
|
1570
|
+
</div>
|
|
1571
|
+
)}
|
|
1430
1572
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1573
|
+
{/* Expanded State - Full Content */}
|
|
1574
|
+
<AnimatePresence>
|
|
1575
|
+
{(isExpanded || isFullPage) && (
|
|
1576
|
+
<motion.div
|
|
1577
|
+
initial={isFullPage ? false : { opacity: 0, x: 50 }}
|
|
1578
|
+
animate={{ opacity: 1, x: 0 }}
|
|
1579
|
+
exit={isFullPage ? undefined : { opacity: 0, x: 50 }}
|
|
1580
|
+
transition={{ duration: 0.2 }}
|
|
1581
|
+
className="flex-1 flex flex-col overflow-hidden"
|
|
1582
|
+
>
|
|
1583
|
+
{/* Navigation Tabs - Oculto em modo fullPage */}
|
|
1584
|
+
{!isFullPage && (
|
|
1585
|
+
<div className="px-4 py-2 border-b border-border">
|
|
1586
|
+
<div className="flex gap-1">
|
|
1587
|
+
<Button
|
|
1588
|
+
variant={abaSelecionada === 'chat' ? 'default' : 'ghost'}
|
|
1589
|
+
size="sm"
|
|
1590
|
+
onClick={() => setAbaSelecionada('chat')}
|
|
1591
|
+
className="flex-1 h-8 text-xs"
|
|
1592
|
+
>
|
|
1593
|
+
<MessageSquare className="w-3 h-3 mr-1" />
|
|
1594
|
+
Chat
|
|
1595
|
+
</Button>
|
|
1596
|
+
<Button
|
|
1597
|
+
variant={abaSelecionada === 'historico' ? 'default' : 'ghost'}
|
|
1598
|
+
size="sm"
|
|
1599
|
+
onClick={() => setAbaSelecionada('historico')}
|
|
1600
|
+
className="flex-1 h-8 text-xs"
|
|
1601
|
+
>
|
|
1602
|
+
<History className="w-3 h-3 mr-1" />
|
|
1603
|
+
Histórico
|
|
1604
|
+
</Button>
|
|
1605
|
+
<Button
|
|
1606
|
+
variant={abaSelecionada === 'favoritos' ? 'default' : 'ghost'}
|
|
1607
|
+
size="sm"
|
|
1608
|
+
onClick={() => setAbaSelecionada('favoritos')}
|
|
1609
|
+
className="flex-1 h-8 text-xs"
|
|
1610
|
+
>
|
|
1611
|
+
<Heart className="w-3 h-3 mr-1" />
|
|
1612
|
+
Favoritos
|
|
1613
|
+
</Button>
|
|
1471
1614
|
</div>
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1615
|
+
</div>
|
|
1616
|
+
)}
|
|
1617
|
+
|
|
1618
|
+
{/* Content Area */}
|
|
1619
|
+
<div className="flex-1 overflow-hidden flex flex-col">
|
|
1620
|
+
{abaSelecionada === 'chat' && (
|
|
1621
|
+
<div className="flex-1 flex flex-col min-h-0 w-full">
|
|
1622
|
+
{/* API Key Warning Banner */}
|
|
1623
|
+
{!isApiKeyValid && (
|
|
1624
|
+
<div className="mx-4 mt-3 p-4 bg-[var(--toast-warning-bg)]/20 border-2 border-[var(--toast-warning-border)] rounded-lg">
|
|
1625
|
+
<div className="flex items-start gap-3">
|
|
1626
|
+
<AlertCircle className="w-5 h-5 text-[var(--toast-warning-icon)] flex-shrink-0 mt-0.5" />
|
|
1627
|
+
<div className="flex-1 space-y-3">
|
|
1628
|
+
<p className="text-small text-foreground">
|
|
1629
|
+
{!geminiApiKey ? (
|
|
1630
|
+
<strong>Nenhuma chave de API configurada</strong>
|
|
1631
|
+
) : (
|
|
1632
|
+
<strong>🔐 Chave de API inválida ou vazada</strong>
|
|
1633
|
+
)}
|
|
1634
|
+
</p>
|
|
1635
|
+
<p className="text-small text-muted-foreground">
|
|
1636
|
+
Configure uma nova chave do Google Gemini para ativar respostas inteligentes do assistente.
|
|
1637
|
+
</p>
|
|
1638
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
1639
|
+
<Button
|
|
1640
|
+
onClick={() => navigate('/settings')}
|
|
1641
|
+
size="sm"
|
|
1642
|
+
className="bg-[var(--toast-warning-icon)] hover:opacity-90 text-white rounded-[12px] shadow-sm"
|
|
1643
|
+
>
|
|
1644
|
+
Ir para Configurações
|
|
1645
|
+
</Button>
|
|
1646
|
+
<a
|
|
1647
|
+
href="https://aistudio.google.com/apikey"
|
|
1648
|
+
target="_blank"
|
|
1649
|
+
rel="noopener noreferrer"
|
|
1650
|
+
className="inline-flex items-center gap-1 text-small text-[var(--toast-warning-icon)] underline hover:opacity-80"
|
|
1651
|
+
>
|
|
1652
|
+
Obter chave gratuitamente
|
|
1653
|
+
<ExternalLink className="w-3 h-3" />
|
|
1654
|
+
</a>
|
|
1655
|
+
</div>
|
|
1656
|
+
</div>
|
|
1657
|
+
</div>
|
|
1480
1658
|
</div>
|
|
1481
|
-
|
|
1482
|
-
Olá, Ariel
|
|
1483
|
-
</h3>
|
|
1484
|
-
<p className="text-muted-foreground">
|
|
1485
|
-
Como posso ajudar?
|
|
1486
|
-
</p>
|
|
1487
|
-
</div>
|
|
1659
|
+
)}
|
|
1488
1660
|
|
|
1489
|
-
{
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
<
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1661
|
+
{mensagens.length === 0 ? (
|
|
1662
|
+
<div className="flex-1 overflow-y-auto min-h-0">
|
|
1663
|
+
{/* Welcome Message */}
|
|
1664
|
+
<div className="p-6 text-center">
|
|
1665
|
+
<div className="mx-auto mb-4 flex items-center justify-center">
|
|
1666
|
+
<XerticaOrbe size={64} />
|
|
1667
|
+
</div>
|
|
1668
|
+
<h3 className="text-foreground mb-2">
|
|
1669
|
+
Olá, Ariel
|
|
1670
|
+
</h3>
|
|
1671
|
+
<p className="text-muted-foreground">
|
|
1672
|
+
Como posso ajudar?
|
|
1673
|
+
</p>
|
|
1674
|
+
</div>
|
|
1675
|
+
|
|
1676
|
+
{/* Suggestions */}
|
|
1677
|
+
<div className="px-4 pb-4 space-y-2">
|
|
1678
|
+
{!showMoreSuggestions ? (
|
|
1679
|
+
<>
|
|
1680
|
+
{sugestoes.map((sugestao) => (
|
|
1681
|
+
<button
|
|
1682
|
+
key={sugestao.id}
|
|
1683
|
+
onClick={() => setMensagem(sugestao.texto)}
|
|
1684
|
+
className="w-full p-3 text-left bg-muted hover:bg-accent rounded-lg transition-colors duration-200 text-foreground"
|
|
1685
|
+
>
|
|
1686
|
+
{sugestao.texto}
|
|
1687
|
+
</button>
|
|
1688
|
+
))}
|
|
1689
|
+
|
|
1690
|
+
<Button
|
|
1691
|
+
variant="ghost"
|
|
1692
|
+
size="sm"
|
|
1693
|
+
onClick={() => setShowMoreSuggestions(true)}
|
|
1694
|
+
className="w-full justify-start text-muted-foreground hover:text-foreground"
|
|
1695
|
+
>
|
|
1696
|
+
<MoreHorizontal className="w-4 h-4 mr-2" />
|
|
1697
|
+
Mais sugestões
|
|
1698
|
+
</Button>
|
|
1699
|
+
</>
|
|
1700
|
+
) : (
|
|
1701
|
+
<>
|
|
1702
|
+
<div className="flex items-center gap-2 mb-2 px-2">
|
|
1703
|
+
<span className="text-xs font-medium text-muted-foreground">Exemplos de respostas ricas</span>
|
|
1704
|
+
</div>
|
|
1705
|
+
{sugestoesRicas.map((sugestao) => (
|
|
1706
|
+
<button
|
|
1707
|
+
key={sugestao.id}
|
|
1708
|
+
onClick={() => handleRichResponse(sugestao.id, sugestao.texto)}
|
|
1709
|
+
className="w-full p-3 text-left bg-muted hover:bg-accent rounded-lg transition-colors duration-200 text-foreground flex items-center"
|
|
1710
|
+
>
|
|
1711
|
+
{sugestao.icon}
|
|
1712
|
+
{sugestao.texto}
|
|
1713
|
+
</button>
|
|
1714
|
+
))}
|
|
1715
|
+
|
|
1716
|
+
<Button
|
|
1717
|
+
variant="ghost"
|
|
1718
|
+
size="sm"
|
|
1719
|
+
onClick={() => setShowMoreSuggestions(false)}
|
|
1720
|
+
className="w-full justify-start text-muted-foreground hover:text-foreground"
|
|
1721
|
+
>
|
|
1722
|
+
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
1723
|
+
Voltar
|
|
1724
|
+
</Button>
|
|
1725
|
+
</>
|
|
1526
1726
|
)}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
{
|
|
1536
|
-
{
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
<
|
|
1543
|
-
<Button
|
|
1544
|
-
variant="ghost"
|
|
1545
|
-
size="sm"
|
|
1546
|
-
onClick={() => msg.documentContent && msg.attachmentName && handleDownloadDocument(msg.documentContent, msg.attachmentName)}
|
|
1547
|
-
className="h-6 px-2 hover:bg-[var(--toast-success-bg)]/30 hover:text-[var(--toast-success-icon)]"
|
|
1548
|
-
title="Download"
|
|
1549
|
-
>
|
|
1550
|
-
<Download className="w-3 h-3" />
|
|
1551
|
-
</Button>
|
|
1552
|
-
<Button
|
|
1553
|
-
variant="ghost"
|
|
1554
|
-
size="sm"
|
|
1555
|
-
onClick={() => msg.documentContent && msg.documentTitle && handleEditDocument(msg.documentContent, msg.documentTitle)}
|
|
1556
|
-
className="h-6 px-2 hover:bg-[var(--toast-info-bg)]/30 hover:text-[var(--toast-info-icon)]"
|
|
1557
|
-
>
|
|
1558
|
-
<Edit className="w-3 h-3 mr-1" />
|
|
1559
|
-
Editar
|
|
1560
|
-
</Button>
|
|
1561
|
-
</div>
|
|
1727
|
+
</div>
|
|
1728
|
+
</div>
|
|
1729
|
+
) : (
|
|
1730
|
+
<ScrollArea className="flex-1 min-h-0 overflow-x-hidden">
|
|
1731
|
+
<div className="space-y-4 px-4 py-4 overflow-x-hidden w-full">
|
|
1732
|
+
{mensagens.map((msg) => (
|
|
1733
|
+
<motion.div
|
|
1734
|
+
key={msg.id}
|
|
1735
|
+
initial={{ opacity: 0, y: 10 }}
|
|
1736
|
+
animate={{ opacity: 1, y: 0 }}
|
|
1737
|
+
className={`flex gap-2 ${msg.type === 'user' ? 'justify-end' : 'justify-start'}`}
|
|
1738
|
+
>
|
|
1739
|
+
{/* Avatar do Assistente */}
|
|
1740
|
+
{msg.type === 'assistant' && (
|
|
1741
|
+
<div className="flex-shrink-0 pt-1">
|
|
1742
|
+
<XerticaOrbe size={32} />
|
|
1562
1743
|
</div>
|
|
1563
1744
|
)}
|
|
1564
1745
|
|
|
1565
|
-
{
|
|
1746
|
+
<div data-chat-message-container className={`max-w-[85%] min-w-0 w-auto ${msg.type === 'user' ? 'order-2' : 'order-1'}`}>
|
|
1566
1747
|
<div className={cn(
|
|
1567
|
-
"
|
|
1568
|
-
msg.type === 'user'
|
|
1748
|
+
"rounded-[var(--radius-card)] px-4 py-2 break-words overflow-hidden overflow-x-hidden w-fit",
|
|
1749
|
+
msg.type === 'user'
|
|
1750
|
+
? "bg-primary text-primary-foreground shadow-sm"
|
|
1751
|
+
: "bg-muted text-foreground"
|
|
1569
1752
|
)}>
|
|
1570
|
-
{
|
|
1571
|
-
{msg.attachmentType === '
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
)}>{msg.attachmentName}</span>
|
|
1577
|
-
</div>
|
|
1578
|
-
)}
|
|
1579
|
-
|
|
1580
|
-
{/* Conteúdo da mensagem */}
|
|
1581
|
-
{msg.type === 'user' ? (
|
|
1582
|
-
<p className="whitespace-pre-wrap break-words text-primary-foreground">{msg.content}</p>
|
|
1583
|
-
) : (
|
|
1584
|
-
<>
|
|
1585
|
-
{/* Alerta visual para mensagens de erro */}
|
|
1586
|
-
{(msg.content.includes('🔐') || msg.content.includes('❌')) && (
|
|
1587
|
-
<div className="mb-3 p-3 bg-[var(--toast-error-bg)]/20 border border-[var(--toast-error-border)] rounded-lg overflow-hidden">
|
|
1588
|
-
<div className="flex items-start gap-2 min-w-0">
|
|
1589
|
-
<AlertCircle className="w-5 h-5 text-[var(--toast-error-icon)] flex-shrink-0 mt-0.5" />
|
|
1590
|
-
<div className="flex-1 min-w-0 overflow-hidden">
|
|
1591
|
-
<MarkdownMessage
|
|
1592
|
-
content={msg.content}
|
|
1593
|
-
className="text-foreground"
|
|
1594
|
-
/>
|
|
1595
|
-
</div>
|
|
1753
|
+
{/* Document Header with Edit and Download Buttons */}
|
|
1754
|
+
{msg.attachmentType === 'document' && (
|
|
1755
|
+
<div className="flex items-center justify-between mb-2 pb-2 border-b border-border min-w-0 overflow-hidden">
|
|
1756
|
+
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
|
1757
|
+
<FileText className="w-4 h-4 flex-shrink-0" />
|
|
1758
|
+
<span className="text-small break-words">{msg.attachmentName}</span>
|
|
1596
1759
|
</div>
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
</div>
|
|
1617
|
-
)}
|
|
1618
|
-
|
|
1619
|
-
{/* Podcast Player */}
|
|
1620
|
-
{msg.attachmentType === 'podcast' && msg.audioUrl && (
|
|
1621
|
-
<div className="mt-3 pt-3 border-t border-border overflow-hidden">
|
|
1622
|
-
<div className="flex items-center justify-between mb-3 min-w-0 overflow-hidden">
|
|
1623
|
-
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
|
1624
|
-
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-[var(--chart-4)] via-[var(--chart-1)] to-[var(--chart-4)] flex items-center justify-center flex-shrink-0">
|
|
1625
|
-
<Radio className="w-3 h-3 text-white" />
|
|
1760
|
+
<div className="flex items-center gap-1 flex-shrink-0">
|
|
1761
|
+
<Button
|
|
1762
|
+
variant="ghost"
|
|
1763
|
+
size="sm"
|
|
1764
|
+
onClick={() => msg.documentContent && msg.attachmentName && handleDownloadDocument(msg.documentContent, msg.attachmentName)}
|
|
1765
|
+
className="h-6 px-2 hover:bg-[var(--toast-success-bg)]/30 hover:text-[var(--toast-success-icon)]"
|
|
1766
|
+
title="Download"
|
|
1767
|
+
>
|
|
1768
|
+
<Download className="w-3 h-3" />
|
|
1769
|
+
</Button>
|
|
1770
|
+
<Button
|
|
1771
|
+
variant="ghost"
|
|
1772
|
+
size="sm"
|
|
1773
|
+
onClick={() => msg.documentContent && msg.documentTitle && handleEditDocument(msg.documentContent, msg.documentTitle)}
|
|
1774
|
+
className="h-6 px-2 hover:bg-[var(--toast-info-bg)]/30 hover:text-[var(--toast-info-icon)]"
|
|
1775
|
+
>
|
|
1776
|
+
<Edit className="w-3 h-3 mr-1" />
|
|
1777
|
+
Editar
|
|
1778
|
+
</Button>
|
|
1626
1779
|
</div>
|
|
1627
|
-
<span className="text-small text-muted-foreground break-words">
|
|
1628
|
-
{msg.attachmentName}
|
|
1629
|
-
</span>
|
|
1630
1780
|
</div>
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
>
|
|
1638
|
-
<
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
>
|
|
1646
|
-
<source src={msg.audioUrl} type="audio/mpeg" />
|
|
1647
|
-
Seu navegador não suporta o elemento de áudio.
|
|
1648
|
-
</audio>
|
|
1649
|
-
</div>
|
|
1650
|
-
</div>
|
|
1651
|
-
)}
|
|
1652
|
-
|
|
1653
|
-
{/* Search Results */}
|
|
1654
|
-
{msg.attachmentType === 'search' && msg.searchResults && (
|
|
1655
|
-
<div className="mt-3 pt-3 border-t border-border space-y-4 overflow-hidden">
|
|
1656
|
-
{/* Search Results List */}
|
|
1657
|
-
<div className="overflow-hidden">
|
|
1658
|
-
<div className="flex items-center gap-2 mb-3">
|
|
1659
|
-
<Search className="w-4 h-4 text-[var(--chart-4)]" />
|
|
1660
|
-
<h4 className="text-foreground">
|
|
1661
|
-
Resultados Encontrados ({msg.searchResults.length})
|
|
1662
|
-
</h4>
|
|
1781
|
+
)}
|
|
1782
|
+
|
|
1783
|
+
{msg.attachmentType && msg.attachmentType !== 'podcast' && msg.attachmentType !== 'document' && (
|
|
1784
|
+
<div className={cn(
|
|
1785
|
+
"flex items-center gap-2 mb-2 pb-2 border-b min-w-0 overflow-hidden",
|
|
1786
|
+
msg.type === 'user' ? "border-primary-foreground/20" : "border-border"
|
|
1787
|
+
)}>
|
|
1788
|
+
{msg.attachmentType === 'file' && <FileText className="w-4 h-4 flex-shrink-0" />}
|
|
1789
|
+
{msg.attachmentType === 'audio' && <Music className="w-4 h-4 flex-shrink-0" />}
|
|
1790
|
+
{msg.attachmentType === 'image' && <ImageIcon className="w-4 h-4 flex-shrink-0" />}
|
|
1791
|
+
<span className={cn(
|
|
1792
|
+
"text-small break-words",
|
|
1793
|
+
msg.type === 'user' ? "text-primary-foreground/90" : "text-muted-foreground"
|
|
1794
|
+
)}>{msg.attachmentName}</span>
|
|
1663
1795
|
</div>
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
{
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
<div className="flex items-start gap-2 min-w-0">
|
|
1682
|
-
<h5 className="text-small text-foreground break-words group-hover:text-primary transition-colors flex-1 min-w-0">
|
|
1683
|
-
{result.title}
|
|
1684
|
-
</h5>
|
|
1685
|
-
<span className="px-1.5 py-0.5 rounded text-small bg-[var(--chart-4)]/10 text-[var(--chart-4)] flex-shrink-0 self-start">
|
|
1686
|
-
{result.relevance}%
|
|
1687
|
-
</span>
|
|
1688
|
-
</div>
|
|
1689
|
-
<p className="text-sm text-muted-foreground mt-1 line-clamp-2 break-words">
|
|
1690
|
-
{result.description}
|
|
1691
|
-
</p>
|
|
1692
|
-
<div className="flex items-center gap-3 mt-2 flex-wrap min-w-0">
|
|
1693
|
-
<span className="text-sm text-muted-foreground flex items-center gap-1 min-w-0 max-w-full">
|
|
1694
|
-
<ExternalLink className="w-3 h-3 flex-shrink-0" />
|
|
1695
|
-
<span className="truncate">{result.path}</span>
|
|
1696
|
-
</span>
|
|
1697
|
-
{result.lastModified && (
|
|
1698
|
-
<span className="text-sm text-muted-foreground flex items-center gap-1 flex-shrink-0">
|
|
1699
|
-
<Clock className="w-3 h-3" />
|
|
1700
|
-
{result.lastModified}
|
|
1701
|
-
</span>
|
|
1702
|
-
)}
|
|
1703
|
-
</div>
|
|
1704
|
-
</div>
|
|
1796
|
+
)}
|
|
1797
|
+
|
|
1798
|
+
{/* Conteúdo da mensagem */}
|
|
1799
|
+
{msg.type === 'user' ? (
|
|
1800
|
+
<p className="whitespace-pre-wrap break-words text-primary-foreground">{msg.content}</p>
|
|
1801
|
+
) : (
|
|
1802
|
+
<>
|
|
1803
|
+
{/* Alerta visual para mensagens de erro */}
|
|
1804
|
+
{(msg.content.includes('🔐') || msg.content.includes('❌')) && (
|
|
1805
|
+
<div className="mb-3 p-3 bg-[var(--toast-error-bg)]/20 border border-[var(--toast-error-border)] rounded-lg overflow-hidden">
|
|
1806
|
+
<div className="flex items-start gap-2 min-w-0">
|
|
1807
|
+
<AlertCircle className="w-5 h-5 text-[var(--toast-error-icon)] flex-shrink-0 mt-0.5" />
|
|
1808
|
+
<div className="flex-1 min-w-0 overflow-hidden">
|
|
1809
|
+
<MarkdownMessage
|
|
1810
|
+
content={msg.content}
|
|
1811
|
+
className="text-foreground"
|
|
1812
|
+
/>
|
|
1705
1813
|
</div>
|
|
1706
1814
|
</div>
|
|
1707
1815
|
</div>
|
|
1708
|
-
)
|
|
1816
|
+
)}
|
|
1817
|
+
{/* Mensagem normal */}
|
|
1818
|
+
{!(msg.content.includes('🔐') || msg.content.includes('❌')) && (
|
|
1819
|
+
<MarkdownMessage
|
|
1820
|
+
content={msg.content}
|
|
1821
|
+
className="text-foreground"
|
|
1822
|
+
/>
|
|
1823
|
+
)}
|
|
1824
|
+
</>
|
|
1825
|
+
)}
|
|
1826
|
+
|
|
1827
|
+
{/* Image Attachment */}
|
|
1828
|
+
{msg.attachmentType === 'image' && msg.documentContent && (
|
|
1829
|
+
<div className="mt-3 rounded-lg overflow-hidden border border-border">
|
|
1830
|
+
<img
|
|
1831
|
+
src={msg.documentContent}
|
|
1832
|
+
alt={msg.attachmentName || 'Image'}
|
|
1833
|
+
className="w-full h-auto max-h-[300px] object-cover"
|
|
1834
|
+
/>
|
|
1709
1835
|
</div>
|
|
1710
|
-
|
|
1836
|
+
)}
|
|
1711
1837
|
|
|
1712
|
-
{/*
|
|
1713
|
-
{msg.
|
|
1714
|
-
<div>
|
|
1715
|
-
<
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1838
|
+
{/* Chart Rendering */}
|
|
1839
|
+
{msg.chartData && msg.chartConfig && (
|
|
1840
|
+
<div className="mt-4 mb-2 p-2 bg-background/50 rounded-lg border border-border">
|
|
1841
|
+
<ChartContainer config={msg.chartConfig}>
|
|
1842
|
+
<BarChart accessibilityLayer data={msg.chartData}>
|
|
1843
|
+
<CartesianGrid vertical={false} />
|
|
1844
|
+
<XAxis
|
|
1845
|
+
dataKey="month"
|
|
1846
|
+
tickLine={false}
|
|
1847
|
+
tickMargin={10}
|
|
1848
|
+
axisLine={false}
|
|
1849
|
+
tickFormatter={(value) => value.slice(0, 3)}
|
|
1850
|
+
/>
|
|
1851
|
+
<ChartTooltip
|
|
1852
|
+
cursor={false}
|
|
1853
|
+
content={<ChartTooltipContent indicator="dashed" />}
|
|
1854
|
+
/>
|
|
1855
|
+
<Bar dataKey="desktop" fill="var(--color-desktop)" radius={4} />
|
|
1856
|
+
<Bar dataKey="mobile" fill="var(--color-mobile)" radius={4} />
|
|
1857
|
+
</BarChart>
|
|
1858
|
+
</ChartContainer>
|
|
1859
|
+
</div>
|
|
1860
|
+
)}
|
|
1861
|
+
|
|
1862
|
+
{/* Table Rendering */}
|
|
1863
|
+
{msg.tableData && (
|
|
1864
|
+
<div className="mt-4 mb-2 rounded-md border">
|
|
1865
|
+
<Table>
|
|
1866
|
+
{msg.tableData.caption && <TableCaption>{msg.tableData.caption}</TableCaption>}
|
|
1867
|
+
<TableHeader>
|
|
1868
|
+
<TableRow>
|
|
1869
|
+
{msg.tableData.headers.map((header, index) => (
|
|
1870
|
+
<TableHead key={index}>{header}</TableHead>
|
|
1871
|
+
))}
|
|
1872
|
+
</TableRow>
|
|
1873
|
+
</TableHeader>
|
|
1874
|
+
<TableBody>
|
|
1875
|
+
{msg.tableData.rows.map((row, rowIndex) => (
|
|
1876
|
+
<TableRow key={rowIndex}>
|
|
1877
|
+
{row.map((cell, cellIndex) => (
|
|
1878
|
+
<TableCell key={cellIndex} className="font-medium">
|
|
1879
|
+
{cell}
|
|
1880
|
+
</TableCell>
|
|
1881
|
+
))}
|
|
1882
|
+
</TableRow>
|
|
1883
|
+
))}
|
|
1884
|
+
</TableBody>
|
|
1885
|
+
</Table>
|
|
1886
|
+
</div>
|
|
1887
|
+
)}
|
|
1888
|
+
|
|
1889
|
+
{/* Document Preview */}
|
|
1890
|
+
{msg.attachmentType === 'document' && msg.documentContent && !msg.chartData && (
|
|
1891
|
+
<div className="mt-3 pt-3 border-t border-border overflow-hidden">
|
|
1892
|
+
<FormattedDocument
|
|
1893
|
+
content={msg.documentContent}
|
|
1894
|
+
maxPreviewLength={250}
|
|
1895
|
+
/>
|
|
1896
|
+
</div>
|
|
1897
|
+
)}
|
|
1898
|
+
|
|
1899
|
+
{/* Podcast Player */}
|
|
1900
|
+
{msg.attachmentType === 'podcast' && msg.audioUrl && (
|
|
1901
|
+
<div className="mt-3 pt-3 border-t border-border overflow-hidden">
|
|
1902
|
+
<div className="flex items-center justify-between mb-3 min-w-0 overflow-hidden">
|
|
1903
|
+
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
|
1904
|
+
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-[var(--chart-4)] via-[var(--chart-1)] to-[var(--chart-4)] flex items-center justify-center flex-shrink-0">
|
|
1905
|
+
<Radio className="w-3 h-3 text-white" />
|
|
1726
1906
|
</div>
|
|
1727
|
-
|
|
1907
|
+
<span className="text-small text-muted-foreground break-words">
|
|
1908
|
+
{msg.attachmentName}
|
|
1909
|
+
</span>
|
|
1910
|
+
</div>
|
|
1911
|
+
<Button
|
|
1912
|
+
variant="ghost"
|
|
1913
|
+
size="sm"
|
|
1914
|
+
onClick={() => msg.audioUrl && msg.attachmentName && handleDownloadPodcast(msg.audioUrl, msg.attachmentName)}
|
|
1915
|
+
className="h-6 px-2 hover:bg-[var(--toast-success-bg)]/30 hover:text-[var(--toast-success-icon)] flex-shrink-0"
|
|
1916
|
+
title="Download"
|
|
1917
|
+
>
|
|
1918
|
+
<Download className="w-3 h-3" />
|
|
1919
|
+
</Button>
|
|
1920
|
+
</div>
|
|
1921
|
+
<div className="bg-gradient-to-r from-[var(--chart-1)]/10 to-[var(--chart-4)]/10 rounded-lg p-2 overflow-hidden">
|
|
1922
|
+
<audio
|
|
1923
|
+
controls
|
|
1924
|
+
className="w-full max-w-full h-10 outline-none"
|
|
1925
|
+
>
|
|
1926
|
+
<source src={msg.audioUrl} type="audio/mpeg" />
|
|
1927
|
+
Seu navegador não suporta o elemento de áudio.
|
|
1928
|
+
</audio>
|
|
1728
1929
|
</div>
|
|
1729
1930
|
</div>
|
|
1730
1931
|
)}
|
|
1731
1932
|
|
|
1732
|
-
{/* Search
|
|
1733
|
-
{msg.
|
|
1734
|
-
<div>
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1933
|
+
{/* Search Results */}
|
|
1934
|
+
{msg.attachmentType === 'search' && msg.searchResults && (
|
|
1935
|
+
<div className="mt-3 pt-3 border-t border-border space-y-4 overflow-hidden">
|
|
1936
|
+
{/* Search Results List */}
|
|
1937
|
+
<div className="overflow-hidden">
|
|
1938
|
+
<div className="flex items-center gap-2 mb-3">
|
|
1939
|
+
<Search className="w-4 h-4 text-[var(--chart-4)]" />
|
|
1940
|
+
<h4 className="text-foreground">
|
|
1941
|
+
Resultados Encontrados ({msg.searchResults.length})
|
|
1942
|
+
</h4>
|
|
1943
|
+
</div>
|
|
1944
|
+
<div className="space-y-2 overflow-hidden">
|
|
1945
|
+
{msg.searchResults.map((result) => (
|
|
1946
|
+
<div
|
|
1947
|
+
key={result.id}
|
|
1948
|
+
onClick={() => handleOpenSearchResult(result)}
|
|
1949
|
+
className="p-3 rounded-lg border border-border hover:bg-[var(--primary-light)] hover:border-[var(--primary-light-foreground)] transition-all cursor-pointer group overflow-hidden"
|
|
1950
|
+
>
|
|
1951
|
+
<div className="flex items-start justify-between gap-2 min-w-0">
|
|
1952
|
+
<div className="flex items-start gap-2 flex-1 min-w-0 overflow-hidden">
|
|
1953
|
+
<div className="mt-0.5 flex-shrink-0">
|
|
1954
|
+
{result.type === 'document' && <FileText className="w-4 h-4 text-[var(--chart-4)] group-hover:scale-110 transition-transform" />}
|
|
1955
|
+
{result.type === 'project' && <FolderOpen className="w-4 h-4 text-[var(--chart-1)] group-hover:scale-110 transition-transform" />}
|
|
1956
|
+
{result.type === 'conversation' && <MessageSquare className="w-4 h-4 text-[var(--chart-2)] group-hover:scale-110 transition-transform" />}
|
|
1957
|
+
{result.type === 'file' && <Folder className="w-4 h-4 text-[var(--chart-3)] group-hover:scale-110 transition-transform" />}
|
|
1958
|
+
{result.type === 'contact' && <Users className="w-4 h-4 text-[var(--chart-5)] group-hover:scale-110 transition-transform" />}
|
|
1959
|
+
</div>
|
|
1960
|
+
<div className="flex-1 min-w-0 overflow-hidden">
|
|
1961
|
+
<div className="flex items-start gap-2 min-w-0">
|
|
1962
|
+
<h5 className="text-small text-foreground break-words group-hover:text-primary transition-colors flex-1 min-w-0">
|
|
1963
|
+
{result.title}
|
|
1964
|
+
</h5>
|
|
1965
|
+
<span className="px-1.5 py-0.5 rounded text-small bg-[var(--chart-4)]/10 text-[var(--chart-4)] flex-shrink-0 self-start">
|
|
1966
|
+
{result.relevance}%
|
|
1967
|
+
</span>
|
|
1968
|
+
</div>
|
|
1969
|
+
<p className="text-sm text-muted-foreground mt-1 line-clamp-2 break-words">
|
|
1970
|
+
{result.description}
|
|
1971
|
+
</p>
|
|
1972
|
+
<div className="flex items-center gap-3 mt-2 flex-wrap min-w-0">
|
|
1973
|
+
<span className="text-sm text-muted-foreground flex items-center gap-1 min-w-0 max-w-full">
|
|
1974
|
+
<ExternalLink className="w-3 h-3 flex-shrink-0" />
|
|
1975
|
+
<span className="truncate">{result.path}</span>
|
|
1976
|
+
</span>
|
|
1977
|
+
{result.lastModified && (
|
|
1978
|
+
<span className="text-sm text-muted-foreground flex items-center gap-1 flex-shrink-0">
|
|
1979
|
+
<Clock className="w-3 h-3" />
|
|
1980
|
+
{result.lastModified}
|
|
1981
|
+
</span>
|
|
1982
|
+
)}
|
|
1983
|
+
</div>
|
|
1984
|
+
</div>
|
|
1985
|
+
</div>
|
|
1764
1986
|
</div>
|
|
1765
1987
|
</div>
|
|
1766
|
-
|
|
1767
|
-
|
|
1988
|
+
))}
|
|
1989
|
+
</div>
|
|
1768
1990
|
</div>
|
|
1991
|
+
|
|
1992
|
+
{/* Search Sources */}
|
|
1993
|
+
{msg.searchSources && (
|
|
1994
|
+
<div>
|
|
1995
|
+
<h4 className="text-foreground mb-2">
|
|
1996
|
+
Fontes Consultadas
|
|
1997
|
+
</h4>
|
|
1998
|
+
<div className="flex flex-wrap gap-2 overflow-hidden">
|
|
1999
|
+
{msg.searchSources.map((source, index) => (
|
|
2000
|
+
<div
|
|
2001
|
+
key={index}
|
|
2002
|
+
className="px-3 py-1.5 rounded-full bg-muted border border-border max-w-full"
|
|
2003
|
+
>
|
|
2004
|
+
<span className="text-sm font-medium text-foreground break-words">{source.name}</span>
|
|
2005
|
+
<span className="text-sm text-muted-foreground ml-1 whitespace-nowrap">({source.count})</span>
|
|
2006
|
+
</div>
|
|
2007
|
+
))}
|
|
2008
|
+
</div>
|
|
2009
|
+
</div>
|
|
2010
|
+
)}
|
|
2011
|
+
|
|
2012
|
+
{/* Search Commands */}
|
|
2013
|
+
{msg.searchCommands && msg.searchResults && (
|
|
2014
|
+
<div>
|
|
2015
|
+
<h4 className="text-foreground mb-2">
|
|
2016
|
+
O que você pode fazer com estes resultados
|
|
2017
|
+
</h4>
|
|
2018
|
+
<div className="grid grid-cols-1 gap-2">
|
|
2019
|
+
{msg.searchCommands.map((command) => (
|
|
2020
|
+
<button
|
|
2021
|
+
key={command.id}
|
|
2022
|
+
onClick={() => handleExecuteSearchCommand(
|
|
2023
|
+
command.id,
|
|
2024
|
+
msg.content.replace('🔍 Pesquisa realizada com sucesso!', '').split('"')[1] || 'pesquisa',
|
|
2025
|
+
msg.searchResults!,
|
|
2026
|
+
msg.id
|
|
2027
|
+
)}
|
|
2028
|
+
disabled={executingCommand === command.id}
|
|
2029
|
+
className={`flex items-start gap-2 p-2 rounded-lg border border-border hover:bg-[var(--primary-light)] hover:border-[var(--primary-light-foreground)] transition-all text-left disabled:opacity-50 disabled:cursor-not-allowed ${savedSearches.includes(msg.id) && command.id === '5' ? 'bg-[var(--toast-warning-bg)]/20 border-[var(--toast-warning-border)]' : ''
|
|
2030
|
+
}`}
|
|
2031
|
+
>
|
|
2032
|
+
{executingCommand === command.id ? (
|
|
2033
|
+
<Loader2 className="w-5 h-5 animate-spin flex-shrink-0 text-primary" />
|
|
2034
|
+
) : (
|
|
2035
|
+
<span className="text-lg flex-shrink-0">{command.icon}</span>
|
|
2036
|
+
)}
|
|
2037
|
+
<div className="flex-1 min-w-0 overflow-hidden">
|
|
2038
|
+
<div className="text-sm font-medium text-foreground break-words">
|
|
2039
|
+
{command.id === '5' && savedSearches.includes(msg.id) ? '⭐ Pesquisa salva' : command.label}
|
|
2040
|
+
</div>
|
|
2041
|
+
<div className="text-sm text-muted-foreground break-words">
|
|
2042
|
+
{executingCommand === command.id ? 'Processando...' : command.description}
|
|
2043
|
+
</div>
|
|
2044
|
+
</div>
|
|
2045
|
+
</button>
|
|
2046
|
+
))}
|
|
2047
|
+
</div>
|
|
2048
|
+
</div>
|
|
2049
|
+
)}
|
|
1769
2050
|
</div>
|
|
1770
2051
|
)}
|
|
1771
2052
|
</div>
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
2053
|
+
|
|
2054
|
+
{/* Message Actions */}
|
|
2055
|
+
<div className="flex items-center gap-2 mt-1 px-2">
|
|
2056
|
+
<span className="text-sm text-muted-foreground">
|
|
2057
|
+
{msg.timestamp.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}
|
|
2058
|
+
</span>
|
|
2059
|
+
|
|
2060
|
+
<Button
|
|
2061
|
+
variant="ghost"
|
|
2062
|
+
size="sm"
|
|
2063
|
+
onClick={() => handleToggleFavorite(msg.id)}
|
|
2064
|
+
className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive"
|
|
2065
|
+
>
|
|
2066
|
+
<Heart className={`w-3 h-3 ${msg.isFavorite ? 'fill-current text-destructive' : ''}`} />
|
|
2067
|
+
</Button>
|
|
2068
|
+
|
|
2069
|
+
{/* Botão Gerar Podcast - apenas para mensagens do assistente */}
|
|
2070
|
+
{msg.type === 'assistant' && msg.attachmentType !== 'podcast' && (
|
|
2071
|
+
<Button
|
|
2072
|
+
variant="ghost"
|
|
2073
|
+
size="sm"
|
|
2074
|
+
onClick={() => handleGeneratePodcast(msg.id, msg.content)}
|
|
2075
|
+
disabled={generatingPodcastId === msg.id}
|
|
2076
|
+
className="h-6 w-6 p-0 text-muted-foreground hover:text-primary disabled:opacity-50"
|
|
2077
|
+
title="Gerar Podcast"
|
|
2078
|
+
>
|
|
2079
|
+
{generatingPodcastId === msg.id ? (
|
|
2080
|
+
<Loader2 className="w-3 h-3 animate-spin" />
|
|
2081
|
+
) : (
|
|
2082
|
+
<Radio className="w-3 h-3" />
|
|
2083
|
+
)}
|
|
2084
|
+
</Button>
|
|
2085
|
+
)}
|
|
2086
|
+
|
|
2087
|
+
<Button
|
|
2088
|
+
variant="ghost"
|
|
2089
|
+
size="sm"
|
|
2090
|
+
onClick={() => handleCopyMessage(msg.content, msg.id)}
|
|
2091
|
+
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
|
|
2092
|
+
>
|
|
2093
|
+
{copiedId === msg.id ? (
|
|
2094
|
+
<Check className="w-3 h-3 text-[var(--toast-success-icon)]" />
|
|
2095
|
+
) : (
|
|
2096
|
+
<Copy className="w-3 h-3" />
|
|
2097
|
+
)}
|
|
2098
|
+
</Button>
|
|
2099
|
+
</div>
|
|
2100
|
+
|
|
2101
|
+
{/* Feedback UI */}
|
|
2102
|
+
{msg.type === 'assistant' && (
|
|
2103
|
+
<div className="px-2 pb-2">
|
|
2104
|
+
<div className="flex items-center gap-1 mt-2">
|
|
2105
|
+
<span className="text-xs text-muted-foreground mr-2">Essa resposta foi útil?</span>
|
|
2106
|
+
<Button
|
|
2107
|
+
variant="ghost"
|
|
2108
|
+
size="icon"
|
|
2109
|
+
className={cn(
|
|
2110
|
+
"h-6 w-6 rounded-full hover:bg-green-100 dark:hover:bg-green-900/20 hover:text-green-600",
|
|
2111
|
+
msg.evaluation === 'like' && "text-green-600 bg-green-100 dark:bg-green-900/20"
|
|
2112
|
+
)}
|
|
2113
|
+
onClick={() => handleEvaluation(msg.id, 'like')}
|
|
2114
|
+
title="Gostei"
|
|
2115
|
+
>
|
|
2116
|
+
<ThumbsUp className="h-3.5 w-3.5" />
|
|
2117
|
+
</Button>
|
|
2118
|
+
|
|
2119
|
+
<DropdownMenu>
|
|
2120
|
+
<DropdownMenuTrigger asChild>
|
|
2121
|
+
<Button
|
|
2122
|
+
variant="ghost"
|
|
2123
|
+
size="icon"
|
|
2124
|
+
className={cn(
|
|
2125
|
+
"h-6 w-6 rounded-full hover:bg-red-100 dark:hover:bg-red-900/20 hover:text-red-600",
|
|
2126
|
+
msg.evaluation === 'dislike' && "text-red-600 bg-red-100 dark:bg-red-900/20"
|
|
2127
|
+
)}
|
|
2128
|
+
title="Não gostei"
|
|
2129
|
+
>
|
|
2130
|
+
<ThumbsDown className="h-3.5 w-3.5" />
|
|
2131
|
+
</Button>
|
|
2132
|
+
</DropdownMenuTrigger>
|
|
2133
|
+
<DropdownMenuContent align="start">
|
|
2134
|
+
<DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, 'Não era o que eu procurava')}>
|
|
2135
|
+
Não era o que eu procurava
|
|
2136
|
+
</DropdownMenuItem>
|
|
2137
|
+
<DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, 'Informação incorreta')}>
|
|
2138
|
+
Informação incorreta
|
|
2139
|
+
</DropdownMenuItem>
|
|
2140
|
+
<DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, 'Resposta incompleta')}>
|
|
2141
|
+
Resposta incompleta
|
|
2142
|
+
</DropdownMenuItem>
|
|
2143
|
+
<DropdownMenuItem onClick={() => openFeedbackDialog(msg.id, null)}>
|
|
2144
|
+
Outros...
|
|
2145
|
+
</DropdownMenuItem>
|
|
2146
|
+
</DropdownMenuContent>
|
|
2147
|
+
</DropdownMenu>
|
|
2148
|
+
</div>
|
|
2149
|
+
</div>
|
|
2150
|
+
)}
|
|
2151
|
+
</div>
|
|
2152
|
+
</motion.div>
|
|
2153
|
+
))}
|
|
2154
|
+
|
|
2155
|
+
{/* Typing Indicator */}
|
|
2156
|
+
{isTyping && (
|
|
2157
|
+
<motion.div
|
|
2158
|
+
initial={{ opacity: 0, y: 10 }}
|
|
2159
|
+
animate={{ opacity: 1, y: 0 }}
|
|
2160
|
+
className="flex gap-2 justify-start"
|
|
2161
|
+
>
|
|
2162
|
+
{/* Avatar do Assistente */}
|
|
2163
|
+
<div className="flex-shrink-0 pt-1">
|
|
2164
|
+
<XerticaOrbe size={32} />
|
|
2165
|
+
</div>
|
|
2166
|
+
|
|
2167
|
+
<div className="bg-muted rounded-2xl px-4 py-3">
|
|
2168
|
+
<div className="flex gap-1">
|
|
2169
|
+
<motion.div
|
|
2170
|
+
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
2171
|
+
animate={{ y: [0, -8, 0] }}
|
|
2172
|
+
transition={{ repeat: Infinity, duration: 0.6, delay: 0 }}
|
|
2173
|
+
/>
|
|
2174
|
+
<motion.div
|
|
2175
|
+
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
2176
|
+
animate={{ y: [0, -8, 0] }}
|
|
2177
|
+
transition={{ repeat: Infinity, duration: 0.6, delay: 0.2 }}
|
|
2178
|
+
/>
|
|
2179
|
+
<motion.div
|
|
2180
|
+
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
2181
|
+
animate={{ y: [0, -8, 0] }}
|
|
2182
|
+
transition={{ repeat: Infinity, duration: 0.6, delay: 0.4 }}
|
|
2183
|
+
/>
|
|
2184
|
+
</div>
|
|
2185
|
+
</div>
|
|
2186
|
+
</motion.div>
|
|
2187
|
+
)}
|
|
2188
|
+
|
|
2189
|
+
<div ref={messagesEndRef} />
|
|
2190
|
+
</div>
|
|
2191
|
+
</ScrollArea>
|
|
2192
|
+
)}
|
|
2193
|
+
</div>
|
|
2194
|
+
)}
|
|
2195
|
+
|
|
2196
|
+
<Dialog open={feedbackDialogOpen} onOpenChange={setFeedbackDialogOpen}>
|
|
2197
|
+
<DialogContent className="sm:max-w-[600px]">
|
|
2198
|
+
<DialogHeader>
|
|
2199
|
+
<DialogTitle className="pr-8">
|
|
2200
|
+
{feedbackCategory ? `Enviar feedback: ${feedbackCategory}` : 'Enviar feedback'}
|
|
2201
|
+
</DialogTitle>
|
|
2202
|
+
<DialogDescription>
|
|
2203
|
+
{feedbackCategory
|
|
2204
|
+
? "Gostaria de adicionar algum comentário? (Opcional)"
|
|
2205
|
+
: "Conte-nos por que essa resposta não foi útil para que possamos melhorar."}
|
|
2206
|
+
</DialogDescription>
|
|
2207
|
+
</DialogHeader>
|
|
2208
|
+
<div className="grid gap-4 py-4 px-6">
|
|
2209
|
+
<Textarea
|
|
2210
|
+
className="min-h-[100px]"
|
|
2211
|
+
placeholder={feedbackCategory ? "Comentário adicional..." : "Descreva o motivo..."}
|
|
2212
|
+
value={feedbackReason}
|
|
2213
|
+
onChange={(e) => setFeedbackReason(e.target.value)}
|
|
2214
|
+
/>
|
|
2215
|
+
</div>
|
|
2216
|
+
<DialogFooter>
|
|
2217
|
+
<Button variant="outline" onClick={() => setFeedbackDialogOpen(false)}>Cancelar</Button>
|
|
2218
|
+
<Button
|
|
2219
|
+
onClick={submitFeedbackDialog}
|
|
2220
|
+
disabled={!feedbackCategory && !feedbackReason.trim()}
|
|
2221
|
+
>
|
|
2222
|
+
{feedbackCategory ? 'Confirmar e Enviar' : 'Enviar feedback'}
|
|
2223
|
+
</Button>
|
|
2224
|
+
</DialogFooter>
|
|
2225
|
+
</DialogContent>
|
|
2226
|
+
</Dialog>
|
|
2227
|
+
|
|
2228
|
+
{(abaSelecionada === 'historico' || abaSelecionada === 'favoritos') && (
|
|
2229
|
+
<ScrollArea className="flex-1 min-h-0">
|
|
2230
|
+
<div className={`p-4 ${isFullPage ? 'mx-auto w-full max-w-6xl' : ''}`}>
|
|
2231
|
+
{/* New Conversation Button */}
|
|
2232
|
+
<Button
|
|
2233
|
+
variant="outline"
|
|
2234
|
+
size="sm"
|
|
2235
|
+
onClick={handleNovaConversa}
|
|
2236
|
+
className="w-full mb-4 justify-start"
|
|
2237
|
+
>
|
|
2238
|
+
<Plus className="w-4 h-4 mr-2" />
|
|
2239
|
+
Nova Conversa
|
|
2240
|
+
</Button>
|
|
2241
|
+
|
|
2242
|
+
{/* Conversations List */}
|
|
2243
|
+
<div className="space-y-2">
|
|
2244
|
+
{conversasFiltradas.length === 0 ? (
|
|
2245
|
+
<div className="text-center py-8">
|
|
2246
|
+
<Heart className="w-12 h-12 mx-auto text-gray-300 dark:text-gray-600 mb-2" />
|
|
2247
|
+
<p className="text-muted-foreground">
|
|
2248
|
+
{abaSelecionada === 'favoritos'
|
|
2249
|
+
? 'Nenhuma conversa favorita ainda'
|
|
2250
|
+
: 'Nenhuma conversa no histórico'}
|
|
2251
|
+
</p>
|
|
2252
|
+
</div>
|
|
2253
|
+
) : (
|
|
2254
|
+
conversasFiltradas.map((conversa) => (
|
|
2255
|
+
<div
|
|
2256
|
+
key={conversa.id}
|
|
2257
|
+
onClick={() => handleSelecionarConversa(conversa.id)}
|
|
2258
|
+
className={`p-3 rounded-lg hover:bg-muted cursor-pointer transition-colors duration-200 border ${conversa.id === conversaAtual
|
|
2259
|
+
? 'border-primary bg-primary/5'
|
|
2260
|
+
: 'border-border'
|
|
2261
|
+
}`}
|
|
2262
|
+
>
|
|
2263
|
+
<div className="flex items-start justify-between mb-1">
|
|
2264
|
+
<h4 className="text-small text-gray-900 dark:text-gray-100 truncate flex-1">
|
|
2265
|
+
{conversa.titulo}
|
|
2266
|
+
</h4>
|
|
1792
2267
|
<Button
|
|
1793
2268
|
variant="ghost"
|
|
1794
2269
|
size="sm"
|
|
1795
|
-
onClick={() =>
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
2270
|
+
onClick={(e) => {
|
|
2271
|
+
e.stopPropagation();
|
|
2272
|
+
handleToggleFavoritaConversa(conversa.id);
|
|
2273
|
+
}}
|
|
2274
|
+
className="h-6 w-6 p-0 flex-shrink-0 ml-1"
|
|
1799
2275
|
>
|
|
1800
|
-
{
|
|
1801
|
-
<Loader2 className="w-3 h-3 animate-spin" />
|
|
1802
|
-
) : (
|
|
1803
|
-
<Radio className="w-3 h-3" />
|
|
1804
|
-
)}
|
|
2276
|
+
<Heart className={`w-3 h-3 ${conversa.favorita ? 'text-red-500 fill-current' : 'text-gray-400'}`} />
|
|
1805
2277
|
</Button>
|
|
2278
|
+
</div>
|
|
2279
|
+
{conversa.ultimaMensagem && (
|
|
2280
|
+
<p className="text-sm text-muted-foreground truncate mb-1">
|
|
2281
|
+
{conversa.ultimaMensagem}
|
|
2282
|
+
</p>
|
|
1806
2283
|
)}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
size="sm"
|
|
1811
|
-
onClick={() => handleCopyMessage(msg.content, msg.id)}
|
|
1812
|
-
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
|
|
1813
|
-
>
|
|
1814
|
-
{copiedId === msg.id ? (
|
|
1815
|
-
<Check className="w-3 h-3 text-[var(--toast-success-icon)]" />
|
|
1816
|
-
) : (
|
|
1817
|
-
<Copy className="w-3 h-3" />
|
|
1818
|
-
)}
|
|
1819
|
-
</Button>
|
|
2284
|
+
<p className="text-sm text-muted-foreground">
|
|
2285
|
+
{conversa.timestamp}
|
|
2286
|
+
</p>
|
|
1820
2287
|
</div>
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
{/* Typing Indicator */}
|
|
1826
|
-
{isTyping && (
|
|
1827
|
-
<motion.div
|
|
1828
|
-
initial={{ opacity: 0, y: 10 }}
|
|
1829
|
-
animate={{ opacity: 1, y: 0 }}
|
|
1830
|
-
className="flex gap-2 justify-start"
|
|
1831
|
-
>
|
|
1832
|
-
{/* Avatar do Assistente */}
|
|
1833
|
-
<div className="flex-shrink-0 pt-1">
|
|
1834
|
-
<XerticaOrbe size={32} />
|
|
1835
|
-
</div>
|
|
1836
|
-
|
|
1837
|
-
<div className="bg-muted rounded-2xl px-4 py-3">
|
|
1838
|
-
<div className="flex gap-1">
|
|
1839
|
-
<motion.div
|
|
1840
|
-
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
1841
|
-
animate={{ y: [0, -8, 0] }}
|
|
1842
|
-
transition={{ repeat: Infinity, duration: 0.6, delay: 0 }}
|
|
1843
|
-
/>
|
|
1844
|
-
<motion.div
|
|
1845
|
-
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
1846
|
-
animate={{ y: [0, -8, 0] }}
|
|
1847
|
-
transition={{ repeat: Infinity, duration: 0.6, delay: 0.2 }}
|
|
1848
|
-
/>
|
|
1849
|
-
<motion.div
|
|
1850
|
-
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
1851
|
-
animate={{ y: [0, -8, 0] }}
|
|
1852
|
-
transition={{ repeat: Infinity, duration: 0.6, delay: 0.4 }}
|
|
1853
|
-
/>
|
|
1854
|
-
</div>
|
|
1855
|
-
</div>
|
|
1856
|
-
</motion.div>
|
|
1857
|
-
)}
|
|
1858
|
-
|
|
1859
|
-
<div ref={messagesEndRef} />
|
|
2288
|
+
))
|
|
2289
|
+
)}
|
|
2290
|
+
</div>
|
|
1860
2291
|
</div>
|
|
1861
2292
|
</ScrollArea>
|
|
1862
2293
|
)}
|
|
1863
2294
|
</div>
|
|
1864
|
-
)}
|
|
1865
2295
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
<
|
|
1869
|
-
{
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
conversa.id === conversaAtual
|
|
1898
|
-
? 'border-primary bg-primary/5'
|
|
1899
|
-
: 'border-border'
|
|
1900
|
-
}`}
|
|
1901
|
-
>
|
|
1902
|
-
<div className="flex items-start justify-between mb-1">
|
|
1903
|
-
<h4 className="text-small text-gray-900 dark:text-gray-100 truncate flex-1">
|
|
1904
|
-
{conversa.titulo}
|
|
1905
|
-
</h4>
|
|
1906
|
-
<Button
|
|
1907
|
-
variant="ghost"
|
|
1908
|
-
size="sm"
|
|
1909
|
-
onClick={(e) => {
|
|
1910
|
-
e.stopPropagation();
|
|
1911
|
-
handleToggleFavoritaConversa(conversa.id);
|
|
1912
|
-
}}
|
|
1913
|
-
className="h-6 w-6 p-0 flex-shrink-0 ml-1"
|
|
1914
|
-
>
|
|
1915
|
-
<Heart className={`w-3 h-3 ${conversa.favorita ? 'text-red-500 fill-current' : 'text-gray-400'}`} />
|
|
1916
|
-
</Button>
|
|
1917
|
-
</div>
|
|
1918
|
-
{conversa.ultimaMensagem && (
|
|
1919
|
-
<p className="text-sm text-muted-foreground truncate mb-1">
|
|
1920
|
-
{conversa.ultimaMensagem}
|
|
1921
|
-
</p>
|
|
1922
|
-
)}
|
|
1923
|
-
<p className="text-sm text-muted-foreground">
|
|
1924
|
-
{conversa.timestamp}
|
|
1925
|
-
</p>
|
|
1926
|
-
</div>
|
|
1927
|
-
))
|
|
1928
|
-
)}
|
|
1929
|
-
</div>
|
|
1930
|
-
</div>
|
|
1931
|
-
</ScrollArea>
|
|
1932
|
-
)}
|
|
1933
|
-
</div>
|
|
2296
|
+
{/* Modern Input Area - Only visible in chat mode */}
|
|
2297
|
+
{abaSelecionada === 'chat' && (
|
|
2298
|
+
<ModernChatInput
|
|
2299
|
+
value={mensagem}
|
|
2300
|
+
onChange={setMensagem}
|
|
2301
|
+
onSubmit={handleEnviarMensagem}
|
|
2302
|
+
onFileUpload={() => fileInputRef.current?.click()}
|
|
2303
|
+
onAudioUpload={() => audioInputRef.current?.click()}
|
|
2304
|
+
onVoiceRecording={(transcript) => {
|
|
2305
|
+
// Ao finalizar gravação, definir a mensagem transcrita e enviar
|
|
2306
|
+
setMensagem(transcript);
|
|
2307
|
+
toast.success('Áudio transcrito com sucesso!', {
|
|
2308
|
+
description: 'Sua mensagem foi convertida de voz para texto.',
|
|
2309
|
+
duration: 3000,
|
|
2310
|
+
});
|
|
2311
|
+
// Enviar automaticamente após um pequeno delay para o usuário ver a transcrição
|
|
2312
|
+
setTimeout(() => {
|
|
2313
|
+
if (!isTyping) {
|
|
2314
|
+
handleEnviarMensagem();
|
|
2315
|
+
}
|
|
2316
|
+
}, 1500);
|
|
2317
|
+
}}
|
|
2318
|
+
placeholder="Envie uma mensagem para Xertica"
|
|
2319
|
+
disabled={isTyping}
|
|
2320
|
+
isFullPage={isFullPage}
|
|
2321
|
+
/>
|
|
2322
|
+
)}
|
|
2323
|
+
</motion.div>
|
|
2324
|
+
)}
|
|
2325
|
+
</AnimatePresence>
|
|
2326
|
+
</div>
|
|
1934
2327
|
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
// Ao finalizar gravação, definir a mensagem transcrita e enviar
|
|
1945
|
-
setMensagem(transcript);
|
|
1946
|
-
toast.success('Áudio transcrito com sucesso!', {
|
|
1947
|
-
description: 'Sua mensagem foi convertida de voz para texto.',
|
|
1948
|
-
duration: 3000,
|
|
1949
|
-
});
|
|
1950
|
-
// Enviar automaticamente após um pequeno delay para o usuário ver a transcrição
|
|
1951
|
-
setTimeout(() => {
|
|
1952
|
-
if (!isTyping) {
|
|
1953
|
-
handleEnviarMensagem();
|
|
1954
|
-
}
|
|
1955
|
-
}, 1500);
|
|
1956
|
-
}}
|
|
1957
|
-
placeholder="Envie uma mensagem para Xertica"
|
|
1958
|
-
disabled={isTyping}
|
|
1959
|
-
isFullPage={isFullPage}
|
|
2328
|
+
{/* Document Editor - Right Side */}
|
|
2329
|
+
{isFullPage ? (
|
|
2330
|
+
// Modo fullPage: editor fixo ocupando 50% da tela quando aberto
|
|
2331
|
+
editingDocument && (
|
|
2332
|
+
<div className="w-1/2 h-full bg-background border-l border-border">
|
|
2333
|
+
<DocumentEditor
|
|
2334
|
+
initialContent={editingDocument.content}
|
|
2335
|
+
initialTitle={editingDocument.title}
|
|
2336
|
+
onClose={handleCloseEditor}
|
|
1960
2337
|
/>
|
|
2338
|
+
</div>
|
|
2339
|
+
)
|
|
2340
|
+
) : (
|
|
2341
|
+
// Modo sidebar: editor com animação deslizante
|
|
2342
|
+
<AnimatePresence>
|
|
2343
|
+
{editingDocument && isExpanded && (
|
|
2344
|
+
<motion.div
|
|
2345
|
+
initial={{ x: 600 }}
|
|
2346
|
+
animate={{ x: 0 }}
|
|
2347
|
+
exit={{ x: 600 }}
|
|
2348
|
+
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
|
2349
|
+
className="w-[600px] h-full bg-background border-l border-border"
|
|
2350
|
+
>
|
|
2351
|
+
<DocumentEditor
|
|
2352
|
+
initialContent={editingDocument.content}
|
|
2353
|
+
initialTitle={editingDocument.title}
|
|
2354
|
+
onClose={handleCloseEditor}
|
|
2355
|
+
/>
|
|
2356
|
+
</motion.div>
|
|
1961
2357
|
)}
|
|
1962
|
-
</
|
|
2358
|
+
</AnimatePresence>
|
|
1963
2359
|
)}
|
|
1964
|
-
</
|
|
2360
|
+
</div>
|
|
1965
2361
|
</div>
|
|
1966
|
-
|
|
1967
|
-
{/* Document Editor - Right Side */}
|
|
1968
|
-
{isFullPage ? (
|
|
1969
|
-
// Modo fullPage: editor fixo ocupando 50% da tela quando aberto
|
|
1970
|
-
editingDocument && (
|
|
1971
|
-
<div className="w-1/2 h-full bg-background border-l border-border">
|
|
1972
|
-
<DocumentEditor
|
|
1973
|
-
initialContent={editingDocument.content}
|
|
1974
|
-
initialTitle={editingDocument.title}
|
|
1975
|
-
onClose={handleCloseEditor}
|
|
1976
|
-
/>
|
|
1977
|
-
</div>
|
|
1978
|
-
)
|
|
1979
|
-
) : (
|
|
1980
|
-
// Modo sidebar: editor com animação deslizante
|
|
1981
|
-
<AnimatePresence>
|
|
1982
|
-
{editingDocument && isExpanded && (
|
|
1983
|
-
<motion.div
|
|
1984
|
-
initial={{ x: 600 }}
|
|
1985
|
-
animate={{ x: 0 }}
|
|
1986
|
-
exit={{ x: 600 }}
|
|
1987
|
-
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
|
1988
|
-
className="w-[600px] h-full bg-background border-l border-border"
|
|
1989
|
-
>
|
|
1990
|
-
<DocumentEditor
|
|
1991
|
-
initialContent={editingDocument.content}
|
|
1992
|
-
initialTitle={editingDocument.title}
|
|
1993
|
-
onClose={handleCloseEditor}
|
|
1994
|
-
/>
|
|
1995
|
-
</motion.div>
|
|
1996
|
-
)}
|
|
1997
|
-
</AnimatePresence>
|
|
1998
|
-
)}
|
|
1999
|
-
</div>
|
|
2000
|
-
</div>
|
|
2001
2362
|
</>
|
|
2002
2363
|
);
|
|
2003
2364
|
}
|