xertica-ui 1.0.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.
Files changed (141) hide show
  1. package/App.tsx +182 -0
  2. package/README.md +330 -0
  3. package/assets/xertica-logo.svg +38 -0
  4. package/assets/xertica-x-logo.svg +21 -0
  5. package/bin/cli.ts +193 -0
  6. package/components/AssistenteXertica.tsx +2003 -0
  7. package/components/AudioPlayer.tsx +203 -0
  8. package/components/CodeBlock.tsx +242 -0
  9. package/components/DocumentEditor.tsx +504 -0
  10. package/components/ForgotPasswordPage.tsx +170 -0
  11. package/components/FormattedDocument.tsx +87 -0
  12. package/components/HomeContent.tsx +123 -0
  13. package/components/HomePage.tsx +70 -0
  14. package/components/LanguageSelector.tsx +54 -0
  15. package/components/LoginPage.tsx +199 -0
  16. package/components/MarkdownMessage.tsx +62 -0
  17. package/components/ModernChatInput.tsx +502 -0
  18. package/components/PodcastPlayer.tsx +409 -0
  19. package/components/ResetPasswordPage.tsx +234 -0
  20. package/components/Sidebar.tsx +489 -0
  21. package/components/TemplateContent.tsx +629 -0
  22. package/components/TemplatePage.tsx +70 -0
  23. package/components/ThemeToggle.tsx +65 -0
  24. package/components/VerifyEmailPage.tsx +187 -0
  25. package/components/XerticaLogo.tsx +69 -0
  26. package/components/XerticaOrbe.tsx +1339 -0
  27. package/components/XerticaXLogo.tsx +53 -0
  28. package/components/examples/DrawingMapExample.tsx +530 -0
  29. package/components/examples/FilterableMapExample.tsx +380 -0
  30. package/components/examples/LocationPickerExample.tsx +330 -0
  31. package/components/examples/MapExamples.tsx +280 -0
  32. package/components/examples/MapShowcase.tsx +446 -0
  33. package/components/examples/RouteMapExamples.tsx +329 -0
  34. package/components/examples/SimpleFilterableMap.tsx +192 -0
  35. package/components/examples/index.ts +52 -0
  36. package/components/figma/ImageWithFallback.tsx +27 -0
  37. package/components/index.ts +44 -0
  38. package/components/media/AudioPlayer.tsx +278 -0
  39. package/components/media/FloatingMediaWrapper.tsx +166 -0
  40. package/components/media/VideoPlayer.tsx +285 -0
  41. package/components/ui/accordion.tsx +66 -0
  42. package/components/ui/alert-dialog.tsx +159 -0
  43. package/components/ui/alert.tsx +91 -0
  44. package/components/ui/aspect-ratio.tsx +11 -0
  45. package/components/ui/avatar.tsx +65 -0
  46. package/components/ui/badge.tsx +55 -0
  47. package/components/ui/breadcrumb.tsx +109 -0
  48. package/components/ui/button.tsx +78 -0
  49. package/components/ui/calendar.tsx +235 -0
  50. package/components/ui/card.tsx +92 -0
  51. package/components/ui/carousel.tsx +241 -0
  52. package/components/ui/chart.tsx +353 -0
  53. package/components/ui/checkbox.tsx +32 -0
  54. package/components/ui/collapsible.tsx +33 -0
  55. package/components/ui/command.tsx +177 -0
  56. package/components/ui/context-menu.tsx +252 -0
  57. package/components/ui/dialog.tsx +138 -0
  58. package/components/ui/drawer.tsx +134 -0
  59. package/components/ui/dropdown-menu.tsx +257 -0
  60. package/components/ui/empty.tsx +90 -0
  61. package/components/ui/file-upload.tsx +152 -0
  62. package/components/ui/form.tsx +195 -0
  63. package/components/ui/google-maps-loader.tsx +379 -0
  64. package/components/ui/hover-card.tsx +44 -0
  65. package/components/ui/index.ts +242 -0
  66. package/components/ui/input-otp.tsx +77 -0
  67. package/components/ui/input.tsx +38 -0
  68. package/components/ui/label.tsx +24 -0
  69. package/components/ui/map-config.ts +12 -0
  70. package/components/ui/map-layers.tsx +129 -0
  71. package/components/ui/map.exports.ts +31 -0
  72. package/components/ui/map.tsx +412 -0
  73. package/components/ui/menubar.tsx +276 -0
  74. package/components/ui/navigation-menu.tsx +162 -0
  75. package/components/ui/notification-badge.tsx +61 -0
  76. package/components/ui/page-header.tsx +229 -0
  77. package/components/ui/pagination.tsx +127 -0
  78. package/components/ui/popover.tsx +48 -0
  79. package/components/ui/progress.tsx +31 -0
  80. package/components/ui/radio-group.tsx +56 -0
  81. package/components/ui/rating.tsx +102 -0
  82. package/components/ui/resizable.tsx +405 -0
  83. package/components/ui/route-map.tsx +246 -0
  84. package/components/ui/scroll-area.tsx +58 -0
  85. package/components/ui/search.tsx +70 -0
  86. package/components/ui/select.tsx +176 -0
  87. package/components/ui/separator.tsx +28 -0
  88. package/components/ui/sheet.tsx +138 -0
  89. package/components/ui/sidebar.tsx +726 -0
  90. package/components/ui/simple-map.tsx +92 -0
  91. package/components/ui/skeleton.tsx +13 -0
  92. package/components/ui/slider.tsx +58 -0
  93. package/components/ui/sonner.tsx +77 -0
  94. package/components/ui/stats-card.tsx +84 -0
  95. package/components/ui/stepper.tsx +126 -0
  96. package/components/ui/switch.tsx +34 -0
  97. package/components/ui/table.tsx +116 -0
  98. package/components/ui/tabs.tsx +66 -0
  99. package/components/ui/textarea.tsx +26 -0
  100. package/components/ui/timeline.tsx +140 -0
  101. package/components/ui/toggle-group.tsx +71 -0
  102. package/components/ui/toggle.tsx +46 -0
  103. package/components/ui/tooltip.tsx +61 -0
  104. package/components/ui/tree-view.tsx +123 -0
  105. package/components/ui/use-mobile.ts +24 -0
  106. package/components/ui/utils.ts +6 -0
  107. package/components/ui/xertica-assistant.tsx +1420 -0
  108. package/contexts/ApiKeyContext.tsx +123 -0
  109. package/contexts/AssistenteContext.tsx +118 -0
  110. package/contexts/BrandColorsContext.tsx +551 -0
  111. package/contexts/LanguageContext.tsx +36 -0
  112. package/contexts/ThemeContext.tsx +85 -0
  113. package/dist/cli.js +20922 -0
  114. package/eslint.config.js +41 -0
  115. package/guidelines/Guidelines.md +61 -0
  116. package/hooks/useTheme.ts +4 -0
  117. package/imports/Podcast.tsx +389 -0
  118. package/imports/XerticaAi.tsx +46 -0
  119. package/imports/XerticaX.tsx +20 -0
  120. package/imports/svg-aueiaqngck.ts +11 -0
  121. package/imports/svg-v9krss1ozd.ts +16 -0
  122. package/imports/svg-vhrdofe3qe.ts +5 -0
  123. package/index.css +4448 -0
  124. package/index.html +14 -0
  125. package/main.tsx +10 -0
  126. package/package.json +119 -0
  127. package/postcss.config.js +6 -0
  128. package/routes.tsx +33 -0
  129. package/styles/globals.css +15 -0
  130. package/styles/xertica/app-overrides/chat.css +61 -0
  131. package/styles/xertica/app-overrides/scrollbar.css +33 -0
  132. package/styles/xertica/base.css +70 -0
  133. package/styles/xertica/integrations/google-maps.css +76 -0
  134. package/styles/xertica/integrations/sonner.css +73 -0
  135. package/styles/xertica/theme-map.css +88 -0
  136. package/styles/xertica/tokens.css +190 -0
  137. package/tsconfig.json +31 -0
  138. package/tsconfig.node.json +10 -0
  139. package/utils/gemini.ts +140 -0
  140. package/vite-env.d.ts +12 -0
  141. package/vite.config.ts +36 -0
@@ -0,0 +1,2003 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { useNavigate } from 'react-router';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import {
5
+ MessageSquare,
6
+ Heart,
7
+ History,
8
+ MoreHorizontal,
9
+ X,
10
+ ChevronLeft,
11
+ ChevronRight,
12
+ PanelRight,
13
+ Plus,
14
+ Copy,
15
+ Check,
16
+ FileText,
17
+ Music,
18
+ Image as ImageIcon,
19
+ Radio,
20
+ Loader2,
21
+ Edit,
22
+ Download,
23
+ Search,
24
+ Folder,
25
+ Users,
26
+ ExternalLink,
27
+ Star,
28
+ Clock,
29
+ FolderOpen,
30
+ Filter,
31
+ Mail,
32
+ BarChart3,
33
+ Bookmark,
34
+ Maximize2,
35
+ AlertCircle
36
+ } from 'lucide-react';
37
+ import { Button } from './ui/button';
38
+ import { ScrollArea } from './ui/scroll-area';
39
+ import { ModernChatInput, ActionType } from './ModernChatInput';
40
+ import { Separator } from './ui/separator';
41
+ import { DocumentEditor } from './DocumentEditor';
42
+ import { FormattedDocument } from './FormattedDocument';
43
+ import { MarkdownMessage } from './MarkdownMessage';
44
+ import { XerticaOrbe } from './XerticaOrbe';
45
+ import { useApiKey } from '../contexts/ApiKeyContext';
46
+ import { useAssistente } from '../contexts/AssistenteContext';
47
+ import { callGeminiAPI, buildSystemPrompt, GeminiMessage } from '../utils/gemini';
48
+ import { toast } from 'sonner';
49
+ import { Tooltip, TooltipTrigger } from './ui/tooltip';
50
+ import * as TooltipPrimitive from '@radix-ui/react-tooltip';
51
+ import { cn } from './ui/utils';
52
+
53
+ // Tooltip customizado estilo sidebar (branco)
54
+ function AssistantTooltipContent({
55
+ className,
56
+ sideOffset = 0,
57
+ children,
58
+ ...props
59
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
60
+ return (
61
+ <TooltipPrimitive.Portal>
62
+ <TooltipPrimitive.Content
63
+ data-slot="tooltip-content"
64
+ sideOffset={sideOffset}
65
+ className={cn(
66
+ "bg-card text-foreground shadow-lg animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
67
+ className,
68
+ )}
69
+ {...props}
70
+ >
71
+ {children}
72
+ <TooltipPrimitive.Arrow
73
+ className="fill-card z-50 drop-shadow-sm"
74
+ width={8}
75
+ height={4}
76
+ />
77
+ </TooltipPrimitive.Content>
78
+ </TooltipPrimitive.Portal>
79
+ );
80
+ }
81
+
82
+ interface AssistenteXerticaProps {
83
+ isExpanded: boolean;
84
+ onToggle: () => void;
85
+ onToggleWithTab?: (tab: 'chat' | 'historico' | 'favoritos') => void;
86
+ isFullPage?: boolean;
87
+ }
88
+
89
+ import type { Message, Conversa, SearchResult, SearchSource, SearchCommand } from '../contexts/AssistenteContext';
90
+
91
+ interface Sugestao {
92
+ id: string;
93
+ texto: string;
94
+ }
95
+
96
+ const sugestoesPadrao: Sugestao[] = [
97
+ { id: '1', texto: 'O que posso pedir para você fazer?' },
98
+ { id: '2', texto: 'O que você faz?' },
99
+ { id: '3', texto: 'Com quais projetos devo me preocupar agora?' },
100
+ { id: '4', texto: 'Qual meu próximo projeto?' },
101
+ { id: '5', texto: 'Qual projetos está tendo o melhor desempenho?' }
102
+ ];
103
+
104
+ // Respostas simuladas da IA
105
+ const gerarResposta = (mensagemUsuario: string): string => {
106
+ const mensagemLower = mensagemUsuario.toLowerCase();
107
+ const mensagemOriginal = mensagemUsuario;
108
+
109
+ if (mensagemLower.includes('o que') && (mensagemLower.includes('fazer') || mensagemLower.includes('pedir'))) {
110
+ 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
+
113
+ if (mensagemLower.includes('o que você faz') || mensagemLower.includes('quem é você')) {
114
+ 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
+
117
+ if (mensagemLower.includes('projeto') && (mensagemLower.includes('preocupar') || mensagemLower.includes('atenção'))) {
118
+ 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
+
121
+ if (mensagemLower.includes('próximo projeto') || mensagemLower.includes('próxima tarefa')) {
122
+ 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
+
125
+ if (mensagemLower.includes('desempenho') || mensagemLower.includes('performance') || mensagemLower.includes('melhor')) {
126
+ 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
+
129
+ if (mensagemLower.includes('olá') || mensagemLower.includes('oi') || mensagemLower.includes('bom dia') || mensagemLower.includes('boa tarde') || mensagemLower.includes('boa noite')) {
130
+ 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
+
133
+ if (mensagemLower.includes('obrigado') || mensagemLower.includes('obrigada')) {
134
+ return 'Por nada! Estou aqui sempre que precisar. 😊 Se tiver mais alguma dúvida ou precisar de ajuda, é só chamar!';
135
+ }
136
+
137
+ // Respostas para ações especiais
138
+ if (mensagemLower.includes('criar documento')) {
139
+ const tema = mensagemOriginal.replace(/📄 \[Criar documento\]/gi, '').trim();
140
+ 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
+
143
+ if (mensagemLower.includes('gerar podcast')) {
144
+ const tema = mensagemOriginal.replace(/🎙️ \[Gerar podcast\]/gi, '').trim();
145
+ 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
+
148
+ if (mensagemLower.includes('pesquisar')) {
149
+ const termo = mensagemOriginal.replace(/🔍 \[Pesquisar\]/gi, '').trim();
150
+ 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
+
153
+ if (mensagemLower.includes('arquivo') || mensagemLower.includes('documento')) {
154
+ 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
+
157
+ // Resposta genérica inteligente
158
+ const respostasGenericas = [
159
+ '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?',
160
+ 'Interessante! Deixe-me processar isso... Com base no seu histórico e nos dados do sistema, recomendo que possamos explorar essa questão em mais detalhes. O que especificamente você gostaria de saber?',
161
+ 'Ó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
+ '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
+
165
+ return respostasGenericas[Math.floor(Math.random() * respostasGenericas.length)];
166
+ };
167
+
168
+ export function AssistenteXertica({ isExpanded, onToggle, onToggleWithTab, isFullPage = false }: AssistenteXerticaProps) {
169
+ const navigate = useNavigate();
170
+ const { geminiApiKey, isApiKeyValid } = useApiKey();
171
+ const {
172
+ conversaAtual,
173
+ setConversaAtual,
174
+ conversas,
175
+ setConversas,
176
+ isTyping,
177
+ setIsTyping,
178
+ abaSelecionada,
179
+ setAbaSelecionada,
180
+ editingDocument,
181
+ setEditingDocument,
182
+ searchFilter,
183
+ setSearchFilter,
184
+ savedSearches,
185
+ setSavedSearches,
186
+ } = useAssistente();
187
+
188
+ const [mensagem, setMensagem] = useState('');
189
+ const [sugestoes] = useState<Sugestao[]>(sugestoesPadrao);
190
+ const [copiedId, setCopiedId] = useState<string | null>(null);
191
+ const [generatingPodcastId, setGeneratingPodcastId] = useState<string | null>(null);
192
+ const [executingCommand, setExecutingCommand] = useState<string | null>(null);
193
+ const messagesEndRef = useRef<HTMLDivElement>(null);
194
+ const fileInputRef = useRef<HTMLInputElement>(null);
195
+ const audioInputRef = useRef<HTMLInputElement>(null);
196
+
197
+ const conversaAtiva = conversas.find(c => c.id === conversaAtual);
198
+ const mensagens = conversaAtiva?.mensagens || [];
199
+
200
+ // Auto-scroll para última mensagem
201
+ const scrollToBottom = () => {
202
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
203
+ };
204
+
205
+ useEffect(() => {
206
+ scrollToBottom();
207
+ }, [mensagens, isTyping]);
208
+
209
+ // Resposta da IA usando Gemini API
210
+ const simularRespostaIA = async (mensagemUsuario: string) => {
211
+ setIsTyping(true);
212
+
213
+ try {
214
+ let resposta: string;
215
+
216
+ // Se a chave da API for válida, usar Gemini
217
+ if (isApiKeyValid && geminiApiKey) {
218
+ // Construir histórico da conversa para o Gemini
219
+ const conversaAtiva = conversas.find(c => c.id === conversaAtual);
220
+ const historico: GeminiMessage[] = [];
221
+
222
+ // Adicionar prompt do sistema
223
+ const systemPrompt = buildSystemPrompt();
224
+
225
+ // Converter mensagens anteriores para formato Gemini (últimas 10 mensagens)
226
+ const mensagensRecentes = (conversaAtiva?.mensagens || []).slice(-10);
227
+ for (const msg of mensagensRecentes) {
228
+ if (msg.type === 'user') {
229
+ // Remover prefixos de ação antes de enviar ao Gemini
230
+ let conteudoLimpo = msg.content
231
+ .replace(/^📄 \[Criar documento\] /, '')
232
+ .replace(/^🎙️ \[Gerar podcast\] /, '')
233
+ .replace(/^🔍 \[Pesquisar\] /, '');
234
+
235
+ historico.push({
236
+ role: 'user',
237
+ parts: [{ text: conteudoLimpo }]
238
+ });
239
+ } else if (msg.type === 'assistant') {
240
+ historico.push({
241
+ role: 'model',
242
+ parts: [{ text: msg.content }]
243
+ });
244
+ }
245
+ }
246
+
247
+ // Se não houver histórico, adicionar o prompt do sistema como primeira mensagem
248
+ if (historico.length === 0) {
249
+ historico.push({
250
+ role: 'user',
251
+ parts: [{ text: systemPrompt }]
252
+ });
253
+ historico.push({
254
+ role: 'model',
255
+ parts: [{ text: 'Entendido! Estou pronto para ajudar você com a plataforma Xertica.ai. Como posso ajudá-lo hoje?' }]
256
+ });
257
+ }
258
+
259
+ try {
260
+ resposta = await callGeminiAPI(geminiApiKey, mensagemUsuario, historico);
261
+ } catch (error) {
262
+ console.error('Erro ao chamar API Gemini:', error);
263
+ // Usar a mensagem de erro do utilitário se disponível
264
+ const errorMessage = error instanceof Error ? error.message : '❌ Erro desconhecido';
265
+
266
+ // 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
+
271
+ if (isLeakedKeyError) {
272
+ toast.error('Chave de API desativada por segurança', {
273
+ description: 'Sua chave foi reportada como vazada. Gere uma nova em Google AI Studio.',
274
+ duration: 6000,
275
+ });
276
+ resposta = `🔐 **Chave de API Desativada por Segurança**\n\n${errorMessage}\n\n**Como resolver:**\n1. Acesse [Google AI Studio](https://aistudio.google.com/apikey)\n2. Gere uma nova chave de API\n3. Configure a nova chave em **Configurações > API**\n\n⚠️ **Importante:** Não compartilhe sua chave de API publicamente.`;
277
+ } else {
278
+ toast.error('Erro ao processar mensagem', {
279
+ description: 'Verifique sua chave de API nas Configurações.',
280
+ duration: 4000,
281
+ });
282
+ resposta = `**Erro ao processar sua mensagem**\n\n${errorMessage}\n\n💡 **Dica:** Verifique se sua chave de API está configurada corretamente em **Configurações > API**.`;
283
+ }
284
+ }
285
+ } else {
286
+ // Fallback para respostas simuladas se não houver API key
287
+ await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 1000));
288
+ resposta = gerarResposta(mensagemUsuario);
289
+ }
290
+
291
+ const novaMensagem: Message = {
292
+ id: Date.now().toString() + '-ia',
293
+ type: 'assistant',
294
+ content: resposta,
295
+ timestamp: new Date(),
296
+ isFavorite: false
297
+ };
298
+
299
+ setConversas(prev => prev.map(conv => {
300
+ if (conv.id === conversaAtual) {
301
+ const novasMensagens = [...conv.mensagens, novaMensagem];
302
+ return {
303
+ ...conv,
304
+ mensagens: novasMensagens,
305
+ ultimaMensagem: resposta.substring(0, 50) + '...',
306
+ timestamp: new Date().toLocaleString('pt-BR')
307
+ };
308
+ }
309
+ return conv;
310
+ }));
311
+ } catch (error) {
312
+ console.error('Erro ao gerar resposta:', error);
313
+ const mensagemErro: Message = {
314
+ id: Date.now().toString() + '-error',
315
+ type: 'assistant',
316
+ content: 'Desculpe, ocorreu um erro ao processar sua mensagem. Por favor, tente novamente.',
317
+ timestamp: new Date(),
318
+ isFavorite: false
319
+ };
320
+
321
+ setConversas(prev => prev.map(conv => {
322
+ if (conv.id === conversaAtual) {
323
+ return {
324
+ ...conv,
325
+ mensagens: [...conv.mensagens, mensagemErro]
326
+ };
327
+ }
328
+ return conv;
329
+ }));
330
+ } finally {
331
+ setIsTyping(false);
332
+ }
333
+ };
334
+
335
+ const simularRespostaDocumento = (tema: string) => {
336
+ setIsTyping(true);
337
+
338
+ setTimeout(() => {
339
+ const temaLimpo = tema.replace(/📄 \[Criar documento\]/gi, '').trim();
340
+ const documentTitle = `Documento: ${temaLimpo}`;
341
+ const documentContent = `# ${temaLimpo}
342
+
343
+ ## Introdução
344
+
345
+ Este documento foi gerado automaticamente pelo Assistente Xertica com base no tema "${temaLimpo}".
346
+
347
+ ## Visão Geral
348
+
349
+ O tema aborda aspectos importantes que serão detalhados nas próximas seções. Este documento serve como um guia completo e pode ser editado conforme necessário.
350
+
351
+ ## Análise Detalhada
352
+
353
+ ### Contexto
354
+
355
+ Analisando o contexto relacionado a ${temaLimpo}, identificamos diversos pontos relevantes que merecem atenção especial.
356
+
357
+ ### Principais Características
358
+
359
+ - **Aspecto 1**: Descrição detalhada do primeiro aspecto relevante
360
+ - **Aspecto 2**: Informações sobre o segundo ponto importante
361
+ - **Aspecto 3**: Análise do terceiro elemento fundamental
362
+
363
+ ### Dados e Métricas
364
+
365
+ Os dados coletados indicam tendências significativas que devem ser consideradas no planejamento e execução das atividades relacionadas.
366
+
367
+ ## Recomendações
368
+
369
+ Com base na análise realizada, recomendamos:
370
+
371
+ 1. Implementar as melhores práticas identificadas
372
+ 2. Monitorar continuamente os indicadores-chave
373
+ 3. Ajustar estratégias conforme necessário
374
+ 4. Documentar todos os processos e resultados
375
+
376
+ ## Conclusão
377
+
378
+ Este documento fornece uma base sólida para entender e trabalhar com ${temaLimpo}. As informações aqui contidas devem ser revisadas e atualizadas regularmente.
379
+
380
+ ## Próximos Passos
381
+
382
+ - [ ] Revisar e validar as informações
383
+ - [ ] Compartilhar com a equipe
384
+ - [ ] Implementar as recomendações
385
+ - [ ] Agendar revisão periódica
386
+
387
+ ---
388
+
389
+ *Documento gerado por Xertica AI em ${new Date().toLocaleDateString('pt-BR')}*`;
390
+
391
+ const novaMensagemIA: Message = {
392
+ id: Date.now().toString(),
393
+ type: 'assistant',
394
+ content: `📝 Documento criado com sucesso!\n\nGerei um documento completo sobre "${temaLimpo}" com as seguintes seções:\n\n• Introdução e contexto\n• Análise detalhada\n• Dados e métricas relevantes\n• Recomendações\n• Conclusões\n• Próximos passos\n\nO documento está pronto para revisão e pode ser editado conforme necessário. Clique no botão "Editar" acima para abrir o editor completo.`,
395
+ timestamp: new Date(),
396
+ isFavorite: false,
397
+ attachmentType: 'document',
398
+ attachmentName: `${temaLimpo}.md`,
399
+ documentContent: documentContent,
400
+ documentTitle: documentTitle
401
+ };
402
+
403
+ setConversas(prev => prev.map(conv => {
404
+ if (conv.id === conversaAtual) {
405
+ return {
406
+ ...conv,
407
+ mensagens: [...conv.mensagens, novaMensagemIA],
408
+ ultimaMensagem: 'Documento criado',
409
+ timestamp: new Date().toLocaleString('pt-BR')
410
+ };
411
+ }
412
+ return conv;
413
+ }));
414
+
415
+ setIsTyping(false);
416
+ }, 2000);
417
+ };
418
+
419
+ const simularRespostaPodcast = (tema: string) => {
420
+ setIsTyping(true);
421
+
422
+ setTimeout(() => {
423
+ const temaLimpo = tema.replace(/🎙️ \[Gerar podcast\]/gi, '').trim();
424
+
425
+ // Gerar URL de áudio simulado (usando um data URL de áudio vazio)
426
+ // Em produção, isso seria substituído por uma chamada real à API de geração de áudio
427
+ const audioUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADhAC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7v///////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAAAAAAAAAAAA4TnTxAOAAAAAAD/+xBkAA';
428
+
429
+ const novaMensagemIA: Message = {
430
+ id: Date.now().toString(),
431
+ type: 'assistant',
432
+ content: `🎙️ Podcast gerado com sucesso!\n\nCriei um podcast completo sobre "${temaLimpo}" com:\n\n• Introdução envolvente ao tema\n• Desenvolvimento detalhado do conteúdo\n• Exemplos práticos e aplicações\n• Reflexões e insights\n• Conclusão com próximos passos\n\nDuração estimada: 5-7 minutos\n\nAperte play abaixo para ouvir:`,
433
+ timestamp: new Date(),
434
+ isFavorite: false,
435
+ attachmentType: 'podcast',
436
+ attachmentName: `Podcast - ${temaLimpo}.mp3`,
437
+ audioUrl: audioUrl
438
+ };
439
+
440
+ setConversas(prev => prev.map(conv => {
441
+ if (conv.id === conversaAtual) {
442
+ return {
443
+ ...conv,
444
+ mensagens: [...conv.mensagens, novaMensagemIA],
445
+ ultimaMensagem: 'Podcast gerado',
446
+ timestamp: new Date().toLocaleString('pt-BR')
447
+ };
448
+ }
449
+ return conv;
450
+ }));
451
+
452
+ setIsTyping(false);
453
+ }, 2000);
454
+ };
455
+
456
+ const simularRespostaPesquisa = (query: string) => {
457
+ setIsTyping(true);
458
+
459
+ setTimeout(() => {
460
+ const queryLimpa = query.replace(/🔍 \[Pesquisar\]/gi, '').trim();
461
+
462
+ // Simular resultados de pesquisa baseados na query
463
+ const mockResults: SearchResult[] = [
464
+ {
465
+ id: '1',
466
+ title: `Projeto relacionado a ${queryLimpa}`,
467
+ description: 'Documento contendo informações detalhadas sobre o projeto e seus objetivos principais.',
468
+ type: 'project',
469
+ path: '/projetos/projeto-principal',
470
+ relevance: 95,
471
+ lastModified: '2 horas atrás'
472
+ },
473
+ {
474
+ id: '2',
475
+ title: `Análise de dados - ${queryLimpa}`,
476
+ description: 'Relatório completo com métricas, gráficos e insights sobre o desempenho.',
477
+ type: 'document',
478
+ path: '/documentos/analise-dados',
479
+ relevance: 87,
480
+ lastModified: '1 dia atrás'
481
+ },
482
+ {
483
+ id: '3',
484
+ title: `Conversas sobre ${queryLimpa}`,
485
+ description: 'Histórico de discussões e decisões tomadas pela equipe.',
486
+ type: 'conversation',
487
+ path: '/conversas/historico-2024',
488
+ relevance: 78,
489
+ lastModified: '3 dias atrás'
490
+ },
491
+ {
492
+ id: '4',
493
+ title: 'Apresentação executiva',
494
+ description: 'Slides e material de apresentação para stakeholders.',
495
+ type: 'file',
496
+ path: '/arquivos/apresentacao.pdf',
497
+ relevance: 72,
498
+ lastModified: '1 semana atrás'
499
+ },
500
+ {
501
+ id: '5',
502
+ title: 'Contatos da equipe',
503
+ description: 'Lista de membros envolvidos e suas responsabilidades.',
504
+ type: 'contact',
505
+ path: '/equipe/contatos',
506
+ relevance: 65,
507
+ lastModified: '2 semanas atrás'
508
+ }
509
+ ];
510
+
511
+ const mockSources: SearchSource[] = [
512
+ { name: 'Documentos', count: 12 },
513
+ { name: 'Projetos', count: 8 },
514
+ { name: 'Conversas', count: 15 },
515
+ { name: 'Arquivos', count: 23 },
516
+ { name: 'Contatos', count: 5 }
517
+ ];
518
+
519
+ const mockCommands: SearchCommand[] = [
520
+ {
521
+ id: '1',
522
+ icon: '📄',
523
+ label: 'Criar documento',
524
+ description: 'Criar um novo documento com os resultados'
525
+ },
526
+ {
527
+ id: '2',
528
+ icon: '📊',
529
+ label: 'Gerar relatório',
530
+ description: 'Compilar informações em um relatório'
531
+ },
532
+ {
533
+ id: '3',
534
+ icon: '🎙️',
535
+ label: 'Gerar podcast',
536
+ description: 'Criar podcast resumindo os achados'
537
+ },
538
+ {
539
+ id: '4',
540
+ icon: '📧',
541
+ label: 'Enviar resumo',
542
+ description: 'Enviar resumo por e-mail para a equipe'
543
+ },
544
+ {
545
+ id: '5',
546
+ icon: '⭐',
547
+ label: 'Salvar pesquisa',
548
+ description: 'Salvar esta pesquisa nos favoritos'
549
+ }
550
+ ];
551
+
552
+ const novaMensagemIA: Message = {
553
+ id: Date.now().toString(),
554
+ type: 'assistant',
555
+ content: `🔍 Pesquisa realizada com sucesso!\n\nEncontrei ${mockResults.length} resultados relevantes sobre "${queryLimpa}" em ${mockSources.reduce((acc, s) => acc + s.count, 0)} fontes diferentes.\n\nOs resultados estão organizados por relevância e incluem documentos, projetos, conversas e arquivos relacionados.`,
556
+ timestamp: new Date(),
557
+ isFavorite: false,
558
+ attachmentType: 'search',
559
+ searchResults: mockResults,
560
+ searchSources: mockSources,
561
+ searchCommands: mockCommands
562
+ };
563
+
564
+ setConversas(prev => prev.map(conv => {
565
+ if (conv.id === conversaAtual) {
566
+ return {
567
+ ...conv,
568
+ mensagens: [...conv.mensagens, novaMensagemIA],
569
+ ultimaMensagem: 'Pesquisa realizada',
570
+ timestamp: new Date().toLocaleString('pt-BR')
571
+ };
572
+ }
573
+ return conv;
574
+ }));
575
+
576
+ setIsTyping(false);
577
+ }, 1500);
578
+ };
579
+
580
+ // Detectar intenção do usuário baseado na mensagem com análise contextual avançada
581
+ const detectarIntencao = (texto: string): ActionType | null => {
582
+ const textoLower = texto.toLowerCase().trim();
583
+
584
+ // Normalizar texto removendo pontuação extra
585
+ const textoNormalizado = textoLower.replace(/[?.!,]/g, ' ').replace(/\s+/g, ' ');
586
+
587
+ // ====== VERBOS DE AÇÃO PARA DOCUMENTO ======
588
+ const verbosDocumento = [
589
+ 'crie', 'criar', 'criação', 'cria',
590
+ 'faça', 'fazer', 'faz', 'fazendo',
591
+ 'gere', 'gerar', 'gera', 'gerando', 'geração',
592
+ 'escreva', 'escrever', 'escreve', 'escrevendo',
593
+ 'monte', 'montar', 'monta', 'montando',
594
+ 'prepare', 'preparar', 'prepara', 'preparando', 'preparação',
595
+ 'elabore', 'elaborar', 'elabora', 'elaborando', 'elaboração',
596
+ 'redija', 'redigir', 'redige', 'redigindo',
597
+ 'produza', 'produzir', 'produz', 'produzindo', 'produção',
598
+ 'compile', 'compilar', 'compila', 'compilando', 'compilação',
599
+ 'desenvolva', 'desenvolver', 'desenvolve', 'desenvolvendo',
600
+ 'formule', 'formular', 'formula', 'formulando'
601
+ ];
602
+
603
+ const substantivosDocumento = [
604
+ 'documento', 'documentos', 'doc',
605
+ 'relatório', 'relatorio', 'relatórios', 'relatorios',
606
+ 'resumo', 'resumos',
607
+ 'nota', 'notas', 'anotação', 'anotações', 'anotacoes',
608
+ 'artigo', 'artigos',
609
+ 'texto', 'textos',
610
+ 'conteúdo', 'conteudo',
611
+ 'análise', 'analise', 'análises', 'analises',
612
+ 'estudo', 'estudos',
613
+ 'resenha', 'resenhas',
614
+ 'apresentação', 'apresentacao', 'apresentações', 'apresentacoes',
615
+ 'memorial', 'memorando',
616
+ 'ata', 'atas',
617
+ 'minuta', 'minutas',
618
+ 'proposta', 'propostas',
619
+ 'briefing', 'brief'
620
+ ];
621
+
622
+ // ====== VERBOS DE AÇÃO PARA PODCAST ======
623
+ const verbosPodcast = [
624
+ 'grave', 'gravar', 'grava', 'gravando', 'gravação', 'gravacao',
625
+ 'narre', 'narrar', 'narra', 'narrando', 'narração', 'narracao',
626
+ 'transforme em áudio', 'transforme em audio',
627
+ 'converta para áudio', 'converta para audio',
628
+ 'sintetize', 'sintetizar'
629
+ ];
630
+
631
+ const substantivosPodcast = [
632
+ 'podcast', 'podcasts',
633
+ 'áudio', 'audio', 'áudios', 'audios',
634
+ 'episódio', 'episodio', 'episódios', 'episodios',
635
+ 'narração', 'narracao', 'narrações', 'narracoes',
636
+ 'locução', 'locucao',
637
+ 'gravação', 'gravacao', 'gravações', 'gravacoes'
638
+ ];
639
+
640
+ // ====== VERBOS DE AÇÃO PARA PESQUISA ======
641
+ const verbosPesquisa = [
642
+ 'pesquise', 'pesquisar', 'pesquisa', 'pesquisando',
643
+ 'busque', 'buscar', 'busca', 'buscando',
644
+ 'procure', 'procurar', 'procura', 'procurando',
645
+ 'encontre', 'encontrar', 'encontra', 'encontrando',
646
+ 'ache', 'achar', 'acha', 'achando',
647
+ 'localize', 'localizar', 'localiza', 'localizando',
648
+ 'consulte', 'consultar', 'consulta', 'consultando',
649
+ 'veja', 'ver', 'vê',
650
+ 'mostre', 'mostrar', 'mostra', 'mostrando',
651
+ 'exiba', 'exibir', 'exibe', 'exibindo',
652
+ 'liste', 'listar', 'lista', 'listando',
653
+ 'traga', 'trazer', 'traz', 'trazendo',
654
+ 'recupere', 'recuperar', 'recupera', 'recuperando'
655
+ ];
656
+
657
+ const complementosPesquisa = [
658
+ 'me mostre', 'mostre-me', 'me mostra',
659
+ 'me traga', 'traga-me', 'me traz',
660
+ 'quero ver', 'quero saber', 'quero encontrar',
661
+ 'preciso de', 'preciso ver', 'preciso encontrar',
662
+ 'gostaria de ver', 'gostaria de saber',
663
+ 'pode me mostrar', 'pode me trazer',
664
+ 'você tem', 'tem algum', 'tem alguma',
665
+ 'existe', 'existem', 'há algum', 'há alguma',
666
+ 'onde está', 'onde estão', 'onde fica', 'onde ficam',
667
+ 'qual é', 'quais são'
668
+ ];
669
+
670
+ // ====== DETECÇÃO CONTEXTUAL ======
671
+
672
+ // 1. Verificar PODCAST primeiro (mais específico)
673
+ for (const verbo of verbosPodcast) {
674
+ if (textoNormalizado.includes(verbo)) {
675
+ return 'podcast';
676
+ }
677
+ }
678
+
679
+ for (const substantivo of substantivosPodcast) {
680
+ for (const verbo of verbosDocumento) {
681
+ if (textoNormalizado.includes(verbo) && textoNormalizado.includes(substantivo)) {
682
+ return 'podcast';
683
+ }
684
+ }
685
+ }
686
+
687
+ // Detecção específica: "transformar/converter em áudio"
688
+ if ((textoNormalizado.includes('transforme') || textoNormalizado.includes('transformar') ||
689
+ textoNormalizado.includes('converta') || textoNormalizado.includes('converter')) &&
690
+ (textoNormalizado.includes('áudio') || textoNormalizado.includes('audio'))) {
691
+ return 'podcast';
692
+ }
693
+
694
+ // 2. Verificar DOCUMENTO (combinação verbo + substantivo)
695
+ for (const verbo of verbosDocumento) {
696
+ for (const substantivo of substantivosDocumento) {
697
+ if (textoNormalizado.includes(verbo) && textoNormalizado.includes(substantivo)) {
698
+ return 'document';
699
+ }
700
+ }
701
+ }
702
+
703
+ // Detecção simples: apenas o verbo "documentar"
704
+ if (textoNormalizado.includes('documente') || textoNormalizado.includes('documentar')) {
705
+ return 'document';
706
+ }
707
+
708
+ // Detecção: "escrever/escreva sobre"
709
+ if ((textoNormalizado.includes('escreva sobre') || textoNormalizado.includes('escrever sobre') ||
710
+ textoNormalizado.includes('escreva um') || textoNormalizado.includes('escreva uma'))) {
711
+ return 'document';
712
+ }
713
+
714
+ // Padrões comuns de criação de documento
715
+ const padrõesDocumento = [
716
+ /^(crie|criar|faça|fazer|gere|gerar).*(documento|relatório|relatorio|resumo|nota|artigo|texto|análise|analise)/,
717
+ /^(preciso|quero|gostaria).*(criar|fazer|gerar).*(documento|relatório|relatorio|resumo)/,
718
+ /^me (ajude|ajuda) (a )?(criar|fazer|gerar).*(documento|relatório|relatorio)/
719
+ ];
720
+
721
+ for (const padrao of padrõesDocumento) {
722
+ if (padrao.test(textoNormalizado)) {
723
+ return 'document';
724
+ }
725
+ }
726
+
727
+ // 3. Verificar PESQUISA
728
+ for (const verbo of verbosPesquisa) {
729
+ if (textoNormalizado.startsWith(verbo + ' ') || textoNormalizado.includes(' ' + verbo + ' ')) {
730
+ return 'search';
731
+ }
732
+ }
733
+
734
+ for (const complemento of complementosPesquisa) {
735
+ if (textoNormalizado.includes(complemento)) {
736
+ return 'search';
737
+ }
738
+ }
739
+
740
+ // Padrões interrogativos que indicam pesquisa
741
+ 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')) {
747
+ return 'search';
748
+ }
749
+
750
+ return null;
751
+ };
752
+
753
+ const handleEnviarMensagem = (action?: ActionType) => {
754
+ if (mensagem.trim()) {
755
+ // Se não houver ação explícita, tentar detectar a intenção
756
+ let acaoDetectada = action;
757
+ if (!acaoDetectada) {
758
+ acaoDetectada = detectarIntencao(mensagem) || undefined;
759
+ }
760
+
761
+ let mensagemComAcao = mensagem;
762
+
763
+ // Adicionar prefixo baseado na ação selecionada ou detectada
764
+ if (acaoDetectada === 'document') {
765
+ mensagemComAcao = `📄 [Criar documento] ${mensagem}`;
766
+ } else if (acaoDetectada === 'podcast') {
767
+ mensagemComAcao = `🎙️ [Gerar podcast] ${mensagem}`;
768
+ } else if (acaoDetectada === 'search') {
769
+ mensagemComAcao = `🔍 [Pesquisar] ${mensagem}`;
770
+ }
771
+
772
+ const novaMensagem: Message = {
773
+ id: Date.now().toString(),
774
+ type: 'user',
775
+ content: mensagemComAcao,
776
+ timestamp: new Date(),
777
+ isFavorite: false
778
+ };
779
+
780
+ // Atualizar título da conversa se for a primeira mensagem
781
+ setConversas(prev => prev.map(conv => {
782
+ if (conv.id === conversaAtual) {
783
+ const novasMensagens = [...conv.mensagens, novaMensagem];
784
+ const novoTitulo = conv.mensagens.length === 0
785
+ ? mensagem.substring(0, 30) + (mensagem.length > 30 ? '...' : '')
786
+ : conv.titulo;
787
+
788
+ return {
789
+ ...conv,
790
+ titulo: novoTitulo,
791
+ mensagens: novasMensagens,
792
+ ultimaMensagem: mensagem,
793
+ timestamp: new Date().toLocaleString('pt-BR')
794
+ };
795
+ }
796
+ return conv;
797
+ }));
798
+
799
+ setMensagem('');
800
+
801
+ // Simular resposta da IA baseada na ação detectada ou explícita
802
+ if (acaoDetectada === 'document') {
803
+ simularRespostaDocumento(mensagem);
804
+ } else if (acaoDetectada === 'podcast') {
805
+ simularRespostaPodcast(mensagem);
806
+ } else if (acaoDetectada === 'search') {
807
+ simularRespostaPesquisa(mensagem);
808
+ } else {
809
+ simularRespostaIA(mensagem);
810
+ }
811
+ }
812
+ };
813
+
814
+ const handleToggleFavorite = (messageId: string) => {
815
+ setConversas(prev => prev.map(conv => {
816
+ if (conv.id === conversaAtual) {
817
+ return {
818
+ ...conv,
819
+ mensagens: conv.mensagens.map(msg =>
820
+ msg.id === messageId ? { ...msg, isFavorite: !msg.isFavorite } : msg
821
+ )
822
+ };
823
+ }
824
+ return conv;
825
+ }));
826
+ };
827
+
828
+ const handleCopyMessage = async (content: string, messageId: string) => {
829
+ try {
830
+ // Try modern Clipboard API first
831
+ if (navigator.clipboard && navigator.clipboard.writeText) {
832
+ try {
833
+ await navigator.clipboard.writeText(content);
834
+ setCopiedId(messageId);
835
+ setTimeout(() => setCopiedId(null), 2000);
836
+ } catch (clipboardError: any) {
837
+ // If clipboard API fails due to permissions, fall back to execCommand
838
+ if (clipboardError.name === 'NotAllowedError' || clipboardError.message.includes('permissions policy')) {
839
+ throw new Error('Clipboard permission denied, falling back');
840
+ }
841
+ throw clipboardError;
842
+ }
843
+ } else {
844
+ throw new Error('Clipboard API not available');
845
+ }
846
+ } catch (err) {
847
+ // Fallback for browsers that don't support Clipboard API or when permissions are denied
848
+ try {
849
+ const textArea = document.createElement('textarea');
850
+ textArea.value = content;
851
+ textArea.style.position = 'fixed';
852
+ textArea.style.left = '-999999px';
853
+ textArea.style.top = '-999999px';
854
+ document.body.appendChild(textArea);
855
+ textArea.focus();
856
+ textArea.select();
857
+
858
+ const successful = document.execCommand('copy');
859
+ document.body.removeChild(textArea);
860
+
861
+ if (successful) {
862
+ setCopiedId(messageId);
863
+ setTimeout(() => setCopiedId(null), 2000);
864
+ } else {
865
+ console.error('Falha ao copiar: execCommand returned false');
866
+ }
867
+ } catch (fallbackErr) {
868
+ console.error('Falha ao copiar:', fallbackErr);
869
+ }
870
+ }
871
+ };
872
+
873
+ const handleToggleFavoritaConversa = (conversaId: string) => {
874
+ setConversas(prev => prev.map(conv =>
875
+ conv.id === conversaId ? { ...conv, favorita: !conv.favorita } : conv
876
+ ));
877
+ };
878
+
879
+ const handleNovaConversa = () => {
880
+ const novaConversa: Conversa = {
881
+ id: Date.now().toString(),
882
+ titulo: 'Nova Conversa',
883
+ mensagens: [],
884
+ ultimaMensagem: '',
885
+ timestamp: new Date().toLocaleString('pt-BR'),
886
+ favorita: false
887
+ };
888
+
889
+ setConversas(prev => [novaConversa, ...prev]);
890
+ setConversaAtual(novaConversa.id);
891
+ setAbaSelecionada('chat');
892
+ };
893
+
894
+ const handleSelecionarConversa = (conversaId: string) => {
895
+ setConversaAtual(conversaId);
896
+ setAbaSelecionada('chat');
897
+ };
898
+
899
+ const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
900
+ const file = e.target.files?.[0];
901
+ if (file) {
902
+ const novaMensagem: Message = {
903
+ id: Date.now().toString(),
904
+ type: 'user',
905
+ content: `Enviei o arquivo: ${file.name}`,
906
+ timestamp: new Date(),
907
+ isFavorite: false,
908
+ attachmentType: 'file',
909
+ attachmentName: file.name
910
+ };
911
+
912
+ setConversas(prev => prev.map(conv => {
913
+ if (conv.id === conversaAtual) {
914
+ return {
915
+ ...conv,
916
+ mensagens: [...conv.mensagens, novaMensagem],
917
+ ultimaMensagem: `Arquivo: ${file.name}`,
918
+ timestamp: new Date().toLocaleString('pt-BR')
919
+ };
920
+ }
921
+ return conv;
922
+ }));
923
+
924
+ // Simular resposta sobre o arquivo
925
+ setTimeout(() => {
926
+ simularRespostaIA(`Analisar arquivo ${file.name}`);
927
+ }, 500);
928
+ }
929
+
930
+ // Reset input
931
+ if (e.target) e.target.value = '';
932
+ };
933
+
934
+ const handleAudioUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
935
+ const file = e.target.files?.[0];
936
+ if (file) {
937
+ const novaMensagem: Message = {
938
+ id: Date.now().toString(),
939
+ type: 'user',
940
+ content: `Enviei um áudio: ${file.name}`,
941
+ timestamp: new Date(),
942
+ isFavorite: false,
943
+ attachmentType: 'audio',
944
+ attachmentName: file.name
945
+ };
946
+
947
+ setConversas(prev => prev.map(conv => {
948
+ if (conv.id === conversaAtual) {
949
+ return {
950
+ ...conv,
951
+ mensagens: [...conv.mensagens, novaMensagem],
952
+ ultimaMensagem: `Áudio: ${file.name}`,
953
+ timestamp: new Date().toLocaleString('pt-BR')
954
+ };
955
+ }
956
+ return conv;
957
+ }));
958
+
959
+ // Simular resposta sobre o áudio
960
+ setTimeout(() => {
961
+ 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
+ const novaMensagemIA: Message = {
964
+ id: Date.now().toString() + '-ia',
965
+ type: 'assistant',
966
+ content: respostaAudio,
967
+ timestamp: new Date(),
968
+ isFavorite: false
969
+ };
970
+
971
+ setConversas(prev => prev.map(conv => {
972
+ if (conv.id === conversaAtual) {
973
+ return {
974
+ ...conv,
975
+ mensagens: [...conv.mensagens, novaMensagemIA],
976
+ ultimaMensagem: respostaAudio.substring(0, 50) + '...',
977
+ timestamp: new Date().toLocaleString('pt-BR')
978
+ };
979
+ }
980
+ return conv;
981
+ }));
982
+ }, 1500);
983
+ }
984
+
985
+ // Reset input
986
+ if (e.target) e.target.value = '';
987
+ };
988
+
989
+ const handleGeneratePodcast = async (messageId: string, messageContent: string) => {
990
+ // Marcar que o podcast está sendo gerado
991
+ setGeneratingPodcastId(messageId);
992
+
993
+ // Simular delay de geração (2-3 segundos)
994
+ await new Promise(resolve => setTimeout(resolve, 2000 + Math.random() * 1000));
995
+
996
+ // Gerar URL de áudio simulado (usando um data URL de áudio vazio)
997
+ // Em produção, isso seria substituído por uma chamada real à API de geração de áudio
998
+ const audioUrl = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4Ljc2LjEwMAAAAAAAAAAAAAAA//tQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWGluZwAAAA8AAAACAAADhAC7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7v///////////////////////////////////////////8AAAAATGF2YzU4LjEzAAAAAAAAAAAAAAAAJAAAAAAAAAAAA4TnTxAOAAAAAAD/+xBkAA';
999
+
1000
+ const temaExtraido = messageContent.substring(0, 50).replace(/[📝🎙️🔍]/g, '').trim();
1001
+
1002
+ const podcastMessage: Message = {
1003
+ id: Date.now().toString() + '-podcast',
1004
+ type: 'assistant',
1005
+ content: `🎙️ Podcast gerado com sucesso!\n\nCriei um podcast baseado na mensagem anterior com:\n\n• Introdução envolvente ao tema\n• Desenvolvimento detalhado do conteúdo\n• Exemplos práticos e aplicações\n• Reflexões e insights\n• Conclusão com próximos passos\n\nDuração estimada: 5-7 minutos\n\nAperte play abaixo para ouvir:`,
1006
+ timestamp: new Date(),
1007
+ isFavorite: false,
1008
+ attachmentType: 'podcast',
1009
+ attachmentName: `Podcast - ${temaExtraido}.mp3`,
1010
+ audioUrl: audioUrl
1011
+ };
1012
+
1013
+ setConversas(prev => prev.map(conv => {
1014
+ if (conv.id === conversaAtual) {
1015
+ return {
1016
+ ...conv,
1017
+ mensagens: [...conv.mensagens, podcastMessage],
1018
+ ultimaMensagem: 'Podcast gerado',
1019
+ timestamp: new Date().toLocaleString('pt-BR')
1020
+ };
1021
+ }
1022
+ return conv;
1023
+ }));
1024
+
1025
+ setGeneratingPodcastId(null);
1026
+ };
1027
+
1028
+ const conversasFiltradas = abaSelecionada === 'favoritos'
1029
+ ? conversas.filter(c => c.favorita)
1030
+ : conversas;
1031
+
1032
+ const handleExpandWithTab = (tab: 'chat' | 'historico' | 'favoritos') => {
1033
+ setAbaSelecionada(tab);
1034
+ if (onToggleWithTab) {
1035
+ onToggleWithTab(tab);
1036
+ } else if (!isExpanded) {
1037
+ onToggle();
1038
+ }
1039
+ };
1040
+
1041
+ const handleEditDocument = (content: string, title: string) => {
1042
+ setEditingDocument({ content, title });
1043
+ };
1044
+
1045
+ const handleCloseEditor = () => {
1046
+ setEditingDocument(null);
1047
+ };
1048
+
1049
+ const handleDownloadDocument = (content: string, filename: string) => {
1050
+ const blob = new Blob([content], { type: 'text/markdown' });
1051
+ const url = URL.createObjectURL(blob);
1052
+ const a = document.createElement('a');
1053
+ a.href = url;
1054
+ a.download = filename;
1055
+ document.body.appendChild(a);
1056
+ a.click();
1057
+ document.body.removeChild(a);
1058
+ URL.revokeObjectURL(url);
1059
+ };
1060
+
1061
+ const handleDownloadPodcast = (audioUrl: string, filename: string) => {
1062
+ const a = document.createElement('a');
1063
+ a.href = audioUrl;
1064
+ a.download = filename;
1065
+ document.body.appendChild(a);
1066
+ a.click();
1067
+ document.body.removeChild(a);
1068
+ };
1069
+
1070
+ // Funções para ações de pesquisa
1071
+ const handleOpenSearchResult = (result: SearchResult) => {
1072
+ // Adicionar mensagem do usuário simulando clique
1073
+ const userMessage: Message = {
1074
+ id: Date.now().toString(),
1075
+ type: 'user',
1076
+ content: `Abrir: ${result.title}`,
1077
+ timestamp: new Date(),
1078
+ isFavorite: false
1079
+ };
1080
+
1081
+ setConversas(prev => prev.map(conv => {
1082
+ if (conv.id === conversaAtual) {
1083
+ return {
1084
+ ...conv,
1085
+ mensagens: [...conv.mensagens, userMessage],
1086
+ ultimaMensagem: userMessage.content,
1087
+ timestamp: new Date().toLocaleString('pt-BR')
1088
+ };
1089
+ }
1090
+ return conv;
1091
+ }));
1092
+
1093
+ // Resposta do assistente com opções baseadas no tipo
1094
+ setTimeout(() => {
1095
+ let responseContent = '';
1096
+ let actionButtons = '';
1097
+
1098
+ if (result.type === 'document') {
1099
+ 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
+ } else if (result.type === 'project') {
1101
+ 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**Opções disponíveis:**\n\n• Ver dashboard do projeto\n• Listar tarefas ativas\n• Ver membros da equipe\n• Gerar relatório de progresso\n• Acessar documentação\n• Visualizar timeline`;
1102
+ } else if (result.type === 'conversation') {
1103
+ 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**Ações disponíveis:**\n\n• Abrir conversa completa\n• Ver participantes\n• Exportar histórico\n• Buscar em mensagens\n• Marcar como importante\n• Criar resumo da conversa`;
1104
+ } else if (result.type === 'file') {
1105
+ 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 posso fazer:**\n\n• Fazer download do arquivo\n• Visualizar prévia\n• Compartilhar link\n• Ver detalhes do arquivo\n• Mover para outra pasta\n• Renomear arquivo`;
1106
+ } else if (result.type === 'contact') {
1107
+ 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
+
1110
+ const assistantMessage: Message = {
1111
+ id: Date.now().toString() + '-response',
1112
+ type: 'assistant',
1113
+ content: responseContent,
1114
+ timestamp: new Date(),
1115
+ isFavorite: false
1116
+ };
1117
+
1118
+ setConversas(prev => prev.map(conv => {
1119
+ if (conv.id === conversaAtual) {
1120
+ return {
1121
+ ...conv,
1122
+ mensagens: [...conv.mensagens, assistantMessage],
1123
+ ultimaMensagem: 'Opções de ação',
1124
+ timestamp: new Date().toLocaleString('pt-BR')
1125
+ };
1126
+ }
1127
+ return conv;
1128
+ }));
1129
+ }, 500);
1130
+ };
1131
+
1132
+ const handleCreateDocumentFromSearch = (searchQuery: string, results: SearchResult[]) => {
1133
+ setExecutingCommand('document');
1134
+
1135
+ // Criar conteúdo do documento baseado nos resultados
1136
+ 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
+ setTimeout(() => {
1139
+ setExecutingCommand(null);
1140
+ simularRespostaDocumento(`Compilar resultados da pesquisa: ${searchQuery}`);
1141
+ }, 1500);
1142
+ };
1143
+
1144
+ const handleGenerateReport = (searchQuery: string, results: SearchResult[]) => {
1145
+ setExecutingCommand('report');
1146
+
1147
+ setTimeout(() => {
1148
+ const reportMessage: Message = {
1149
+ id: Date.now().toString(),
1150
+ type: 'assistant',
1151
+ content: `📊 Relatório Analítico gerado!\n\n**Pesquisa:** ${searchQuery}\n\n**Métricas:**\n• ${results.length} resultados encontrados\n• Relevância média: ${Math.round(results.reduce((acc, r) => acc + r.relevance, 0) / results.length)}%\n• Tipos de conteúdo: ${new Set(results.map(r => r.type)).size}\n\n**Distribuição por tipo:**\n${Object.entries(results.reduce((acc, r) => ({ ...acc, [r.type]: (acc[r.type] || 0) + 1 }), {} as Record<string, number>)).map(([type, count]) => `• ${type}: ${count}`).join('\n')}\n\n**Recomenda��ões:**\n• Revisar os 3 resultados com maior relevância\n• Atualizar documentos com mais de 1 semana\n• Consolidar informações similares`,
1152
+ timestamp: new Date(),
1153
+ isFavorite: false
1154
+ };
1155
+
1156
+ setConversas(prev => prev.map(conv => {
1157
+ if (conv.id === conversaAtual) {
1158
+ return {
1159
+ ...conv,
1160
+ mensagens: [...conv.mensagens, reportMessage],
1161
+ ultimaMensagem: 'Relatório gerado',
1162
+ timestamp: new Date().toLocaleString('pt-BR')
1163
+ };
1164
+ }
1165
+ return conv;
1166
+ }));
1167
+
1168
+ setExecutingCommand(null);
1169
+ }, 2000);
1170
+ };
1171
+
1172
+ const handleGeneratePodcastFromSearch = (searchQuery: string) => {
1173
+ setExecutingCommand('podcast');
1174
+
1175
+ setTimeout(() => {
1176
+ simularRespostaPodcast(`Criar podcast sobre os resultados da pesquisa: ${searchQuery}`);
1177
+ setExecutingCommand(null);
1178
+ }, 1000);
1179
+ };
1180
+
1181
+ const handleSendSummary = (searchQuery: string, results: SearchResult[]) => {
1182
+ setExecutingCommand('email');
1183
+
1184
+ setTimeout(() => {
1185
+ toast.success('Resumo enviado!', {
1186
+ description: `Email enviado para sua equipe com ${results.length} resultados sobre "${searchQuery}"`
1187
+ });
1188
+ setExecutingCommand(null);
1189
+ }, 1500);
1190
+ };
1191
+
1192
+ const handleSaveSearch = (searchQuery: string, messageId: string) => {
1193
+ if (savedSearches.includes(messageId)) {
1194
+ setSavedSearches(prev => prev.filter(id => id !== messageId));
1195
+ toast.info('Pesquisa removida dos favoritos');
1196
+ } else {
1197
+ setSavedSearches(prev => [...prev, messageId]);
1198
+ toast.success('Pesquisa salva nos favoritos!', {
1199
+ description: `"${searchQuery}" foi adicionada aos seus favoritos`
1200
+ });
1201
+ }
1202
+ };
1203
+
1204
+ const handleExecuteSearchCommand = (commandId: string, searchQuery: string, results: SearchResult[], messageId: string) => {
1205
+ switch (commandId) {
1206
+ case '1':
1207
+ handleCreateDocumentFromSearch(searchQuery, results);
1208
+ break;
1209
+ case '2':
1210
+ handleGenerateReport(searchQuery, results);
1211
+ break;
1212
+ case '3':
1213
+ handleGeneratePodcastFromSearch(searchQuery);
1214
+ break;
1215
+ case '4':
1216
+ handleSendSummary(searchQuery, results);
1217
+ break;
1218
+ case '5':
1219
+ handleSaveSearch(searchQuery, messageId);
1220
+ break;
1221
+ default:
1222
+ toast.info('Comando não implementado');
1223
+ }
1224
+ };
1225
+
1226
+ return (
1227
+ <>
1228
+ {/* 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
+ }`}>
1236
+ <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
1256
+ ? 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" />
1300
+ )}
1301
+ </Button>
1302
+ </div>
1303
+ </div>
1304
+ )}
1305
+
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
+ )}
1385
+
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">
1400
+ <Button
1401
+ variant={abaSelecionada === 'chat' ? 'default' : 'ghost'}
1402
+ size="sm"
1403
+ onClick={() => setAbaSelecionada('chat')}
1404
+ className="flex-1 h-8 text-xs"
1405
+ >
1406
+ <MessageSquare className="w-3 h-3 mr-1" />
1407
+ Chat
1408
+ </Button>
1409
+ <Button
1410
+ variant={abaSelecionada === 'historico' ? 'default' : 'ghost'}
1411
+ size="sm"
1412
+ onClick={() => setAbaSelecionada('historico')}
1413
+ className="flex-1 h-8 text-xs"
1414
+ >
1415
+ <History className="w-3 h-3 mr-1" />
1416
+ Histórico
1417
+ </Button>
1418
+ <Button
1419
+ variant={abaSelecionada === 'favoritos' ? 'default' : 'ghost'}
1420
+ size="sm"
1421
+ onClick={() => setAbaSelecionada('favoritos')}
1422
+ className="flex-1 h-8 text-xs"
1423
+ >
1424
+ <Heart className="w-3 h-3 mr-1" />
1425
+ Favoritos
1426
+ </Button>
1427
+ </div>
1428
+ </div>
1429
+ )}
1430
+
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>
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>
1562
+ </div>
1563
+ )}
1564
+
1565
+ {msg.attachmentType && msg.attachmentType !== 'podcast' && msg.attachmentType !== 'document' && (
1566
+ <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"
1569
+ )}>
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>
1596
+ </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" />
1626
+ </div>
1627
+ <span className="text-small text-muted-foreground break-words">
1628
+ {msg.attachmentName}
1629
+ </span>
1630
+ </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>
1663
+ </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>
1705
+ </div>
1706
+ </div>
1707
+ </div>
1708
+ ))}
1709
+ </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>
1726
+ </div>
1727
+ ))}
1728
+ </div>
1729
+ </div>
1730
+ )}
1731
+
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}
1764
+ </div>
1765
+ </div>
1766
+ </button>
1767
+ ))}
1768
+ </div>
1769
+ </div>
1770
+ )}
1771
+ </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' && (
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 ${
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
+
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}
1960
+ />
1961
+ )}
1962
+ </motion.div>
1963
+ )}
1964
+ </AnimatePresence>
1965
+ </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
+ </>
2002
+ );
2003
+ }