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>
|