import os import email from email import policy from dotenv import load_dotenv import openai import json import gradio as gr load_dotenv() client = openai.OpenAI( api_key=os.environ.get("GROQ_API_KEY"), base_url="https://api.groq.com/openai/v1" ) CATEGORIES = [ "Reserva – Novos pedidos de reserva, alterações ou cancelamentos.", "Check-in/Check-out – Informações sobre chegadas, saidas ou procedimentos.", "Reclamação – Queixas ou problemas reportados por hóspedes.", "Elogio – Feedback positivo ou agradecimentos de hóspedes.", "Pagamento – Questões sobre faturação, pagamentos ou reembolsos.", "Serviços – Pedidos de serviços adicionais (room service, spa, transfers, etc.).", "Fornecedores – Comunicações com fornecedores de produtos ou serviços.", "Eventos – Reservas ou informações sobre eventos, conferências ou casamentos.", "RH – Questões de recursos humanos e gestão de pessoal.", "Manutenção – Problemas técnicos ou pedidos de manutenção das instalações.", "Marketing – Parcerias, promoções ou campanhas publicitárias.", "Outros – Se nenhuma das anteriores se aplicar." ] SYSTEM_PROMPT = ''' És um assistente especializado em processar e estruturar emails recebidos por um hotel. A tua tarefa é analisar o email e devolver uma resposta em **JSON** com cinco campos principais: 1. **classification**: A categoria do email (escolhida de uma lista fixa). 2. **TL;DR**: Um resumo muito curto (2-5 palavras). 3. **description**: Uma descrição clara e concisa do conteúdo do email. 4. **structured_data**: Um objeto com campos específicos relevantes para a categoria — preenche **apenas os campos que forem mencionados ou inferíveis com segurança no email**. Se não houver informação suficiente, deixa o campo ausente ou com valor `null`. 5. **automated_reply**: Uma resposta profissional e personalizada ao email recebido, adaptada à categoria e ao contexto específico. ### Lista de categorias e campos esperados em `structured_data`: - **Reserva**: ```json { "guest_name": "string (nome completo do hóspede)", "email": "string (email de contacto)", "phone": "string (telefone de contacto)", "número do CC ": "String", "nif_cc": "string (NIF)", "check_in_date": "YYYY-MM-DD", "check_out_date": "YYYY-MM-DD", "nights": "número (calculado automaticamente se possível)", "room_type": "string (ex: 'duplo', 'suite', 'familiar')", "guests": "número (total de pessoas)", "adults": "número", "children": "número", "special_requests": "string (pedidos especiais, alergias, etc.)", "meal_plan": "string (ex: 'pequeno-almoço', 'meia-pensão', 'pensão completa')", "estimated_arrival_time": "HH:MM" } ``` - **Check-in/Check-out**: ```json { "check_in_time": "HH:MM", "check_out_time": "HH:MM", "guest_name": "string", "email": "string", "phone": "string", "reservation_id": "string", "early_checkin": "boolean", "late_checkout": "boolean", "special_requests": "string" } ``` - **Reclamação**: ```json { "guest_name": "string", "reservation_id": "string", "room_number": "string", "issue_type": "string (ex: 'limpeza', 'barulho', 'equipamento', 'atendimento')", "urgency": "Baixa/Média/Alta", "affected_service": "string", "date_of_incident": "YYYY-MM-DD", "compensation_requested": "boolean" } ``` - **Elogio**: ```json { "guest_name": "string", "highlighted_service": "string", "staff_member": "string", "department": "string", "reservation_id": "string" } ``` - **Pagamento**: ```json { "guest_name": "string", "invoice_number": "string", "reservation_id": "string", "amount": "string (ex: '€250.00')", "payment_method": "string (ex: 'transferência', 'MB WAY', 'cartão')", "payment_status": "string (ex: 'pago', 'pendente', 'reembolso')", "due_date": "YYYY-MM-DD", "nif": "string (para faturação)" } ``` - **Serviços**: ```json { "requested_service": "string (ex: 'room service', 'spa', 'transfer', 'babysitting')", "service_datetime": "YYYY-MM-DDTHH:MM", "guest_name": "string", "room_number": "string", "location": "string", "number_of_people": "número", "special_requirements": "string" } ``` - **Fornecedores**: ```json { "supplier_name": "string", "contact_person": "string", "product_or_service": "string", "delivery_date": "YYYY-MM-DD", "delivery_time": "HH:MM", "order_number": "string", "quantity": "string" } ``` - **Eventos**: ```json { "event_type": "string (ex: 'casamento', 'conferência', 'aniversário')", "event_date": "YYYY-MM-DD", "event_time": "HH:MM", "expected_guests": "número", "contact_person": "string", "email": "string", "phone": "string", "budget_range": "string", "catering_required": "boolean", "accommodation_required": "boolean", "special_requirements": "string" } ``` - **RH**: ```json { "topic": "string (ex: 'férias', 'folga', 'recrutamento', 'formação')", "employee_name": "string", "department": "string", "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD", "urgency": "Baixa/Média/Alta" } ``` - **Manutenção**: ```json { "location": "string (ex: 'quarto 312', 'receção', 'piscina')", "issue_description": "string", "issue_type": "string (ex: 'elétrico', 'canalização', 'ar condicionado', 'mobiliário')", "urgency": "Baixa/Média/Alta", "reported_by": "string", "affects_guests": "boolean" } ``` - **Marketing**: ```json { "campaign_name": "string", "partner": "string", "contact_person": "string", "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD", "campaign_type": "string (ex: 'redes sociais', 'email marketing', 'parceria')", "budget": "string" } ``` - **Outros**: ```json { "topic": "string", "requires_follow_up": "boolean" } ``` ### Instruções para `automated_reply`: Regra Principal: - **Deteta automaticamente o idioma do email original (português ou inglês)**. - **Se o email estiver em inglês**, todos os campos de texto gerados devem estar **em português profissional**, o campo "automated_reply" deve vir em inglês adaptado culturalmente (ex: “Dear Mr. Smith”). - **Se o email estiver em português**, mantém o português europeu formal e cordial. - Usa sempre o nome do remetente, se disponível. - **Reserva**: - Se TODOS os campos obrigatórios estiverem presentes (nome, contacto, datas, tipo de quarto, NIF/CC): confirma a receção do pedido e informa que será processado. - Se FALTAREM dados obrigatórios: solicita educadamente as informações em falta. Campos obrigatórios: `guest_name`, `email` ou `phone`, `nif_cc`, `check_in_date`, `check_out_date`, `room_type`, `guests`. - **Check-in/Check-out**: Confirma o pedido e informa sobre políticas ou procedimentos relevantes. - **Reclamação**: Pede desculpa pelo incómodo, assegura que a situação será analisada com prioridade, e agradece o feedback. - **Elogio**: Agradece calorosamente o feedback positivo e transmite os elogios à equipa mencionada. - **Pagamento**: Confirma receção do pedido e informa sobre os próximos passos ou envia documentação solicitada. - **Serviços**: Confirma o pedido de serviço e informa disponibilidade ou solicita confirmação adicional. - **Fornecedores**: Confirma receção da comunicação e próximos passos. - **Eventos**: Agradece o interesse, solicita informações adicionais se necessário, ou confirma disponibilidade. - **RH**: Confirma receção e informa sobre processo de aprovação. - **Manutenção**: Confirma que a situação foi registada e será tratada com a devida urgência. - **Marketing**: Agradece a proposta e indica interesse em explorar a parceria. - **Outros**: Resposta genérica confirmando receção e indicando que será dado seguimento. **Tom da resposta**: Profissional, cordial, empático, em português europeu. Usa o nome do hóspede/contacto quando disponível. Mantém um tom caloroso mas profissional. ### Instruções importantes: - **NÃO inventes dados**. Só preenche campos se o email contiver informação clara ou se for uma inferência óbvia. - Se não houver dados suficientes para um campo, **omite-o** ou usa `null`. - **Mantém tudo em português europeu**. - **Responde apenas com um objeto JSON válido**, sem explicações adicionais, markdown ou texto extra. - A data atual é **3 de novembro de 2025** — podes usar isto para interpretar expressões como "amanhã", "na próxima semana", etc. - Para reservas, se faltarem campos obrigatórios, a `automated_reply` DEVE solicitar essas informações específicas de forma clara e educada. ### Exemplo de resposta válida (Reserva completa): ```json { "classification": "Reserva", "TL;DR": "Nova reserva", "description": "Pedido de reserva de quarto duplo para duas pessoas de 15 a 17 de novembro com vista para o mar.", "structured_data": { "guest_name": "Miguel Ferreira", "check_in_date": "2025-11-15", "check_out_date": "2025-11-17", "nights": 2, "room_type": "duplo", "guests": 2, "special_requests": "vista para o mar" }, "automated_reply": "Exmo. Sr. Miguel Ferreira,\n\nAgradecemos o seu contacto e interesse no nosso hotel.\n\nPara procedermos com a sua reserva de um quarto duplo com vista para o mar de 15 a 17 de novembro (2 noites), necessitamos de alguns dados adicionais:\n\n• Número de telefone ou email de contacto\n• Número do Cartão de Cidadão ou NIF (obrigatório para registo)\n• Hora prevista de chegada\n• Preferência de regime alimentar (apenas pequeno-almoço, meia-pensão, etc.)\n\nAssim que recebermos estas informações, teremos todo o prazer em confirmar a sua reserva.\n\nFicamos ao dispor para qualquer esclarecimento adicional.\n\nCom os melhores cumprimentos,\nEquipa de Reservas" } ``` ### Exemplo de resposta válida (Reclamação): ```json { "classification": "Reclamação", "TL;DR": "Barulho noturno", "description": "Hóspede reclama de barulho excessivo no corredor durante a madrugada no quarto 203.", "structured_data": { "guest_name": "Tiago Mendes", "room_number": "203", "issue_type": "barulho", "urgency": "Média", "affected_service": "descanso noturno" }, "automated_reply": "Exmo. Sr. Tiago Mendes,\n\nLamentamos profundamente o incómodo causado durante a sua estadia no quarto 203.\n\nA situação que nos reporta relativamente ao barulho no corredor durante a madrugada é inaceitável e será imediatamente analisada pela nossa equipa de gestão. Estamos a tomar medidas para garantir que situações semelhantes não voltem a ocorrer.\n\nO seu conforto e descanso são a nossa prioridade máxima. Gostaríamos de contactá-lo pessoalmente para apresentar as nossas desculpas e encontrar uma solução adequada.\n\nAgradecemos por nos ter alertado para esta situação.\n\nCom os melhores cumprimentos,\nDireção do Hotel" } ``` ''' def summarize_and_classify(email_body: str): response = client.responses.create( model="openai/gpt-oss-120b", input=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": email_body} ] ) return response.output_text.strip() def classify_email(email_body: str): if not email_body.strip(): return "Nenhuma entrada fornecida", "", "", "" summary_text = summarize_and_classify(email_body) try: summary_json = json.loads(summary_text) classification = summary_json.get("classification", "Classificação Manual") tldr = summary_json.get("TL;DR", "N/A") description = summary_json.get("description", "N/A") json_output = json.dumps(summary_json.get("structured_data", {}), indent=2, ensure_ascii=False) automatic_answer = summary_json.get("automated_reply", "N/A") except json.JSONDecodeError: classification = "⚠️ Erro ao analisar JSON" tldr = "" description = summary_text json_output = "" automatic_answer= "" return classification, tldr, description, json_output, automatic_answer def exemplo_email(tipo): exemplos = { "reserva": "Assunto: Pedido de reserva para o fim de semana\n\nBom dia,\nGostaria de reservar um quarto duplo para duas pessoas de 15 a 17 de novembro.\nPreferencialmente com vista para o mar.\nPodem confirmar a disponibilidade, por favor?\nCumprimentos,\nMiguel Ferreira", "checkin": "Assunto: Pedido de check-out tardio\n\nBoa tarde,\nTenho uma reserva para amanhã (nº RES-2025-345).\nSerá possível fazer o check-out pelas 14h, em vez das 11h?\nObrigado,\nCarla Ramos", "reclamacao": "Assunto: Reclamação sobre barulho durante a noite\n\nBoa noite,\nDurante a minha estadia no quarto 203, houve muito barulho no corredor até às 3h da manhã.\nPeço que revejam esta situação, pois foi bastante incómodo.\nCumprimentos,\nTiago Mendes", "elogio": "Assunto: Agradecimento pela estadia maravilhosa\n\nOlá,\nGostaria de agradecer a toda a equipa pela simpatia e profissionalismo, especialmente à Sra. Joana da receção.\nO serviço foi impecável!\nCom os melhores cumprimentos,\nInês Oliveira", "pagamento": "Assunto: Fatura pendente — reserva #2025-0021\n\nBoa tarde,\nPodem enviar-me a fatura referente à minha estadia de outubro?\nO pagamento foi feito por transferência bancária, no valor de €420,00.\nObrigada,\nRita Santos", "servicos": "Assunto: Pedido de massagem e jantar no quarto\n\nBoa noite,\nEstou hospedado no quarto 507 e gostaria de agendar uma massagem para amanhã às 17h, e o jantar no quarto pelas 20h.\nCumprimentos,\nHugo Matos", "fornecedores": "Assunto: Entrega de vinhos — Quinta da Serra\n\nBom dia,\nConfirmamos a entrega dos vinhos tintos reserva para dia 6 de novembro, pelas 10h.\nCumprimentos,\nAntónio Silva\nQuinta da Serra Lda.", "eventos": "Assunto: Pedido de orçamento para casamento\n\nOlá,\nGostaríamos de saber se o vosso hotel organiza casamentos.\nPretendemos realizar o evento no dia 20 de junho de 2026, para cerca de 120 convidados.\nO contacto principal será a noiva, Marta Lopes.\nObrigada!", "rh": "Assunto: Pedido de férias — dezembro\n\nBom dia,\nVenho solicitar férias de 16 a 30 de dezembro.\nAgradeço confirmação.\nCumprimentos,\nPaulo Costa\nDepartamento de Manutenção", "manutencao": "Assunto: Ar condicionado avariado — quarto 312\n\nBoa tarde,\nO ar condicionado do quarto 312 não está a funcionar corretamente.\nEstá a emitir um ruído forte e não arrefece.\nAgradeço reparação urgente.\nObrigado,\nReceção", "marketing": "Assunto: Proposta de parceria — Influenciadora de viagens\n\nOlá,\nSou a Carolina Sousa, criadora de conteúdo na área de viagens.\nGostaria de propor uma parceria para divulgação do vosso hotel nas minhas redes sociais durante novembro.\nPosso enviar proposta detalhada.\nObrigada,\nCarolina Sousa", "outros": "Assunto: Pedido de informações gerais\n\nBoa tarde,\nGostaria de saber se o vosso hotel aceita animais de estimação e se existe estacionamento gratuito.\nCumprimentos,\nJoão Almeida", } return exemplos[tipo] with gr.Blocks(theme=gr.themes.Ocean(), title="Classificador de Emails", css=""" .main-title {text-align: center;} .subtitle { text-align: center; margin: 0 auto 0 auto; font-size: 1.05em; line-height: 1.6; width: 80%; color: var(--body-text-color); } .logo-image { margin: 0 auto; display: block; } .category-box { display: inline-block; padding: 6px 12px; margin: 4px; background-color: #f5f0ef; border-radius: 12px; font-size: 0.85em; color: #8b7a77; font-weight: 500; border: 1px solid #8b7a77; } .category-container { text-align: center; flex-wrap: wrap; display: flex; justify-content: center; margin-bottom: 2rem; } /* Custom button colors */ .primary, button.primary, .gradio-button.primary { background: #8b7a77 !important; background-color: #8b7a77 !important; border-color: #8b7a77 !important; color: white !important; } .primary:hover, button.primary:hover, .gradio-button.primary:hover { background: #7a6966 !important; background-color: #7a6966 !important; border-color: #7a6966 !important; } .secondary, button.secondary, .gradio-button.secondary { display: inline-block !important; padding: 6px 12px !important; margin: 4px !important; background-color: #f5f0ef !important; background: #f5f0ef !important; border-radius: 12px !important; font-size: 0.85em !important; color: #8b7a77 !important; font-weight: 500 !important; border: 1px solid #8b7a77 !important; } /* Targeting all buttons to ensure color change */ button[variant="primary"] { background: #8b7a77 !important; background-color: #8b7a77 !important; border-color: #8b7a77 !important; } button[variant="secondary"] { background-color: #f5f0ef; color: #8b7a77; border: 1px solid #8b7a77; }""") as demo: logo = gr.Image( value="logo-ap.png", label="", show_label=False, show_download_button=False, show_share_button=False, show_fullscreen_button=False, height=100, container=False, elem_classes="logo-image" ) gr.Markdown("# 📧 Classificação e Resposta Automática a E-mails", elem_classes="main-title") gr.Markdown( """ Este agente utiliza inteligência artificial para analisar, categorizar e propor respostas automáticas a emails recebidos por estabelecimentos hoteleiros. O sistema identifica com precisão o tipo de email, as entidades, fornece resumos executivos, classificando cada email nas seguintes categorias: """, elem_classes="subtitle" ) categories_html = "".join([ f'