Spaces:
Sleeping
Sleeping
| import os | |
| import io | |
| import sys | |
| import asyncio | |
| import random | |
| import logging | |
| import requests | |
| import chromadb | |
| import gradio as gr | |
| import pytesseract | |
| import edge_tts | |
| from PIL import Image | |
| from pdf2image import convert_from_path | |
| from pathlib import Path | |
| # ========================= | |
| # CONFIGURAÇÕES E ESTILO | |
| # ========================= | |
| sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") | |
| GROQ_API_KEY = os.getenv("GROQ_API_KEY") | |
| GROQ_URL = "https://api.groq.com/openai/v1/chat/completions" | |
| CHROMA_PATH = Path("./chroma_arabic_tutor") | |
| DOCS_PATH = Path("./docs_arabic") | |
| DOCS_PATH.mkdir(parents=True, exist_ok=True) | |
| # CSS para organizar o layout e destacar o Árabe | |
| custom_css = """ | |
| .gradio-container { background-color: #f8fafc; } | |
| .arabic-display { | |
| font-size: 32px !important; | |
| line-height: 1.6; | |
| direction: rtl; | |
| text-align: right; | |
| background: #fff; | |
| padding: 15px; | |
| border-radius: 8px; | |
| border: 1px solid #e2e8f0; | |
| } | |
| .explanation-box { margin-bottom: 20px; padding: 10px; border-left: 4px solid #0369a1; } | |
| """ | |
| # ========================= | |
| # AUTO-INDEXAÇÃO (RAG) | |
| # ========================= | |
| chroma_client = chromadb.PersistentClient(path=str(CHROMA_PATH)) | |
| collection = chroma_client.get_or_create_collection(name="arabic_lessons") | |
| def auto_indexar(): | |
| if collection.count() > 0: | |
| return f"✅ Biblioteca Ativa: {collection.count()} trechos." | |
| pdfs = list(DOCS_PATH.glob("*.pdf")) | |
| if not pdfs: return "⚠️ Pasta 'docs_arabic' vazia." | |
| for pdf in pdfs: | |
| try: | |
| images = convert_from_path(pdf, last_page=20) | |
| for i, image in enumerate(images): | |
| text = pytesseract.image_to_string(image, lang='ara+eng') | |
| if text and len(text.strip()) > 50: | |
| chunks = [text[j:j+1000] for j in range(0, len(text), 800)] | |
| ids = [f"{pdf.name}_p{i}_{k}" for k in range(len(chunks))] | |
| collection.add(ids=ids, documents=chunks) | |
| except Exception as e: print(f"Erro OCR: {e}") | |
| return f"✅ Pronta: {collection.count()} trechos." | |
| status_inicial = auto_indexar() | |
| # ========================= | |
| # MOTOR DE ÁUDIO PREMIUM (EDGE-TTS) | |
| # ========================= | |
| async def gerar_audio_premium(texto): | |
| # Selecionamos uma voz árabe natural (Fatima ou Hamed) | |
| voice = "ar-SA-ZariyahNeural" | |
| output_path = "pronuncia_premium.mp3" | |
| communicate = edge_tts.Communicate(texto, voice) | |
| await communicate.save(output_path) | |
| return output_path | |
| # ========================= | |
| # MOTOR DO TUTOR | |
| # ========================= | |
| def tutor_engine(mensagem, historico): | |
| if not GROQ_API_KEY: | |
| return historico + [["Erro", "Configure a GROQ_API_KEY."]], "", None | |
| contexto = "" | |
| if collection.count() > 0: | |
| busca = collection.query(query_texts=[mensagem], n_results=3) | |
| contexto = "\n".join(busca['documents'][0]) | |
| # Prompt instruindo organização visual clara | |
| prompt_sistema = ( | |
| "Você é Al-Mu'allim. Responda com organização visual impecável em Markdown.\n" | |
| "Use títulos (###), listas e negrito. Separe a parte árabe da explicação.\n" | |
| "IMPORTANTE: Não retorne códigos JSON, colchetes ou termos como {'text': }.\n" | |
| f"CONTEXTO TÉCNICO:\n{contexto}" | |
| ) | |
| api_msgs = [{"role": "system", "content": prompt_sistema}] | |
| for u, b in historico: | |
| api_msgs.append({"role": "user", "content": u}) | |
| api_msgs.append({"role": "assistant", "content": b}) | |
| api_msgs.append({"role": "user", "content": mensagem}) | |
| try: | |
| r = requests.post(GROQ_URL, | |
| headers={"Authorization": f"Bearer {GROQ_API_KEY}"}, | |
| json={"model": "llama-3.1-8b-instant", "messages": api_msgs, "temperature": 0.3}) | |
| resposta = r.json()['choices'][0]['message']['content'] | |
| # Limpeza de caracteres indesejados caso a IA os gere | |
| resposta = resposta.replace("{'text':", "").replace("}", "").strip() | |
| except: | |
| resposta = "Houve um erro na comunicação." | |
| # Gera o áudio de alta qualidade de forma assíncrona | |
| audio_path = asyncio.run(gerar_audio_premium(resposta)) | |
| historico.append((mensagem, resposta)) | |
| return historico, "", audio_path | |
| # ========================= | |
| # INTERFACE ORGANIZADA | |
| # ========================= | |
| with gr.Blocks(css=custom_css) as demo: | |
| gr.Markdown("## 🌙 Al-Mu'allim: Tutor de Árabe Premium") | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| chatbot = gr.Chatbot(label="Aula e Explicações", height=500) | |
| with gr.Row(): | |
| msg = gr.Textbox(placeholder="Sua pergunta aqui...", scale=4, show_label=False) | |
| btn = gr.Button("Enviar", variant="primary", scale=1) | |
| with gr.Column(scale=1): | |
| gr.Markdown("### 🔊 Pronúncia Nativa") | |
| audio_out = gr.Audio(label="Clique para ouvir", autoplay=True) | |
| gr.Markdown("---") | |
| gr.Markdown(f"**Status da Biblioteca:**\n{status_inicial}") | |
| btn.click(tutor_engine, [msg, chatbot], [chatbot, msg, audio_out]) | |
| msg.submit(tutor_engine, [msg, chatbot], [chatbot, msg, audio_out]) | |
| if __name__ == "__main__": | |
| demo.launch() |