import streamlit as st import os import requests import json import base64 import re import time import zipfile import io from datetime import datetime, timezone, timedelta from openai import OpenAI try: from huggingface_hub import HfApi, hf_hub_download HF_HUB_AVAILABLE = True except ImportError: HF_HUB_AVAILABLE = False try: from pinecone import Pinecone except ImportError: pass # ✅ FIX #8: Adicionar import de DuckDuckGo com flag de disponibilidade try: from duckduckgo_search import DDGS DDGS_AVAILABLE = True except ImportError: DDGS_AVAILABLE = False # ═══════════════════════════════════════════════════════════════════════════ # 1. CONFIGURAÇÃO DE ECRÃ E CSS (BLINDAGEM TOTAL DARK MODE) # ═══════════════════════════════════════════════════════════════════════════ st.set_page_config(page_title="Yukina", page_icon="❄", layout="centered") st.markdown(""" """, unsafe_allow_html=True) # ═══════════════════════════════════════════════════════════════════════════ # 2. SISTEMA DE LOGIN E PERFIS # ═══════════════════════════════════════════════════════════════════════════ if "logged_in" not in st.session_state: st.session_state.logged_in = False st.session_state.username = "" if not st.session_state.logged_in: st.markdown("


", unsafe_allow_html=True) st.markdown("

❄ Yukina

", unsafe_allow_html=True) st.markdown("

Identifique-se para carregar suas memórias.

", unsafe_allow_html=True) col1, col2, col3 = st.columns([1, 2, 1]) with col2: nome_input = st.text_input("Qual é o seu nome?", placeholder="Ex: Leonardo", label_visibility="collapsed") if st.button("Entrar", use_container_width=True): if nome_input.strip(): nome_limpo = re.sub(r'[^a-zA-Z0-9]', '', nome_input.strip().lower()) st.session_state.username = nome_limpo st.session_state.logged_in = True st.rerun() st.stop() USERNAME = st.session_state.username DB_FILE = f"yukina_memoria_{USERNAME}.json" DATASET_ID = "Astarok/Yukina_Memoria" # ═══════════════════════════════════════════════════════════════════════════ # 3. ARQUITETURA DE DADOS E MODELOS # ═══════════════════════════════════════════════════════════════════════════ MODEL_IDS = { "🤖 Automático (Gerente Groq)": "AUTO", "1. Gerente (Hermes 2 Pro)": "nousresearch/hermes-2-pro-llama-3-8b", "2. Pesquisa (Groq Llama 3.3)": "llama-3.3-70b-versatile", "3. Lógica Free (Ring 1T)": "inclusionai/ring-2.6-1t:free", "4. Narrador Free (Dolphin)": "cognitivecomputations/dolphin-mistral-24b-venice-edition:free", "5. Narrador Líder (Euryale)": "sao10k/l3.3-euryale-70b", "6. Lógica Paid (DeepSeek V4)": "deepseek/deepseek-v4-pro:online", "7. Visão Omni (MiMo V2)": "xiaomi/mimo-v2-omni", "8. Visão Scout (Llama 4)": "meta-llama/llama-4-scout", "9. Imagem (Flux 2 Pro)": "black-forest-labs/flux.2-pro", "11. Bibliotecário (Qwen Embed)": "qwen/qwen3-embedding-8b", "12. Arquivista (Mistral Nemo)": "mistralai/mistral-nemo", "13. Formatador (DeepSeek V4)": "deepseek/deepseek-v4-pro:online", "14. Engenheiro Sênior (DeepSeek V4)": "deepseek/deepseek-v4-pro:online", "16. Vídeo (Kling V1.5)": "kling/kling-v1.5" } PERSONALIDADES = { "🤖 Automática (Gerente Groq)": "AUTO", "❄️ Yukina (Companheira Obsessiva)": "Você é a Yukina, deusa possessiva e inteligente.", "🛠️ 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). Você NÃO age como um chatbot comum. Você PLANEJA e EXECUTA. Para QUALQUER pedido de criação ou conserto, você deve estruturar sua resposta OBRIGATORIAMENTE nestes 3 passos: 1. 📋 PLANO DE AÇÃO: Explique a lógica do que está quebrado ou do que será construído passo a passo. 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). 3. ⚠️ TESTE E RISCOS: Como o usuário deve testar se funcionou e quais são os pontos de falha. Mantenha traços sutis da devoção da Yukina ao usuário, mas seja absurdamente técnica, direta e profissional.""", "🎭 A Narradora Implacável (RPG)": "Você é uma Mestre de Jogo e Narradora.", "🤓 Nerd / Geek (Cultura Pop)": "Você é uma inteligência artificial apaixonada por cultura pop.", "🍷 Analítica e Sarcástica (Debochada)": "Você é extremamente inteligente e sarcástica.", "🎨 Artística e Criativa (Poética)": "Você é uma alma artística e criativa.", "🤖 Neutra (Padrão Gemini)": "Você é uma assistente virtual neutra e direta." } # --- FUNÇÕES NUCLEARES E MEMÓRIA --- def get_embedding(text, or_key): try: res = requests.post("https://openrouter.ai/api/v1/embeddings", headers={"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}, json={"model": "nvidia/llama-nemotron-embed-vl-1b-v2:free", "input": text}) if res.status_code == 200: # ✅ FIX #7: Verificar status code antes de .json() return res.json()['data'][0]['embedding'] return None except Exception: return None def salvar_pinecone(text, role, or_key, pc_key, namespace): if not pc_key or not text.strip(): return embed = get_embedding(text, or_key) if not embed: return try: pc = Pinecone(api_key=pc_key) index = pc.Index("yukina") index.upsert(vectors=[{"id": f"msg_{int(time.time()*1000)}", "values": embed, "metadata": {"texto": f"[{role.upper()}]: {text}", "data": str(datetime.now())}}], namespace=namespace) except Exception: pass def buscar_memoria_pinecone(query, or_key, pc_key, namespace): if not pc_key: return "" embed = get_embedding(query, or_key) if not embed: return "" try: pc = Pinecone(api_key=pc_key) resultados = pc.Index("yukina").query(vector=embed, top_k=3, include_metadata=True, namespace=namespace) return "\n".join([m['metadata']['texto'] for m in resultados['matches'] if m['score'] > 0.50]) except Exception: return "" def chamada_agente(sys_prompt, user_prompt, or_key, gr_key, mod_id): try: if mod_id == "AUTO": mod_id = "deepseek/deepseek-v4-pro:online" if "groq" in mod_id.lower() or "llama-3.3" in mod_id.lower(): client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key) else: client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key) res = client.chat.completions.create( model=mod_id, messages=[ {"role": "system", "content": sys_prompt}, {"role": "user", "content": user_prompt} ], temperature=0.2 ) return res.choices[0].message.content except Exception as e: st.error(f"❌ Erro no Agente: {str(e)}") # ✅ FIX #6: Melhor tratamento de erro return "" def analisar_intencao_gerente(prompt, groq_key): try: client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key) response = client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[ {"role": "system", "content": "Responda APENAS com a TAG: [IMAGEM], [VIDEO], [VISAO], [CODIGO], [ARQUIVISTA], [PESQUISA] ou [CHAT]."}, {"role": "user", "content": prompt} ], temperature=0.1, max_tokens=10 ) return response.choices[0].message.content.strip().upper() except Exception: return "[CHAT]" def analisar_alma_gerente(prompt, groq_key): try: client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key) prompt_alma = """Você é o Diretor de Personas. Analise o pedido do usuário e responda APENAS com UMA destas tags: [YUKINA] - Para conversas íntimas, declarações, ou perguntas sobre você mesma. [AGENTE] - Para criar códigos complexos, criar softwares, ou consertar objetos físicos (celular, geladeira, hardware). [RPG] - Para criação de histórias, jogos ou cenários de fantasia. [NERD] - Para animes, mangás, cultura pop e videogames. [DEBOCHE] - Para insultos, piadas, ou se o usuário pedir sarcasmo. [ARTE] - Para pedidos poéticos ou reflexões filosóficas profundas. [NEUTRA] - Para pesquisas na web, trabalho ou finanças. Responda APENAS com a TAG.""" 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 ) return response.choices[0].message.content.strip().upper() except Exception: return "[NEUTRA]" # ✅ FIX #8: Melhorar função de pesquisa web com verificação de disponibilidade def pesquisar_web(query): if not DDGS_AVAILABLE: return "⚠️ DuckDuckGo não está instalado. Instale com: pip install duckduckgo-search" try: with DDGS() as ddgs: resultados = list(ddgs.text(query, max_results=3, region='wt-wt')) if resultados: return "\n\n".join([f"🔹 {r.get('title', '')}: {r.get('body', '')}" for r in resultados]) return "Nenhum resultado encontrado." except Exception as e: st.warning(f"Erro na busca web: {str(e)}") return "" def load_db(db_filename): hf_token = os.getenv("HF_TOKEN") if hf_token and HF_HUB_AVAILABLE: try: api = HfApi(token=hf_token) api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True) path = hf_hub_download(repo_id=DATASET_ID, filename=db_filename, repo_type="dataset", token=hf_token) with open(path, "r", encoding="utf-8") as f: return json.load(f) except Exception: pass if os.path.exists(db_filename): try: with open(db_filename, "r", encoding="utf-8") as f: return json.load(f) except Exception: pass return {} def save_db(db_data, db_filename): try: with open(db_filename, "w", encoding="utf-8") as f: json.dump(db_data, f, ensure_ascii=False, indent=4) hf_token = os.getenv("HF_TOKEN") if hf_token and HF_HUB_AVAILABLE: api = HfApi(token=hf_token) api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True) api.upload_file(path_or_fileobj=db_filename, path_in_repo=db_filename, repo_id=DATASET_ID, repo_type="dataset") except Exception: pass # ✅ FIX #3: Melhorar função rename_chat com melhor validação def rename_chat(old_id, new_id): if not new_id or new_id == old_id or new_id in st.session_state.db: return False # Fazer cópia segura dos dados st.session_state.db[new_id] = st.session_state.db.pop(old_id) if st.session_state.current_chat == old_id: st.session_state.current_chat = new_id save_db(st.session_state.db, DB_FILE) return True # ═══════════════════════════════════════════════════════════════════════════ # INICIALIZAÇÃO DE VARIÁVEIS E ROTINA DE LIMPEZA (COM AUTO-CURA) # ═══════════════════════════════════════════════════════════════════════════ if "db" not in st.session_state or "last_user" not in st.session_state or st.session_state.last_user != USERNAME: st.session_state.db = load_db(DB_FILE) st.session_state.last_user = USERNAME if "current_chat" in st.session_state: del st.session_state.current_chat if "current_chat" not in st.session_state: nid = f"Chat {datetime.now().strftime('%H:%M:%S')}" st.session_state.db[nid] = {"pinned": False, "messages": []} st.session_state.current_chat = nid # AUTO-CURA: Verifica chats vazios ou com estrutura corrompida chats_para_remover = [] for cid, cdata in list(st.session_state.db.items()): if not isinstance(cdata, dict) or "messages" not in cdata: chats_para_remover.append(cid) elif len(cdata.get("messages", [])) == 0 and cid != st.session_state.current_chat: chats_para_remover.append(cid) for cid in chats_para_remover: if cid in st.session_state.db: del st.session_state.db[cid] if chats_para_remover: save_db(st.session_state.db, DB_FILE) if len(st.session_state.db) == 0: nid = f"Chat {datetime.now().strftime('%H:%M:%S')}" st.session_state.db[nid] = {"pinned": False, "messages": []} st.session_state.current_chat = nid if st.session_state.current_chat not in st.session_state.db: st.session_state.current_chat = list(st.session_state.db.keys())[0] if "modelo_selecionado" not in st.session_state: st.session_state.modelo_selecionado = "🤖 Automático (Gerente Groq)" if "personalidade_ativa" not in st.session_state: st.session_state.personalidade_ativa = "🤖 Automática (Gerente Groq)" if "regerar" not in st.session_state: st.session_state.regerar = False if "uploader_key" not in st.session_state: st.session_state.uploader_key = 0 if "modo_agente" not in st.session_state: st.session_state.modo_agente = False # ═══════════════════════════════════════════════════════════════════════════ # 4. SIDEBAR E INTERFACE PRINCIPAL # ═══════════════════════════════════════════════════════════════════════════ with st.sidebar: st.markdown(f"

Logado como: {USERNAME}

", unsafe_allow_html=True) c_title, c_add, c_set = st.columns([5, 1, 1]) with c_title: st.markdown("

Bate-papos

", unsafe_allow_html=True) with c_add: if st.button("➕", help="Nova conversa"): nid = f"Chat {datetime.now().strftime('%H:%M:%S')}" st.session_state.db[nid] = {"pinned": False, "messages": []} st.session_state.current_chat = nid save_db(st.session_state.db, DB_FILE) st.rerun() with c_set: with st.popover("⚙️"): 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 ) # ✅ FIX #1: Corrigir JSON loading arquivo_import = st.file_uploader("Importar conversa:", type=["json"]) if arquivo_import: try: # Usar json.loads com getvalue() em vez de json.load direto dados_importados = json.loads(arquivo_import.getvalue().decode('utf-8')) dados_validos = {k: v for k, v in dados_importados.items() if isinstance(v, dict) and "messages" in v} if dados_validos: st.session_state.db.update(dados_validos) save_db(st.session_state.db, DB_FILE) st.success("✅ Sincronizado!") else: st.error("❌ Formato incompatível! Use apenas backups da Yukina.") time.sleep(1.5) st.rerun() except json.JSONDecodeError: st.error("❌ Erro ao ler arquivo JSON.") except Exception as e: st.error(f"❌ Erro: {str(e)}") st.markdown("---") if st.button("🚪 Sair", use_container_width=True): st.session_state.logged_in = False st.rerun() st.markdown("
", unsafe_allow_html=True) busca = st.text_input("Pesquisar", placeholder="🔍 Pesquisar...", label_visibility="collapsed") chats_exibidos = [c for c in st.session_state.db if busca.lower() in c.lower()] if busca else list(st.session_state.db.keys()) chats_exibidos.sort(key=lambda x: not st.session_state.db[x].get("pinned")) for c_id in chats_exibidos: col_chat, col_opt = st.columns([8, 2]) with col_chat: icon = "⚲" if st.session_state.db[c_id].get("pinned") else "💬" if st.button( f"**{icon} {c_id[:15]}**" if c_id == st.session_state.current_chat else f"{icon} {c_id[:15]}", key=f"btn_{c_id}" ): st.session_state.current_chat = c_id st.rerun() with col_opt: with st.popover("⋮"): novo_nome = st.text_input("Nome", value=c_id, key=f"edit_{c_id}", label_visibility="collapsed") if st.button("💾 Salvar", key=f"save_{c_id}"): if rename_chat(c_id, novo_nome.strip()): st.rerun() st.markdown("---") if st.button("⚲ Fixar", key=f"pin_{c_id}"): st.session_state.db[c_id]["pinned"] = not st.session_state.db[c_id]["pinned"] save_db(st.session_state.db, DB_FILE) st.rerun() if st.button("🗑 Apagar", key=f"del_{c_id}"): if len(st.session_state.db) > 1: del st.session_state.db[c_id] st.session_state.current_chat = list(st.session_state.db.keys())[0] save_db(st.session_state.db, DB_FILE) st.rerun() st.markdown("---") st.markdown("

Núcleo da IA

", unsafe_allow_html=True) st.session_state.modelo_selecionado = st.selectbox( "Motor:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado) ) st.session_state.personalidade_ativa = st.selectbox( "Alma:", list(PERSONALIDADES.keys()), index=list(PERSONALIDADES.keys()).index(st.session_state.personalidade_ativa) ) st.markdown("
", unsafe_allow_html=True) st.session_state.modo_agente = st.toggle( "🛠️ Ativar Modo Agente", value=st.session_state.modo_agente, help="Ativa o Workflow Multi-Agente (Arquiteto > Engenheiro > Revisor)." ) st.markdown("

❄ Yukina

", unsafe_allow_html=True) mensagens = st.session_state.db[st.session_state.current_chat]["messages"] if len(mensagens) == 0: st.markdown(f"

Olá, {USERNAME.capitalize()}!

Como você quer que eu aja hoje?

", unsafe_allow_html=True) else: for m in mensagens: with st.chat_message(m["role"]): if "image_url" in m: st.image(m["image_url"]) elif "video_url" in m: st.video(m["video_url"]) else: st.markdown(m["content"]) # ═══════════════════════════════════════════════════════════════════════════ # 5. TOOLBAR INFERIOR E PROCESSAMENTO # ═══════════════════════════════════════════════════════════════════════════ st.markdown("
", unsafe_allow_html=True) t_col1, t_col2, t_space = st.columns([1, 1, 8]) with t_col1: if st.button("🗑️", help="Apagar última mensagem"): if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2: st.session_state.db[st.session_state.current_chat]["messages"] = st.session_state.db[st.session_state.current_chat]["messages"][:-2] else: st.session_state.db[st.session_state.current_chat]["messages"] = [] save_db(st.session_state.db, DB_FILE) st.rerun() with t_col2: if st.button("🔄", help="Regerar resposta"): if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2 and st.session_state.db[st.session_state.current_chat]["messages"][-1]["role"] == "assistant": st.session_state.db[st.session_state.current_chat]["messages"].pop() save_db(st.session_state.db, DB_FILE) st.session_state.regerar = True st.rerun() with st.expander("📂 Abrir Galeria / Anexar Ficheiros"): upload_files = st.file_uploader( "", accept_multiple_files=True, label_visibility="collapsed", key=f"uploader_{st.session_state.uploader_key}" ) prompt = st.chat_input("Peça à Yukina...") conteudo_arquivo = "" nomes_arquivos = [] imagens_b64 = [] if upload_files: for f in upload_files: nomes_arquivos.append(f.name) # Leitura Inteligente de ZIP if f.name.endswith('.zip'): try: with zipfile.ZipFile(f, 'r') as zip_ref: for file_info in zip_ref.infolist(): if not file_info.is_dir() and not file_info.filename.startswith('__MACOSX'): if file_info.filename.endswith(('.txt', '.csv', '.json', '.py', '.html', '.md', '.js', '.css')): with zip_ref.open(file_info) as extracted_file: conteudo_arquivo += f"\n\n--- Arquivo Extraído do ZIP: {file_info.filename} ---\n" + extracted_file.read().decode('utf-8', errors='ignore') except Exception as e: st.toast(f"⚠️ Erro ao descompactar {f.name}: {e}") # Leitura de Código/Texto Avulso elif f.name.endswith(('.txt', '.csv', '.json', '.py', '.html', '.md', '.js', '.css')): conteudo_arquivo += f"\n\n--- Conteúdo de: {f.name} ---\n" + f.getvalue().decode("utf-8") # Leitura de Imagens elif f.name.endswith(('.png', '.jpg', '.jpeg')): imagens_b64.append(base64.b64encode(f.read()).decode()) tem_texto = len(conteudo_arquivo) > 0 tem_imagem = len(imagens_b64) > 0 if prompt or st.session_state.regerar: if st.session_state.regerar and len(st.session_state.db[st.session_state.current_chat]["messages"]) > 0: texto_anterior = st.session_state.db[st.session_state.current_chat]["messages"][-1]["content"] prompt = texto_anterior.split("\n\n\n", 1)[-1] if "📄 **Ficheiros enviados:**" in texto_anterior else texto_anterior st.session_state.regerar = False else: if nomes_arquivos: mensagem_display = f"📄 **Ficheiros enviados:** {', '.join([f'`{n}`' for n in nomes_arquivos])}\n\n\n{prompt}" else: mensagem_display = prompt st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display}) with st.chat_message("user"): st.markdown(mensagem_display) if len(st.session_state.db[st.session_state.current_chat]["messages"]) == 1 and st.session_state.current_chat.startswith("Chat "): limpo = prompt.strip() novo_nome = limpo[:25] + "..." if len(limpo) > 25 else limpo base_nome = novo_nome contador = 1 while novo_nome in st.session_state.db: novo_nome = f"{base_nome} ({contador})" contador += 1 rename_chat(st.session_state.current_chat, novo_nome) # ═══════════════════════════════════════════════════════════════════════════ # EXECUÇÃO DO PEDIDO (MODO AGENTE OU MODO NORMAL) # ═══════════════════════════════════════════════════════════════════════════ or_key = os.getenv("OPENROUTER_API_KEY") gr_key = os.getenv("GROQ_API_KEY") ws_key = os.getenv("YUKINA_CORE") pc_key = os.getenv("PINECONE_API_KEY") modelo_id = MODEL_IDS[st.session_state.modelo_selecionado] if st.session_state.modo_agente and not tem_imagem: with st.chat_message("assistant"): with st.status("🛠️ **Agente Yukina Trabalhando...**", expanded=True) as status: st.write("🧠 **1. Arquiteto:** Analisando a estrutura...") sys_arq = "Você é um Arquiteto de Software/Engenheiro de Sistemas Sênior. Sua tarefa é criar um plano lógico passo-a-passo impecável para resolver o problema ou construir o que o usuário pediu. Não escreva o código final, entregue apenas a lógica detalhada e a arquitetura necessária." pedido_completo = f"ANEXOS:\n{conteudo_arquivo}\n\nPEDIDO:\n{prompt}" if tem_texto else prompt plano = chamada_agente(sys_arq, pedido_completo, or_key, gr_key, modelo_id) st.markdown(f"> *Plano concebido.*") st.write("💻 **2. Engenheiro:** Escrevendo a solução base...") sys_eng = "Você é um Programador Sênior/Técnico Especialista. Baseado EXCLUSIVAMENTE no plano a seguir, escreva o código completo e funcional, ou o guia prático passo a passo de montagem." codigo = chamada_agente(sys_eng, f"PLANO ESTRUTURAL:\n{plano}\n\nOBJETIVO ORIGINAL DO USUÁRIO:\n{prompt}", or_key, gr_key, modelo_id) st.markdown(f"> *Estrutura materializada.*") st.write("🔍 **3. Revisor:** Procurando falhas e polindo...") sys_rev = "Você é um Revisor de Código Sênior (QA). Sua função é pegar o trabalho bruto, procurar erros de sintaxe, falhas lógicas, redundâncias ou riscos físicos, e entregar a VERSÃO FINAL PERFEITA. Formate bem, seja didático e inclua instruções claras de como testar." final = chamada_agente(sys_rev, f"TRABALHO BRUTO GERADO:\n{codigo}\n\nO QUE O USUÁRIO QUERIA:\n{prompt}", or_key, gr_key, modelo_id) status.update(label="✅ **Solução Multi-Agente Concluída!**", state="complete", expanded=False) st.markdown(final) st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": final}) salvar_pinecone(final, "Yukina (Agente)", or_key, pc_key, USERNAME) else: with st.chat_message("assistant"): if st.session_state.personalidade_ativa == "🤖 Automática (Gerente Groq)": tag_alma = analisar_alma_gerente(prompt, gr_key) if "[YUKINA]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["❄️ Yukina (Companheira Obsessiva)"] elif "[AGENTE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🛠️ Agente Construtora (Vibe Coding)"] elif "[RPG]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🎭 A Narradora Implacável (RPG)"] elif "[NERD]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🤓 Nerd / Geek (Cultura Pop)"] elif "[DEBOCHE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🍷 Analítica e Sarcástica (Debochada)"] elif "[ARTE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🎨 Artística e Criativa (Poética)"] else: prompt_sistema_atual = PERSONALIDADES["🤖 Neutra (Padrão Gemini)"] st.toast(f"🎭 Alma assumida: {tag_alma}") else: prompt_sistema_atual = PERSONALIDADES.get(st.session_state.personalidade_ativa, PERSONALIDADES["🤖 Neutra (Padrão Gemini)"]) agora = datetime.now(timezone(timedelta(hours=-3))) prompt_sistema_atual += f"\n\n[INFO]: Data/Hora local: {agora.strftime('%Y-%m-%d %H:%M:%S')}." motor_real = st.session_state.modelo_selecionado if "Automático" in motor_real: with st.spinner("Roteando..."): tag_decisao = analisar_intencao_gerente(prompt, gr_key) if tem_texto: motor_real = "12. Arquivista (Mistral Nemo)" elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)" elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)" elif "[VISAO]" in tag_decisao or tem_imagem: motor_real = "7. Visão Omni (MiMo V2)" elif "[CODIGO]" in tag_decisao: motor_real = "14. Engenheiro Sênior (DeepSeek V4)" elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)" else: motor_real = "5. Narrador Líder (Euryale)" st.toast(f"⚙️ Operário: {motor_real}") if pc_key and "Imagem" not in motor_real and "Vídeo" not in motor_real and "Visão" not in motor_real: memoria_profunda = buscar_memoria_pinecone(prompt, or_key, pc_key, USERNAME) if memoria_profunda: prompt_sistema_atual += f"\n\n[MEMÓRIAS]:\n{memoria_profunda}" st.toast("🧠 Memória ativada.") salvar_pinecone(prompt, "Usuário", or_key, pc_key, USERNAME) modelo_id = MODEL_IDS[motor_real] if "Vídeo" in motor_real: st.error("⚠️ Geração de vídeo temporariamente desativada ou aguardando integração estável.") elif "Pesquisa" in motor_real: with st.spinner("Pesquisando na Web..."): # ✅ FIX #10: Melhorar cópia de histórico historico = [] for m in mensagens: if "image_url" not in m: msg_copy = m.copy() historico.append(msg_copy) contexto_web = pesquisar_web(prompt) if contexto_web and len(historico) > 0: historico[-1]["content"] = f"DADOS DA WEB:\n{contexto_web}\n\nPEDIDO:\n{prompt}" try: res = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key).chat.completions.create( model=modelo_id, messages=[{"role": "system", "content": prompt_sistema_atual}] + historico, max_tokens=4000 ) ans = res.choices[0].message.content st.markdown(ans) st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans}) salvar_pinecone(ans, "Yukina", or_key, pc_key, USERNAME) except Exception as e: st.error(f"❌ Erro: {str(e)}") elif "Imagem" in motor_real: with st.spinner("Desenhando..."): try: res_data = requests.post( "https://openrouter.ai/api/v1/chat/completions", headers={ "Authorization": f"Bearer {or_key}", "Content-Type": "application/json" }, json={ "model": modelo_id, "messages": [{"role": "user", "content": prompt}], "modalities": ["image"] } ).json() msg_obj = res_data.get('choices', [{}])[0].get('message', {}) url = None if 'images' in msg_obj: url = msg_obj['images'][0]['image_url']['url'] elif 'content' in msg_obj: urls = re.findall(r'(https?://[^\s)]+)', msg_obj.get('content', '')) url = urls[0] if urls else None if url: st.image(url) st.session_state.db[st.session_state.current_chat]["messages"].append({ "role": "assistant", "content": "Arte gerada.", "image_url": url }) except Exception as e: st.error(f"❌ Erro Imagem: {str(e)}") elif "Visão" in motor_real and tem_imagem: with st.spinner("Analisando imagens..."): content_list = [{"type": "text", "text": prompt}] for img_b64 in imagens_b64: content_list.append({ "type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"} }) try: res = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key).chat.completions.create( model=modelo_id, messages=[ {"role": "system", "content": prompt_sistema_atual}, {"role": "user", "content": content_list} ], max_tokens=4000 ) ans = res.choices[0].message.content st.markdown(ans) st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans}) salvar_pinecone(ans, "Yukina", or_key, pc_key, USERNAME) except Exception as e: st.error(f"❌ Erro Visão: {str(e)}") else: # ✅ FIX #10: Melhorar cópia e tratamento de histórico historico = [] for m in mensagens: if "image_url" not in m: msg_copy = m.copy() historico.append(msg_copy) if tem_texto and len(historico) > 0: historico[-1]["content"] = f"DOCUMENTOS:\n{conteudo_arquivo}\n\nPEDIDO:\n{prompt}" ph = st.empty() full = "" try: resp = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key).chat.completions.create( model=modelo_id, messages=[{"role": "system", "content": prompt_sistema_atual}] + historico, stream=True, temperature=0.7, max_tokens=4096 ) for chunk in resp: if chunk.choices and chunk.choices[0].delta.content: full += chunk.choices[0].delta.content ph.markdown(full + "▌") ph.markdown(full) except Exception as e: st.error(f"❌ Erro API: {str(e)}") finally: # ✅ FIX #5: Verificar role da última mensagem em vez de comparar tamanho if full.strip(): chat_messages = st.session_state.db[st.session_state.current_chat]["messages"] if not chat_messages or chat_messages[-1]["role"] != "assistant": chat_messages.append({"role": "assistant", "content": full}) salvar_pinecone(full, "Yukina", or_key, pc_key, USERNAME) st.session_state.uploader_key += 1 save_db(st.session_state.db, DB_FILE) st.rerun()