xertica-ui 1.2.8 → 1.2.10

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,
@@ -49,6 +49,7 @@ import { toast } from 'sonner';
49
49
  import { Tooltip, TooltipTrigger } from './ui/tooltip';
50
50
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
51
51
  import { cn } from './ui/utils';
52
+ import { ASSISTANT_EXPANDED_WIDTH, ASSISTANT_COLLAPSED_WIDTH } from './layout-constants';
52
53
 
53
54
  // Tooltip customizado estilo sidebar (branco)
54
55
  function AssistantTooltipContent({
@@ -105,55 +106,55 @@ const sugestoesPadrao: Sugestao[] = [
105
106
  const gerarResposta = (mensagemUsuario: string): string => {
106
107
  const mensagemLower = mensagemUsuario.toLowerCase();
107
108
  const mensagemOriginal = mensagemUsuario;
108
-
109
+
109
110
  if (mensagemLower.includes('o que') && (mensagemLower.includes('fazer') || mensagemLower.includes('pedir'))) {
110
111
  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
112
  }
112
-
113
+
113
114
  if (mensagemLower.includes('o que você faz') || mensagemLower.includes('quem é você')) {
114
115
  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
116
  }
116
-
117
+
117
118
  if (mensagemLower.includes('projeto') && (mensagemLower.includes('preocupar') || mensagemLower.includes('atenção'))) {
118
119
  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
120
  }
120
-
121
+
121
122
  if (mensagemLower.includes('próximo projeto') || mensagemLower.includes('próxima tarefa')) {
122
123
  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
124
  }
124
-
125
+
125
126
  if (mensagemLower.includes('desempenho') || mensagemLower.includes('performance') || mensagemLower.includes('melhor')) {
126
127
  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
128
  }
128
-
129
+
129
130
  if (mensagemLower.includes('olá') || mensagemLower.includes('oi') || mensagemLower.includes('bom dia') || mensagemLower.includes('boa tarde') || mensagemLower.includes('boa noite')) {
130
131
  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
132
  }
132
-
133
+
133
134
  if (mensagemLower.includes('obrigado') || mensagemLower.includes('obrigada')) {
134
135
  return 'Por nada! Estou aqui sempre que precisar. 😊 Se tiver mais alguma dúvida ou precisar de ajuda, é só chamar!';
135
136
  }
136
-
137
+
137
138
  // Respostas para ações especiais
138
139
  if (mensagemLower.includes('criar documento')) {
139
140
  const tema = mensagemOriginal.replace(/📄 \[Criar documento\]/gi, '').trim();
140
141
  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
142
  }
142
-
143
+
143
144
  if (mensagemLower.includes('gerar podcast')) {
144
145
  const tema = mensagemOriginal.replace(/🎙️ \[Gerar podcast\]/gi, '').trim();
145
146
  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
147
  }
147
-
148
+
148
149
  if (mensagemLower.includes('pesquisar')) {
149
150
  const termo = mensagemOriginal.replace(/🔍 \[Pesquisar\]/gi, '').trim();
150
151
  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
152
  }
152
-
153
+
153
154
  if (mensagemLower.includes('arquivo') || mensagemLower.includes('documento')) {
154
155
  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
156
  }
156
-
157
+
157
158
  // Resposta genérica inteligente
158
159
  const respostasGenericas = [
159
160
  '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 +162,7 @@ const gerarResposta = (mensagemUsuario: string): string => {
161
162
  'Ó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
163
  '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
164
  ];
164
-
165
+
165
166
  return respostasGenericas[Math.floor(Math.random() * respostasGenericas.length)];
166
167
  };
167
168
 
@@ -184,7 +185,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
184
185
  savedSearches,
185
186
  setSavedSearches,
186
187
  } = useAssistente();
187
-
188
+
188
189
  const [mensagem, setMensagem] = useState('');
189
190
  const [sugestoes] = useState<Sugestao[]>(sugestoesPadrao);
190
191
  const [copiedId, setCopiedId] = useState<string | null>(null);
@@ -209,19 +210,19 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
209
210
  // Resposta da IA usando Gemini API
210
211
  const simularRespostaIA = async (mensagemUsuario: string) => {
211
212
  setIsTyping(true);
212
-
213
+
213
214
  try {
214
215
  let resposta: string;
215
-
216
+
216
217
  // Se a chave da API for válida, usar Gemini
217
218
  if (isApiKeyValid && geminiApiKey) {
218
219
  // Construir histórico da conversa para o Gemini
219
220
  const conversaAtiva = conversas.find(c => c.id === conversaAtual);
220
221
  const historico: GeminiMessage[] = [];
221
-
222
+
222
223
  // Adicionar prompt do sistema
223
224
  const systemPrompt = buildSystemPrompt();
224
-
225
+
225
226
  // Converter mensagens anteriores para formato Gemini (últimas 10 mensagens)
226
227
  const mensagensRecentes = (conversaAtiva?.mensagens || []).slice(-10);
227
228
  for (const msg of mensagensRecentes) {
@@ -231,7 +232,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
231
232
  .replace(/^📄 \[Criar documento\] /, '')
232
233
  .replace(/^🎙️ \[Gerar podcast\] /, '')
233
234
  .replace(/^🔍 \[Pesquisar\] /, '');
234
-
235
+
235
236
  historico.push({
236
237
  role: 'user',
237
238
  parts: [{ text: conteudoLimpo }]
@@ -243,7 +244,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
243
244
  });
244
245
  }
245
246
  }
246
-
247
+
247
248
  // Se não houver histórico, adicionar o prompt do sistema como primeira mensagem
248
249
  if (historico.length === 0) {
249
250
  historico.push({
@@ -255,19 +256,19 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
255
256
  parts: [{ text: 'Entendido! Estou pronto para ajudar você com a plataforma Xertica.ai. Como posso ajudá-lo hoje?' }]
256
257
  });
257
258
  }
258
-
259
+
259
260
  try {
260
261
  resposta = await callGeminiAPI(geminiApiKey, mensagemUsuario, historico);
261
262
  } catch (error) {
262
263
  console.error('Erro ao chamar API Gemini:', error);
263
264
  // Usar a mensagem de erro do utilitário se disponível
264
265
  const errorMessage = error instanceof Error ? error.message : '❌ Erro desconhecido';
265
-
266
+
266
267
  // 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
-
268
+ const isLeakedKeyError = errorMessage.toLowerCase().includes('leaked') ||
269
+ errorMessage.toLowerCase().includes('reported') ||
270
+ errorMessage.toLowerCase().includes('403');
271
+
271
272
  if (isLeakedKeyError) {
272
273
  toast.error('Chave de API desativada por segurança', {
273
274
  description: 'Sua chave foi reportada como vazada. Gere uma nova em Google AI Studio.',
@@ -287,7 +288,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
287
288
  await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000));
288
289
  resposta = gerarResposta(mensagemUsuario);
289
290
  }
290
-
291
+
291
292
  const novaMensagem: Message = {
292
293
  id: Date.now().toString() + '-ia',
293
294
  type: 'assistant',
@@ -295,7 +296,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
295
296
  timestamp: new Date(),
296
297
  isFavorite: false
297
298
  };
298
-
299
+
299
300
  setConversas(prev => prev.map(conv => {
300
301
  if (conv.id === conversaAtual) {
301
302
  const novasMensagens = [...conv.mensagens, novaMensagem];
@@ -317,7 +318,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
317
318
  timestamp: new Date(),
318
319
  isFavorite: false
319
320
  };
320
-
321
+
321
322
  setConversas(prev => prev.map(conv => {
322
323
  if (conv.id === conversaAtual) {
323
324
  return {
@@ -334,7 +335,7 @@ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFul
334
335
 
335
336
  const simularRespostaDocumento = (tema: string) => {
336
337
  setIsTyping(true);
337
-
338
+
338
339
  setTimeout(() => {
339
340
  const temaLimpo = tema.replace(/📄 \[Criar documento\]/gi, '').trim();
340
341
  const documentTitle = `Documento: ${temaLimpo}`;
@@ -399,7 +400,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
399
400
  documentContent: documentContent,
400
401
  documentTitle: documentTitle
401
402
  };
402
-
403
+
403
404
  setConversas(prev => prev.map(conv => {
404
405
  if (conv.id === conversaAtual) {
405
406
  return {
@@ -411,21 +412,21 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
411
412
  }
412
413
  return conv;
413
414
  }));
414
-
415
+
415
416
  setIsTyping(false);
416
417
  }, 2000);
417
418
  };
418
419
 
419
420
  const simularRespostaPodcast = (tema: string) => {
420
421
  setIsTyping(true);
421
-
422
+
422
423
  setTimeout(() => {
423
424
  const temaLimpo = tema.replace(/🎙️ \[Gerar podcast\]/gi, '').trim();
424
-
425
+
425
426
  // Gerar URL de áudio simulado (usando um data URL de áudio vazio)
426
427
  // Em produção, isso seria substituído por uma chamada real à API de geração de áudio
427
428
  const audioUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADhAC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7v///////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAAAAAAAAAAAA4TnTxAOAAAAAAD/+xBkAA';
428
-
429
+
429
430
  const novaMensagemIA: Message = {
430
431
  id: Date.now().toString(),
431
432
  type: 'assistant',
@@ -436,7 +437,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
436
437
  attachmentName: `Podcast - ${temaLimpo}.mp3`,
437
438
  audioUrl: audioUrl
438
439
  };
439
-
440
+
440
441
  setConversas(prev => prev.map(conv => {
441
442
  if (conv.id === conversaAtual) {
442
443
  return {
@@ -448,17 +449,17 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
448
449
  }
449
450
  return conv;
450
451
  }));
451
-
452
+
452
453
  setIsTyping(false);
453
454
  }, 2000);
454
455
  };
455
456
 
456
457
  const simularRespostaPesquisa = (query: string) => {
457
458
  setIsTyping(true);
458
-
459
+
459
460
  setTimeout(() => {
460
461
  const queryLimpa = query.replace(/🔍 \[Pesquisar\]/gi, '').trim();
461
-
462
+
462
463
  // Simular resultados de pesquisa baseados na query
463
464
  const mockResults: SearchResult[] = [
464
465
  {
@@ -548,7 +549,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
548
549
  description: 'Salvar esta pesquisa nos favoritos'
549
550
  }
550
551
  ];
551
-
552
+
552
553
  const novaMensagemIA: Message = {
553
554
  id: Date.now().toString(),
554
555
  type: 'assistant',
@@ -560,7 +561,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
560
561
  searchSources: mockSources,
561
562
  searchCommands: mockCommands
562
563
  };
563
-
564
+
564
565
  setConversas(prev => prev.map(conv => {
565
566
  if (conv.id === conversaAtual) {
566
567
  return {
@@ -572,7 +573,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
572
573
  }
573
574
  return conv;
574
575
  }));
575
-
576
+
576
577
  setIsTyping(false);
577
578
  }, 1500);
578
579
  };
@@ -580,10 +581,10 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
580
581
  // Detectar intenção do usuário baseado na mensagem com análise contextual avançada
581
582
  const detectarIntencao = (texto: string): ActionType | null => {
582
583
  const textoLower = texto.toLowerCase().trim();
583
-
584
+
584
585
  // Normalizar texto removendo pontuação extra
585
586
  const textoNormalizado = textoLower.replace(/[?.!,]/g, ' ').replace(/\s+/g, ' ');
586
-
587
+
587
588
  // ====== VERBOS DE AÇÃO PARA DOCUMENTO ======
588
589
  const verbosDocumento = [
589
590
  'crie', 'criar', 'criação', 'cria',
@@ -599,7 +600,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
599
600
  'desenvolva', 'desenvolver', 'desenvolve', 'desenvolvendo',
600
601
  'formule', 'formular', 'formula', 'formulando'
601
602
  ];
602
-
603
+
603
604
  const substantivosDocumento = [
604
605
  'documento', 'documentos', 'doc',
605
606
  'relatório', 'relatorio', 'relatórios', 'relatorios',
@@ -618,7 +619,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
618
619
  'proposta', 'propostas',
619
620
  'briefing', 'brief'
620
621
  ];
621
-
622
+
622
623
  // ====== VERBOS DE AÇÃO PARA PODCAST ======
623
624
  const verbosPodcast = [
624
625
  'grave', 'gravar', 'grava', 'gravando', 'gravação', 'gravacao',
@@ -627,7 +628,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
627
628
  'converta para áudio', 'converta para audio',
628
629
  'sintetize', 'sintetizar'
629
630
  ];
630
-
631
+
631
632
  const substantivosPodcast = [
632
633
  'podcast', 'podcasts',
633
634
  'áudio', 'audio', 'áudios', 'audios',
@@ -636,7 +637,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
636
637
  'locução', 'locucao',
637
638
  'gravação', 'gravacao', 'gravações', 'gravacoes'
638
639
  ];
639
-
640
+
640
641
  // ====== VERBOS DE AÇÃO PARA PESQUISA ======
641
642
  const verbosPesquisa = [
642
643
  'pesquise', 'pesquisar', 'pesquisa', 'pesquisando',
@@ -653,7 +654,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
653
654
  'traga', 'trazer', 'traz', 'trazendo',
654
655
  'recupere', 'recuperar', 'recupera', 'recuperando'
655
656
  ];
656
-
657
+
657
658
  const complementosPesquisa = [
658
659
  'me mostre', 'mostre-me', 'me mostra',
659
660
  'me traga', 'traga-me', 'me traz',
@@ -666,16 +667,16 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
666
667
  'onde está', 'onde estão', 'onde fica', 'onde ficam',
667
668
  'qual é', 'quais são'
668
669
  ];
669
-
670
+
670
671
  // ====== DETECÇÃO CONTEXTUAL ======
671
-
672
+
672
673
  // 1. Verificar PODCAST primeiro (mais específico)
673
674
  for (const verbo of verbosPodcast) {
674
675
  if (textoNormalizado.includes(verbo)) {
675
676
  return 'podcast';
676
677
  }
677
678
  }
678
-
679
+
679
680
  for (const substantivo of substantivosPodcast) {
680
681
  for (const verbo of verbosDocumento) {
681
682
  if (textoNormalizado.includes(verbo) && textoNormalizado.includes(substantivo)) {
@@ -683,14 +684,14 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
683
684
  }
684
685
  }
685
686
  }
686
-
687
+
687
688
  // Detecção específica: "transformar/converter em áudio"
688
689
  if ((textoNormalizado.includes('transforme') || textoNormalizado.includes('transformar') ||
689
- textoNormalizado.includes('converta') || textoNormalizado.includes('converter')) &&
690
- (textoNormalizado.includes('áudio') || textoNormalizado.includes('audio'))) {
690
+ textoNormalizado.includes('converta') || textoNormalizado.includes('converter')) &&
691
+ (textoNormalizado.includes('áudio') || textoNormalizado.includes('audio'))) {
691
692
  return 'podcast';
692
693
  }
693
-
694
+
694
695
  // 2. Verificar DOCUMENTO (combinação verbo + substantivo)
695
696
  for (const verbo of verbosDocumento) {
696
697
  for (const substantivo of substantivosDocumento) {
@@ -699,54 +700,54 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
699
700
  }
700
701
  }
701
702
  }
702
-
703
+
703
704
  // Detecção simples: apenas o verbo "documentar"
704
705
  if (textoNormalizado.includes('documente') || textoNormalizado.includes('documentar')) {
705
706
  return 'document';
706
707
  }
707
-
708
+
708
709
  // Detecção: "escrever/escreva sobre"
709
710
  if ((textoNormalizado.includes('escreva sobre') || textoNormalizado.includes('escrever sobre') ||
710
- textoNormalizado.includes('escreva um') || textoNormalizado.includes('escreva uma'))) {
711
+ textoNormalizado.includes('escreva um') || textoNormalizado.includes('escreva uma'))) {
711
712
  return 'document';
712
713
  }
713
-
714
+
714
715
  // Padrões comuns de criação de documento
715
716
  const padrõesDocumento = [
716
717
  /^(crie|criar|faça|fazer|gere|gerar).*(documento|relatório|relatorio|resumo|nota|artigo|texto|análise|analise)/,
717
718
  /^(preciso|quero|gostaria).*(criar|fazer|gerar).*(documento|relatório|relatorio|resumo)/,
718
719
  /^me (ajude|ajuda) (a )?(criar|fazer|gerar).*(documento|relatório|relatorio)/
719
720
  ];
720
-
721
+
721
722
  for (const padrao of padrõesDocumento) {
722
723
  if (padrao.test(textoNormalizado)) {
723
724
  return 'document';
724
725
  }
725
726
  }
726
-
727
+
727
728
  // 3. Verificar PESQUISA
728
729
  for (const verbo of verbosPesquisa) {
729
730
  if (textoNormalizado.startsWith(verbo + ' ') || textoNormalizado.includes(' ' + verbo + ' ')) {
730
731
  return 'search';
731
732
  }
732
733
  }
733
-
734
+
734
735
  for (const complemento of complementosPesquisa) {
735
736
  if (textoNormalizado.includes(complemento)) {
736
737
  return 'search';
737
738
  }
738
739
  }
739
-
740
+
740
741
  // Padrões interrogativos que indicam pesquisa
741
742
  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')) {
743
+ textoNormalizado.startsWith('quais ') || textoNormalizado.startsWith('quem ') ||
744
+ textoNormalizado.startsWith('como encontrar') || textoNormalizado.startsWith('tem algum') ||
745
+ textoNormalizado.startsWith('tem alguma') || textoNormalizado.includes('você tem')) &&
746
+ !textoNormalizado.includes('documento') && !textoNormalizado.includes('relatório') &&
747
+ !textoNormalizado.includes('relatorio') && !textoNormalizado.includes('podcast')) {
747
748
  return 'search';
748
749
  }
749
-
750
+
750
751
  return null;
751
752
  };
752
753
 
@@ -757,9 +758,9 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
757
758
  if (!acaoDetectada) {
758
759
  acaoDetectada = detectarIntencao(mensagem) || undefined;
759
760
  }
760
-
761
+
761
762
  let mensagemComAcao = mensagem;
762
-
763
+
763
764
  // Adicionar prefixo baseado na ação selecionada ou detectada
764
765
  if (acaoDetectada === 'document') {
765
766
  mensagemComAcao = `📄 [Criar documento] ${mensagem}`;
@@ -768,7 +769,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
768
769
  } else if (acaoDetectada === 'search') {
769
770
  mensagemComAcao = `🔍 [Pesquisar] ${mensagem}`;
770
771
  }
771
-
772
+
772
773
  const novaMensagem: Message = {
773
774
  id: Date.now().toString(),
774
775
  type: 'user',
@@ -776,15 +777,15 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
776
777
  timestamp: new Date(),
777
778
  isFavorite: false
778
779
  };
779
-
780
+
780
781
  // Atualizar título da conversa se for a primeira mensagem
781
782
  setConversas(prev => prev.map(conv => {
782
783
  if (conv.id === conversaAtual) {
783
784
  const novasMensagens = [...conv.mensagens, novaMensagem];
784
- const novoTitulo = conv.mensagens.length === 0
785
+ const novoTitulo = conv.mensagens.length === 0
785
786
  ? mensagem.substring(0, 30) + (mensagem.length > 30 ? '...' : '')
786
787
  : conv.titulo;
787
-
788
+
788
789
  return {
789
790
  ...conv,
790
791
  titulo: novoTitulo,
@@ -795,9 +796,9 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
795
796
  }
796
797
  return conv;
797
798
  }));
798
-
799
+
799
800
  setMensagem('');
800
-
801
+
801
802
  // Simular resposta da IA baseada na ação detectada ou explícita
802
803
  if (acaoDetectada === 'document') {
803
804
  simularRespostaDocumento(mensagem);
@@ -854,10 +855,10 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
854
855
  document.body.appendChild(textArea);
855
856
  textArea.focus();
856
857
  textArea.select();
857
-
858
+
858
859
  const successful = document.execCommand('copy');
859
860
  document.body.removeChild(textArea);
860
-
861
+
861
862
  if (successful) {
862
863
  setCopiedId(messageId);
863
864
  setTimeout(() => setCopiedId(null), 2000);
@@ -885,7 +886,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
885
886
  timestamp: new Date().toLocaleString('pt-BR'),
886
887
  favorita: false
887
888
  };
888
-
889
+
889
890
  setConversas(prev => [novaConversa, ...prev]);
890
891
  setConversaAtual(novaConversa.id);
891
892
  setAbaSelecionada('chat');
@@ -908,7 +909,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
908
909
  attachmentType: 'file',
909
910
  attachmentName: file.name
910
911
  };
911
-
912
+
912
913
  setConversas(prev => prev.map(conv => {
913
914
  if (conv.id === conversaAtual) {
914
915
  return {
@@ -920,13 +921,13 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
920
921
  }
921
922
  return conv;
922
923
  }));
923
-
924
+
924
925
  // Simular resposta sobre o arquivo
925
926
  setTimeout(() => {
926
927
  simularRespostaIA(`Analisar arquivo ${file.name}`);
927
928
  }, 500);
928
929
  }
929
-
930
+
930
931
  // Reset input
931
932
  if (e.target) e.target.value = '';
932
933
  };
@@ -943,7 +944,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
943
944
  attachmentType: 'audio',
944
945
  attachmentName: file.name
945
946
  };
946
-
947
+
947
948
  setConversas(prev => prev.map(conv => {
948
949
  if (conv.id === conversaAtual) {
949
950
  return {
@@ -955,11 +956,11 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
955
956
  }
956
957
  return conv;
957
958
  }));
958
-
959
+
959
960
  // Simular resposta sobre o áudio
960
961
  setTimeout(() => {
961
962
  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
-
963
+
963
964
  const novaMensagemIA: Message = {
964
965
  id: Date.now().toString() + '-ia',
965
966
  type: 'assistant',
@@ -967,7 +968,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
967
968
  timestamp: new Date(),
968
969
  isFavorite: false
969
970
  };
970
-
971
+
971
972
  setConversas(prev => prev.map(conv => {
972
973
  if (conv.id === conversaAtual) {
973
974
  return {
@@ -981,7 +982,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
981
982
  }));
982
983
  }, 1500);
983
984
  }
984
-
985
+
985
986
  // Reset input
986
987
  if (e.target) e.target.value = '';
987
988
  };
@@ -989,16 +990,16 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
989
990
  const handleGeneratePodcast = async (messageId: string, messageContent: string) => {
990
991
  // Marcar que o podcast está sendo gerado
991
992
  setGeneratingPodcastId(messageId);
992
-
993
+
993
994
  // Simular delay de geração (2-3 segundos)
994
995
  await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 1000));
995
-
996
+
996
997
  // Gerar URL de áudio simulado (usando um data URL de áudio vazio)
997
998
  // Em produção, isso seria substituído por uma chamada real à API de geração de áudio
998
999
  const audioUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADhAC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7v///////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAAAAAAAAAAAA4TnTxAOAAAAAAD/+xBkAA';
999
-
1000
+
1000
1001
  const temaExtraido = messageContent.substring(0, 50).replace(/[📝🎙️🔍]/g, '').trim();
1001
-
1002
+
1002
1003
  const podcastMessage: Message = {
1003
1004
  id: Date.now().toString() + '-podcast',
1004
1005
  type: 'assistant',
@@ -1009,7 +1010,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1009
1010
  attachmentName: `Podcast - ${temaExtraido}.mp3`,
1010
1011
  audioUrl: audioUrl
1011
1012
  };
1012
-
1013
+
1013
1014
  setConversas(prev => prev.map(conv => {
1014
1015
  if (conv.id === conversaAtual) {
1015
1016
  return {
@@ -1021,11 +1022,11 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1021
1022
  }
1022
1023
  return conv;
1023
1024
  }));
1024
-
1025
+
1025
1026
  setGeneratingPodcastId(null);
1026
1027
  };
1027
1028
 
1028
- const conversasFiltradas = abaSelecionada === 'favoritos'
1029
+ const conversasFiltradas = abaSelecionada === 'favoritos'
1029
1030
  ? conversas.filter(c => c.favorita)
1030
1031
  : conversas;
1031
1032
 
@@ -1077,7 +1078,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1077
1078
  timestamp: new Date(),
1078
1079
  isFavorite: false
1079
1080
  };
1080
-
1081
+
1081
1082
  setConversas(prev => prev.map(conv => {
1082
1083
  if (conv.id === conversaAtual) {
1083
1084
  return {
@@ -1089,12 +1090,12 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1089
1090
  }
1090
1091
  return conv;
1091
1092
  }));
1092
-
1093
+
1093
1094
  // Resposta do assistente com opções baseadas no tipo
1094
1095
  setTimeout(() => {
1095
1096
  let responseContent = '';
1096
1097
  let actionButtons = '';
1097
-
1098
+
1098
1099
  if (result.type === 'document') {
1099
1100
  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
1101
  } else if (result.type === 'project') {
@@ -1106,7 +1107,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1106
1107
  } else if (result.type === 'contact') {
1107
1108
  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
1109
  }
1109
-
1110
+
1110
1111
  const assistantMessage: Message = {
1111
1112
  id: Date.now().toString() + '-response',
1112
1113
  type: 'assistant',
@@ -1114,7 +1115,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1114
1115
  timestamp: new Date(),
1115
1116
  isFavorite: false
1116
1117
  };
1117
-
1118
+
1118
1119
  setConversas(prev => prev.map(conv => {
1119
1120
  if (conv.id === conversaAtual) {
1120
1121
  return {
@@ -1131,10 +1132,10 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1131
1132
 
1132
1133
  const handleCreateDocumentFromSearch = (searchQuery: string, results: SearchResult[]) => {
1133
1134
  setExecutingCommand('document');
1134
-
1135
+
1135
1136
  // Criar conteúdo do documento baseado nos resultados
1136
1137
  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
-
1138
+
1138
1139
  setTimeout(() => {
1139
1140
  setExecutingCommand(null);
1140
1141
  simularRespostaDocumento(`Compilar resultados da pesquisa: ${searchQuery}`);
@@ -1143,7 +1144,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1143
1144
 
1144
1145
  const handleGenerateReport = (searchQuery: string, results: SearchResult[]) => {
1145
1146
  setExecutingCommand('report');
1146
-
1147
+
1147
1148
  setTimeout(() => {
1148
1149
  const reportMessage: Message = {
1149
1150
  id: Date.now().toString(),
@@ -1152,7 +1153,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1152
1153
  timestamp: new Date(),
1153
1154
  isFavorite: false
1154
1155
  };
1155
-
1156
+
1156
1157
  setConversas(prev => prev.map(conv => {
1157
1158
  if (conv.id === conversaAtual) {
1158
1159
  return {
@@ -1164,14 +1165,14 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1164
1165
  }
1165
1166
  return conv;
1166
1167
  }));
1167
-
1168
+
1168
1169
  setExecutingCommand(null);
1169
1170
  }, 2000);
1170
1171
  };
1171
1172
 
1172
1173
  const handleGeneratePodcastFromSearch = (searchQuery: string) => {
1173
1174
  setExecutingCommand('podcast');
1174
-
1175
+
1175
1176
  setTimeout(() => {
1176
1177
  simularRespostaPodcast(`Criar podcast sobre os resultados da pesquisa: ${searchQuery}`);
1177
1178
  setExecutingCommand(null);
@@ -1180,7 +1181,7 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1180
1181
 
1181
1182
  const handleSendSummary = (searchQuery: string, results: SearchResult[]) => {
1182
1183
  setExecutingCommand('email');
1183
-
1184
+
1184
1185
  setTimeout(() => {
1185
1186
  toast.success('Resumo enviado!', {
1186
1187
  description: `Email enviado para sua equipe com ${results.length} resultados sobre "${searchQuery}"`
@@ -1226,778 +1227,776 @@ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimp
1226
1227
  return (
1227
1228
  <>
1228
1229
  {/* 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
- }`}>
1230
+ <div
1231
+ style={{
1232
+ width: editingDocument && isExpanded
1233
+ ? '50%'
1234
+ : isExpanded
1235
+ ? ASSISTANT_EXPANDED_WIDTH
1236
+ : ASSISTANT_COLLAPSED_WIDTH
1237
+ }}
1238
+ className={`fixed top-0 right-0 h-full z-40 transition-all duration-300 ease-in-out hidden md:block`}
1239
+ >
1236
1240
  <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
1241
+ {/* Hidden file inputs */}
1242
+ <input
1243
+ ref={fileInputRef}
1244
+ type="file"
1245
+ accept=".pdf,.doc,.docx,.xls,.xlsx,.txt,.csv"
1246
+ onChange={handleFileUpload}
1247
+ className="hidden"
1248
+ />
1249
+ <input
1250
+ ref={audioInputRef}
1251
+ type="file"
1252
+ accept="audio/*"
1253
+ onChange={handleAudioUpload}
1254
+ className="hidden"
1255
+ />
1256
+
1257
+ {/* Assistente Sidebar */}
1258
+ <div className={`h-full flex flex-col ${isFullPage
1256
1259
  ? 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" />
1260
+ : `bg-card border-l border-border shadow-xl w-full`
1261
+ }`}>
1262
+
1263
+ {/* Header - Toggle Button - Apenas visível quando não está em fullPage */}
1264
+ {!isFullPage && (
1265
+ <div className="border-b border-border flex items-center justify-between px-[14px] px-[18px] py-[16px]">
1266
+ {isExpanded && (
1267
+ <motion.div
1268
+ initial={{ opacity: 0, x: 20 }}
1269
+ animate={{ opacity: 1, x: 0 }}
1270
+ exit={{ opacity: 0, x: 20 }}
1271
+ className="flex items-center gap-2"
1272
+ >
1273
+ <XerticaOrbe size={32} />
1274
+ <span className="text-foreground">Assistente Xertica</span>
1275
+ </motion.div>
1300
1276
  )}
1301
- </Button>
1302
- </div>
1303
- </div>
1304
- )}
1305
1277
 
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
- )}
1278
+ <div className="flex items-center gap-1">
1279
+ {isExpanded && (
1280
+ <Button
1281
+ variant="ghost"
1282
+ size="sm"
1283
+ onClick={() => navigate('/assistente')}
1284
+ className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1285
+ title="Expandir assistente"
1286
+ >
1287
+ <Maximize2 className="w-4 h-4" />
1288
+ </Button>
1289
+ )}
1290
+
1291
+ <Button
1292
+ variant="ghost"
1293
+ size="sm"
1294
+ onClick={onToggle}
1295
+ className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1296
+ >
1297
+ {isExpanded ? (
1298
+ <ChevronRight className="w-4 h-4" />
1299
+ ) : (
1300
+ <PanelRight className="w-4 h-4" />
1301
+ )}
1302
+ </Button>
1303
+ </div>
1304
+ </div>
1305
+ )}
1385
1306
 
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">
1307
+ {/* Collapsed State - Icons Only */}
1308
+ {!isExpanded && !isFullPage && (
1309
+ <div className="flex flex-col items-center p-4 space-y-4">
1310
+ {/* Ícone do Assistente - Xertica Orbe */}
1311
+ <Tooltip>
1312
+ <TooltipTrigger asChild>
1313
+ <button
1314
+ onClick={onToggle}
1315
+ className="flex items-center justify-center hover:opacity-90 transition-opacity duration-200 cursor-pointer"
1316
+ >
1317
+ <XerticaOrbe size={32} />
1318
+ </button>
1319
+ </TooltipTrigger>
1320
+ <AssistantTooltipContent
1321
+ side="left"
1322
+ sideOffset={8}
1323
+ >
1324
+ <p>Assistente Xertica</p>
1325
+ </AssistantTooltipContent>
1326
+ </Tooltip>
1327
+
1328
+ <Tooltip>
1329
+ <TooltipTrigger asChild>
1400
1330
  <Button
1401
- variant={abaSelecionada === 'chat' ? 'default' : 'ghost'}
1331
+ variant="ghost"
1402
1332
  size="sm"
1403
- onClick={() => setAbaSelecionada('chat')}
1404
- className="flex-1 h-8 text-xs"
1333
+ onClick={() => handleExpandWithTab('chat')}
1334
+ className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1405
1335
  >
1406
- <MessageSquare className="w-3 h-3 mr-1" />
1407
- Chat
1336
+ <MessageSquare className="w-4 h-4" />
1408
1337
  </Button>
1338
+ </TooltipTrigger>
1339
+ <AssistantTooltipContent
1340
+ side="left"
1341
+ sideOffset={8}
1342
+ >
1343
+ <p>Chat</p>
1344
+ </AssistantTooltipContent>
1345
+ </Tooltip>
1346
+
1347
+ <Tooltip>
1348
+ <TooltipTrigger asChild>
1409
1349
  <Button
1410
- variant={abaSelecionada === 'historico' ? 'default' : 'ghost'}
1350
+ variant="ghost"
1411
1351
  size="sm"
1412
- onClick={() => setAbaSelecionada('historico')}
1413
- className="flex-1 h-8 text-xs"
1352
+ onClick={() => handleExpandWithTab('favoritos')}
1353
+ className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1414
1354
  >
1415
- <History className="w-3 h-3 mr-1" />
1416
- Histórico
1355
+ <Heart className="w-4 h-4" />
1417
1356
  </Button>
1357
+ </TooltipTrigger>
1358
+ <AssistantTooltipContent
1359
+ side="left"
1360
+ sideOffset={8}
1361
+ >
1362
+ <p>Favoritos</p>
1363
+ </AssistantTooltipContent>
1364
+ </Tooltip>
1365
+
1366
+ <Tooltip>
1367
+ <TooltipTrigger asChild>
1418
1368
  <Button
1419
- variant={abaSelecionada === 'favoritos' ? 'default' : 'ghost'}
1369
+ variant="ghost"
1420
1370
  size="sm"
1421
- onClick={() => setAbaSelecionada('favoritos')}
1422
- className="flex-1 h-8 text-xs"
1371
+ onClick={() => handleExpandWithTab('historico')}
1372
+ className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
1423
1373
  >
1424
- <Heart className="w-3 h-3 mr-1" />
1425
- Favoritos
1374
+ <History className="w-4 h-4" />
1426
1375
  </Button>
1427
- </div>
1428
- </div>
1429
- )}
1376
+ </TooltipTrigger>
1377
+ <AssistantTooltipContent
1378
+ side="left"
1379
+ sideOffset={8}
1380
+ >
1381
+ <p>Histórico</p>
1382
+ </AssistantTooltipContent>
1383
+ </Tooltip>
1384
+ </div>
1385
+ )}
1386
+
1387
+ {/* Expanded State - Full Content */}
1388
+ <AnimatePresence>
1389
+ {(isExpanded || isFullPage) && (
1390
+ <motion.div
1391
+ initial={isFullPage ? false : { opacity: 0, x: 50 }}
1392
+ animate={{ opacity: 1, x: 0 }}
1393
+ exit={isFullPage ? undefined : { opacity: 0, x: 50 }}
1394
+ transition={{ duration: 0.2 }}
1395
+ className="flex-1 flex flex-col overflow-hidden"
1396
+ >
1397
+ {/* Navigation Tabs - Oculto em modo fullPage */}
1398
+ {!isFullPage && (
1399
+ <div className="px-4 py-2 border-b border-border">
1400
+ <div className="flex gap-1">
1401
+ <Button
1402
+ variant={abaSelecionada === 'chat' ? 'default' : 'ghost'}
1403
+ size="sm"
1404
+ onClick={() => setAbaSelecionada('chat')}
1405
+ className="flex-1 h-8 text-xs"
1406
+ >
1407
+ <MessageSquare className="w-3 h-3 mr-1" />
1408
+ Chat
1409
+ </Button>
1410
+ <Button
1411
+ variant={abaSelecionada === 'historico' ? 'default' : 'ghost'}
1412
+ size="sm"
1413
+ onClick={() => setAbaSelecionada('historico')}
1414
+ className="flex-1 h-8 text-xs"
1415
+ >
1416
+ <History className="w-3 h-3 mr-1" />
1417
+ Histórico
1418
+ </Button>
1419
+ <Button
1420
+ variant={abaSelecionada === 'favoritos' ? 'default' : 'ghost'}
1421
+ size="sm"
1422
+ onClick={() => setAbaSelecionada('favoritos')}
1423
+ className="flex-1 h-8 text-xs"
1424
+ >
1425
+ <Heart className="w-3 h-3 mr-1" />
1426
+ Favoritos
1427
+ </Button>
1428
+ </div>
1429
+ </div>
1430
+ )}
1431
+
1432
+ {/* Content Area */}
1433
+ <div className="flex-1 overflow-hidden flex flex-col">
1434
+ {abaSelecionada === 'chat' && (
1435
+ <div className="flex-1 flex flex-col min-h-0 w-full">
1436
+ {/* API Key Warning Banner */}
1437
+ {!isApiKeyValid && (
1438
+ <div className="mx-4 mt-3 p-4 bg-[var(--toast-warning-bg)]/20 border-2 border-[var(--toast-warning-border)] rounded-lg">
1439
+ <div className="flex items-start gap-3">
1440
+ <AlertCircle className="w-5 h-5 text-[var(--toast-warning-icon)] flex-shrink-0 mt-0.5" />
1441
+ <div className="flex-1 space-y-3">
1442
+ <p className="text-small text-foreground">
1443
+ {!geminiApiKey ? (
1444
+ <strong>Nenhuma chave de API configurada</strong>
1445
+ ) : (
1446
+ <strong>🔐 Chave de API inválida ou vazada</strong>
1447
+ )}
1448
+ </p>
1449
+ <p className="text-small text-muted-foreground">
1450
+ Configure uma nova chave do Google Gemini para ativar respostas inteligentes do assistente.
1451
+ </p>
1452
+ <div className="flex flex-wrap items-center gap-2">
1453
+ <Button
1454
+ onClick={() => navigate('/settings')}
1455
+ size="sm"
1456
+ className="bg-[var(--toast-warning-icon)] hover:opacity-90 text-white rounded-[12px] shadow-sm"
1457
+ >
1458
+ Ir para Configurações
1459
+ </Button>
1460
+ <a
1461
+ href="https://aistudio.google.com/apikey"
1462
+ target="_blank"
1463
+ rel="noopener noreferrer"
1464
+ className="inline-flex items-center gap-1 text-small text-[var(--toast-warning-icon)] underline hover:opacity-80"
1465
+ >
1466
+ Obter chave gratuitamente
1467
+ <ExternalLink className="w-3 h-3" />
1468
+ </a>
1469
+ </div>
1470
+ </div>
1471
+ </div>
1472
+ </div>
1473
+ )}
1474
+
1475
+ {mensagens.length === 0 ? (
1476
+ <div className="flex-1 overflow-y-auto min-h-0">
1477
+ {/* Welcome Message */}
1478
+ <div className="p-6 text-center">
1479
+ <div className="mx-auto mb-4 flex items-center justify-center">
1480
+ <XerticaOrbe size={64} />
1481
+ </div>
1482
+ <h3 className="text-foreground mb-2">
1483
+ Olá, Ariel
1484
+ </h3>
1485
+ <p className="text-muted-foreground">
1486
+ Como posso ajudar?
1487
+ </p>
1488
+ </div>
1489
+
1490
+ {/* Suggestions */}
1491
+ <div className="px-4 pb-4 space-y-2">
1492
+ {sugestoes.map((sugestao) => (
1493
+ <button
1494
+ key={sugestao.id}
1495
+ onClick={() => setMensagem(sugestao.texto)}
1496
+ className="w-full p-3 text-left bg-muted hover:bg-accent rounded-lg transition-colors duration-200 text-foreground"
1497
+ >
1498
+ {sugestao.texto}
1499
+ </button>
1500
+ ))}
1430
1501
 
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
1502
  <Button
1453
- onClick={() => navigate('/settings')}
1503
+ variant="ghost"
1454
1504
  size="sm"
1455
- className="bg-[var(--toast-warning-icon)] hover:opacity-90 text-white rounded-[12px] shadow-sm"
1505
+ className="w-full justify-start text-muted-foreground hover:text-foreground"
1456
1506
  >
1457
- Ir para Configurações
1507
+ <MoreHorizontal className="w-4 h-4 mr-2" />
1508
+ Mais sugestões
1458
1509
  </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
1510
  </div>
1469
1511
  </div>
1470
- </div>
1471
- </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} />
1480
- </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>
1488
-
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>
1526
- )}
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>
1512
+ ) : (
1513
+ <ScrollArea className="flex-1 min-h-0 overflow-x-hidden">
1514
+ <div className="space-y-4 px-4 py-4 overflow-x-hidden w-full">
1515
+ {mensagens.map((msg) => (
1516
+ <motion.div
1517
+ key={msg.id}
1518
+ initial={{ opacity: 0, y: 10 }}
1519
+ animate={{ opacity: 1, y: 0 }}
1520
+ className={`flex gap-2 ${msg.type === 'user' ? 'justify-end' : 'justify-start'}`}
1521
+ >
1522
+ {/* Avatar do Assistente */}
1523
+ {msg.type === 'assistant' && (
1524
+ <div className="flex-shrink-0 pt-1">
1525
+ <XerticaOrbe size={32} />
1562
1526
  </div>
1563
1527
  )}
1564
1528
 
1565
- {msg.attachmentType && msg.attachmentType !== 'podcast' && msg.attachmentType !== 'document' && (
1529
+ <div data-chat-message-container className={`max-w-[85%] min-w-0 w-auto ${msg.type === 'user' ? 'order-2' : 'order-1'}`}>
1566
1530
  <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"
1531
+ "rounded-[var(--radius-card)] px-4 py-2 break-words overflow-hidden overflow-x-hidden w-fit",
1532
+ msg.type === 'user'
1533
+ ? "bg-primary text-primary-foreground shadow-sm"
1534
+ : "bg-muted text-foreground"
1569
1535
  )}>
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>
1536
+ {/* Document Header with Edit and Download Buttons */}
1537
+ {msg.attachmentType === 'document' && (
1538
+ <div className="flex items-center justify-between mb-2 pb-2 border-b border-border min-w-0 overflow-hidden">
1539
+ <div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
1540
+ <FileText className="w-4 h-4 flex-shrink-0" />
1541
+ <span className="text-small break-words">{msg.attachmentName}</span>
1596
1542
  </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" />
1543
+ <div className="flex items-center gap-1 flex-shrink-0">
1544
+ <Button
1545
+ variant="ghost"
1546
+ size="sm"
1547
+ onClick={() => msg.documentContent && msg.attachmentName && handleDownloadDocument(msg.documentContent, msg.attachmentName)}
1548
+ className="h-6 px-2 hover:bg-[var(--toast-success-bg)]/30 hover:text-[var(--toast-success-icon)]"
1549
+ title="Download"
1550
+ >
1551
+ <Download className="w-3 h-3" />
1552
+ </Button>
1553
+ <Button
1554
+ variant="ghost"
1555
+ size="sm"
1556
+ onClick={() => msg.documentContent && msg.documentTitle && handleEditDocument(msg.documentContent, msg.documentTitle)}
1557
+ className="h-6 px-2 hover:bg-[var(--toast-info-bg)]/30 hover:text-[var(--toast-info-icon)]"
1558
+ >
1559
+ <Edit className="w-3 h-3 mr-1" />
1560
+ Editar
1561
+ </Button>
1626
1562
  </div>
1627
- <span className="text-small text-muted-foreground break-words">
1628
- {msg.attachmentName}
1629
- </span>
1630
1563
  </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>
1564
+ )}
1565
+
1566
+ {msg.attachmentType && msg.attachmentType !== 'podcast' && msg.attachmentType !== 'document' && (
1567
+ <div className={cn(
1568
+ "flex items-center gap-2 mb-2 pb-2 border-b min-w-0 overflow-hidden",
1569
+ msg.type === 'user' ? "border-primary-foreground/20" : "border-border"
1570
+ )}>
1571
+ {msg.attachmentType === 'file' && <FileText className="w-4 h-4 flex-shrink-0" />}
1572
+ {msg.attachmentType === 'audio' && <Music className="w-4 h-4 flex-shrink-0" />}
1573
+ {msg.attachmentType === 'image' && <ImageIcon className="w-4 h-4 flex-shrink-0" />}
1574
+ <span className={cn(
1575
+ "text-small break-words",
1576
+ msg.type === 'user' ? "text-primary-foreground/90" : "text-muted-foreground"
1577
+ )}>{msg.attachmentName}</span>
1663
1578
  </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>
1579
+ )}
1580
+
1581
+ {/* Conteúdo da mensagem */}
1582
+ {msg.type === 'user' ? (
1583
+ <p className="whitespace-pre-wrap break-words text-primary-foreground">{msg.content}</p>
1584
+ ) : (
1585
+ <>
1586
+ {/* Alerta visual para mensagens de erro */}
1587
+ {(msg.content.includes('🔐') || msg.content.includes('❌')) && (
1588
+ <div className="mb-3 p-3 bg-[var(--toast-error-bg)]/20 border border-[var(--toast-error-border)] rounded-lg overflow-hidden">
1589
+ <div className="flex items-start gap-2 min-w-0">
1590
+ <AlertCircle className="w-5 h-5 text-[var(--toast-error-icon)] flex-shrink-0 mt-0.5" />
1591
+ <div className="flex-1 min-w-0 overflow-hidden">
1592
+ <MarkdownMessage
1593
+ content={msg.content}
1594
+ className="text-foreground"
1595
+ />
1705
1596
  </div>
1706
1597
  </div>
1707
1598
  </div>
1708
- ))}
1599
+ )}
1600
+ {/* Mensagem normal */}
1601
+ {!(msg.content.includes('🔐') || msg.content.includes('❌')) && (
1602
+ <MarkdownMessage
1603
+ content={msg.content}
1604
+ className="text-foreground"
1605
+ />
1606
+ )}
1607
+ </>
1608
+ )}
1609
+
1610
+ {/* Document Preview - Mostrar documento formatado */}
1611
+ {msg.attachmentType === 'document' && msg.documentContent && (
1612
+ <div className="mt-3 pt-3 border-t border-border overflow-hidden">
1613
+ <FormattedDocument
1614
+ content={msg.documentContent}
1615
+ maxPreviewLength={250}
1616
+ />
1709
1617
  </div>
1710
- </div>
1711
-
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>
1618
+ )}
1619
+
1620
+ {/* Podcast Player */}
1621
+ {msg.attachmentType === 'podcast' && msg.audioUrl && (
1622
+ <div className="mt-3 pt-3 border-t border-border overflow-hidden">
1623
+ <div className="flex items-center justify-between mb-3 min-w-0 overflow-hidden">
1624
+ <div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
1625
+ <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">
1626
+ <Radio className="w-3 h-3 text-white" />
1726
1627
  </div>
1727
- ))}
1628
+ <span className="text-small text-muted-foreground break-words">
1629
+ {msg.attachmentName}
1630
+ </span>
1631
+ </div>
1632
+ <Button
1633
+ variant="ghost"
1634
+ size="sm"
1635
+ onClick={() => msg.audioUrl && msg.attachmentName && handleDownloadPodcast(msg.audioUrl, msg.attachmentName)}
1636
+ className="h-6 px-2 hover:bg-[var(--toast-success-bg)]/30 hover:text-[var(--toast-success-icon)] flex-shrink-0"
1637
+ title="Download"
1638
+ >
1639
+ <Download className="w-3 h-3" />
1640
+ </Button>
1641
+ </div>
1642
+ <div className="bg-gradient-to-r from-[var(--chart-1)]/10 to-[var(--chart-4)]/10 rounded-lg p-2 overflow-hidden">
1643
+ <audio
1644
+ controls
1645
+ className="w-full max-w-full h-10 outline-none"
1646
+ >
1647
+ <source src={msg.audioUrl} type="audio/mpeg" />
1648
+ Seu navegador não suporta o elemento de áudio.
1649
+ </audio>
1728
1650
  </div>
1729
1651
  </div>
1730
1652
  )}
1731
1653
 
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}
1654
+ {/* Search Results */}
1655
+ {msg.attachmentType === 'search' && msg.searchResults && (
1656
+ <div className="mt-3 pt-3 border-t border-border space-y-4 overflow-hidden">
1657
+ {/* Search Results List */}
1658
+ <div className="overflow-hidden">
1659
+ <div className="flex items-center gap-2 mb-3">
1660
+ <Search className="w-4 h-4 text-[var(--chart-4)]" />
1661
+ <h4 className="text-foreground">
1662
+ Resultados Encontrados ({msg.searchResults.length})
1663
+ </h4>
1664
+ </div>
1665
+ <div className="space-y-2 overflow-hidden">
1666
+ {msg.searchResults.map((result) => (
1667
+ <div
1668
+ key={result.id}
1669
+ onClick={() => handleOpenSearchResult(result)}
1670
+ 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"
1671
+ >
1672
+ <div className="flex items-start justify-between gap-2 min-w-0">
1673
+ <div className="flex items-start gap-2 flex-1 min-w-0 overflow-hidden">
1674
+ <div className="mt-0.5 flex-shrink-0">
1675
+ {result.type === 'document' && <FileText className="w-4 h-4 text-[var(--chart-4)] group-hover:scale-110 transition-transform" />}
1676
+ {result.type === 'project' && <FolderOpen className="w-4 h-4 text-[var(--chart-1)] group-hover:scale-110 transition-transform" />}
1677
+ {result.type === 'conversation' && <MessageSquare className="w-4 h-4 text-[var(--chart-2)] group-hover:scale-110 transition-transform" />}
1678
+ {result.type === 'file' && <Folder className="w-4 h-4 text-[var(--chart-3)] group-hover:scale-110 transition-transform" />}
1679
+ {result.type === 'contact' && <Users className="w-4 h-4 text-[var(--chart-5)] group-hover:scale-110 transition-transform" />}
1680
+ </div>
1681
+ <div className="flex-1 min-w-0 overflow-hidden">
1682
+ <div className="flex items-start gap-2 min-w-0">
1683
+ <h5 className="text-small text-foreground break-words group-hover:text-primary transition-colors flex-1 min-w-0">
1684
+ {result.title}
1685
+ </h5>
1686
+ <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">
1687
+ {result.relevance}%
1688
+ </span>
1689
+ </div>
1690
+ <p className="text-sm text-muted-foreground mt-1 line-clamp-2 break-words">
1691
+ {result.description}
1692
+ </p>
1693
+ <div className="flex items-center gap-3 mt-2 flex-wrap min-w-0">
1694
+ <span className="text-sm text-muted-foreground flex items-center gap-1 min-w-0 max-w-full">
1695
+ <ExternalLink className="w-3 h-3 flex-shrink-0" />
1696
+ <span className="truncate">{result.path}</span>
1697
+ </span>
1698
+ {result.lastModified && (
1699
+ <span className="text-sm text-muted-foreground flex items-center gap-1 flex-shrink-0">
1700
+ <Clock className="w-3 h-3" />
1701
+ {result.lastModified}
1702
+ </span>
1703
+ )}
1704
+ </div>
1705
+ </div>
1706
+ </div>
1764
1707
  </div>
1765
1708
  </div>
1766
- </button>
1767
- ))}
1709
+ ))}
1710
+ </div>
1768
1711
  </div>
1712
+
1713
+ {/* Search Sources */}
1714
+ {msg.searchSources && (
1715
+ <div>
1716
+ <h4 className="text-foreground mb-2">
1717
+ Fontes Consultadas
1718
+ </h4>
1719
+ <div className="flex flex-wrap gap-2 overflow-hidden">
1720
+ {msg.searchSources.map((source, index) => (
1721
+ <div
1722
+ key={index}
1723
+ className="px-3 py-1.5 rounded-full bg-muted border border-border max-w-full"
1724
+ >
1725
+ <span className="text-sm font-medium text-foreground break-words">{source.name}</span>
1726
+ <span className="text-sm text-muted-foreground ml-1 whitespace-nowrap">({source.count})</span>
1727
+ </div>
1728
+ ))}
1729
+ </div>
1730
+ </div>
1731
+ )}
1732
+
1733
+ {/* Search Commands */}
1734
+ {msg.searchCommands && msg.searchResults && (
1735
+ <div>
1736
+ <h4 className="text-foreground mb-2">
1737
+ O que você pode fazer com estes resultados
1738
+ </h4>
1739
+ <div className="grid grid-cols-1 gap-2">
1740
+ {msg.searchCommands.map((command) => (
1741
+ <button
1742
+ key={command.id}
1743
+ onClick={() => handleExecuteSearchCommand(
1744
+ command.id,
1745
+ msg.content.replace('🔍 Pesquisa realizada com sucesso!', '').split('"')[1] || 'pesquisa',
1746
+ msg.searchResults!,
1747
+ msg.id
1748
+ )}
1749
+ disabled={executingCommand === command.id}
1750
+ 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)]' : ''
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}
1764
+ </div>
1765
+ </div>
1766
+ </button>
1767
+ ))}
1768
+ </div>
1769
+ </div>
1770
+ )}
1769
1771
  </div>
1770
1772
  )}
1771
1773
  </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' && (
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' && (
1792
+ <Button
1793
+ variant="ghost"
1794
+ 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"
1799
+ >
1800
+ {generatingPodcastId === msg.id ? (
1801
+ <Loader2 className="w-3 h-3 animate-spin" />
1802
+ ) : (
1803
+ <Radio className="w-3 h-3" />
1804
+ )}
1805
+ </Button>
1806
+ )}
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>
1820
+ </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} />
1860
+ </div>
1861
+ </ScrollArea>
1862
+ )}
1863
+ </div>
1864
+ )}
1865
+
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 ${conversa.id === conversaAtual
1897
+ ? 'border-primary bg-primary/5'
1898
+ : 'border-border'
1899
+ }`}
1900
+ >
1901
+ <div className="flex items-start justify-between mb-1">
1902
+ <h4 className="text-small text-gray-900 dark:text-gray-100 truncate flex-1">
1903
+ {conversa.titulo}
1904
+ </h4>
1792
1905
  <Button
1793
1906
  variant="ghost"
1794
1907
  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"
1908
+ onClick={(e) => {
1909
+ e.stopPropagation();
1910
+ handleToggleFavoritaConversa(conversa.id);
1911
+ }}
1912
+ className="h-6 w-6 p-0 flex-shrink-0 ml-1"
1799
1913
  >
1800
- {generatingPodcastId === msg.id ? (
1801
- <Loader2 className="w-3 h-3 animate-spin" />
1802
- ) : (
1803
- <Radio className="w-3 h-3" />
1804
- )}
1914
+ <Heart className={`w-3 h-3 ${conversa.favorita ? 'text-red-500 fill-current' : 'text-gray-400'}`} />
1805
1915
  </Button>
1916
+ </div>
1917
+ {conversa.ultimaMensagem && (
1918
+ <p className="text-sm text-muted-foreground truncate mb-1">
1919
+ {conversa.ultimaMensagem}
1920
+ </p>
1806
1921
  )}
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>
1922
+ <p className="text-sm text-muted-foreground">
1923
+ {conversa.timestamp}
1924
+ </p>
1820
1925
  </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} />
1926
+ ))
1927
+ )}
1928
+ </div>
1860
1929
  </div>
1861
1930
  </ScrollArea>
1862
1931
  )}
1863
1932
  </div>
1864
- )}
1865
1933
 
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>
1934
+ {/* Modern Input Area - Only visible in chat mode */}
1935
+ {abaSelecionada === 'chat' && (
1936
+ <ModernChatInput
1937
+ value={mensagem}
1938
+ onChange={setMensagem}
1939
+ onSubmit={handleEnviarMensagem}
1940
+ onFileUpload={() => fileInputRef.current?.click()}
1941
+ onAudioUpload={() => audioInputRef.current?.click()}
1942
+ onVoiceRecording={(transcript) => {
1943
+ // Ao finalizar gravação, definir a mensagem transcrita e enviar
1944
+ setMensagem(transcript);
1945
+ toast.success('Áudio transcrito com sucesso!', {
1946
+ description: 'Sua mensagem foi convertida de voz para texto.',
1947
+ duration: 3000,
1948
+ });
1949
+ // Enviar automaticamente após um pequeno delay para o usuário ver a transcrição
1950
+ setTimeout(() => {
1951
+ if (!isTyping) {
1952
+ handleEnviarMensagem();
1953
+ }
1954
+ }, 1500);
1955
+ }}
1956
+ placeholder="Envie uma mensagem para Xertica"
1957
+ disabled={isTyping}
1958
+ isFullPage={isFullPage}
1959
+ />
1960
+ )}
1961
+ </motion.div>
1962
+ )}
1963
+ </AnimatePresence>
1964
+ </div>
1934
1965
 
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}
1966
+ {/* Document Editor - Right Side */}
1967
+ {isFullPage ? (
1968
+ // Modo fullPage: editor fixo ocupando 50% da tela quando aberto
1969
+ editingDocument && (
1970
+ <div className="w-1/2 h-full bg-background border-l border-border">
1971
+ <DocumentEditor
1972
+ initialContent={editingDocument.content}
1973
+ initialTitle={editingDocument.title}
1974
+ onClose={handleCloseEditor}
1960
1975
  />
1976
+ </div>
1977
+ )
1978
+ ) : (
1979
+ // Modo sidebar: editor com animação deslizante
1980
+ <AnimatePresence>
1981
+ {editingDocument && isExpanded && (
1982
+ <motion.div
1983
+ initial={{ x: 600 }}
1984
+ animate={{ x: 0 }}
1985
+ exit={{ x: 600 }}
1986
+ transition={{ duration: 0.3, ease: 'easeInOut' }}
1987
+ className="w-[600px] h-full bg-background border-l border-border"
1988
+ >
1989
+ <DocumentEditor
1990
+ initialContent={editingDocument.content}
1991
+ initialTitle={editingDocument.title}
1992
+ onClose={handleCloseEditor}
1993
+ />
1994
+ </motion.div>
1961
1995
  )}
1962
- </motion.div>
1996
+ </AnimatePresence>
1963
1997
  )}
1964
- </AnimatePresence>
1998
+ </div>
1965
1999
  </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
2000
  </>
2002
2001
  );
2003
2002
  }