import React, { useState, useEffect, useRef } from 'react'; import { Activity, Brain, ClipboardList, MessageSquare, Timer, LayoutDashboard, Settings, User, Send, RefreshCcw, CheckCircle2, AlertTriangle, GraduationCap, ChevronRight, Stethoscope, ShieldAlert, Wind, HeartPulse, ArrowLeft, Mic, Users, Trash2, FileText, Award, BookOpen, BookA, Search, FileSignature, Clock, Thermometer, Droplets, ClipboardCheck, Volume2, VolumeX, Download, Dices, Scale, Ruler, Calculator, Info, Lightbulb, Quote, PenTool, ListChecks, Layers, PlayCircle, X, ExternalLink, Moon, Sun, Menu } from 'lucide-react'; // --- API Helper para Integração com Gemini --- const apiKey = ""; // A chave é injetada pelo ambiente async function fetchGeminiWithRetry(prompt, systemInstruction, isJson = false, retries = 3) { const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: prompt }] }], systemInstruction: { parts: [{ text: systemInstruction }] }, }; if (isJson) { payload.generationConfig = { responseMimeType: "application/json", }; } for (let i = 0; i < retries; i++) { try { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); const data = await response.json(); if (!response.ok) throw new Error(data.error?.message || 'API Error'); const text = data.candidates?.[0]?.content?.parts?.[0]?.text; return isJson ? JSON.parse(text) : text; } catch (err) { if (i === retries - 1) throw err; await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000)); } } } // --- Funções para TTS (Text-to-Speech) com IA Gemini --- async function fetchGeminiTTS(text, voiceName = "Aoede") { const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text }] }], generationConfig: { responseModalities: ["AUDIO"], speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName } } } } }; const response = await fetch(url, { method: 'POST', body: JSON.stringify(payload) }); const data = await response.json(); if (!response.ok) throw new Error(data.error?.message || 'Erro na API TTS'); const inlineData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData; if (inlineData) return inlineData; throw new Error("Nenhum dado de áudio retornado"); } function pcmToWav(pcmBase64, sampleRate = 24000) { const binaryString = atob(pcmBase64); const len = binaryString.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) bytes[i] = binaryString.charCodeAt(i); const buffer = new ArrayBuffer(44 + len); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + len, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true); view.setUint32(24, sampleRate, true); view.setUint32(28, sampleRate * 2, true); view.setUint16(32, 2, true); view.setUint16(34, 16, true); writeString(view, 36, 'data'); view.setUint32(40, len, true); const pcmData = new Uint8Array(buffer, 44); pcmData.set(bytes); return new Blob([buffer], { type: 'audio/wav' }); } // --- Gerador de PDF Dinâmico --- const generatePDF = async (htmlContent, filename) => { try { const container = document.createElement('div'); container.innerHTML = htmlContent; container.style.padding = '20px'; container.style.fontFamily = 'Helvetica, Arial, sans-serif'; container.style.color = '#0f172a'; container.style.backgroundColor = '#ffffff'; // Garante fundo branco no PDF if (!window.html2pdf) { await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js'; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } const opt = { margin: 15, filename: filename, image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } }; await window.html2pdf().set(opt).from(container).save(); } catch (error) { console.error("Erro ao gerar PDF:", error); alert("Ocorreu um erro ao gerar o PDF. Tente novamente."); } }; // --- Componentes Compartilhados --- const Card = ({ children, className = '', onClick }) => (
{children}
); const Button = ({ children, onClick, variant = 'primary', className = '', disabled = false, type = 'button' }) => { const baseStyle = "px-4 py-2 rounded-md font-medium transition-colors flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed text-sm"; const variants = { primary: "bg-slate-900 dark:bg-indigo-600 text-white hover:bg-slate-800 dark:hover:bg-indigo-700", secondary: "bg-slate-100 dark:bg-slate-700 text-slate-800 dark:text-slate-100 hover:bg-slate-200 dark:hover:bg-slate-600 border border-slate-200 dark:border-slate-600", outline: "border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 hover:bg-slate-50 dark:hover:bg-slate-700", danger: "bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-400 hover:bg-red-100 dark:hover:bg-red-900/50 border border-red-200 dark:border-red-800", medical: "bg-blue-800 dark:bg-blue-600 text-white hover:bg-blue-900 dark:hover:bg-blue-700", success: "bg-emerald-600 dark:bg-emerald-500 text-white hover:bg-emerald-700 dark:hover:bg-emerald-600", }; return ( ); }; // --- Telas Principais --- // 0. Tela de Boas-Vindas (Landing Page) const WelcomeScreen = ({ onEnter }) => { const [name, setName] = useState(''); const [quoteIndex, setQuoteIndex] = useState(0); const quotes = [ "A tecnologia fascina, mas é o lado humano que cura.", "O paciente não é apenas uma doença, é uma biografia que precisa ser ouvida.", "A clínica é soberana, e a base da clínica é a história do paciente.", "A boa medicina se faz com ciência, arte e muito humanismo." ]; useEffect(() => { const interval = setInterval(() => { setQuoteIndex((prev) => (prev + 1) % quotes.length); }, 6000); return () => clearInterval(interval); }, []); const handleSubmit = (e) => { e.preventDefault(); if (name.trim()) { onEnter(name.trim()); } }; return (
{/* Elementos Decorativos de Fundo */}
{/* Cartão Principal */}
{/* Lado Esquerdo - Branding (Visível apenas em Desktop) */}
{/* Fundo dinâmico interno */}
Plataforma Acadêmica

Semiologia
UFRR

Ambiente virtual imersivo para simulação e aprimoramento do raciocínio clínico.

{/* Lado Direito - Formulário */}
{/* Cabeçalho de Branding para Mobile */}
Plataforma Acadêmica

Semiologia UFRR

Bem-vindo(a)!

Identifique-se para iniciar a sua sessão de prática.

setName(e.target.value)} />
{/* Citação Diária */}

"{quotes[quoteIndex]}"

— Dr. Celmo Celeno Porto

); }; // 1. Tela Inicial (Hub de Ferramentas) const Home = ({ setCurrentView, userName }) => (

Olá, Acadêmico(a) {userName}!

Selecione o módulo de prática semiológica desejado para iniciarmos.

setCurrentView('builder')} >
Passo a Passo

Tutor de Anamnese

Aprenda a construir a história clínica secção por secção (ID, QP, HDA). Receba correções da IA focadas nas regras semiológicas de cada etapa antes de juntar tudo.

Acessar Tutor
setCurrentView('analyzer')} >
Revisão Global

Revisão de Rascunho

Submeta um rascunho de evolução clínica completo. O sistema avaliará a coerência global, o raciocínio diagnóstico e identificará lacunas metodológicas no texto.

Acessar Módulo
setCurrentView('simulator')} >
Simulação

Paciente Virtual

Interrogue doentes simulados em tempo real (texto ou voz) para desenvolver a sua capacidade investigativa e agilidade de raciocínio clínico.

Acessar Módulo
setCurrentView('videos')} >
Aulas Práticas

Semio Vídeos

Acesse as videoaulas demonstrativas de como realizar o exame físico de forma correta e padronizada em cada sistema do corpo.

Acessar Módulo
setCurrentView('audios')} >
Biblioteca de Sons

Semio Áudios

Treine a sua audição clínica com os principais sons normais e patológicos das auscultas respiratória e cardiovascular.

Acessar Módulo
setCurrentView('glossary')} >
Termos Técnicos

Glossário Semiológico

Consulte rapidamente o dicionário de termos médicos, sinais clássicos e epônimos essenciais para a sua prática clínica.

Acessar Glossário
); // 2. Tutor de Anamnese Guiado com Metadados Acadêmicos const SECTIONS_CONFIG = [ { id: 'meta', title: 'Dados Acadêmicos', shortTitle: 'Autoria', tip: 'Identificação do aluno e do monitor para o cabeçalho do relatório final.', isMeta: true }, { id: 'id', title: 'Identificação (ID)', shortTitle: 'Identificação', tip: 'Nome, idade, sexo/género, cor/etnia, estado civil, profissão, naturalidade, procedência e religião. Estes dados têm importância epidemiológica e criam vínculo.', videoUrl: 'https://drive.google.com/file/d/1497AxXnsL0sBUefoZ1hbuZ4JPkJmYbwd/view?usp=sharing' }, { id: 'qp', title: 'Queixa Principal (QP)', shortTitle: 'Queixa Principal', tip: 'O sintoma que motivou a consulta e a duração exata. Seja muito breve e, se possível, utilize as palavras do doente entre aspas.', videoUrl: 'COLOQUE_SEU_LINK_AQUI' }, { id: 'hda', title: 'História da Doença Atual (HDA)', shortTitle: 'História da Doença Atual', tip: 'Início cronológico, características do sintoma (PQRST: Provocação, Qualidade, Região/Irradiação, Severidade, Tempo), fatores de melhora/piora e sintomas associados.', videoUrl: 'COLOQUE_SEU_LINK_AQUI' }, { id: 'is', title: 'Revisão de Sistemas (IS)', shortTitle: 'Revisão de Sistemas', tip: 'Interrogatório sintomatológico. Faça um rastreio rápido de sintomas noutros sistemas corporais que não foram abordados na HDA.', videoUrl: 'COLOQUE_SEU_LINK_AQUI' }, { id: 'hpp', title: 'História Patol. Pregressa (HPP)', shortTitle: 'Hist. Patológica Pregressa', tip: 'Doenças crónicas de infância/adulto, cirurgias prévias, traumatismos, transfusões, alergias e medicações de uso contínuo.', videoUrl: 'COLOQUE_SEU_LINK_AQUI' }, { id: 'hf', title: 'História Familiar (HF)', shortTitle: 'História Familiar', tip: 'Estado de saúde de pais e irmãos (ou causa de óbito). Foco em doenças hereditárias ou crónicas na família (Cancro, HAS, DM).', videoUrl: 'COLOQUE_SEU_LINK_AQUI' }, { id: 'hs', title: 'História Social (HS)', shortTitle: 'História Social', tip: 'Condições de habitação, saneamento, alimentação, atividade física, tabagismo (carga), etilismo, uso de drogas e sono.', videoUrl: 'COLOQUE_SEU_LINK_AQUI' } ]; const AnamnesisBuilder = ({ userName }) => { const [activeSection, setActiveSection] = useState(SECTIONS_CONFIG[0].id); const [formData, setFormData] = useState({ meta_student: userName || '', meta_monitor: '' }); const [feedbacks, setFeedbacks] = useState({}); const [isAnalyzing, setIsAnalyzing] = useState(false); const [showFinalDoc, setShowFinalDoc] = useState(false); const [copied, setCopied] = useState(false); const [isDownloadingPdf, setIsDownloadingPdf] = useState(false); const handleInputChange = (e) => { setFormData({ ...formData, [activeSection]: e.target.value }); }; const evaluateSection = async () => { const currentText = formData[activeSection]; if (!currentText || !currentText.trim()) return; setIsAnalyzing(true); const sectionInfo = SECTIONS_CONFIG.find(s => s.id === activeSection); const prompt = `É um rigoroso professor de semiologia médica. O aluno está a treinar a construção de uma anamnese, passo a passo. Avalie ESTRITAMENTE a secção: ${sectionInfo.title}. Texto do aluno para esta secção: "${currentText}" A regra semiológica clássica para esta secção exige: ${sectionInfo.tip} Verifique detalhadamente se o aluno abordou os pontos necessários APENAS para esta secção. Retorne um JSON: { "status": "excelente" | "incompleto" | "inadequado", "feedback": "O seu comentário construtivo direto aos erros/acertos.", "faltou": ["Lista de 1 a 3 itens obrigatórios que o aluno esqueceu (ex: 'Não perguntou sobre alergias'). Deixe vazio se não faltar nada crítico."] }`; try { const result = await fetchGeminiWithRetry(prompt, "Seja um tutor acadêmico de semiologia focado na metodologia.", true); setFeedbacks({ ...feedbacks, [activeSection]: result }); } catch (e) { alert("Erro ao avaliar a secção. Tente novamente."); } finally { setIsAnalyzing(false); } }; const generateFinalDocument = () => { let doc = "======================================================\n"; doc += " ANÁLISE DE ANAMNESE \n"; doc += "======================================================\n"; doc += `ACADÊMICO(A): ${formData.meta_student || 'Não informado'}\n`; doc += `MONITOR(A): ${formData.meta_monitor || 'Não informado'}\n`; doc += `DATA: ${new Date().toLocaleDateString('pt-BR')}\n`; doc += "======================================================\n\n"; SECTIONS_CONFIG.forEach(sec => { if (!sec.isMeta && formData[sec.id] && formData[sec.id].trim()) { doc += `[ ${sec.title.toUpperCase()} ]\n`; doc += `${formData[sec.id]}\n`; if (feedbacks[sec.id]) { const fb = feedbacks[sec.id]; doc += `\n >> AVALIAÇÃO DO MONITOR (${fb.status.toUpperCase()}):\n`; doc += ` ${fb.feedback}\n`; if (fb.faltou && fb.faltou.length > 0) { doc += ` [Pendências identificadas:]\n`; fb.faltou.forEach(item => { doc += ` - ${item}\n`; }); } } doc += `\n------------------------------------------------------\n\n`; } }); return doc; }; const handleCopyFinal = () => { navigator.clipboard.writeText(generateFinalDocument()); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleDownloadPdf = async () => { setIsDownloadingPdf(true); let html = `

Análise de Anamnese

Avaliação Semiológica Guiada

Acadêmico(a) ${formData.meta_student || 'Não informado'} Monitor(a) ${formData.meta_monitor || 'Não informado'} Data ${new Date().toLocaleDateString('pt-BR')}
`; SECTIONS_CONFIG.forEach(sec => { if (!sec.isMeta && formData[sec.id] && formData[sec.id].trim()) { html += `

${sec.title}

${formData[sec.id]}
`; if (feedbacks[sec.id]) { const fb = feedbacks[sec.id]; const statusColor = fb.status === 'excelente' ? '#059669' : fb.status === 'incompleto' ? '#d97706' : '#dc2626'; const borderColor = fb.status === 'excelente' ? '#34d399' : fb.status === 'incompleto' ? '#fbbf24' : '#f87171'; const iconMark = fb.status === 'excelente' ? '✓' : fb.status === 'incompleto' ? '⚠' : '✕'; html += `
${iconMark} Avaliação do Monitor - ${fb.status} ${fb.feedback} `; if (fb.faltou && fb.faltou.length > 0) { html += `
Pendências Identificadas:
    ${fb.faltou.map(f => `
  • ${f}
  • `).join('')}
`; } html += `
`; } html += `
`; } }); html += `
`; await generatePDF(html, 'analise_anamnese_ufrr.pdf'); setIsDownloadingPdf(false); }; const activeSectionInfo = SECTIONS_CONFIG.find(s => s.id === activeSection); const currentFeedback = feedbacks[activeSection]; if (showFinalDoc) { return (

Análise de Anamnese

O seu texto com os comentários do tutor para correção e estudo.

Visualização do Documento
{/* Cabeçalho Acadêmico Estilo Documento Oficial */}

Análise de Anamnese

Avaliação Semiológica Guiada

{/* Metadados Estilo Tabela */}
Acadêmico(a) {formData.meta_student || 'Não informado'}
Monitor(a) {formData.meta_monitor || 'Não informado'}
Data {new Date().toLocaleDateString('pt-BR')}
{/* Renderização das Seções */} {SECTIONS_CONFIG.map(sec => { if (sec.isMeta || !formData[sec.id] || !formData[sec.id].trim()) return null; const fb = feedbacks[sec.id]; return (

{sec.title}

{formData[sec.id]}
{fb && (
{fb.status === 'excelente' && } {fb.status === 'incompleto' && } {fb.status === 'inadequado' && } Avaliação do Monitor ({fb.status})

{fb.feedback}

{fb.faltou && fb.faltou.length > 0 && (
Pendências Identificadas:
    {fb.faltou.map((item, idx) => (
  • {item}
  • ))}
)}
)}
); })}
); } return (

Tutor de Anamnese

Aprenda a construir a metodologia clínica secção por secção.

{/* Menu Lateral de Seções */}
{SECTIONS_CONFIG.map(section => { const hasData = formData[section.id] && formData[section.id].trim().length > 0; const status = feedbacks[section.id]?.status; let statusColor = "text-slate-300 dark:text-slate-600"; if (section.isMeta) { const hasStudent = formData.meta_student?.trim().length > 0; const hasMonitor = formData.meta_monitor?.trim().length > 0; if (hasStudent && hasMonitor) statusColor = "text-emerald-500"; else if (hasStudent || hasMonitor) statusColor = "text-amber-500"; } else { if (status === 'excelente') statusColor = "text-emerald-500"; else if (status === 'incompleto') statusColor = "text-amber-500"; else if (status === 'inadequado') statusColor = "text-red-500"; else if (hasData) statusColor = "text-blue-400 dark:text-indigo-400"; } return ( ); })}

Como Usar

Preencha os seus dados de autoria e, de seguida, redija uma secção de cada vez. Peça à IA para avaliar se esqueceu algum elemento antes de avançar.

{/* Área de Edição Central */}

{activeSectionInfo.title}

{activeSectionInfo.isMeta ? (
setFormData({...formData, meta_student: e.target.value})} />
setFormData({...formData, meta_monitor: e.target.value})} />
) : (
)} {!activeSectionInfo.isMeta && (
)}
{/* Área de Feedback Lateral */}
{activeSectionInfo.isMeta ? (

Dados de Autoria

Estes dados serão incluídos no cabeçalho do PDF final para que o seu monitor possa identificar a sua atividade.

) : ( <> {!currentFeedback && !isAnalyzing && (

Sem avaliação

Escreva o texto e solicite a análise para verificar se omitiu detalhes semiológicos.

)} {isAnalyzing && (

A Avaliar Secção...

)} {currentFeedback && !isAnalyzing && (
{currentFeedback.status === 'excelente' && } {currentFeedback.status === 'incompleto' && } {currentFeedback.status === 'inadequado' && } Status: {currentFeedback.status}

{currentFeedback.feedback}

{currentFeedback.faltou && currentFeedback.faltou.length > 0 && (

Elementos Omitidos

    {currentFeedback.faltou.map((item, i) => (
  • {item}
  • ))}
)}
)} )}