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.
@@ -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
- errorMessage.toLowerCase().includes('reported') ||
269
- errorMessage.toLowerCase().includes('403');
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
- textoNormalizado.includes('converta') || textoNormalizado.includes('converter')) &&
690
- (textoNormalizado.includes('áudio') || textoNormalizado.includes('audio'))) {
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
- textoNormalizado.includes('escreva um') || textoNormalizado.includes('escreva uma'))) {
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
- textoNormalizado.startsWith('quais ') || textoNormalizado.startsWith('quem ') ||
743
- textoNormalizado.startsWith('como encontrar') || textoNormalizado.startsWith('tem algum') ||
744
- textoNormalizado.startsWith('tem alguma') || textoNormalizado.includes('você tem')) &&
745
- !textoNormalizado.includes('documento') && !textoNormalizado.includes('relatório') &&
746
- !textoNormalizado.includes('relatorio') && !textoNormalizado.includes('podcast')) {
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 className={`${
1230
- isFullPage
1231
- ? 'w-full h-full'
1232
- : `fixed top-0 right-0 h-full z-40 transition-all duration-300 ease-in-out ${
1233
- editingDocument && isExpanded ? '' : isExpanded ? 'w-[420px]' : 'w-20'
1234
- } hidden md:block`
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
- {/* Hidden file inputs */}
1238
- <input
1239
- ref={fileInputRef}
1240
- type="file"
1241
- accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,.csv"
1242
- onChange={handleFileUpload}
1243
- className="hidden"
1244
- />
1245
- <input
1246
- ref={audioInputRef}
1247
- type="file"
1248
- accept="audio/*"
1249
- onChange={handleAudioUpload}
1250
- className="hidden"
1251
- />
1252
-
1253
- {/* Assistente Sidebar */}
1254
- <div className={`h-full flex flex-col ${
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
- editingDocument && isExpanded ? 'w-[420px]' : 'w-full'
1259
- }`
1260
- }`}>
1261
-
1262
- {/* Header - Toggle Button - Apenas visível quando não está em fullPage */}
1263
- {!isFullPage && (
1264
- <div className="border-b border-border flex items-center justify-between px-[14px] px-[18px] py-[16px]">
1265
- {isExpanded && (
1266
- <motion.div
1267
- initial={{ opacity: 0, x: 20 }}
1268
- animate={{ opacity: 1, x: 0 }}
1269
- exit={{ opacity: 0, x: 20 }}
1270
- className="flex items-center gap-2"
1271
- >
1272
- <XerticaOrbe size={32} />
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
- {/* Collapsed State - Icons Only */}
1307
- {!isExpanded && !isFullPage && (
1308
- <div className="flex flex-col items-center p-4 space-y-4">
1309
- {/* Ícone do Assistente - Xertica Orbe */}
1310
- <Tooltip>
1311
- <TooltipTrigger asChild>
1312
- <button
1313
- onClick={onToggle}
1314
- className="flex items-center justify-center hover:opacity-90 transition-opacity duration-200 cursor-pointer"
1315
- >
1316
- <XerticaOrbe size={32} />
1317
- </button>
1318
- </TooltipTrigger>
1319
- <AssistantTooltipContent
1320
- side="left"
1321
- sideOffset={8}
1322
- >
1323
- <p>Assistente Xertica</p>
1324
- </AssistantTooltipContent>
1325
- </Tooltip>
1326
-
1327
- <Tooltip>
1328
- <TooltipTrigger asChild>
1329
- <Button
1330
- variant="ghost"
1331
- size="sm"
1332
- onClick={() => handleExpandWithTab('chat')}
1333
- className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
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
- {/* Expanded State - Full Content */}
1387
- <AnimatePresence>
1388
- {(isExpanded || isFullPage) && (
1389
- <motion.div
1390
- initial={isFullPage ? false : { opacity: 0, x: 50 }}
1391
- animate={{ opacity: 1, x: 0 }}
1392
- exit={isFullPage ? undefined : { opacity: 0, x: 50 }}
1393
- transition={{ duration: 0.2 }}
1394
- className="flex-1 flex flex-col overflow-hidden"
1395
- >
1396
- {/* Navigation Tabs - Oculto em modo fullPage */}
1397
- {!isFullPage && (
1398
- <div className="px-4 py-2 border-b border-border">
1399
- <div className="flex gap-1">
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={abaSelecionada === 'chat' ? 'default' : 'ghost'}
1517
+ variant="ghost"
1402
1518
  size="sm"
1403
- onClick={() => setAbaSelecionada('chat')}
1404
- className="flex-1 h-8 text-xs"
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-3 h-3 mr-1" />
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={abaSelecionada === 'historico' ? 'default' : 'ghost'}
1536
+ variant="ghost"
1411
1537
  size="sm"
1412
- onClick={() => setAbaSelecionada('historico')}
1413
- className="flex-1 h-8 text-xs"
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
- <History className="w-3 h-3 mr-1" />
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={abaSelecionada === 'favoritos' ? 'default' : 'ghost'}
1555
+ variant="ghost"
1420
1556
  size="sm"
1421
- onClick={() => setAbaSelecionada('favoritos')}
1422
- className="flex-1 h-8 text-xs"
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
- <Heart className="w-3 h-3 mr-1" />
1425
- Favoritos
1560
+ <History className="w-4 h-4" />
1426
1561
  </Button>
1427
- </div>
1428
- </div>
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
- {/* Content Area */}
1432
- <div className="flex-1 overflow-hidden flex flex-col">
1433
- {abaSelecionada === 'chat' && (
1434
- <div className="flex-1 flex flex-col min-h-0 w-full">
1435
- {/* API Key Warning Banner */}
1436
- {!isApiKeyValid && (
1437
- <div className="mx-4 mt-3 p-4 bg-[var(--toast-warning-bg)]/20 border-2 border-[var(--toast-warning-border)] rounded-lg">
1438
- <div className="flex items-start gap-3">
1439
- <AlertCircle className="w-5 h-5 text-[var(--toast-warning-icon)] flex-shrink-0 mt-0.5" />
1440
- <div className="flex-1 space-y-3">
1441
- <p className="text-small text-foreground">
1442
- {!geminiApiKey ? (
1443
- <strong>Nenhuma chave de API configurada</strong>
1444
- ) : (
1445
- <strong>🔐 Chave de API inválida ou vazada</strong>
1446
- )}
1447
- </p>
1448
- <p className="text-small text-muted-foreground">
1449
- Configure uma nova chave do Google Gemini para ativar respostas inteligentes do assistente.
1450
- </p>
1451
- <div className="flex flex-wrap items-center gap-2">
1452
- <Button
1453
- onClick={() => navigate('/settings')}
1454
- size="sm"
1455
- className="bg-[var(--toast-warning-icon)] hover:opacity-90 text-white rounded-[12px] shadow-sm"
1456
- >
1457
- Ir para Configurações
1458
- </Button>
1459
- <a
1460
- href="https://aistudio.google.com/apikey"
1461
- target="_blank"
1462
- rel="noopener noreferrer"
1463
- className="inline-flex items-center gap-1 text-small text-[var(--toast-warning-icon)] underline hover:opacity-80"
1464
- >
1465
- Obter chave gratuitamente
1466
- <ExternalLink className="w-3 h-3" />
1467
- </a>
1468
- </div>
1469
- </div>
1470
- </div>
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
- {mensagens.length === 0 ? (
1475
- <div className="flex-1 overflow-y-auto min-h-0">
1476
- {/* Welcome Message */}
1477
- <div className="p-6 text-center">
1478
- <div className="mx-auto mb-4 flex items-center justify-center">
1479
- <XerticaOrbe size={64} />
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
- <h3 className="text-foreground mb-2">
1482
- Olá, Ariel
1483
- </h3>
1484
- <p className="text-muted-foreground">
1485
- Como posso ajudar?
1486
- </p>
1487
- </div>
1659
+ )}
1488
1660
 
1489
- {/* Suggestions */}
1490
- <div className="px-4 pb-4 space-y-2">
1491
- {sugestoes.map((sugestao) => (
1492
- <button
1493
- key={sugestao.id}
1494
- onClick={() => setMensagem(sugestao.texto)}
1495
- className="w-full p-3 text-left bg-muted hover:bg-accent rounded-lg transition-colors duration-200 text-foreground"
1496
- >
1497
- {sugestao.texto}
1498
- </button>
1499
- ))}
1500
-
1501
- <Button
1502
- variant="ghost"
1503
- size="sm"
1504
- className="w-full justify-start text-muted-foreground hover:text-foreground"
1505
- >
1506
- <MoreHorizontal className="w-4 h-4 mr-2" />
1507
- Mais sugestões
1508
- </Button>
1509
- </div>
1510
- </div>
1511
- ) : (
1512
- <ScrollArea className="flex-1 min-h-0 overflow-x-hidden">
1513
- <div className="space-y-4 px-4 py-4 overflow-x-hidden w-full">
1514
- {mensagens.map((msg) => (
1515
- <motion.div
1516
- key={msg.id}
1517
- initial={{ opacity: 0, y: 10 }}
1518
- animate={{ opacity: 1, y: 0 }}
1519
- className={`flex gap-2 ${msg.type === 'user' ? 'justify-end' : 'justify-start'}`}
1520
- >
1521
- {/* Avatar do Assistente */}
1522
- {msg.type === 'assistant' && (
1523
- <div className="flex-shrink-0 pt-1">
1524
- <XerticaOrbe size={32} />
1525
- </div>
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
- <div data-chat-message-container className={`max-w-[85%] min-w-0 w-auto ${msg.type === 'user' ? 'order-2' : 'order-1'}`}>
1529
- <div className={cn(
1530
- "rounded-[var(--radius-card)] px-4 py-2 break-words overflow-hidden overflow-x-hidden w-fit",
1531
- msg.type === 'user'
1532
- ? "bg-primary text-primary-foreground shadow-sm"
1533
- : "bg-muted text-foreground"
1534
- )}>
1535
- {/* Document Header with Edit and Download Buttons */}
1536
- {msg.attachmentType === 'document' && (
1537
- <div className="flex items-center justify-between mb-2 pb-2 border-b border-border min-w-0 overflow-hidden">
1538
- <div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
1539
- <FileText className="w-4 h-4 flex-shrink-0" />
1540
- <span className="text-small break-words">{msg.attachmentName}</span>
1541
- </div>
1542
- <div className="flex items-center gap-1 flex-shrink-0">
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
- {msg.attachmentType && msg.attachmentType !== 'podcast' && msg.attachmentType !== 'document' && (
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
- "flex items-center gap-2 mb-2 pb-2 border-b min-w-0 overflow-hidden",
1568
- msg.type === 'user' ? "border-primary-foreground/20" : "border-border"
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
- {msg.attachmentType === 'file' && <FileText className="w-4 h-4 flex-shrink-0" />}
1571
- {msg.attachmentType === 'audio' && <Music className="w-4 h-4 flex-shrink-0" />}
1572
- {msg.attachmentType === 'image' && <ImageIcon className="w-4 h-4 flex-shrink-0" />}
1573
- <span className={cn(
1574
- "text-small break-words",
1575
- msg.type === 'user' ? "text-primary-foreground/90" : "text-muted-foreground"
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
- </div>
1598
- )}
1599
- {/* Mensagem normal */}
1600
- {!(msg.content.includes('🔐') || msg.content.includes('❌')) && (
1601
- <MarkdownMessage
1602
- content={msg.content}
1603
- className="text-foreground"
1604
- />
1605
- )}
1606
- </>
1607
- )}
1608
-
1609
- {/* Document Preview - Mostrar documento formatado */}
1610
- {msg.attachmentType === 'document' && msg.documentContent && (
1611
- <div className="mt-3 pt-3 border-t border-border overflow-hidden">
1612
- <FormattedDocument
1613
- content={msg.documentContent}
1614
- maxPreviewLength={250}
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
- <Button
1632
- variant="ghost"
1633
- size="sm"
1634
- onClick={() => msg.audioUrl && msg.attachmentName && handleDownloadPodcast(msg.audioUrl, msg.attachmentName)}
1635
- className="h-6 px-2 hover:bg-[var(--toast-success-bg)]/30 hover:text-[var(--toast-success-icon)] flex-shrink-0"
1636
- title="Download"
1637
- >
1638
- <Download className="w-3 h-3" />
1639
- </Button>
1640
- </div>
1641
- <div className="bg-gradient-to-r from-[var(--chart-1)]/10 to-[var(--chart-4)]/10 rounded-lg p-2 overflow-hidden">
1642
- <audio
1643
- controls
1644
- className="w-full max-w-full h-10 outline-none"
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
- <div className="space-y-2 overflow-hidden">
1665
- {msg.searchResults.map((result) => (
1666
- <div
1667
- key={result.id}
1668
- onClick={() => handleOpenSearchResult(result)}
1669
- 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"
1670
- >
1671
- <div className="flex items-start justify-between gap-2 min-w-0">
1672
- <div className="flex items-start gap-2 flex-1 min-w-0 overflow-hidden">
1673
- <div className="mt-0.5 flex-shrink-0">
1674
- {result.type === 'document' && <FileText className="w-4 h-4 text-[var(--chart-4)] group-hover:scale-110 transition-transform" />}
1675
- {result.type === 'project' && <FolderOpen className="w-4 h-4 text-[var(--chart-1)] group-hover:scale-110 transition-transform" />}
1676
- {result.type === 'conversation' && <MessageSquare className="w-4 h-4 text-[var(--chart-2)] group-hover:scale-110 transition-transform" />}
1677
- {result.type === 'file' && <Folder className="w-4 h-4 text-[var(--chart-3)] group-hover:scale-110 transition-transform" />}
1678
- {result.type === 'contact' && <Users className="w-4 h-4 text-[var(--chart-5)] group-hover:scale-110 transition-transform" />}
1679
- </div>
1680
- <div className="flex-1 min-w-0 overflow-hidden">
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
- </div>
1836
+ )}
1711
1837
 
1712
- {/* Search Sources */}
1713
- {msg.searchSources && (
1714
- <div>
1715
- <h4 className="text-foreground mb-2">
1716
- Fontes Consultadas
1717
- </h4>
1718
- <div className="flex flex-wrap gap-2 overflow-hidden">
1719
- {msg.searchSources.map((source, index) => (
1720
- <div
1721
- key={index}
1722
- className="px-3 py-1.5 rounded-full bg-muted border border-border max-w-full"
1723
- >
1724
- <span className="text-sm font-medium text-foreground break-words">{source.name}</span>
1725
- <span className="text-sm text-muted-foreground ml-1 whitespace-nowrap">({source.count})</span>
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 Commands */}
1733
- {msg.searchCommands && msg.searchResults && (
1734
- <div>
1735
- <h4 className="text-foreground mb-2">
1736
- O que você pode fazer com estes resultados
1737
- </h4>
1738
- <div className="grid grid-cols-1 gap-2">
1739
- {msg.searchCommands.map((command) => (
1740
- <button
1741
- key={command.id}
1742
- onClick={() => handleExecuteSearchCommand(
1743
- command.id,
1744
- msg.content.replace('🔍 Pesquisa realizada com sucesso!', '').split('"')[1] || 'pesquisa',
1745
- msg.searchResults!,
1746
- msg.id
1747
- )}
1748
- disabled={executingCommand === command.id}
1749
- 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 ${
1750
- savedSearches.includes(msg.id) && command.id === '5' ? 'bg-[var(--toast-warning-bg)]/20 border-[var(--toast-warning-border)]' : ''
1751
- }`}
1752
- >
1753
- {executingCommand === command.id ? (
1754
- <Loader2 className="w-5 h-5 animate-spin flex-shrink-0 text-primary" />
1755
- ) : (
1756
- <span className="text-lg flex-shrink-0">{command.icon}</span>
1757
- )}
1758
- <div className="flex-1 min-w-0 overflow-hidden">
1759
- <div className="text-sm font-medium text-foreground break-words">
1760
- {command.id === '5' && savedSearches.includes(msg.id) ? '⭐ Pesquisa salva' : command.label}
1761
- </div>
1762
- <div className="text-sm text-muted-foreground break-words">
1763
- {executingCommand === command.id ? 'Processando...' : command.description}
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
- </button>
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
- </div>
1774
-
1775
- {/* Message Actions */}
1776
- <div className="flex items-center gap-2 mt-1 px-2">
1777
- <span className="text-sm text-muted-foreground">
1778
- {msg.timestamp.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' })}
1779
- </span>
1780
-
1781
- <Button
1782
- variant="ghost"
1783
- size="sm"
1784
- onClick={() => handleToggleFavorite(msg.id)}
1785
- className="h-6 w-6 p-0 text-muted-foreground hover:text-destructive"
1786
- >
1787
- <Heart className={`w-3 h-3 ${msg.isFavorite ? 'fill-current text-destructive' : ''}`} />
1788
- </Button>
1789
-
1790
- {/* Botão Gerar Podcast - apenas para mensagens do assistente */}
1791
- {msg.type === 'assistant' && msg.attachmentType !== 'podcast' && (
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={() => handleGeneratePodcast(msg.id, msg.content)}
1796
- disabled={generatingPodcastId === msg.id}
1797
- className="h-6 w-6 p-0 text-muted-foreground hover:text-primary disabled:opacity-50"
1798
- title="Gerar Podcast"
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
- {generatingPodcastId === msg.id ? (
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
- <Button
1809
- variant="ghost"
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
- </div>
1822
- </motion.div>
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
- {(abaSelecionada === 'historico' || abaSelecionada === 'favoritos') && (
1867
- <ScrollArea className="flex-1 min-h-0">
1868
- <div className={`p-4 ${isFullPage ? 'mx-auto w-full max-w-6xl' : ''}`}>
1869
- {/* New Conversation Button */}
1870
- <Button
1871
- variant="outline"
1872
- size="sm"
1873
- onClick={handleNovaConversa}
1874
- className="w-full mb-4 justify-start"
1875
- >
1876
- <Plus className="w-4 h-4 mr-2" />
1877
- Nova Conversa
1878
- </Button>
1879
-
1880
- {/* Conversations List */}
1881
- <div className="space-y-2">
1882
- {conversasFiltradas.length === 0 ? (
1883
- <div className="text-center py-8">
1884
- <Heart className="w-12 h-12 mx-auto text-gray-300 dark:text-gray-600 mb-2" />
1885
- <p className="text-muted-foreground">
1886
- {abaSelecionada === 'favoritos'
1887
- ? 'Nenhuma conversa favorita ainda'
1888
- : 'Nenhuma conversa no histórico'}
1889
- </p>
1890
- </div>
1891
- ) : (
1892
- conversasFiltradas.map((conversa) => (
1893
- <div
1894
- key={conversa.id}
1895
- onClick={() => handleSelecionarConversa(conversa.id)}
1896
- className={`p-3 rounded-lg hover:bg-muted cursor-pointer transition-colors duration-200 border ${
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
- {/* Modern Input Area - Only visible in chat mode */}
1936
- {abaSelecionada === 'chat' && (
1937
- <ModernChatInput
1938
- value={mensagem}
1939
- onChange={setMensagem}
1940
- onSubmit={handleEnviarMensagem}
1941
- onFileUpload={() => fileInputRef.current?.click()}
1942
- onAudioUpload={() => audioInputRef.current?.click()}
1943
- onVoiceRecording={(transcript) => {
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
- </motion.div>
2358
+ </AnimatePresence>
1963
2359
  )}
1964
- </AnimatePresence>
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
  }