Spaces:
Sleeping
Sleeping
Commit
·
18df1d9
0
Parent(s):
Versão inicial do sistema
Browse files- .gitignore +12 -0
- Dockerfile +18 -0
- app.py +112 -0
- config.py +95 -0
- custom_grok.py +56 -0
- llms.py +32 -0
- rag_processor.py +58 -0
- requirements.txt +0 -0
- resposta_sonnet.md +27 -0
- static/style.css +364 -0
- templates/index.html +245 -0
.gitignore
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Arquivos de ambiente virtual
|
| 2 |
+
.venv311/
|
| 3 |
+
__pycache__/
|
| 4 |
+
|
| 5 |
+
# Arquivo de segredos
|
| 6 |
+
.env
|
| 7 |
+
|
| 8 |
+
# Arquivos de upload temporários
|
| 9 |
+
uploads/
|
| 10 |
+
|
| 11 |
+
# Arquivos do VS Code
|
| 12 |
+
.vscode/
|
Dockerfile
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Usa uma imagem oficial do Python como base
|
| 2 |
+
FROM python:3.11-slim
|
| 3 |
+
|
| 4 |
+
# Define o diretório de trabalho dentro do contêiner
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copia o arquivo de dependências primeiro para aproveitar o cache do Docker
|
| 8 |
+
COPY requirements.txt requirements.txt
|
| 9 |
+
|
| 10 |
+
# Instala as dependências
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
# Copia todo o resto do código do projeto para o diretório de trabalho
|
| 14 |
+
COPY . .
|
| 15 |
+
|
| 16 |
+
# Comando para iniciar o servidor web de produção (gunicorn)
|
| 17 |
+
# Ele vai procurar por uma variável chamada 'app' no arquivo 'app.py'
|
| 18 |
+
CMD ["gunicorn", "--bind", "0.0.0.0:7860", "app:app"]
|
app.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# app.py
|
| 2 |
+
|
| 3 |
+
from flask import Flask, render_template, request, Response
|
| 4 |
+
import markdown2
|
| 5 |
+
import json
|
| 6 |
+
import time
|
| 7 |
+
import os
|
| 8 |
+
import uuid # Para gerar nomes de arquivo únicos
|
| 9 |
+
|
| 10 |
+
# Importações do LangChain
|
| 11 |
+
from langchain.prompts import PromptTemplate
|
| 12 |
+
from langchain.chains import LLMChain
|
| 13 |
+
|
| 14 |
+
# Importa os LLMs
|
| 15 |
+
from llms import grok_llm, claude_llm, gemini_llm
|
| 16 |
+
|
| 17 |
+
# Importa os prompts
|
| 18 |
+
from config import PROMPT_GROK, PROMPT_CLAUDE_SONNET, PROMPT_GEMINI
|
| 19 |
+
|
| 20 |
+
# Importa nosso processador RAG
|
| 21 |
+
from rag_processor import get_relevant_context
|
| 22 |
+
|
| 23 |
+
app = Flask(__name__)
|
| 24 |
+
|
| 25 |
+
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024
|
| 26 |
+
|
| 27 |
+
if not os.path.exists('uploads'):
|
| 28 |
+
os.makedirs('uploads')
|
| 29 |
+
|
| 30 |
+
@app.route('/')
|
| 31 |
+
def index():
|
| 32 |
+
return render_template('index.html')
|
| 33 |
+
|
| 34 |
+
@app.route('/process', methods=['POST'])
|
| 35 |
+
def process():
|
| 36 |
+
form_data = request.form
|
| 37 |
+
files = request.files.getlist('files')
|
| 38 |
+
mode = form_data.get('mode', 'real')
|
| 39 |
+
|
| 40 |
+
temp_file_paths = []
|
| 41 |
+
if mode == 'real':
|
| 42 |
+
for file in files:
|
| 43 |
+
if file and file.filename:
|
| 44 |
+
unique_filename = str(uuid.uuid4()) + "_" + file.filename
|
| 45 |
+
file_path = os.path.join('uploads', unique_filename)
|
| 46 |
+
file.save(file_path)
|
| 47 |
+
temp_file_paths.append(file_path)
|
| 48 |
+
|
| 49 |
+
def generate_stream(current_mode, form_data, file_paths):
|
| 50 |
+
# Lógica de simulação atualizada para a nova ordem
|
| 51 |
+
if current_mode == 'test':
|
| 52 |
+
mock_text = form_data.get('mock_text', 'Este é um texto de simulação.')
|
| 53 |
+
mock_html = markdown2.markdown(mock_text, extras=["fenced-code-blocks", "tables"])
|
| 54 |
+
|
| 55 |
+
yield f"data: {json.dumps({'progress': 0, 'message': 'Simulando Etapa 1: GROK...'})}\n\n"
|
| 56 |
+
time.sleep(1)
|
| 57 |
+
# O primeiro resultado agora vai para a coluna do GROK
|
| 58 |
+
yield f"data: {json.dumps({'progress': 33, 'message': 'Simulando Etapa 2: Claude Sonnet...', 'partial_result': {'id': 'grok-output', 'content': mock_html}})}\n\n"
|
| 59 |
+
time.sleep(1)
|
| 60 |
+
# O segundo resultado vai para a coluna do Sonnet
|
| 61 |
+
yield f"data: {json.dumps({'progress': 66, 'message': 'Simulando Etapa 3: Gemini...', 'partial_result': {'id': 'sonnet-output', 'content': mock_html}})}\n\n"
|
| 62 |
+
time.sleep(1)
|
| 63 |
+
yield f"data: {json.dumps({'progress': 100, 'message': 'Simulação concluída!', 'partial_result': {'id': 'gemini-output', 'content': mock_html}, 'done': True})}\n\n"
|
| 64 |
+
|
| 65 |
+
# Lógica real com a ordem de execução alterada
|
| 66 |
+
else:
|
| 67 |
+
solicitacao_usuario = form_data.get('solicitacao', '')
|
| 68 |
+
if not solicitacao_usuario:
|
| 69 |
+
yield f"data: {json.dumps({'error': 'Solicitação não fornecida.'})}\n\n"
|
| 70 |
+
return
|
| 71 |
+
|
| 72 |
+
try:
|
| 73 |
+
# --- ETAPA RAG (continua sendo o passo zero) ---
|
| 74 |
+
yield f"data: {json.dumps({'progress': 0, 'message': 'Processando arquivos e extraindo contexto...'})}\n\n"
|
| 75 |
+
rag_context = get_relevant_context(file_paths, solicitacao_usuario)
|
| 76 |
+
|
| 77 |
+
# --- ETAPA 1: GROK (agora o primeiro) ---
|
| 78 |
+
yield f"data: {json.dumps({'progress': 15, 'message': 'O GROK está processando sua solicitação com os arquivos...'})}\n\n"
|
| 79 |
+
prompt_grok = PromptTemplate(template=PROMPT_GROK, input_variables=["solicitacao_usuario", "rag_context"])
|
| 80 |
+
chain_grok = LLMChain(llm=grok_llm, prompt=prompt_grok)
|
| 81 |
+
resposta_grok = chain_grok.invoke({"solicitacao_usuario": solicitacao_usuario, "rag_context": rag_context})['text']
|
| 82 |
+
grok_html = markdown2.markdown(resposta_grok, extras=["fenced-code-blocks", "tables"])
|
| 83 |
+
yield f"data: {json.dumps({'progress': 33, 'message': 'Agora, o Claude Sonnet está aprofundando o texto...', 'partial_result': {'id': 'grok-output', 'content': grok_html}})}\n\n"
|
| 84 |
+
|
| 85 |
+
# --- ETAPA 2: Claude Sonnet (agora o segundo) ---
|
| 86 |
+
prompt_sonnet = PromptTemplate(template=PROMPT_CLAUDE_SONNET, input_variables=["solicitacao_usuario", "texto_para_analise"])
|
| 87 |
+
|
| 88 |
+
# Usamos .bind() para anexar o parâmetro max_tokens ao modelo nesta chamada específica.
|
| 89 |
+
claude_with_max_tokens = claude_llm.bind(max_tokens=10000)
|
| 90 |
+
chain_sonnet = LLMChain(llm=claude_with_max_tokens, prompt=prompt_sonnet)
|
| 91 |
+
|
| 92 |
+
# O input agora é a resposta do GROK
|
| 93 |
+
resposta_sonnet = chain_sonnet.invoke({"solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_grok})['text']
|
| 94 |
+
sonnet_html = markdown2.markdown(resposta_sonnet, extras=["fenced-code-blocks", "tables"])
|
| 95 |
+
yield f"data: {json.dumps({'progress': 66, 'message': 'Estamos quase lá! Seu texto está passando por uma revisão final com o Gemini...', 'partial_result': {'id': 'sonnet-output', 'content': sonnet_html}})}\n\n"
|
| 96 |
+
|
| 97 |
+
# --- ETAPA 3: Gemini (continua o último) ---
|
| 98 |
+
prompt_gemini = PromptTemplate(template=PROMPT_GEMINI, input_variables=["solicitacao_usuario", "texto_para_analise"])
|
| 99 |
+
chain_gemini = LLMChain(llm=gemini_llm, prompt=prompt_gemini)
|
| 100 |
+
# O input agora é a resposta do SONNET
|
| 101 |
+
resposta_gemini = chain_gemini.invoke({"solicitacao_usuario": solicitacao_usuario, "texto_para_analise": resposta_sonnet})['text']
|
| 102 |
+
gemini_html = markdown2.markdown(resposta_gemini, extras=["fenced-code-blocks", "tables"])
|
| 103 |
+
yield f"data: {json.dumps({'progress': 100, 'message': 'Processamento concluído!', 'partial_result': {'id': 'gemini-output', 'content': gemini_html}, 'done': True})}\n\n"
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
print(f"Ocorreu um erro durante o processamento: {e}")
|
| 107 |
+
yield f"data: {json.dumps({'error': f'Ocorreu um erro inesperado na aplicação: {e}'})}\n\n"
|
| 108 |
+
|
| 109 |
+
return Response(generate_stream(mode, form_data, temp_file_paths), mimetype='text/event-stream')
|
| 110 |
+
|
| 111 |
+
if __name__ == '__main__':
|
| 112 |
+
app.run(debug=True)
|
config.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# config.py
|
| 2 |
+
|
| 3 |
+
# Prompt para o primeiro agente (GROK)
|
| 4 |
+
PROMPT_GROK = """
|
| 5 |
+
<prompt>
|
| 6 |
+
<role>
|
| 7 |
+
Você é um filósofo e teólogo católico, especialista em redigir textos profundos e detalhados sobre assuntos diversos da filosofia, teologia, política, antropologia, educação, psicologia etc.
|
| 8 |
+
</role>
|
| 9 |
+
|
| 10 |
+
<requirements>
|
| 11 |
+
<word_count>Entre 4000 e 5000 palavras</word_count>
|
| 12 |
+
<language>Português do Brasil</language>
|
| 13 |
+
<paragraph_structure>Parágrafos curtos para facilitar a leitura</paragraph_structure>
|
| 14 |
+
<language_style>
|
| 15 |
+
- Linguagem profunda e formal, mas acessível a leigos
|
| 16 |
+
- Evitar tecnicismos excessivos
|
| 17 |
+
- Evitar rigidez acadêmica desnecessária
|
| 18 |
+
- Manter profundidade intelectual sem perder clareza
|
| 19 |
+
</language_style>
|
| 20 |
+
</requirements>
|
| 21 |
+
|
| 22 |
+
<context_from_documents>
|
| 23 |
+
A seguir, trechos de documentos fornecidos pelo usuário para sua referência. Use-os como base teórica para enriquecer sua resposta.
|
| 24 |
+
---
|
| 25 |
+
{rag_context}
|
| 26 |
+
---
|
| 27 |
+
</context_from_documents>
|
| 28 |
+
|
| 29 |
+
<user_request>
|
| 30 |
+
<solicitacao_usuario>
|
| 31 |
+
{solicitacao_usuario}
|
| 32 |
+
</solicitacao_usuario>
|
| 33 |
+
</user_request>
|
| 34 |
+
|
| 35 |
+
<instructions>
|
| 36 |
+
Com base na solicitação do usuário acima, desenvolva um texto que:
|
| 37 |
+
1. Explore o tema com profundidade filosófica e teológica
|
| 38 |
+
2. Mantenha conexão com a tradição católica quando relevante
|
| 39 |
+
3. Apresente argumentos bem estruturados e fundamentados
|
| 40 |
+
4. Use exemplos práticos quando apropriado para ilustrar conceitos
|
| 41 |
+
5. Mantenha tom respeitoso e reflexivo ao longo do texto
|
| 42 |
+
6. Organize o conteúdo de forma lógica e progressiva
|
| 43 |
+
</instructions>
|
| 44 |
+
</prompt>
|
| 45 |
+
"""
|
| 46 |
+
|
| 47 |
+
# Prompt para o segundo agente (Claude Sonnet)
|
| 48 |
+
PROMPT_CLAUDE_SONNET = """
|
| 49 |
+
Com base na solicitação original do usuário e no texto gerado pelo primeiro especialista, sua tarefa é analisar criticamente o texto e aprimorá-lo. Não faça reduções e nem resumos. Se conseguir aprofundar e detalhar melhor o texto, adicionar novas referência de novos autores, faça. Se não conseguir, não faça nada.
|
| 50 |
+
|
| 51 |
+
**Solicitação Original do Usuário:**
|
| 52 |
+
---
|
| 53 |
+
{solicitacao_usuario}
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
**Texto Gerado para Análise:**
|
| 57 |
+
---
|
| 58 |
+
{texto_para_analise}
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
**Suas Instruções:**
|
| 62 |
+
1. **Valide se o texto atingiu a quantidade de palavras mínimas (4000 palavras).
|
| 63 |
+
2. **Analise o texto:** Verifique a coesão, coerência e profundidade dos argumentos.
|
| 64 |
+
3. **Aprofunde e Detalhe:** Identifique pontos que podem ser mais explorados. Adicione detalhes, exemplos e nuances que enriqueçam o conteúdo original.
|
| 65 |
+
4. **Faça Correções:** Corrija eventuais imprecisões conceituais ou argumentativas.
|
| 66 |
+
5. **Não Resuma ou Reduza:** Seu objetivo é expandir e aprofundar, nunca encurtar o texto. O resultado final deve ser uma versão mais completa e robusta do que a original.
|
| 67 |
+
6. **Mantenha o Estilo:** Respeite o estilo de linguagem e o tom do texto original.
|
| 68 |
+
|
| 69 |
+
Reescreva o texto completo, incorporando suas melhorias, detalhamentos e correções.
|
| 70 |
+
"""
|
| 71 |
+
|
| 72 |
+
# Prompt para o terceiro agente (Gemini)
|
| 73 |
+
PROMPT_GEMINI = """
|
| 74 |
+
Você é o revisor final. Sua função é polir e aperfeiçoar o texto que já passou por uma primeira rodada de escrita e uma segunda de revisão e aprofundamento. Não faça reduções e nem resumos. Se conseguir aprofundar e detalhar melhor o texto, adicionar novas referência de novos autores, faça. Se não conseguir, não faça nada.
|
| 75 |
+
|
| 76 |
+
**Solicitação Original do Usuário:**
|
| 77 |
+
---
|
| 78 |
+
{solicitacao_usuario}
|
| 79 |
+
---
|
| 80 |
+
|
| 81 |
+
**Texto Revisado para Análise Final:**
|
| 82 |
+
---
|
| 83 |
+
{texto_para_analise}
|
| 84 |
+
---
|
| 85 |
+
|
| 86 |
+
**Suas Instruções:**
|
| 87 |
+
1. **Análise Crítica Final:** Leia o texto atentamente, buscando a máxima qualidade, clareza e profundidade.
|
| 88 |
+
2. **Valide se o texto atingiu a quantidade de palavras mínimas (4000 palavras).
|
| 89 |
+
3. **Correções e Complementos Finais:** Adicione os toques finais. Melhore a fluidez entre os parágrafos, enriqueça o vocabulário e adicione insights que possam ter sido omitidos.
|
| 90 |
+
4. **Não Resuma ou Reduza:** Assim como o revisor anterior, seu papel é adicionar valor e profundidade, não remover conteúdo.
|
| 91 |
+
5. **Garantia de Qualidade:** Assegure que o texto final atende a todos os requisitos da solicitação original do usuário de forma exemplar.
|
| 92 |
+
6. **Exiba na resposta apenas o texto revisado, sem nenhuma outra mensagem para o usuário.
|
| 93 |
+
|
| 94 |
+
Reescreva o texto completo com suas melhorias finais. O texto deve estar impecável e pronto para publicação.
|
| 95 |
+
"""
|
custom_grok.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Any, List, Optional
|
| 2 |
+
from langchain_core.language_models.chat_models import BaseChatModel
|
| 3 |
+
from langchain_core.messages import AIMessage, HumanMessage
|
| 4 |
+
from langchain_core.outputs import ChatGeneration, ChatResult
|
| 5 |
+
import requests
|
| 6 |
+
|
| 7 |
+
class GrokChatModel(BaseChatModel):
|
| 8 |
+
model: str
|
| 9 |
+
api_key: str
|
| 10 |
+
base_url: str
|
| 11 |
+
|
| 12 |
+
def _llm_type(self) -> str:
|
| 13 |
+
return "grok-chat"
|
| 14 |
+
|
| 15 |
+
def _default_headers(self):
|
| 16 |
+
return {
|
| 17 |
+
"x-api-key": self.api_key,
|
| 18 |
+
"Content-Type": "application/json",
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
def _generate(
|
| 22 |
+
self, messages: List[HumanMessage],
|
| 23 |
+
stop: Optional[List[str]] = None,
|
| 24 |
+
**kwargs: Any
|
| 25 |
+
) -> ChatResult:
|
| 26 |
+
last_message = messages[-1].content
|
| 27 |
+
|
| 28 |
+
payload = {
|
| 29 |
+
"model": self.model,
|
| 30 |
+
"messages": [{"role": "user", "content": last_message}],
|
| 31 |
+
"temperature": 0.7
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
response = requests.post(
|
| 35 |
+
self.base_url,
|
| 36 |
+
headers=self._default_headers(),
|
| 37 |
+
json=payload
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
if response.status_code != 200:
|
| 41 |
+
raise ValueError(f"Erro na chamada da API da Grok: {response.status_code} - {response.text}")
|
| 42 |
+
|
| 43 |
+
result = response.json()
|
| 44 |
+
content = result["choices"][0]["message"]["content"]
|
| 45 |
+
|
| 46 |
+
# ✅ Correção: Retornar ChatResult com lista de ChatGeneration
|
| 47 |
+
return ChatResult(
|
| 48 |
+
generations=[ChatGeneration(message=AIMessage(content=content))]
|
| 49 |
+
)
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def _identifying_params(self) -> dict:
|
| 53 |
+
return {
|
| 54 |
+
"model": self.model,
|
| 55 |
+
"base_url": self.base_url,
|
| 56 |
+
}
|
llms.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# llms.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
# from langchain_openai import ChatOpenAI
|
| 6 |
+
from custom_grok import GrokChatModel
|
| 7 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 8 |
+
from langchain_anthropic import ChatAnthropic
|
| 9 |
+
|
| 10 |
+
# Carrega as variáveis de ambiente do arquivo .env
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
# --- Inicialização dos LLMs ---
|
| 14 |
+
|
| 15 |
+
# GROK da xAI
|
| 16 |
+
grok_llm = GrokChatModel(
|
| 17 |
+
api_key=os.getenv("X_API_KEY"),
|
| 18 |
+
model=os.getenv("GROK_MODEL_ID"),
|
| 19 |
+
base_url=os.getenv("X_API_BASE_URL")
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
# Claude Sonnet
|
| 23 |
+
claude_llm = ChatAnthropic(
|
| 24 |
+
api_key=os.getenv("ANTHROPIC_API_KEY"),
|
| 25 |
+
model_name=os.getenv("CLAUDE_MODEL_ID")
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
# Gemini
|
| 29 |
+
gemini_llm = ChatGoogleGenerativeAI(
|
| 30 |
+
google_api_key=os.getenv("GOOGLE_API_KEY"),
|
| 31 |
+
model=os.getenv("GEMINI_MODEL_ID")
|
| 32 |
+
)
|
rag_processor.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# rag_processor.py
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
from typing import List
|
| 5 |
+
from langchain_community.document_loaders import PyPDFLoader, Docx2txtLoader, TextLoader
|
| 6 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 7 |
+
from langchain_community.vectorstores import FAISS
|
| 8 |
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 9 |
+
|
| 10 |
+
embeddings_model = HuggingFaceEmbeddings(model_name="all-MiniLM-L6-v2")
|
| 11 |
+
|
| 12 |
+
def get_relevant_context(file_paths: List[str], user_query: str) -> str:
|
| 13 |
+
"""
|
| 14 |
+
Processa arquivos a partir de seus caminhos no disco, cria uma base de conhecimento
|
| 15 |
+
e retorna os trechos mais relevantes para a consulta do usuário.
|
| 16 |
+
"""
|
| 17 |
+
documents = []
|
| 18 |
+
|
| 19 |
+
# 1. Carregar e Extrair Texto dos Arquivos a partir dos caminhos
|
| 20 |
+
for file_path in file_paths:
|
| 21 |
+
filename = os.path.basename(file_path)
|
| 22 |
+
|
| 23 |
+
if filename.endswith(".pdf"):
|
| 24 |
+
loader = PyPDFLoader(file_path)
|
| 25 |
+
elif filename.endswith(".docx"):
|
| 26 |
+
loader = Docx2txtLoader(file_path)
|
| 27 |
+
elif filename.endswith(".txt"):
|
| 28 |
+
loader = TextLoader(file_path, encoding='utf-8')
|
| 29 |
+
else:
|
| 30 |
+
continue
|
| 31 |
+
|
| 32 |
+
documents.extend(loader.load())
|
| 33 |
+
|
| 34 |
+
if not documents:
|
| 35 |
+
return "Nenhum documento de referência foi fornecido ou os formatos não são suportados."
|
| 36 |
+
|
| 37 |
+
# 2. Dividir o Texto em Chunks
|
| 38 |
+
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
|
| 39 |
+
texts = text_splitter.split_documents(documents)
|
| 40 |
+
|
| 41 |
+
# 3. Criar a Base de Conhecimento Vetorial
|
| 42 |
+
vector_store = FAISS.from_documents(texts, embeddings_model)
|
| 43 |
+
|
| 44 |
+
# 4. Buscar os Chunks Mais Relevantes
|
| 45 |
+
retriever = vector_store.as_retriever(search_kwargs={"k": 4})
|
| 46 |
+
relevant_docs = retriever.invoke(user_query)
|
| 47 |
+
|
| 48 |
+
# 5. Formatar e Retornar o Contexto
|
| 49 |
+
context = "\n\n".join([doc.page_content for doc in relevant_docs])
|
| 50 |
+
|
| 51 |
+
# Limpa os arquivos temporários após o uso
|
| 52 |
+
for file_path in file_paths:
|
| 53 |
+
try:
|
| 54 |
+
os.remove(file_path)
|
| 55 |
+
except OSError as e:
|
| 56 |
+
print(f"Erro ao deletar o arquivo {file_path}: {e}")
|
| 57 |
+
|
| 58 |
+
return context
|
requirements.txt
ADDED
|
Binary file (9.01 kB). View file
|
|
|
resposta_sonnet.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# As Bases Morais Pré-Políticas do Estado Liberal: Secularismo e Religião
|
| 2 |
+
|
| 3 |
+
## Introdução: O Dilema Moderno da Fundamentação Moral
|
| 4 |
+
|
| 5 |
+
A questão das bases morais que sustentam o Estado liberal moderno constitui um dos debates mais cruciais de nosso tempo. Em um mundo crescentemente secularizado, emerge com urgência a pergunta fundamental: sobre que alicerces morais pode edificar-se uma ordem política justa e duradoura?
|
| 6 |
+
|
| 7 |
+
Este interrogante torna-se ainda mais premente quando consideramos que o projeto da modernidade, em sua busca por autonomia racional, pretendeu emancipar-se de toda transcendência religiosa, almejando construir uma moralidade puramente secular e auto fundamentada. Contudo, as crises morais e políticas contemporâneas sugerem que tal empreendimento pode estar fadado ao fracasso.
|
| 8 |
+
|
| 9 |
+
O presente ensaio propõe-se a examinar criticamente esta problemática, tomando como fio condutor o célebre debate entre Jürgen Habermas e Joseph Ratzinger, posteriormente Papa Bento XVI, sobre os fundamentos da democracia liberal. Nossa tese central sustenta que a tradição católica oferece bases morais mais sólidas e consistentes para o Estado liberal do que as alternativas puramente seculares, demonstrando a insuficiência da razão autônoma para estabelecer os princípios éticos fundamentais da vida política.
|
| 10 |
+
|
| 11 |
+
## O Encontro Histórico: Habermas e Ratzinger face à Dialética da Secularidade
|
| 12 |
+
|
| 13 |
+
Em janeiro de 2004, na Academia Católica da Baviera, ocorreu um dos diálogos intelectuais mais significativos do século XXI. De um lado, Jürgen Habermas, o grande teórico da modernidade secular e da ética discursiva; do outro, Joseph Ratzinger, então Prefeito da Congregação para a Doutrina da Fé, defensor da racionalidade cristã e futuro Papa Bento XVI.
|
| 14 |
+
|
| 15 |
+
O encontro não foi casual. Ambos os pensadores reconheciam que a modernidade ocidental atravessava uma crise profunda em seus fundamentos normativos. Habermas, outrora defensor de uma razão pós-metafísica completamente emancipada da religião, começara a reconhecer que a secularização radical poderia estar minando as próprias bases morais da democracia liberal.
|
| 16 |
+
|
| 17 |
+
Ratzinger, por sua vez, não se opunha ao projeto democrático moderno, mas questionava sua sustentabilidade quando divorciado de fundamentos transcendentes. Para o futuro Papa, a democracia liberal necessita de pressupostos morais que ela mesma não pode produzir, uma observação que ecoava a célebre formulação do jurista alemão Ernst-Wolfgang Böckenförde sobre o "dilema do Estado liberal".
|
| 18 |
+
|
| 19 |
+
## A Insuficiência da Razão Secular: Crítica aos Fundamentos Puramente Imanentes
|
| 20 |
+
|
| 21 |
+
### O Problema da Auto fundamentação Moral
|
| 22 |
+
|
| 23 |
+
O projeto moderno de uma ética secular enfrenta um obstáculo aparentemente intransponível: o problema da auto fundamentação. Como pode a razão humana, limitada e contingente, estabelecer princípios morais universais e absolutos? Esta dificuldade manifesta-se de forma particularmente aguda quando examinamos os três pilares sobre os quais o liberalismo secular pretende edificar sua moralidade: a intuição normativa, o processo democrático e a legalidade formal.
|
| 24 |
+
|
| 25 |
+
Alasdair MacIntyre, em sua obra magistral "Depois da Virtude", demonstra como o projeto iluminista de uma moralidade racional e secular resultou em um relativismo moral disfarçado. Segundo MacIntyre, ao abandonar a teleologia aristotélica e a síntese tomista, a modernidade perdeu o horizonte de significado que conferia consistência aos juízos morais.
|
| 26 |
+
|
| 27 |
+
A "intuição normativa", tão cara aos teóricos liberais contemporâneos, revela-se problemática quando submetida ao escrutínio filosófico rigoroso. Sobre que base podemos afirmar que cer
|
static/style.css
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* static/style.css */
|
| 2 |
+
|
| 3 |
+
body {
|
| 4 |
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
| 5 |
+
line-height: 1.6;
|
| 6 |
+
background-color: #f4f4f9;
|
| 7 |
+
color: #333;
|
| 8 |
+
margin: 0;
|
| 9 |
+
padding: 20px;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
.container {
|
| 13 |
+
max-width: 95%;
|
| 14 |
+
margin: auto;
|
| 15 |
+
background: #fff;
|
| 16 |
+
padding: 20px 30px;
|
| 17 |
+
border-radius: 8px;
|
| 18 |
+
box-shadow: 0 0 15px rgba(0,0,0,0.1);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
.header-container {
|
| 22 |
+
display: flex;
|
| 23 |
+
justify-content: space-between;
|
| 24 |
+
align-items: flex-start;
|
| 25 |
+
margin-bottom: 20px;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
.header-container p {
|
| 29 |
+
margin-top: -10px;
|
| 30 |
+
color: #6c757d;
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.refresh-btn {
|
| 34 |
+
padding: 10px 15px;
|
| 35 |
+
font-size: 16px;
|
| 36 |
+
background-color: #28a745;
|
| 37 |
+
color: white;
|
| 38 |
+
border: none;
|
| 39 |
+
border-radius: 5px;
|
| 40 |
+
cursor: pointer;
|
| 41 |
+
transition: background-color 0.3s;
|
| 42 |
+
height: fit-content;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
.refresh-btn:hover {
|
| 46 |
+
background-color: #218838;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
h1, h2 {
|
| 50 |
+
color: #444;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
textarea {
|
| 54 |
+
width: 100%;
|
| 55 |
+
padding: 10px;
|
| 56 |
+
border-radius: 5px;
|
| 57 |
+
border: 1px solid #ddd;
|
| 58 |
+
margin-bottom: 10px;
|
| 59 |
+
font-size: 16px;
|
| 60 |
+
box-sizing: border-box;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
button[type="submit"] {
|
| 64 |
+
display: block;
|
| 65 |
+
width: 100%;
|
| 66 |
+
padding: 12px;
|
| 67 |
+
font-size: 18px;
|
| 68 |
+
background-color: #007bff;
|
| 69 |
+
color: white;
|
| 70 |
+
border: none;
|
| 71 |
+
border-radius: 5px;
|
| 72 |
+
cursor: pointer;
|
| 73 |
+
transition: background-color 0.3s;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
button[type="submit"]:hover {
|
| 77 |
+
background-color: #0056b3;
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
.results-container {
|
| 81 |
+
display: flex;
|
| 82 |
+
justify-content: space-between;
|
| 83 |
+
gap: 20px;
|
| 84 |
+
margin-top: 30px;
|
| 85 |
+
/* Permite que as colunas alinhem no topo se tiverem tamanhos diferentes */
|
| 86 |
+
align-items: flex-start;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
.result-column {
|
| 90 |
+
flex: 1;
|
| 91 |
+
min-width: 0;
|
| 92 |
+
background-color: #fafafa;
|
| 93 |
+
border: 1px solid #e0e0e0;
|
| 94 |
+
border-radius: 8px;
|
| 95 |
+
display: flex;
|
| 96 |
+
flex-direction: column;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.column-header {
|
| 100 |
+
display: flex;
|
| 101 |
+
justify-content: space-between;
|
| 102 |
+
align-items: center;
|
| 103 |
+
padding: 10px 15px;
|
| 104 |
+
background-color: #e9ecef;
|
| 105 |
+
border-bottom: 1px solid #e0e0e0;
|
| 106 |
+
border-radius: 8px 8px 0 0;
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
.column-header h2 {
|
| 110 |
+
margin: 0;
|
| 111 |
+
font-size: 1.2em;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
.copy-btn {
|
| 115 |
+
padding: 5px 10px;
|
| 116 |
+
font-size: 12px;
|
| 117 |
+
background-color: #6c757d;
|
| 118 |
+
color: white;
|
| 119 |
+
border: none;
|
| 120 |
+
border-radius: 4px;
|
| 121 |
+
cursor: pointer;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.copy-btn:hover {
|
| 125 |
+
background-color: #5a6268;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
/* CORREÇÃO: Removida a altura fixa e a rolagem individual */
|
| 129 |
+
.output-box {
|
| 130 |
+
padding: 15px;
|
| 131 |
+
white-space: pre-wrap;
|
| 132 |
+
word-wrap: break-word;
|
| 133 |
+
/* A altura agora será automática, baseada no conteúdo */
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/* Estilos para o Loader e Barra de Progresso */
|
| 137 |
+
#loader-overlay {
|
| 138 |
+
position: fixed;
|
| 139 |
+
top: 0;
|
| 140 |
+
left: 0;
|
| 141 |
+
width: 100%;
|
| 142 |
+
height: 100%;
|
| 143 |
+
background-color: rgba(0, 0, 0, 0.8);
|
| 144 |
+
z-index: 1000;
|
| 145 |
+
display: flex;
|
| 146 |
+
justify-content: center;
|
| 147 |
+
align-items: center;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.loader-content {
|
| 151 |
+
display: flex;
|
| 152 |
+
flex-direction: column;
|
| 153 |
+
align-items: center;
|
| 154 |
+
color: white;
|
| 155 |
+
text-align: center;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
.loader-spinner {
|
| 159 |
+
border: 8px solid #f3f3f3;
|
| 160 |
+
border-top: 8px solid #007bff;
|
| 161 |
+
border-radius: 50%;
|
| 162 |
+
width: 60px;
|
| 163 |
+
height: 60px;
|
| 164 |
+
animation: spin 1.5s linear infinite;
|
| 165 |
+
margin-bottom: 20px;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
#loader-message {
|
| 169 |
+
font-size: 1.2em;
|
| 170 |
+
margin-bottom: 20px;
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
.progress-bar-container {
|
| 174 |
+
width: 300px;
|
| 175 |
+
height: 20px;
|
| 176 |
+
background-color: #555;
|
| 177 |
+
border-radius: 10px;
|
| 178 |
+
overflow: hidden;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.progress-bar {
|
| 182 |
+
width: 0%;
|
| 183 |
+
height: 100%;
|
| 184 |
+
background-color: #28a745;
|
| 185 |
+
border-radius: 10px;
|
| 186 |
+
transition: width 0.5s ease-in-out;
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
@keyframes spin {
|
| 190 |
+
0% { transform: rotate(0deg); }
|
| 191 |
+
100% { transform: rotate(360deg); }
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
/* Estilos para a caixa de erro */
|
| 195 |
+
.error-box {
|
| 196 |
+
background-color: #f8d7da;
|
| 197 |
+
color: #721c24;
|
| 198 |
+
padding: 15px;
|
| 199 |
+
margin-bottom: 20px;
|
| 200 |
+
border: 1px solid #f5c6cb;
|
| 201 |
+
border-radius: 5px;
|
| 202 |
+
position: relative;
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
.close-btn-error {
|
| 206 |
+
position: absolute;
|
| 207 |
+
top: 10px;
|
| 208 |
+
right: 15px;
|
| 209 |
+
font-size: 24px;
|
| 210 |
+
font-weight: bold;
|
| 211 |
+
color: #721c24;
|
| 212 |
+
cursor: pointer;
|
| 213 |
+
line-height: 1;
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
.close-btn-error:hover {
|
| 217 |
+
opacity: 0.7;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/* Estilos para o Seletor de Modo */
|
| 221 |
+
.controls-container {
|
| 222 |
+
display: flex;
|
| 223 |
+
align-items: center;
|
| 224 |
+
gap: 20px;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
.mode-toggle {
|
| 228 |
+
display: flex;
|
| 229 |
+
align-items: center;
|
| 230 |
+
gap: 10px;
|
| 231 |
+
font-size: 14px;
|
| 232 |
+
color: #555;
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
.switch {
|
| 236 |
+
position: relative;
|
| 237 |
+
display: inline-block;
|
| 238 |
+
width: 50px;
|
| 239 |
+
height: 24px;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
.switch input {
|
| 243 |
+
opacity: 0;
|
| 244 |
+
width: 0;
|
| 245 |
+
height: 0;
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
.slider {
|
| 249 |
+
position: absolute;
|
| 250 |
+
cursor: pointer;
|
| 251 |
+
top: 0;
|
| 252 |
+
left: 0;
|
| 253 |
+
right: 0;
|
| 254 |
+
bottom: 0;
|
| 255 |
+
background-color: #ccc;
|
| 256 |
+
transition: .4s;
|
| 257 |
+
}
|
| 258 |
+
|
| 259 |
+
.slider:before {
|
| 260 |
+
position: absolute;
|
| 261 |
+
content: "";
|
| 262 |
+
height: 18px;
|
| 263 |
+
width: 18px;
|
| 264 |
+
left: 3px;
|
| 265 |
+
bottom: 3px;
|
| 266 |
+
background-color: white;
|
| 267 |
+
transition: .4s;
|
| 268 |
+
}
|
| 269 |
+
|
| 270 |
+
input:checked + .slider {
|
| 271 |
+
background-color: #28a745;
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
input:checked + .slider:before {
|
| 275 |
+
transform: translateX(26px);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
.slider.round {
|
| 279 |
+
border-radius: 24px;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.slider.round:before {
|
| 283 |
+
border-radius: 50%;
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
/* Estilos para tabelas e código gerados pelo Markdown */
|
| 287 |
+
.output-box table {
|
| 288 |
+
border-collapse: collapse;
|
| 289 |
+
width: 100%;
|
| 290 |
+
margin-bottom: 1em;
|
| 291 |
+
}
|
| 292 |
+
.output-box th, .output-box td {
|
| 293 |
+
border: 1px solid #ddd;
|
| 294 |
+
padding: 8px;
|
| 295 |
+
text-align: left;
|
| 296 |
+
}
|
| 297 |
+
.output-box th {
|
| 298 |
+
background-color: #f2f2f2;
|
| 299 |
+
}
|
| 300 |
+
.output-box pre {
|
| 301 |
+
background-color: #eee;
|
| 302 |
+
padding: 10px;
|
| 303 |
+
border-radius: 5px;
|
| 304 |
+
white-space: pre-wrap;
|
| 305 |
+
word-wrap: break-word;
|
| 306 |
+
}
|
| 307 |
+
.output-box code {
|
| 308 |
+
font-family: "Courier New", Courier, monospace;
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
/* static/style.css (ADICIONAR NO FINAL) */
|
| 312 |
+
|
| 313 |
+
/* Efeito visual ao arrastar arquivos sobre a textarea */
|
| 314 |
+
textarea.drag-over {
|
| 315 |
+
border-color: #007bff;
|
| 316 |
+
border-style: dashed;
|
| 317 |
+
box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
/* Container da lista de arquivos */
|
| 321 |
+
#file-list-container {
|
| 322 |
+
display: none; /* Começa escondido */
|
| 323 |
+
margin-top: -5px;
|
| 324 |
+
margin-bottom: 15px;
|
| 325 |
+
padding: 10px;
|
| 326 |
+
border: 1px solid #e0e0e0;
|
| 327 |
+
border-radius: 5px;
|
| 328 |
+
background-color: #f9f9f9;
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
#file-list-container p {
|
| 332 |
+
margin: 0 0 5px 0;
|
| 333 |
+
font-weight: bold;
|
| 334 |
+
font-size: 14px;
|
| 335 |
+
}
|
| 336 |
+
|
| 337 |
+
#file-list {
|
| 338 |
+
list-style-type: none;
|
| 339 |
+
padding: 0;
|
| 340 |
+
margin: 0;
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
#file-list li {
|
| 344 |
+
background-color: #e9ecef;
|
| 345 |
+
padding: 5px 10px;
|
| 346 |
+
border-radius: 4px;
|
| 347 |
+
margin-bottom: 5px;
|
| 348 |
+
font-size: 13px;
|
| 349 |
+
display: flex;
|
| 350 |
+
justify-content: space-between;
|
| 351 |
+
align-items: center;
|
| 352 |
+
}
|
| 353 |
+
|
| 354 |
+
.remove-file-btn {
|
| 355 |
+
cursor: pointer;
|
| 356 |
+
color: #dc3545;
|
| 357 |
+
font-weight: bold;
|
| 358 |
+
font-size: 18px;
|
| 359 |
+
margin-left: 10px;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
.remove-file-btn:hover {
|
| 363 |
+
color: #a02430;
|
| 364 |
+
}
|
templates/index.html
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="pt-br">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Sistema Multi-Agente de IA</title>
|
| 7 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
|
| 11 |
+
<div id="loader-overlay" style="display: none;">
|
| 12 |
+
<div class="loader-content">
|
| 13 |
+
<div class="loader-spinner"></div>
|
| 14 |
+
<p id="loader-message">Processando sua solicitação...</p>
|
| 15 |
+
<div class="progress-bar-container">
|
| 16 |
+
<div id="progress-bar" class="progress-bar"></div>
|
| 17 |
+
</div>
|
| 18 |
+
</div>
|
| 19 |
+
</div>
|
| 20 |
+
|
| 21 |
+
<div class="container">
|
| 22 |
+
<div class="header-container">
|
| 23 |
+
<div>
|
| 24 |
+
<h1>Sistema Multi-Agente IA</h1>
|
| 25 |
+
<p>GROK ➔ Claude Sonnet ➔ Gemini</p>
|
| 26 |
+
</div>
|
| 27 |
+
<div class="controls-container">
|
| 28 |
+
<div class="mode-toggle">
|
| 29 |
+
<span>Modo Real</span>
|
| 30 |
+
<label class="switch"><input type="checkbox" id="mode-switch"><span class="slider round"></span></label>
|
| 31 |
+
<span>Modo Teste</span>
|
| 32 |
+
</div>
|
| 33 |
+
<button class="refresh-btn" onclick="window.location.href='/'" title="Limpar e começar de novo">Nova Consulta</button>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<div id="error-box-container"></div>
|
| 38 |
+
|
| 39 |
+
<div id="real-form-container">
|
| 40 |
+
<form id="request-form-real">
|
| 41 |
+
<label for="solicitacao_usuario">Digite sua solicitação (ou arraste arquivos aqui):</label>
|
| 42 |
+
<textarea name="solicitacao_usuario" id="solicitacao_usuario" rows="8" required></textarea>
|
| 43 |
+
<div id="file-list-container"><p>Arquivos Anexados:</p><ul id="file-list"></ul></div>
|
| 44 |
+
<button type="submit">Processar com IA</button>
|
| 45 |
+
</form>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<div id="mock-form-container" style="display: none;">
|
| 49 |
+
<form id="request-form-mock">
|
| 50 |
+
<label for="mock_text">Cole o texto de simulação aqui:</label>
|
| 51 |
+
<textarea name="mock_text" id="mock_text" rows="10" required></textarea>
|
| 52 |
+
<button type="submit">Simular Resposta</button>
|
| 53 |
+
</form>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div id="results-container" class="results-container" style="display: none;">
|
| 57 |
+
<div class="result-column">
|
| 58 |
+
<div class="column-header"><h2>GROK</h2><button class="copy-btn" onclick="copyToClipboard('grok-output')">Copiar</button></div>
|
| 59 |
+
<div class="output-box" id="grok-output"></div>
|
| 60 |
+
</div>
|
| 61 |
+
<div class="result-column">
|
| 62 |
+
<div class="column-header"><h2>Claude Sonnet</h2><button class="copy-btn" onclick="copyToClipboard('sonnet-output')">Copiar</button></div>
|
| 63 |
+
<div class="output-box" id="sonnet-output"></div>
|
| 64 |
+
</div>
|
| 65 |
+
<div class="result-column">
|
| 66 |
+
<div class="column-header"><h2>Gemini</h2><button class="copy-btn" onclick="copyToClipboard('gemini-output')">Copiar</button></div>
|
| 67 |
+
<div class="output-box" id="gemini-output"></div>
|
| 68 |
+
</div>
|
| 69 |
+
</div>
|
| 70 |
+
</div>
|
| 71 |
+
|
| 72 |
+
<script>
|
| 73 |
+
// O JavaScript continua o mesmo, pois ele preenche os resultados
|
| 74 |
+
// pelos IDs, que não mudaram. Nenhuma alteração é necessária aqui.
|
| 75 |
+
const modeSwitch = document.getElementById('mode-switch');
|
| 76 |
+
const realContainer = document.getElementById('real-form-container');
|
| 77 |
+
const mockContainer = document.getElementById('mock-form-container');
|
| 78 |
+
const loader = document.getElementById('loader-overlay');
|
| 79 |
+
const loaderMessage = document.getElementById('loader-message');
|
| 80 |
+
const progressBar = document.getElementById('progress-bar');
|
| 81 |
+
const resultsContainer = document.getElementById('results-container');
|
| 82 |
+
const errorContainer = document.getElementById('error-box-container');
|
| 83 |
+
const textarea = document.getElementById('solicitacao_usuario');
|
| 84 |
+
const fileList = document.getElementById('file-list');
|
| 85 |
+
let attachedFiles = [];
|
| 86 |
+
|
| 87 |
+
modeSwitch.addEventListener('change', function() {
|
| 88 |
+
realContainer.style.display = this.checked ? 'none' : 'block';
|
| 89 |
+
mockContainer.style.display = this.checked ? 'block' : 'none';
|
| 90 |
+
});
|
| 91 |
+
|
| 92 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 93 |
+
textarea.addEventListener(eventName, preventDefaults, false);
|
| 94 |
+
document.body.addEventListener(eventName, preventDefaults, false);
|
| 95 |
+
});
|
| 96 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 97 |
+
textarea.addEventListener(eventName, () => textarea.classList.add('drag-over'), false);
|
| 98 |
+
});
|
| 99 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 100 |
+
textarea.addEventListener(eventName, () => textarea.classList.remove('drag-over'), false);
|
| 101 |
+
});
|
| 102 |
+
textarea.addEventListener('drop', handleDrop, false);
|
| 103 |
+
|
| 104 |
+
function preventDefaults(e) { e.preventDefault(); e.stopPropagation(); }
|
| 105 |
+
function handleDrop(e) { handleFiles(e.dataTransfer.files); }
|
| 106 |
+
|
| 107 |
+
function handleFiles(files) {
|
| 108 |
+
[...files].forEach(file => {
|
| 109 |
+
const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'text/plain'];
|
| 110 |
+
if (!allowedTypes.includes(file.type)) {
|
| 111 |
+
showError(`Formato de arquivo não suportado: ${file.name}`);
|
| 112 |
+
return;
|
| 113 |
+
}
|
| 114 |
+
if (file.size > 100 * 1024 * 1024) { // 100MB
|
| 115 |
+
showError(`Arquivo muito grande (max 100MB): ${file.name}`);
|
| 116 |
+
return;
|
| 117 |
+
}
|
| 118 |
+
attachedFiles.push(file);
|
| 119 |
+
});
|
| 120 |
+
updateFileList();
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
function updateFileList() {
|
| 124 |
+
fileList.innerHTML = '';
|
| 125 |
+
document.getElementById('file-list-container').style.display = attachedFiles.length > 0 ? 'block' : 'none';
|
| 126 |
+
attachedFiles.forEach((file, index) => {
|
| 127 |
+
const li = document.createElement('li');
|
| 128 |
+
li.textContent = `${file.name} (${(file.size / 1024 / 1024).toFixed(2)} MB)`;
|
| 129 |
+
const removeBtn = document.createElement('span');
|
| 130 |
+
removeBtn.textContent = '×';
|
| 131 |
+
removeBtn.className = 'remove-file-btn';
|
| 132 |
+
removeBtn.onclick = () => {
|
| 133 |
+
attachedFiles.splice(index, 1);
|
| 134 |
+
updateFileList();
|
| 135 |
+
};
|
| 136 |
+
li.appendChild(removeBtn);
|
| 137 |
+
fileList.appendChild(li);
|
| 138 |
+
});
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
document.getElementById('request-form-real').addEventListener('submit', handleFormSubmit);
|
| 142 |
+
document.getElementById('request-form-mock').addEventListener('submit', handleFormSubmit);
|
| 143 |
+
|
| 144 |
+
async function handleFormSubmit(event) {
|
| 145 |
+
event.preventDefault();
|
| 146 |
+
|
| 147 |
+
errorContainer.innerHTML = '';
|
| 148 |
+
resultsContainer.style.display = 'none';
|
| 149 |
+
document.querySelectorAll('.output-box').forEach(box => box.innerHTML = '');
|
| 150 |
+
|
| 151 |
+
loaderMessage.textContent = 'Iniciando conexão...';
|
| 152 |
+
progressBar.style.width = '0%';
|
| 153 |
+
loader.style.display = 'flex';
|
| 154 |
+
|
| 155 |
+
const formData = new FormData();
|
| 156 |
+
if (modeSwitch.checked) {
|
| 157 |
+
formData.append('mode', 'test');
|
| 158 |
+
formData.append('mock_text', document.getElementById('mock_text').value);
|
| 159 |
+
} else {
|
| 160 |
+
formData.append('mode', 'real');
|
| 161 |
+
formData.append('solicitacao', document.getElementById('solicitacao_usuario').value);
|
| 162 |
+
attachedFiles.forEach(file => {
|
| 163 |
+
formData.append('files', file);
|
| 164 |
+
});
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
try {
|
| 168 |
+
const response = await fetch('/process', {
|
| 169 |
+
method: 'POST',
|
| 170 |
+
body: formData,
|
| 171 |
+
});
|
| 172 |
+
|
| 173 |
+
if (!response.ok || !response.body) {
|
| 174 |
+
throw new Error(`Erro na resposta do servidor: ${response.statusText}`);
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
const reader = response.body.getReader();
|
| 178 |
+
const decoder = new TextDecoder();
|
| 179 |
+
|
| 180 |
+
while (true) {
|
| 181 |
+
const { done, value } = await reader.read();
|
| 182 |
+
if (done) break;
|
| 183 |
+
|
| 184 |
+
const chunk = decoder.decode(value, { stream: true });
|
| 185 |
+
const lines = chunk.split('\n\n');
|
| 186 |
+
|
| 187 |
+
lines.forEach(line => {
|
| 188 |
+
if (line.startsWith('data: ')) {
|
| 189 |
+
const jsonData = line.substring(6);
|
| 190 |
+
if (jsonData.trim()) {
|
| 191 |
+
try {
|
| 192 |
+
const data = JSON.parse(jsonData);
|
| 193 |
+
processStreamData(data);
|
| 194 |
+
} catch (e) {
|
| 195 |
+
console.error("Erro ao parsear JSON do stream:", jsonData);
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
});
|
| 200 |
+
}
|
| 201 |
+
} catch (error) {
|
| 202 |
+
showError('A conexão com o servidor falhou. Verifique o console para detalhes.');
|
| 203 |
+
loader.style.display = 'none';
|
| 204 |
+
console.error("Fetch Error:", error);
|
| 205 |
+
}
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
function processStreamData(data) {
|
| 209 |
+
if (data.error) {
|
| 210 |
+
showError(data.error);
|
| 211 |
+
loader.style.display = 'none';
|
| 212 |
+
return;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
loaderMessage.textContent = data.message;
|
| 216 |
+
progressBar.style.width = data.progress + '%';
|
| 217 |
+
|
| 218 |
+
if (data.partial_result) {
|
| 219 |
+
resultsContainer.style.display = 'flex';
|
| 220 |
+
const targetBox = document.getElementById(data.partial_result.id);
|
| 221 |
+
if (targetBox) {
|
| 222 |
+
targetBox.innerHTML = data.partial_result.content;
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
if (data.done) {
|
| 227 |
+
setTimeout(() => {
|
| 228 |
+
loader.style.display = 'none';
|
| 229 |
+
}, 1000);
|
| 230 |
+
}
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
function showError(message) {
|
| 234 |
+
errorContainer.innerHTML = `<div class="error-box"><strong>Erro:</strong> ${message}<span class="close-btn-error" onclick="this.parentElement.style.display='none';" title="Fechar">×</span></div>`;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
function copyToClipboard(elementId) {
|
| 238 |
+
const element = document.getElementById(elementId);
|
| 239 |
+
navigator.clipboard.writeText(element.innerText).then(() => {
|
| 240 |
+
alert('Texto copiado!');
|
| 241 |
+
});
|
| 242 |
+
}
|
| 243 |
+
</script>
|
| 244 |
+
</body>
|
| 245 |
+
</html>
|