| 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 |
|
|
| |
| try: |
| from duckduckgo_search import DDGS |
| DDGS_AVAILABLE = True |
| except ImportError: |
| DDGS_AVAILABLE = False |
|
|
| |
| |
| |
| st.set_page_config(page_title="Yukina", page_icon="❄", layout="centered") |
|
|
| st.markdown(""" |
| <style> |
| #MainMenu {visibility: hidden;} |
| footer {visibility: hidden;} |
| [data-testid="stHeader"] { background-color: transparent !important; } |
| .stApp { background-color: #131314 !important; color: #ededed !important; } |
| |
| /* --- BARRA LATERAL --- */ |
| [data-testid="stSidebar"] { background-color: #0b0b0b !important; border-right: 1px solid #1e1f20 !important; } |
| [data-testid="stSidebar"] button { background-color: transparent !important; border: none !important; box-shadow: none !important; color: #a0a0a0 !important; } |
| [data-testid="stSidebar"] button:hover { color: #ffffff !important; background-color: #1e1f20 !important; } |
| [data-testid="stSidebarNav"] .stButton > button, [data-testid="stSidebarContent"] .stButton > button { width: 100% !important; justify-content: flex-start !important; } |
| |
| /* --- CAIXAS DE SELEÇÃO E TOGGLE --- */ |
| div[data-baseweb="select"] > div { background-color: #1e1f20 !important; color: white !important; border: 1px solid #3c4043 !important; } |
| div[role="listbox"] { background-color: #1e1f20 !important; color: white !important; } |
| |
| /* --- A MARRETA DEFINITIVA PARA O TECLADO E FUNDO --- */ |
| div[data-testid="stBottom"], div[data-testid="stBottom"] > div, div[data-testid="stBottomBlock"], div[data-testid="stBottomBlock"] > div { |
| background-color: #131314 !important; background: #131314 !important; |
| } |
| .stChatInputContainer, div[class*="stChatInputContainer"] { |
| background-color: #131314 !important; background: #131314 !important; padding-bottom: 15px !important; border: none !important; |
| } |
| [data-testid="stChatInput"] { background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 24px !important; } |
| [data-testid="stChatInput"] textarea { color: #ffffff !important; background-color: transparent !important; } |
| |
| /* --- BOTÕES GLOBAIS --- */ |
| [data-testid="stMain"] [data-testid="stHorizontalBlock"] button { |
| background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 12px !important; |
| font-size: 20px !important; color: #a0a0a0 !important; padding: 5px !important; |
| } |
| [data-testid="stMain"] [data-testid="stHorizontalBlock"] button:hover { |
| color: #ffffff !important; background-color: #3c4043 !important; transform: scale(1.05); transition: 0.2s ease-in-out; |
| } |
| |
| .stChatMessage { background-color: transparent !important; border: none !important; padding-bottom: 8px !important; } |
| [data-testid="stExpander"] { background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 15px !important; margin-bottom: 10px; } |
| [data-testid="stExpander"] summary { color: #e3e3e3 !important; } |
| |
| /* Estilo do Status (Agente) */ |
| [data-testid="stStatusWidget"] { background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 10px !important; } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
| |
| |
| |
| 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("<br><br><br>", unsafe_allow_html=True) |
| st.markdown("<h1 style='text-align: center; color: white; font-size: 50px;'>❄ Yukina</h1>", unsafe_allow_html=True) |
| st.markdown("<p style='text-align: center; color: #a0a0a0;'>Identifique-se para carregar suas memórias.</p>", 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" |
|
|
| |
| |
| |
| 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." |
| } |
|
|
| |
| 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: |
| 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)}") |
| 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]" |
|
|
| |
| 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 |
|
|
| |
| 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 |
| |
| |
| 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 |
|
|
| |
| |
| |
| 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 |
|
|
| |
| 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 |
|
|
| |
| |
| |
| with st.sidebar: |
| st.markdown(f"<p style='color: #888; margin-bottom: 0px;'>Logado como: <b>{USERNAME}</b></p>", unsafe_allow_html=True) |
| c_title, c_add, c_set = st.columns([5, 1, 1]) |
| with c_title: |
| st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", 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 |
| ) |
| |
| |
| arquivo_import = st.file_uploader("Importar conversa:", type=["json"]) |
| if arquivo_import: |
| try: |
| |
| 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("<br>", 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("<h4 style='color: #ededed;'>Núcleo da IA</h4>", 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("<br>", 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("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True) |
| mensagens = st.session_state.db[st.session_state.current_chat]["messages"] |
|
|
| if len(mensagens) == 0: |
| st.markdown(f"<br><br><h3 style='color: #888; font-weight: 400;'>Olá, {USERNAME.capitalize()}!</h3><h1 style='color: #fff; font-size: 32px;'>Como você quer que eu aja hoje?</h1>", 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"]) |
|
|
| |
| |
| |
| st.markdown("<br>", 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) |
| |
| |
| 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}") |
| |
| |
| 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") |
| |
| |
| 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) |
| |
| |
| |
| |
| 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..."): |
| |
| 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: |
| |
| 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: |
| |
| 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() |
|
|