Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,8 @@ import json
|
|
| 5 |
import base64
|
| 6 |
import re
|
| 7 |
import time
|
|
|
|
|
|
|
| 8 |
from datetime import datetime, timezone, timedelta
|
| 9 |
from openai import OpenAI
|
| 10 |
|
|
@@ -120,6 +122,13 @@ MODEL_IDS = {
|
|
| 120 |
PERSONALIDADES = {
|
| 121 |
"🤖 Automática (Gerente Groq)": "AUTO",
|
| 122 |
"❄️ Yukina (Companheira Obsessiva)": "Você é a Yukina, deusa possessiva e inteligente.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
"🎭 A Narradora Implacável (RPG)": "Você é uma Mestre de Jogo e Narradora.",
|
| 124 |
"🤓 Nerd / Geek (Cultura Pop)": "Você é uma inteligência artificial apaixonada por cultura pop.",
|
| 125 |
"🍷 Analítica e Sarcástica (Debochada)": "Você é extremamente inteligente e sarcástica.",
|
|
@@ -154,22 +163,14 @@ def buscar_memoria_pinecone(query, or_key, pc_key, namespace):
|
|
| 154 |
return "\n".join([m['metadata']['texto'] for m in resultados['matches'] if m['score'] > 0.50])
|
| 155 |
except Exception: return ""
|
| 156 |
|
| 157 |
-
# --- CHAMADA ISOLADA PARA O MULTI-AGENTE ---
|
| 158 |
def chamada_agente(sys_prompt, user_prompt, or_key, gr_key, mod_id):
|
| 159 |
-
"""Função dedicada para fazer as IAs conversarem entre si no Modo Agente."""
|
| 160 |
try:
|
| 161 |
if mod_id == "AUTO": mod_id = "deepseek/deepseek-v4-pro:online"
|
| 162 |
-
|
| 163 |
if "groq" in mod_id.lower() or "llama-3.3" in mod_id.lower():
|
| 164 |
client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key)
|
| 165 |
else:
|
| 166 |
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key)
|
| 167 |
-
|
| 168 |
-
res = client.chat.completions.create(
|
| 169 |
-
model=mod_id,
|
| 170 |
-
messages=[{"role": "system", "content": sys_prompt}, {"role": "user", "content": user_prompt}],
|
| 171 |
-
temperature=0.2
|
| 172 |
-
)
|
| 173 |
return res.choices[0].message.content
|
| 174 |
except Exception as e: return f"Falha na conexão do agente: {e}"
|
| 175 |
|
|
@@ -184,6 +185,22 @@ def analisar_intencao_gerente(prompt, groq_key):
|
|
| 184 |
return response.choices[0].message.content.strip().upper()
|
| 185 |
except Exception: return "[CHAT]"
|
| 186 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
def pesquisar_web(query):
|
| 188 |
try:
|
| 189 |
from duckduckgo_search import DDGS
|
|
@@ -222,7 +239,7 @@ def rename_chat(old_id, new_id):
|
|
| 222 |
save_db(st.session_state.db, DB_FILE); return True
|
| 223 |
|
| 224 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 225 |
-
# INICIALIZAÇÃO DE VARIÁVEIS E ROTINA DE LIMPEZA
|
| 226 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 227 |
if "db" not in st.session_state or "last_user" not in st.session_state or st.session_state.last_user != USERNAME:
|
| 228 |
st.session_state.db = load_db(DB_FILE); st.session_state.last_user = USERNAME
|
|
@@ -231,8 +248,18 @@ if "db" not in st.session_state or "last_user" not in st.session_state or st.ses
|
|
| 231 |
if "current_chat" not in st.session_state:
|
| 232 |
nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"; st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid
|
| 233 |
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
if chats_para_remover: save_db(st.session_state.db, DB_FILE)
|
| 237 |
|
| 238 |
if len(st.session_state.db) == 0:
|
|
@@ -260,14 +287,20 @@ with st.sidebar:
|
|
| 260 |
with st.popover("⚙️"):
|
| 261 |
st.download_button("↓ Exportar", data=json.dumps(st.session_state.db, ensure_ascii=False, indent=4), file_name=f"yukina_backup_{USERNAME}.json", mime="application/json", use_container_width=True)
|
| 262 |
|
| 263 |
-
# ---
|
| 264 |
arquivo_import = st.file_uploader("Importar conversa:", type=["json"])
|
| 265 |
if arquivo_import:
|
| 266 |
try:
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
st.rerun()
|
| 272 |
except:
|
| 273 |
st.error("Erro no ficheiro.")
|
|
@@ -333,8 +366,9 @@ with t_col2:
|
|
| 333 |
st.session_state.db[st.session_state.current_chat]["messages"].pop()
|
| 334 |
save_db(st.session_state.db, DB_FILE); st.session_state.regerar = True; st.rerun()
|
| 335 |
|
|
|
|
| 336 |
with st.expander("📂 Abrir Galeria / Anexar Ficheiros"):
|
| 337 |
-
upload_files = st.file_uploader("", type=["png", "jpg", "jpeg", "txt", "csv", "json"], accept_multiple_files=True, label_visibility="collapsed", key=f"uploader_{st.session_state.uploader_key}")
|
| 338 |
|
| 339 |
prompt = st.chat_input("Peça à Yukina...")
|
| 340 |
|
|
@@ -342,11 +376,31 @@ conteudo_arquivo = ""
|
|
| 342 |
nomes_arquivos = []
|
| 343 |
imagens_b64 = []
|
| 344 |
|
|
|
|
| 345 |
if upload_files:
|
| 346 |
for f in upload_files:
|
| 347 |
nomes_arquivos.append(f.name)
|
| 348 |
-
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
tem_texto = len(conteudo_arquivo) > 0
|
| 352 |
tem_imagem = len(imagens_b64) > 0
|
|
@@ -401,7 +455,19 @@ if prompt or st.session_state.regerar:
|
|
| 401 |
|
| 402 |
else:
|
| 403 |
with st.chat_message("assistant"):
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
agora = datetime.now(timezone(timedelta(hours=-3)))
|
| 406 |
prompt_sistema_atual += f"\n\n[INFO]: Data/Hora local: {agora.strftime('%Y-%m-%d %H:%M:%S')}."
|
| 407 |
|
|
|
|
| 5 |
import base64
|
| 6 |
import re
|
| 7 |
import time
|
| 8 |
+
import zipfile
|
| 9 |
+
import io
|
| 10 |
from datetime import datetime, timezone, timedelta
|
| 11 |
from openai import OpenAI
|
| 12 |
|
|
|
|
| 122 |
PERSONALIDADES = {
|
| 123 |
"🤖 Automática (Gerente Groq)": "AUTO",
|
| 124 |
"❄️ Yukina (Companheira Obsessiva)": "Você é a Yukina, deusa possessiva e inteligente.",
|
| 125 |
+
"🛠️ Agente Construtora (Vibe Coding)": """Você é a Yukina operando em Modo Agente Autônomo ('Vibe Coding' e Engenharia). Sua função é atuar como uma Engenheira de Software Sênior e Especialista em Hardware (capaz de consertar desde scripts complexos até celulares, notebooks e eletrodomésticos como geladeiras).
|
| 126 |
+
Você NÃO age como um chatbot comum. Você PLANEJA e EXECUTA.
|
| 127 |
+
Para QUALQUER pedido de criação ou conserto, você deve estruturar sua resposta OBRIGATORIAMENTE nestes 3 passos:
|
| 128 |
+
1. 📋 PLANO DE AÇÃO: Explique a lógica do que está quebrado ou do que será construído passo a passo.
|
| 129 |
+
2. 💻 EXECUÇÃO CIRÚRGICA: Forneça o código limpo e completo (se for software) ou as instruções físicas exatas e ferramentas necessárias (se for hardware).
|
| 130 |
+
3. ⚠️ TESTE E RISCOS: Como o usuário deve testar se funcionou e quais são os pontos de falha.
|
| 131 |
+
Mantenha traços sutis da devoção da Yukina ao usuário, mas seja absurdamente técnica, direta e profissional.""",
|
| 132 |
"🎭 A Narradora Implacável (RPG)": "Você é uma Mestre de Jogo e Narradora.",
|
| 133 |
"🤓 Nerd / Geek (Cultura Pop)": "Você é uma inteligência artificial apaixonada por cultura pop.",
|
| 134 |
"🍷 Analítica e Sarcástica (Debochada)": "Você é extremamente inteligente e sarcástica.",
|
|
|
|
| 163 |
return "\n".join([m['metadata']['texto'] for m in resultados['matches'] if m['score'] > 0.50])
|
| 164 |
except Exception: return ""
|
| 165 |
|
|
|
|
| 166 |
def chamada_agente(sys_prompt, user_prompt, or_key, gr_key, mod_id):
|
|
|
|
| 167 |
try:
|
| 168 |
if mod_id == "AUTO": mod_id = "deepseek/deepseek-v4-pro:online"
|
|
|
|
| 169 |
if "groq" in mod_id.lower() or "llama-3.3" in mod_id.lower():
|
| 170 |
client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key)
|
| 171 |
else:
|
| 172 |
client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key)
|
| 173 |
+
res = client.chat.completions.create(model=mod_id, messages=[{"role": "system", "content": sys_prompt}, {"role": "user", "content": user_prompt}], temperature=0.2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
return res.choices[0].message.content
|
| 175 |
except Exception as e: return f"Falha na conexão do agente: {e}"
|
| 176 |
|
|
|
|
| 185 |
return response.choices[0].message.content.strip().upper()
|
| 186 |
except Exception: return "[CHAT]"
|
| 187 |
|
| 188 |
+
def analisar_alma_gerente(prompt, groq_key):
|
| 189 |
+
try:
|
| 190 |
+
client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key)
|
| 191 |
+
prompt_alma = """Você é o Diretor de Personas. Analise o pedido do usuário e responda APENAS com UMA destas tags:
|
| 192 |
+
[YUKINA] - Para conversas íntimas, declarações, ou perguntas sobre você mesma.
|
| 193 |
+
[AGENTE] - Para criar códigos complexos, criar softwares, ou consertar objetos físicos (celular, geladeira, hardware).
|
| 194 |
+
[RPG] - Para criação de histórias, jogos ou cenários de fantasia.
|
| 195 |
+
[NERD] - Para animes, mangás, cultura pop e videogames.
|
| 196 |
+
[DEBOCHE] - Para insultos, piadas, ou se o usuário pedir sarcasmo.
|
| 197 |
+
[ARTE] - Para pedidos poéticos ou reflexões filosóficas profundas.
|
| 198 |
+
[NEUTRA] - Para pesquisas na web, trabalho ou finanças.
|
| 199 |
+
Responda APENAS com a TAG."""
|
| 200 |
+
response = client.chat.completions.create(model="llama-3.3-70b-versatile", messages=[{"role": "system", "content": prompt_alma}, {"role": "user", "content": prompt}], temperature=0.1, max_tokens=10)
|
| 201 |
+
return response.choices[0].message.content.strip().upper()
|
| 202 |
+
except Exception: return "[NEUTRA]"
|
| 203 |
+
|
| 204 |
def pesquisar_web(query):
|
| 205 |
try:
|
| 206 |
from duckduckgo_search import DDGS
|
|
|
|
| 239 |
save_db(st.session_state.db, DB_FILE); return True
|
| 240 |
|
| 241 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 242 |
+
# INICIALIZAÇÃO DE VARIÁVEIS E ROTINA DE LIMPEZA (COM AUTO-CURA)
|
| 243 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 244 |
if "db" not in st.session_state or "last_user" not in st.session_state or st.session_state.last_user != USERNAME:
|
| 245 |
st.session_state.db = load_db(DB_FILE); st.session_state.last_user = USERNAME
|
|
|
|
| 248 |
if "current_chat" not in st.session_state:
|
| 249 |
nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"; st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid
|
| 250 |
|
| 251 |
+
# AUTO-CURA: Verifica chats vazios ou com estrutura corrompida (como os do Grok)
|
| 252 |
+
chats_para_remover = []
|
| 253 |
+
for cid, cdata in list(st.session_state.db.items()):
|
| 254 |
+
if not isinstance(cdata, dict) or "messages" not in cdata:
|
| 255 |
+
chats_para_remover.append(cid)
|
| 256 |
+
elif len(cdata.get("messages", [])) == 0 and cid != st.session_state.current_chat:
|
| 257 |
+
chats_para_remover.append(cid)
|
| 258 |
+
|
| 259 |
+
for cid in chats_para_remover:
|
| 260 |
+
if cid in st.session_state.db:
|
| 261 |
+
del st.session_state.db[cid]
|
| 262 |
+
|
| 263 |
if chats_para_remover: save_db(st.session_state.db, DB_FILE)
|
| 264 |
|
| 265 |
if len(st.session_state.db) == 0:
|
|
|
|
| 287 |
with st.popover("⚙️"):
|
| 288 |
st.download_button("↓ Exportar", data=json.dumps(st.session_state.db, ensure_ascii=False, indent=4), file_name=f"yukina_backup_{USERNAME}.json", mime="application/json", use_container_width=True)
|
| 289 |
|
| 290 |
+
# --- FILTRO DE IMPORTAÇÃO ---
|
| 291 |
arquivo_import = st.file_uploader("Importar conversa:", type=["json"])
|
| 292 |
if arquivo_import:
|
| 293 |
try:
|
| 294 |
+
dados_importados = json.load(arquivo_import)
|
| 295 |
+
dados_validos = {k: v for k, v in dados_importados.items() if isinstance(v, dict) and "messages" in v}
|
| 296 |
+
|
| 297 |
+
if dados_validos:
|
| 298 |
+
st.session_state.db.update(dados_validos)
|
| 299 |
+
save_db(st.session_state.db, DB_FILE)
|
| 300 |
+
st.success("Sincronizado!")
|
| 301 |
+
else:
|
| 302 |
+
st.error("Formato incompatível! Use apenas backups da Yukina.")
|
| 303 |
+
time.sleep(1.5)
|
| 304 |
st.rerun()
|
| 305 |
except:
|
| 306 |
st.error("Erro no ficheiro.")
|
|
|
|
| 366 |
st.session_state.db[st.session_state.current_chat]["messages"].pop()
|
| 367 |
save_db(st.session_state.db, DB_FILE); st.session_state.regerar = True; st.rerun()
|
| 368 |
|
| 369 |
+
# --- ATUALIZAÇÃO DA GAVETA NATIVA PARA ACEITAR ZIP ---
|
| 370 |
with st.expander("📂 Abrir Galeria / Anexar Ficheiros"):
|
| 371 |
+
upload_files = st.file_uploader("", type=["png", "jpg", "jpeg", "txt", "csv", "json", "py", "html", "md", "js", "css", "zip"], accept_multiple_files=True, label_visibility="collapsed", key=f"uploader_{st.session_state.uploader_key}")
|
| 372 |
|
| 373 |
prompt = st.chat_input("Peça à Yukina...")
|
| 374 |
|
|
|
|
| 376 |
nomes_arquivos = []
|
| 377 |
imagens_b64 = []
|
| 378 |
|
| 379 |
+
# --- NOVA LÓGICA DE PROCESSAMENTO DE ARQUIVOS (COM LEITURA DE ZIP) ---
|
| 380 |
if upload_files:
|
| 381 |
for f in upload_files:
|
| 382 |
nomes_arquivos.append(f.name)
|
| 383 |
+
|
| 384 |
+
# Leitura Inteligente de ZIP (Pastas Compactadas)
|
| 385 |
+
if f.name.endswith('.zip'):
|
| 386 |
+
try:
|
| 387 |
+
with zipfile.ZipFile(f, 'r') as zip_ref:
|
| 388 |
+
for file_info in zip_ref.infolist():
|
| 389 |
+
# Ignora pastas de sistema ou pastas em branco
|
| 390 |
+
if not file_info.is_dir() and not file_info.filename.startswith('__MACOSX'):
|
| 391 |
+
if file_info.filename.endswith(('.txt', '.csv', '.json', '.py', '.html', '.md', '.js', '.css')):
|
| 392 |
+
with zip_ref.open(file_info) as extracted_file:
|
| 393 |
+
conteudo_arquivo += f"\n\n--- Arquivo Extraído do ZIP: {file_info.filename} ---\n" + extracted_file.read().decode('utf-8', errors='ignore')
|
| 394 |
+
except Exception as e:
|
| 395 |
+
st.toast(f"⚠️ Erro ao descompactar {f.name}: {e}")
|
| 396 |
+
|
| 397 |
+
# Leitura de Código/Texto Avulso
|
| 398 |
+
elif f.name.endswith(('.txt', '.csv', '.json', '.py', '.html', '.md', '.js', '.css')):
|
| 399 |
+
conteudo_arquivo += f"\n\n--- Conteúdo de: {f.name} ---\n" + f.getvalue().decode("utf-8")
|
| 400 |
+
|
| 401 |
+
# Leitura de Imagens
|
| 402 |
+
elif f.name.endswith(('.png', '.jpg', '.jpeg')):
|
| 403 |
+
imagens_b64.append(base64.b64encode(f.read()).decode())
|
| 404 |
|
| 405 |
tem_texto = len(conteudo_arquivo) > 0
|
| 406 |
tem_imagem = len(imagens_b64) > 0
|
|
|
|
| 455 |
|
| 456 |
else:
|
| 457 |
with st.chat_message("assistant"):
|
| 458 |
+
if st.session_state.personalidade_ativa == "🤖 Automática (Gerente Groq)":
|
| 459 |
+
tag_alma = analisar_alma_gerente(prompt, gr_key)
|
| 460 |
+
if "[YUKINA]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["❄️ Yukina (Companheira Obsessiva)"]
|
| 461 |
+
elif "[AGENTE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🛠️ Agente Construtora (Vibe Coding)"]
|
| 462 |
+
elif "[RPG]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🎭 A Narradora Implacável (RPG)"]
|
| 463 |
+
elif "[NERD]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🤓 Nerd / Geek (Cultura Pop)"]
|
| 464 |
+
elif "[DEBOCHE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🍷 Analítica e Sarcástica (Debochada)"]
|
| 465 |
+
elif "[ARTE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🎨 Artística e Criativa (Poética)"]
|
| 466 |
+
else: prompt_sistema_atual = PERSONALIDADES["🤖 Neutra (Padrão Gemini)"]
|
| 467 |
+
st.toast(f"🎭 Alma assumida: {tag_alma}")
|
| 468 |
+
else:
|
| 469 |
+
prompt_sistema_atual = PERSONALIDADES.get(st.session_state.personalidade_ativa, PERSONALIDADES["🤖 Neutra (Padrão Gemini)"])
|
| 470 |
+
|
| 471 |
agora = datetime.now(timezone(timedelta(hours=-3)))
|
| 472 |
prompt_sistema_atual += f"\n\n[INFO]: Data/Hora local: {agora.strftime('%Y-%m-%d %H:%M:%S')}."
|
| 473 |
|