Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,111 +1,202 @@
|
|
1 |
import gradio as gr
|
2 |
import numpy as np
|
3 |
-
from typing import List, Dict
|
4 |
import logging
|
5 |
import re
|
6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
logging.basicConfig(level=logging.INFO)
|
9 |
logger = logging.getLogger(__name__)
|
10 |
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
def __init__(self):
|
13 |
-
self.
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
49 |
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
(
|
55 |
-
|
56 |
-
|
57 |
-
):
|
58 |
-
self.conquistas.add(titulo)
|
59 |
-
novas_conquistas.append(f"🏆 Nova Conquista: {titulo} - {descricao}")
|
60 |
|
61 |
-
|
|
|
62 |
|
63 |
class AssistenteBiblico:
|
64 |
def __init__(self):
|
65 |
self.dados_biblia = self.carregar_dados_biblia()
|
66 |
self.prompts_tematicos = self.carregar_prompts()
|
67 |
-
self.
|
|
|
|
|
68 |
|
69 |
-
|
70 |
-
|
71 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
72 |
try:
|
73 |
-
#
|
74 |
-
self.
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
|
81 |
-
#
|
82 |
-
|
83 |
-
subiu_nivel = self.sistema_pontuacao.adicionar_pontos(pontos_ganhos)
|
84 |
|
85 |
-
|
86 |
-
resposta = "📖 Encontrei estes versículos para sua situação:\n\n"
|
87 |
|
88 |
-
|
89 |
-
|
90 |
-
|
|
|
|
|
|
|
|
|
|
|
91 |
|
92 |
-
#
|
93 |
-
|
94 |
-
resposta += f"✨ Pontos: {self.sistema_pontuacao.pontos} (+{pontos_ganhos})\n"
|
95 |
-
resposta += f"📊 Nível: {self.sistema_pontuacao.nivel}\n"
|
96 |
-
resposta += f"🔥 Streak Diária: {self.sistema_pontuacao.streak_diaria} dias\n"
|
97 |
|
98 |
-
|
99 |
-
resposta += f"\n🎉 PARABÉNS! Você alcançou o nível {self.sistema_pontuacao.nivel}!\n"
|
100 |
|
101 |
-
#
|
102 |
-
|
103 |
-
|
104 |
-
|
|
|
|
|
|
|
|
|
105 |
|
106 |
-
|
107 |
-
if
|
108 |
-
resposta +=
|
|
|
|
|
|
|
109 |
|
110 |
resposta += "\n💭 Reflita sobre estas palavras e busque orientação pastoral se necessário."
|
111 |
|
@@ -118,6 +209,11 @@ class AssistenteBiblico:
|
|
118 |
def criar_interface():
|
119 |
assistente = AssistenteBiblico()
|
120 |
|
|
|
|
|
|
|
|
|
|
|
121 |
def processar_prompt(prompt: str, historico: List) -> List:
|
122 |
try:
|
123 |
resposta = assistente.gerar_resposta(prompt)
|
@@ -126,59 +222,43 @@ def criar_interface():
|
|
126 |
logger.error(f"Erro: {str(e)}")
|
127 |
return historico + [(prompt, "Desculpe, ocorreu um erro.")]
|
128 |
|
129 |
-
with gr.Blocks(title="Assistente Bíblico
|
130 |
gr.HTML("""
|
131 |
<div style="text-align: center; padding: 20px; background-color: #f0f8ff; border-radius: 10px;">
|
132 |
-
<h1 style="color: #2c3e50;"
|
133 |
-
<p style="color: #34495e;">
|
134 |
</div>
|
135 |
""")
|
136 |
|
137 |
with gr.Row():
|
138 |
with gr.Column(scale=2):
|
139 |
-
chatbot = gr.Chatbot(
|
140 |
-
height=500,
|
141 |
-
label="Sua Jornada de Aprendizado"
|
142 |
-
)
|
143 |
|
144 |
with gr.Row():
|
145 |
msg = gr.Textbox(
|
146 |
show_label=False,
|
147 |
-
placeholder="Digite sua dúvida ou escolha uma
|
148 |
scale=8
|
149 |
)
|
150 |
-
limpar = gr.Button("🔄
|
151 |
-
|
152 |
-
with gr.Column(scale=1):
|
153 |
-
gr.Markdown("### 🎯 Missões Disponíveis")
|
154 |
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
outputs=[chatbot]
|
163 |
-
)
|
164 |
-
|
165 |
-
gr.Markdown("""
|
166 |
-
### 🏆 Sistema de Progresso
|
167 |
-
- Ganhe pontos ao fazer perguntas
|
168 |
-
- Suba de nível a cada 100 pontos
|
169 |
-
- Mantenha sua streak diária
|
170 |
-
- Desbloqueie conquistas especiais
|
171 |
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
- 🏅 Perseverante: Faça 50 perguntas
|
178 |
-
""")
|
179 |
|
180 |
msg.submit(processar_prompt, [msg, chatbot], [chatbot])
|
181 |
limpar.click(lambda: [], None, chatbot, queue=False)
|
|
|
182 |
|
183 |
return demo
|
184 |
|
|
|
1 |
import gradio as gr
|
2 |
import numpy as np
|
3 |
+
from typing import List, Dict, Optional
|
4 |
import logging
|
5 |
import re
|
6 |
+
import requests
|
7 |
+
from bs4 import BeautifulSoup
|
8 |
+
from sentence_transformers import SentenceTransformer
|
9 |
+
import torch
|
10 |
+
from urllib.parse import urlparse
|
11 |
+
import numpy as np
|
12 |
+
from dataclasses import dataclass
|
13 |
+
import json
|
14 |
+
import os
|
15 |
|
16 |
logging.basicConfig(level=logging.INFO)
|
17 |
logger = logging.getLogger(__name__)
|
18 |
|
19 |
+
@dataclass
|
20 |
+
class TextChunk:
|
21 |
+
text: str
|
22 |
+
source_url: str
|
23 |
+
embedding: Optional[np.ndarray] = None
|
24 |
+
|
25 |
+
class WebScraper:
|
26 |
def __init__(self):
|
27 |
+
self.headers = {
|
28 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
29 |
+
}
|
30 |
+
|
31 |
+
def is_valid_url(self, url: str) -> bool:
|
32 |
+
try:
|
33 |
+
result = urlparse(url)
|
34 |
+
return all([result.scheme, result.netloc])
|
35 |
+
except:
|
36 |
+
return False
|
37 |
+
|
38 |
+
def scrape_text(self, url: str) -> str:
|
39 |
+
if not self.is_valid_url(url):
|
40 |
+
raise ValueError(f"Invalid URL: {url}")
|
41 |
|
42 |
+
try:
|
43 |
+
response = requests.get(url, headers=self.headers, timeout=10)
|
44 |
+
response.raise_for_status()
|
45 |
+
|
46 |
+
soup = BeautifulSoup(response.text, 'html.parser')
|
47 |
+
|
48 |
+
# Remove script and style elements
|
49 |
+
for script in soup(["script", "style"]):
|
50 |
+
script.decompose()
|
51 |
+
|
52 |
+
text = soup.get_text(separator=' ', strip=True)
|
53 |
+
|
54 |
+
# Clean up whitespace
|
55 |
+
text = re.sub(r'\s+', ' ', text)
|
56 |
+
return text
|
57 |
+
|
58 |
+
except Exception as e:
|
59 |
+
logger.error(f"Error scraping {url}: {str(e)}")
|
60 |
+
return ""
|
61 |
+
|
62 |
+
class TextChunker:
|
63 |
+
def __init__(self, chunk_size: int = 500, overlap: int = 50):
|
64 |
+
self.chunk_size = chunk_size
|
65 |
+
self.overlap = overlap
|
66 |
+
|
67 |
+
def chunk_text(self, text: str, source_url: str) -> List[TextChunk]:
|
68 |
+
words = text.split()
|
69 |
+
chunks = []
|
70 |
|
71 |
+
for i in range(0, len(words), self.chunk_size - self.overlap):
|
72 |
+
chunk_words = words[i:i + self.chunk_size]
|
73 |
+
chunk_text = ' '.join(chunk_words)
|
74 |
+
chunks.append(TextChunk(text=chunk_text, source_url=source_url))
|
75 |
+
|
76 |
+
return chunks
|
77 |
+
|
78 |
+
class VectorStore:
|
79 |
+
def __init__(self, model_name: str = 'all-MiniLM-L6-v2'):
|
80 |
+
self.model = SentenceTransformer(model_name)
|
81 |
+
self.chunks: List[TextChunk] = []
|
82 |
+
self.cache_file = "vector_store_cache.json"
|
83 |
+
self.load_cache()
|
84 |
+
|
85 |
+
def load_cache(self):
|
86 |
+
if os.path.exists(self.cache_file):
|
87 |
+
try:
|
88 |
+
with open(self.cache_file, 'r', encoding='utf-8') as f:
|
89 |
+
data = json.load(f)
|
90 |
+
self.chunks = [
|
91 |
+
TextChunk(
|
92 |
+
text=item['text'],
|
93 |
+
source_url=item['source_url'],
|
94 |
+
embedding=np.array(item['embedding']) if item['embedding'] else None
|
95 |
+
)
|
96 |
+
for item in data
|
97 |
+
]
|
98 |
+
logger.info(f"Loaded {len(self.chunks)} chunks from cache")
|
99 |
+
except Exception as e:
|
100 |
+
logger.error(f"Error loading cache: {str(e)}")
|
101 |
+
|
102 |
+
def save_cache(self):
|
103 |
+
try:
|
104 |
+
data = [
|
105 |
+
{
|
106 |
+
'text': chunk.text,
|
107 |
+
'source_url': chunk.source_url,
|
108 |
+
'embedding': chunk.embedding.tolist() if chunk.embedding is not None else None
|
109 |
+
}
|
110 |
+
for chunk in self.chunks
|
111 |
+
]
|
112 |
+
with open(self.cache_file, 'w', encoding='utf-8') as f:
|
113 |
+
json.dump(data, f)
|
114 |
+
logger.info(f"Saved {len(self.chunks)} chunks to cache")
|
115 |
+
except Exception as e:
|
116 |
+
logger.error(f"Error saving cache: {str(e)}")
|
117 |
+
|
118 |
+
def add_chunks(self, chunks: List[TextChunk]):
|
119 |
+
for chunk in chunks:
|
120 |
+
if chunk.embedding is None:
|
121 |
+
chunk.embedding = self.model.encode(chunk.text)
|
122 |
+
self.chunks.extend(chunks)
|
123 |
+
self.save_cache()
|
124 |
+
|
125 |
+
def search(self, query: str, k: int = 3) -> List[TextChunk]:
|
126 |
+
query_embedding = self.model.encode(query)
|
127 |
|
128 |
+
similarities = []
|
129 |
+
for chunk in self.chunks:
|
130 |
+
if chunk.embedding is not None:
|
131 |
+
similarity = np.dot(query_embedding, chunk.embedding) / (
|
132 |
+
np.linalg.norm(query_embedding) * np.linalg.norm(chunk.embedding)
|
133 |
+
)
|
134 |
+
similarities.append((similarity, chunk))
|
|
|
|
|
|
|
135 |
|
136 |
+
similarities.sort(reverse=True)
|
137 |
+
return [chunk for _, chunk in similarities[:k]]
|
138 |
|
139 |
class AssistenteBiblico:
|
140 |
def __init__(self):
|
141 |
self.dados_biblia = self.carregar_dados_biblia()
|
142 |
self.prompts_tematicos = self.carregar_prompts()
|
143 |
+
self.scraper = WebScraper()
|
144 |
+
self.chunker = TextChunker()
|
145 |
+
self.vector_store = VectorStore()
|
146 |
|
147 |
+
def carregar_dados_biblia(self) -> List[Dict]:
|
148 |
+
# [Previous Bible data loading code remains the same]
|
149 |
+
return [] # Simplified for example
|
150 |
+
|
151 |
+
def carregar_prompts(self) -> Dict:
|
152 |
+
# [Previous prompts loading code remains the same]
|
153 |
+
return {} # Simplified for example
|
154 |
+
|
155 |
+
def adicionar_fonte_web(self, url: str) -> bool:
|
156 |
+
"""Add content from a web URL to the knowledge base"""
|
157 |
try:
|
158 |
+
# Scrape the content
|
159 |
+
text = self.scraper.scrape_text(url)
|
160 |
+
if not text:
|
161 |
+
return False
|
162 |
+
|
163 |
+
# Chunk the text
|
164 |
+
chunks = self.chunker.chunk_text(text, url)
|
165 |
|
166 |
+
# Add to vector store
|
167 |
+
self.vector_store.add_chunks(chunks)
|
|
|
168 |
|
169 |
+
return True
|
|
|
170 |
|
171 |
+
except Exception as e:
|
172 |
+
logger.error(f"Error adding web source: {str(e)}")
|
173 |
+
return False
|
174 |
+
|
175 |
+
def gerar_resposta(self, pergunta: str) -> str:
|
176 |
+
try:
|
177 |
+
# Get relevant chunks from vector store
|
178 |
+
chunks_relevantes = self.vector_store.search(pergunta)
|
179 |
|
180 |
+
# Get relevant Bible verses
|
181 |
+
versiculos = self.buscar_versiculos(pergunta)
|
|
|
|
|
|
|
182 |
|
183 |
+
resposta = "📖 Encontrei estas informações relevantes:\n\n"
|
|
|
184 |
|
185 |
+
# Add relevant chunks from web sources
|
186 |
+
if chunks_relevantes:
|
187 |
+
resposta += "🌐 De fontes complementares:\n"
|
188 |
+
for chunk in chunks_relevantes:
|
189 |
+
# Add a brief excerpt from the chunk
|
190 |
+
excerpt = chunk.text[:200] + "..."
|
191 |
+
resposta += f"- {excerpt}\n"
|
192 |
+
resposta += f" Fonte: {chunk.source_url}\n\n"
|
193 |
|
194 |
+
# Add Bible verses
|
195 |
+
if versiculos:
|
196 |
+
resposta += "✝️ Versículos bíblicos relacionados:\n"
|
197 |
+
for versiculo in versiculos:
|
198 |
+
resposta += f"➤ {versiculo['referencia']}\n"
|
199 |
+
resposta += f"{versiculo['texto']}\n\n"
|
200 |
|
201 |
resposta += "\n💭 Reflita sobre estas palavras e busque orientação pastoral se necessário."
|
202 |
|
|
|
209 |
def criar_interface():
|
210 |
assistente = AssistenteBiblico()
|
211 |
|
212 |
+
def adicionar_fonte(url: str) -> str:
|
213 |
+
if assistente.adicionar_fonte_web(url):
|
214 |
+
return f"✅ Fonte adicionada com sucesso: {url}"
|
215 |
+
return f"❌ Erro ao adicionar fonte: {url}"
|
216 |
+
|
217 |
def processar_prompt(prompt: str, historico: List) -> List:
|
218 |
try:
|
219 |
resposta = assistente.gerar_resposta(prompt)
|
|
|
222 |
logger.error(f"Erro: {str(e)}")
|
223 |
return historico + [(prompt, "Desculpe, ocorreu um erro.")]
|
224 |
|
225 |
+
with gr.Blocks(title="Assistente Bíblico") as demo:
|
226 |
gr.HTML("""
|
227 |
<div style="text-align: center; padding: 20px; background-color: #f0f8ff; border-radius: 10px;">
|
228 |
+
<h1 style="color: #2c3e50;">🙏 Conselheiro Bíblico</h1>
|
229 |
+
<p style="color: #34495e;">Encontre orientação e conforto na Palavra de Deus e recursos complementares</p>
|
230 |
</div>
|
231 |
""")
|
232 |
|
233 |
with gr.Row():
|
234 |
with gr.Column(scale=2):
|
235 |
+
chatbot = gr.Chatbot(height=500, label="Conversa")
|
|
|
|
|
|
|
236 |
|
237 |
with gr.Row():
|
238 |
msg = gr.Textbox(
|
239 |
show_label=False,
|
240 |
+
placeholder="Digite sua dúvida ou escolha uma sugestão ao lado...",
|
241 |
scale=8
|
242 |
)
|
243 |
+
limpar = gr.Button("🔄 Recomeçar", scale=1)
|
|
|
|
|
|
|
244 |
|
245 |
+
with gr.Row():
|
246 |
+
url_input = gr.Textbox(
|
247 |
+
label="Adicionar fonte de conhecimento (URL)",
|
248 |
+
placeholder="https://exemplo.com/artigo-cristao",
|
249 |
+
scale=8
|
250 |
+
)
|
251 |
+
add_source = gr.Button("➕ Adicionar", scale=1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
252 |
|
253 |
+
fonte_status = gr.Textbox(label="Status da fonte", interactive=False)
|
254 |
+
|
255 |
+
with gr.Column(scale=1):
|
256 |
+
gr.Markdown("### 📚 Temas para Explorar")
|
257 |
+
# [Rest of the interface code remains the same]
|
|
|
|
|
258 |
|
259 |
msg.submit(processar_prompt, [msg, chatbot], [chatbot])
|
260 |
limpar.click(lambda: [], None, chatbot, queue=False)
|
261 |
+
add_source.click(adicionar_fonte, url_input, fonte_status)
|
262 |
|
263 |
return demo
|
264 |
|