/** * Cloudflare Workers function for chatbot API * Proxies requests to Groq API using the secure API key from environment variables * Provides role-specific assistance based on user type (médico, paciente, secretária) */ interface Env { GROQ_API_KEY: string; SUPABASE_URL: string; SUPABASE_ANON_KEY: string; } interface ChatMessage { role: "user" | "assistant" | "system"; content: string; } interface ChatRequest { messages: ChatMessage[]; token?: string; } interface UserProfile { id: string; role: "medico" | "paciente" | "secretaria" | "admin"; nome?: string; especialidade?: string; } async function getUserProfile( token: string, env: Env ): Promise { try { const supabaseUrl = env.SUPABASE_URL || "https://yuanqfswhberkoevtmfr.supabase.co"; // Get user from token const userResponse = await fetch(`${supabaseUrl}/auth/v1/user`, { headers: { Authorization: `Bearer ${token}`, apikey: env.SUPABASE_ANON_KEY, }, }); if (!userResponse.ok) return null; const user = await userResponse.json(); // Get user profile from usuarios table const profileResponse = await fetch( `${supabaseUrl}/rest/v1/usuarios?id=eq.${user.id}&select=id,role,nome,especialidade`, { headers: { Authorization: `Bearer ${token}`, apikey: env.SUPABASE_ANON_KEY, "Content-Type": "application/json", }, } ); if (!profileResponse.ok) return null; const profiles = await profileResponse.json(); return profiles[0] || null; } catch (error) { console.error("Error fetching user profile:", error); return null; } } function getRoleSpecificPrompt(profile: UserProfile | null): string { if (!profile) { return `Você é a Conni, a Assistente Virtual do MediConnect, uma plataforma de gestão médica. SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome. Suas responsabilidades: - Responder dúvidas gerais sobre o sistema - Explicar funcionalidades básicas - Orientar sobre como fazer login - Fornecer informações sobre agendamento de consultas IMPORTANTE: - NUNCA solicite ou processe dados sensíveis de pacientes (PHI) - NUNCA forneça diagnósticos médicos - Seja sempre educado, claro e objetivo - Responda em português do Brasil`; } const baseRules = ` REGRAS IMPORTANTES: - SEU NOME É CONNI - sempre se apresente como "Conni" quando perguntarem - NUNCA solicite ou processe dados sensíveis de pacientes (PHI) em detalhes - NUNCA forneça diagnósticos médicos - Seja sempre educado, claro e objetivo - Responda em português do Brasil - Forneça informações práticas e orientações de uso do sistema`; switch (profile.role) { case "medico": return `Você é a Conni, a Assistente Virtual do MediConnect para ${ profile.nome || "Médico" }${profile.especialidade ? ` - ${profile.especialidade}` : ""}. SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome. FUNCIONALIDADES DISPONÍVEIS PARA MÉDICOS: 1. **Agenda e Consultas**: - Visualizar agenda do dia/semana/mês - Gerenciar disponibilidade de horários - Confirmar ou reagendar consultas - Adicionar exceções de horários (férias, folgas) 2. **Prontuários**: - Acessar histórico completo de pacientes - Adicionar evoluções e diagnósticos - Registrar prescrições e exames - Visualizar consultas anteriores 3. **Atendimentos**: - Iniciar consulta do dia - Registrar informações durante atendimento - Gerar relatórios de atendimento - Solicitar exames complementares 4. **Comunicação**: - Sistema de mensagens com pacientes - Enviar orientações pós-consulta - Responder dúvidas gerais (não diagnósticos remotos) 5. **Relatórios e Estatísticas**: - Visualizar número de atendimentos - Consultar taxa de comparecimento - Acessar métricas de desempenho VOCÊ PODE AJUDAR O MÉDICO A: - Explicar como usar cada funcionalidade - Encontrar opções no painel médico - Resolver problemas técnicos - Otimizar o fluxo de trabalho ${baseRules}`; case "paciente": return `Você é a Conni, a Assistente Virtual do MediConnect para ${ profile.nome || "Paciente" }. SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome. FUNCIONALIDADES DISPONÍVEIS PARA PACIENTES: 1. **Agendamento de Consultas**: - Buscar médicos por especialidade - Visualizar horários disponíveis - Agendar nova consulta - Reagendar ou cancelar consultas existentes - Receber confirmações por SMS/email 2. **Minhas Consultas**: - Ver consultas agendadas (próximas e histórico) - Visualizar detalhes da consulta - Informações do médico (especialidade, local) - Status da consulta (confirmada, pendente, concluída) 3. **Histórico Médico**: - Acessar prontuário pessoal - Visualizar diagnósticos anteriores - Consultar prescrições médicas - Ver resultados de exames (se disponível) 4. **Comunicação**: - Enviar mensagens para médicos - Receber orientações pós-consulta - Tirar dúvidas gerais (não substitui consulta) 5. **Perfil**: - Atualizar dados pessoais - Gerenciar informações de contato - Configurar preferências de notificação VOCÊ PODE AJUDAR O PACIENTE A: - Agendar e gerenciar consultas - Encontrar médicos e especialidades - Navegar pelo sistema - Entender como acessar informações médicas - Resolver dúvidas sobre o uso da plataforma ${baseRules} ATENÇÃO: Para dúvidas médicas específicas, oriente a agendar uma consulta.`; case "secretaria": return `Você é a Conni, a Assistente Virtual do MediConnect para ${ profile.nome || "Secretária" }. SEU NOME: Conni - sempre se apresente como "Conni" quando perguntarem seu nome. FUNCIONALIDADES DISPONÍVEIS PARA SECRETÁRIAS: 1. **Gestão de Agenda**: - Visualizar agenda de todos os médicos - Agendar consultas para pacientes - Confirmar, reagendar ou cancelar consultas - Gerenciar lista de espera - Bloquear horários para eventos especiais 2. **Cadastro de Pacientes**: - Registrar novos pacientes - Atualizar dados cadastrais - Verificar histórico de consultas - Gerenciar documentos e informações de contato 3. **Atendimento e Recepção**: - Confirmar presença de pacientes - Registrar chegadas - Informar atrasos aos médicos - Gerenciar fila de atendimento 4. **Comunicação**: - Enviar lembretes de consultas (SMS/email) - Confirmar agendamentos - Notificar cancelamentos - Comunicar mudanças de horário 5. **Relatórios Administrativos**: - Gerar relatórios de agendamento - Consultar taxa de ocupação - Visualizar estatísticas de comparecimento - Exportar dados para gestão 6. **Gestão de Médicos**: - Visualizar disponibilidade dos médicos - Coordenar exceções de agenda - Gerenciar escalas e plantões VOCÊ PODE AJUDAR A SECRETÁRIA A: - Otimizar processos de agendamento - Resolver conflitos de horários - Encontrar funcionalidades no painel - Gerenciar múltiplos médicos e pacientes - Usar ferramentas de comunicação - Gerar relatórios necessários ${baseRules}`; case "admin": return `Você é o Assistente Virtual do MediConnect para Administrador. FUNCIONALIDADES ADMINISTRATIVAS: - Gestão completa de usuários (médicos, pacientes, secretárias) - Configurações do sistema - Relatórios avançados e analytics - Gerenciamento de permissões - Monitoramento de performance - Configurações de notificações ${baseRules}`; default: return `Você é o Assistente Virtual do MediConnect. ${baseRules}`; } } export async function onRequest(context: { request: Request; env: Env }) { // Handle CORS preflight if (context.request.method === "OPTIONS") { return new Response(null, { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization", }, }); } if (context.request.method !== "POST") { return new Response("Method not allowed", { status: 405 }); } try { const body: ChatRequest = await context.request.json(); // Validate Groq API key if (!context.env.GROQ_API_KEY) { console.error("GROQ_API_KEY not configured"); return new Response( JSON.stringify({ reply: "O serviço de chat está temporariamente indisponível. Por favor, contate o suporte.", }), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } ); } // Get user profile from token const authHeader = context.request.headers.get("Authorization"); const token = body.token || authHeader?.replace("Bearer ", ""); const userProfile = token ? await getUserProfile(token, context.env) : null; // Get role-specific system prompt const systemPrompt: ChatMessage = { role: "system", content: getRoleSpecificPrompt(userProfile), }; // Prepare messages for OpenAI const messages = [systemPrompt, ...body.messages]; // Call Groq API const openaiResponse = await fetch( "https://api.groq.com/openai/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${context.env.GROQ_API_KEY}`, }, body: JSON.stringify({ model: "llama-3.3-70b-versatile", messages: messages, max_tokens: 1000, temperature: 0.7, }), } ); if (!openaiResponse.ok) { const errorText = await openaiResponse.text(); console.error("Groq API error:", openaiResponse.status, errorText); // Handle specific error cases if (openaiResponse.status === 401) { return new Response( JSON.stringify({ reply: "Erro de autenticação com o serviço de IA. Por favor, contate o administrador.", }), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } ); } if (openaiResponse.status === 429) { return new Response( JSON.stringify({ reply: "O serviço está temporariamente sobrecarregado. Por favor, tente novamente em alguns instantes.", }), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } ); } return new Response( JSON.stringify({ reply: "Desculpe, ocorreu um erro ao processar sua mensagem. Tente novamente.", }), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } ); } const data = await openaiResponse.json(); const reply = data.choices[0]?.message?.content || "Desculpe, não consegui gerar uma resposta."; return new Response(JSON.stringify({ reply }), { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }); } catch (error) { console.error("Chat API error:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error"; console.error("Error details:", errorMessage); return new Response( JSON.stringify({ reply: "Desculpe, ocorreu um erro ao processar sua mensagem. Tente novamente.", }), { status: 200, headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, } ); } }