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.
- package/components/AssistenteXertica.tsx +818 -819
- package/components/HomeContent.tsx +11 -3
- package/components/layout-constants.ts +4 -0
- package/dist/components/layout-constants.d.ts +4 -0
- package/dist/index.es.js +607 -596
- package/dist/index.umd.js +607 -596
- package/package.json +5 -2
|
@@ -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
|
-
|
|
269
|
-
|
|
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
|
-
|
|
690
|
-
|
|
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
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
<span className="text-foreground">Assistente Xertica</span>
|
|
1274
|
-
</motion.div>
|
|
1275
|
-
)}
|
|
1276
|
-
|
|
1277
|
-
<div className="flex items-center gap-1">
|
|
1278
|
-
{isExpanded && (
|
|
1279
|
-
<Button
|
|
1280
|
-
variant="ghost"
|
|
1281
|
-
size="sm"
|
|
1282
|
-
onClick={() => navigate('/assistente')}
|
|
1283
|
-
className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1284
|
-
title="Expandir assistente"
|
|
1285
|
-
>
|
|
1286
|
-
<Maximize2 className="w-4 h-4" />
|
|
1287
|
-
</Button>
|
|
1288
|
-
)}
|
|
1289
|
-
|
|
1290
|
-
<Button
|
|
1291
|
-
variant="ghost"
|
|
1292
|
-
size="sm"
|
|
1293
|
-
onClick={onToggle}
|
|
1294
|
-
className="p-2 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1295
|
-
>
|
|
1296
|
-
{isExpanded ? (
|
|
1297
|
-
<ChevronRight className="w-4 h-4" />
|
|
1298
|
-
) : (
|
|
1299
|
-
<PanelRight className="w-4 h-4" />
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
>
|
|
1335
|
-
<MessageSquare className="w-4 h-4" />
|
|
1336
|
-
</Button>
|
|
1337
|
-
</TooltipTrigger>
|
|
1338
|
-
<AssistantTooltipContent
|
|
1339
|
-
side="left"
|
|
1340
|
-
sideOffset={8}
|
|
1341
|
-
>
|
|
1342
|
-
<p>Chat</p>
|
|
1343
|
-
</AssistantTooltipContent>
|
|
1344
|
-
</Tooltip>
|
|
1345
|
-
|
|
1346
|
-
<Tooltip>
|
|
1347
|
-
<TooltipTrigger asChild>
|
|
1348
|
-
<Button
|
|
1349
|
-
variant="ghost"
|
|
1350
|
-
size="sm"
|
|
1351
|
-
onClick={() => handleExpandWithTab('favoritos')}
|
|
1352
|
-
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1353
|
-
>
|
|
1354
|
-
<Heart className="w-4 h-4" />
|
|
1355
|
-
</Button>
|
|
1356
|
-
</TooltipTrigger>
|
|
1357
|
-
<AssistantTooltipContent
|
|
1358
|
-
side="left"
|
|
1359
|
-
sideOffset={8}
|
|
1360
|
-
>
|
|
1361
|
-
<p>Favoritos</p>
|
|
1362
|
-
</AssistantTooltipContent>
|
|
1363
|
-
</Tooltip>
|
|
1364
|
-
|
|
1365
|
-
<Tooltip>
|
|
1366
|
-
<TooltipTrigger asChild>
|
|
1367
|
-
<Button
|
|
1368
|
-
variant="ghost"
|
|
1369
|
-
size="sm"
|
|
1370
|
-
onClick={() => handleExpandWithTab('historico')}
|
|
1371
|
-
className="w-8 h-8 p-0 text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
|
1372
|
-
>
|
|
1373
|
-
<History className="w-4 h-4" />
|
|
1374
|
-
</Button>
|
|
1375
|
-
</TooltipTrigger>
|
|
1376
|
-
<AssistantTooltipContent
|
|
1377
|
-
side="left"
|
|
1378
|
-
sideOffset={8}
|
|
1379
|
-
>
|
|
1380
|
-
<p>Histórico</p>
|
|
1381
|
-
</AssistantTooltipContent>
|
|
1382
|
-
</Tooltip>
|
|
1383
|
-
</div>
|
|
1384
|
-
)}
|
|
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
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
<
|
|
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=
|
|
1331
|
+
variant="ghost"
|
|
1402
1332
|
size="sm"
|
|
1403
|
-
onClick={() =>
|
|
1404
|
-
className="
|
|
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-
|
|
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=
|
|
1350
|
+
variant="ghost"
|
|
1411
1351
|
size="sm"
|
|
1412
|
-
onClick={() =>
|
|
1413
|
-
className="
|
|
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
|
-
<
|
|
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=
|
|
1369
|
+
variant="ghost"
|
|
1420
1370
|
size="sm"
|
|
1421
|
-
onClick={() =>
|
|
1422
|
-
className="
|
|
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
|
-
<
|
|
1425
|
-
Favoritos
|
|
1374
|
+
<History className="w-4 h-4" />
|
|
1426
1375
|
</Button>
|
|
1427
|
-
</
|
|
1428
|
-
|
|
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
|
-
|
|
1503
|
+
variant="ghost"
|
|
1454
1504
|
size="sm"
|
|
1455
|
-
className="
|
|
1505
|
+
className="w-full justify-start text-muted-foreground hover:text-foreground"
|
|
1456
1506
|
>
|
|
1457
|
-
|
|
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
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
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
|
-
{
|
|
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
|
-
"
|
|
1568
|
-
msg.type === 'user'
|
|
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
|
-
{
|
|
1571
|
-
{msg.attachmentType === '
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
)}>{msg.attachmentName}</span>
|
|
1577
|
-
</div>
|
|
1578
|
-
)}
|
|
1579
|
-
|
|
1580
|
-
{/* Conteúdo da mensagem */}
|
|
1581
|
-
{msg.type === 'user' ? (
|
|
1582
|
-
<p className="whitespace-pre-wrap break-words text-primary-foreground">{msg.content}</p>
|
|
1583
|
-
) : (
|
|
1584
|
-
<>
|
|
1585
|
-
{/* Alerta visual para mensagens de erro */}
|
|
1586
|
-
{(msg.content.includes('🔐') || msg.content.includes('❌')) && (
|
|
1587
|
-
<div className="mb-3 p-3 bg-[var(--toast-error-bg)]/20 border border-[var(--toast-error-border)] rounded-lg overflow-hidden">
|
|
1588
|
-
<div className="flex items-start gap-2 min-w-0">
|
|
1589
|
-
<AlertCircle className="w-5 h-5 text-[var(--toast-error-icon)] flex-shrink-0 mt-0.5" />
|
|
1590
|
-
<div className="flex-1 min-w-0 overflow-hidden">
|
|
1591
|
-
<MarkdownMessage
|
|
1592
|
-
content={msg.content}
|
|
1593
|
-
className="text-foreground"
|
|
1594
|
-
/>
|
|
1595
|
-
</div>
|
|
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
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
</div>
|
|
1617
|
-
)}
|
|
1618
|
-
|
|
1619
|
-
{/* Podcast Player */}
|
|
1620
|
-
{msg.attachmentType === 'podcast' && msg.audioUrl && (
|
|
1621
|
-
<div className="mt-3 pt-3 border-t border-border overflow-hidden">
|
|
1622
|
-
<div className="flex items-center justify-between mb-3 min-w-0 overflow-hidden">
|
|
1623
|
-
<div className="flex items-center gap-2 min-w-0 flex-1 overflow-hidden">
|
|
1624
|
-
<div className="w-6 h-6 rounded-full bg-gradient-to-br from-[var(--chart-4)] via-[var(--chart-1)] to-[var(--chart-4)] flex items-center justify-center flex-shrink-0">
|
|
1625
|
-
<Radio className="w-3 h-3 text-white" />
|
|
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
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
>
|
|
1638
|
-
<
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
>
|
|
1646
|
-
<source src={msg.audioUrl} type="audio/mpeg" />
|
|
1647
|
-
Seu navegador não suporta o elemento de áudio.
|
|
1648
|
-
</audio>
|
|
1649
|
-
</div>
|
|
1650
|
-
</div>
|
|
1651
|
-
)}
|
|
1652
|
-
|
|
1653
|
-
{/* Search Results */}
|
|
1654
|
-
{msg.attachmentType === 'search' && msg.searchResults && (
|
|
1655
|
-
<div className="mt-3 pt-3 border-t border-border space-y-4 overflow-hidden">
|
|
1656
|
-
{/* Search Results List */}
|
|
1657
|
-
<div className="overflow-hidden">
|
|
1658
|
-
<div className="flex items-center gap-2 mb-3">
|
|
1659
|
-
<Search className="w-4 h-4 text-[var(--chart-4)]" />
|
|
1660
|
-
<h4 className="text-foreground">
|
|
1661
|
-
Resultados Encontrados ({msg.searchResults.length})
|
|
1662
|
-
</h4>
|
|
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
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
{
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
<div className="flex items-start gap-2 min-w-0">
|
|
1682
|
-
<h5 className="text-small text-foreground break-words group-hover:text-primary transition-colors flex-1 min-w-0">
|
|
1683
|
-
{result.title}
|
|
1684
|
-
</h5>
|
|
1685
|
-
<span className="px-1.5 py-0.5 rounded text-small bg-[var(--chart-4)]/10 text-[var(--chart-4)] flex-shrink-0 self-start">
|
|
1686
|
-
{result.relevance}%
|
|
1687
|
-
</span>
|
|
1688
|
-
</div>
|
|
1689
|
-
<p className="text-sm text-muted-foreground mt-1 line-clamp-2 break-words">
|
|
1690
|
-
{result.description}
|
|
1691
|
-
</p>
|
|
1692
|
-
<div className="flex items-center gap-3 mt-2 flex-wrap min-w-0">
|
|
1693
|
-
<span className="text-sm text-muted-foreground flex items-center gap-1 min-w-0 max-w-full">
|
|
1694
|
-
<ExternalLink className="w-3 h-3 flex-shrink-0" />
|
|
1695
|
-
<span className="truncate">{result.path}</span>
|
|
1696
|
-
</span>
|
|
1697
|
-
{result.lastModified && (
|
|
1698
|
-
<span className="text-sm text-muted-foreground flex items-center gap-1 flex-shrink-0">
|
|
1699
|
-
<Clock className="w-3 h-3" />
|
|
1700
|
-
{result.lastModified}
|
|
1701
|
-
</span>
|
|
1702
|
-
)}
|
|
1703
|
-
</div>
|
|
1704
|
-
</div>
|
|
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
|
-
|
|
1711
|
-
|
|
1712
|
-
{/*
|
|
1713
|
-
{msg.
|
|
1714
|
-
<div>
|
|
1715
|
-
<
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
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
|
|
1733
|
-
{msg.
|
|
1734
|
-
<div>
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
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={() =>
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
1809
|
-
|
|
1810
|
-
size="sm"
|
|
1811
|
-
onClick={() => handleCopyMessage(msg.content, msg.id)}
|
|
1812
|
-
className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
|
|
1813
|
-
>
|
|
1814
|
-
{copiedId === msg.id ? (
|
|
1815
|
-
<Check className="w-3 h-3 text-[var(--toast-success-icon)]" />
|
|
1816
|
-
) : (
|
|
1817
|
-
<Copy className="w-3 h-3" />
|
|
1818
|
-
)}
|
|
1819
|
-
</Button>
|
|
1922
|
+
<p className="text-sm text-muted-foreground">
|
|
1923
|
+
{conversa.timestamp}
|
|
1924
|
+
</p>
|
|
1820
1925
|
</div>
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
{/* Typing Indicator */}
|
|
1826
|
-
{isTyping && (
|
|
1827
|
-
<motion.div
|
|
1828
|
-
initial={{ opacity: 0, y: 10 }}
|
|
1829
|
-
animate={{ opacity: 1, y: 0 }}
|
|
1830
|
-
className="flex gap-2 justify-start"
|
|
1831
|
-
>
|
|
1832
|
-
{/* Avatar do Assistente */}
|
|
1833
|
-
<div className="flex-shrink-0 pt-1">
|
|
1834
|
-
<XerticaOrbe size={32} />
|
|
1835
|
-
</div>
|
|
1836
|
-
|
|
1837
|
-
<div className="bg-muted rounded-2xl px-4 py-3">
|
|
1838
|
-
<div className="flex gap-1">
|
|
1839
|
-
<motion.div
|
|
1840
|
-
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
1841
|
-
animate={{ y: [0, -8, 0] }}
|
|
1842
|
-
transition={{ repeat: Infinity, duration: 0.6, delay: 0 }}
|
|
1843
|
-
/>
|
|
1844
|
-
<motion.div
|
|
1845
|
-
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
1846
|
-
animate={{ y: [0, -8, 0] }}
|
|
1847
|
-
transition={{ repeat: Infinity, duration: 0.6, delay: 0.2 }}
|
|
1848
|
-
/>
|
|
1849
|
-
<motion.div
|
|
1850
|
-
className="w-2 h-2 bg-muted-foreground rounded-full"
|
|
1851
|
-
animate={{ y: [0, -8, 0] }}
|
|
1852
|
-
transition={{ repeat: Infinity, duration: 0.6, delay: 0.4 }}
|
|
1853
|
-
/>
|
|
1854
|
-
</div>
|
|
1855
|
-
</div>
|
|
1856
|
-
</motion.div>
|
|
1857
|
-
)}
|
|
1858
|
-
|
|
1859
|
-
<div ref={messagesEndRef} />
|
|
1926
|
+
))
|
|
1927
|
+
)}
|
|
1928
|
+
</div>
|
|
1860
1929
|
</div>
|
|
1861
1930
|
</ScrollArea>
|
|
1862
1931
|
)}
|
|
1863
1932
|
</div>
|
|
1864
|
-
)}
|
|
1865
1933
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
<
|
|
1869
|
-
{
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
conversa.id === conversaAtual
|
|
1898
|
-
? 'border-primary bg-primary/5'
|
|
1899
|
-
: 'border-border'
|
|
1900
|
-
}`}
|
|
1901
|
-
>
|
|
1902
|
-
<div className="flex items-start justify-between mb-1">
|
|
1903
|
-
<h4 className="text-small text-gray-900 dark:text-gray-100 truncate flex-1">
|
|
1904
|
-
{conversa.titulo}
|
|
1905
|
-
</h4>
|
|
1906
|
-
<Button
|
|
1907
|
-
variant="ghost"
|
|
1908
|
-
size="sm"
|
|
1909
|
-
onClick={(e) => {
|
|
1910
|
-
e.stopPropagation();
|
|
1911
|
-
handleToggleFavoritaConversa(conversa.id);
|
|
1912
|
-
}}
|
|
1913
|
-
className="h-6 w-6 p-0 flex-shrink-0 ml-1"
|
|
1914
|
-
>
|
|
1915
|
-
<Heart className={`w-3 h-3 ${conversa.favorita ? 'text-red-500 fill-current' : 'text-gray-400'}`} />
|
|
1916
|
-
</Button>
|
|
1917
|
-
</div>
|
|
1918
|
-
{conversa.ultimaMensagem && (
|
|
1919
|
-
<p className="text-sm text-muted-foreground truncate mb-1">
|
|
1920
|
-
{conversa.ultimaMensagem}
|
|
1921
|
-
</p>
|
|
1922
|
-
)}
|
|
1923
|
-
<p className="text-sm text-muted-foreground">
|
|
1924
|
-
{conversa.timestamp}
|
|
1925
|
-
</p>
|
|
1926
|
-
</div>
|
|
1927
|
-
))
|
|
1928
|
-
)}
|
|
1929
|
-
</div>
|
|
1930
|
-
</div>
|
|
1931
|
-
</ScrollArea>
|
|
1932
|
-
)}
|
|
1933
|
-
</div>
|
|
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
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
// Ao finalizar gravação, definir a mensagem transcrita e enviar
|
|
1945
|
-
setMensagem(transcript);
|
|
1946
|
-
toast.success('Áudio transcrito com sucesso!', {
|
|
1947
|
-
description: 'Sua mensagem foi convertida de voz para texto.',
|
|
1948
|
-
duration: 3000,
|
|
1949
|
-
});
|
|
1950
|
-
// Enviar automaticamente após um pequeno delay para o usuário ver a transcrição
|
|
1951
|
-
setTimeout(() => {
|
|
1952
|
-
if (!isTyping) {
|
|
1953
|
-
handleEnviarMensagem();
|
|
1954
|
-
}
|
|
1955
|
-
}, 1500);
|
|
1956
|
-
}}
|
|
1957
|
-
placeholder="Envie uma mensagem para Xertica"
|
|
1958
|
-
disabled={isTyping}
|
|
1959
|
-
isFullPage={isFullPage}
|
|
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
|
-
</
|
|
1996
|
+
</AnimatePresence>
|
|
1963
1997
|
)}
|
|
1964
|
-
</
|
|
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
|
}
|