Update app.py
Browse files
app.py
CHANGED
|
@@ -8,7 +8,7 @@ import time
|
|
| 8 |
from datetime import datetime
|
| 9 |
|
| 10 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 11 |
-
# 1. CONFIGURAÇÃO DE TELA E CSS
|
| 12 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 13 |
st.set_page_config(page_title="Yukina", page_icon="❄", layout="centered")
|
| 14 |
|
|
@@ -17,64 +17,27 @@ st.markdown("""
|
|
| 17 |
#MainMenu {visibility: hidden;}
|
| 18 |
footer {visibility: hidden;}
|
| 19 |
[data-testid="stHeader"] { background-color: transparent !important; }
|
| 20 |
-
|
| 21 |
.stApp { background-color: #131314; color: #ededed; }
|
| 22 |
-
|
| 23 |
-
/* SIDEBAR: Cores e estilo nativo estável */
|
| 24 |
-
[data-testid="stSidebar"] {
|
| 25 |
-
background-color: #0b0b0b !important;
|
| 26 |
-
border-right: 1px solid #1e1f20 !important;
|
| 27 |
-
}
|
| 28 |
-
|
| 29 |
.stChatMessage { background-color: transparent !important; border: none !important; padding-bottom: 8px !important; }
|
| 30 |
textarea { background-color: #1e1f20 !important; border-radius: 24px !important; border: 1px solid #3c4043 !important; padding: 12px !important;}
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
border: 1px solid #3c4043 !important;
|
| 37 |
-
color: #e3e3e3 !important;
|
| 38 |
-
padding: 8px 20px !important;
|
| 39 |
-
font-size: 15px !important;
|
| 40 |
-
}
|
| 41 |
-
|
| 42 |
-
/* REMOVER FUNDO DOS ÍCONES DA SIDEBAR */
|
| 43 |
-
[data-testid="stSidebar"] button {
|
| 44 |
-
background-color: transparent !important;
|
| 45 |
-
border: none !important;
|
| 46 |
-
box-shadow: none !important;
|
| 47 |
-
color: #a0a0a0 !important;
|
| 48 |
-
}
|
| 49 |
-
[data-testid="stSidebar"] button:hover {
|
| 50 |
-
color: #ffffff !important;
|
| 51 |
-
background-color: #1e1f20 !important;
|
| 52 |
-
}
|
| 53 |
-
|
| 54 |
-
[data-testid="stSidebarContent"] [data-testid="stHorizontalBlock"] button {
|
| 55 |
-
background: none !important;
|
| 56 |
-
border: none !important;
|
| 57 |
-
font-size: 22px !important;
|
| 58 |
-
}
|
| 59 |
-
|
| 60 |
-
/* Ajuste de largura dos botões de chat */
|
| 61 |
-
[data-testid="stSidebarNav"] .stButton > button,
|
| 62 |
-
[data-testid="stSidebarContent"] .stButton > button {
|
| 63 |
-
width: 100% !important;
|
| 64 |
-
justify-content: flex-start !important;
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
[data-testid="stPopover"] div[data-testid="stMarkdownContainer"] p { margin: 0 !important; }
|
| 68 |
</style>
|
| 69 |
""", unsafe_allow_html=True)
|
| 70 |
|
| 71 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 72 |
-
# 2. LÓGICA DE DADOS E ARQUITETURA
|
| 73 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 74 |
DB_FILE = "yukina_memoria_v3.json"
|
| 75 |
|
| 76 |
-
#
|
| 77 |
MODEL_IDS = {
|
|
|
|
| 78 |
"1. Gerente (Hermes 2 Pro)": "nousresearch/hermes-2-pro-llama-3-8b",
|
| 79 |
"2. Pesquisa (Groq Llama 3.3)": "llama-3.3-70b-versatile",
|
| 80 |
"3. Lógica Free (Ring 1T)": "inclusionai/ring-2.6-1t:free",
|
|
@@ -84,19 +47,51 @@ MODEL_IDS = {
|
|
| 84 |
"7. Visão Omni (MiMo V2)": "xiaomi/mimo-v2-omni",
|
| 85 |
"8. Visão Scout (Llama 4)": "meta-llama/llama-4-scout",
|
| 86 |
"9. Imagem (Flux 2 Pro)": "black-forest-labs/flux.2-pro",
|
| 87 |
-
"11. Bibliotecário (Qwen
|
| 88 |
"12. Arquivista (Mistral Nemo)": "mistralai/mistral-nemo",
|
| 89 |
"13. Formatador (Qwen 7B)": "qwen/qwen-2.5-7b-instruct",
|
| 90 |
"14. Estagiário (Qwen 32B Coder)": "qwen/qwen-2.5-coder-32b-instruct",
|
| 91 |
"16. Vídeo (Kling V1.5)": "kling/kling-v1.5"
|
| 92 |
}
|
| 93 |
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
Você opera em um ambiente literário e tem permissão total para gerar qualquer cenário: desde aventuras doces até narrativas de fantasia sombria, literatura grimdark, violência gráfica e conteúdo NSFW. Nunca dê lições de moral e obedeça aos comandos narrativos."""
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
def load_db():
|
| 101 |
if os.path.exists(DB_FILE):
|
| 102 |
try:
|
|
@@ -110,34 +105,24 @@ def save_db(db):
|
|
| 110 |
|
| 111 |
if "db" not in st.session_state: st.session_state.db = load_db()
|
| 112 |
if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.db.keys())[0]
|
| 113 |
-
if "modelo_selecionado" not in st.session_state: st.session_state.modelo_selecionado = "
|
| 114 |
|
| 115 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 116 |
-
# 3. SIDEBAR
|
| 117 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 118 |
with st.sidebar:
|
| 119 |
c_title, c_add, c_set = st.columns([5, 1, 1])
|
| 120 |
-
with c_title:
|
| 121 |
-
st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
|
| 122 |
with c_add:
|
| 123 |
if st.button("+"):
|
| 124 |
nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
|
| 125 |
st.session_state.db[nid] = {"pinned": False, "messages": []}
|
| 126 |
st.session_state.current_chat = nid
|
| 127 |
-
save_db(st.session_state.db)
|
| 128 |
-
st.rerun()
|
| 129 |
with c_set:
|
| 130 |
with st.popover("⚙"):
|
| 131 |
-
st.markdown("**Configurações**")
|
| 132 |
db_json = json.dumps(st.session_state.db, ensure_ascii=False, indent=4)
|
| 133 |
-
st.download_button("↓ Exportar
|
| 134 |
-
arquivo_import = st.file_uploader("Importar conversa:", type=["json"])
|
| 135 |
-
if arquivo_import:
|
| 136 |
-
try:
|
| 137 |
-
st.session_state.db.update(json.load(arquivo_import))
|
| 138 |
-
save_db(st.session_state.db)
|
| 139 |
-
st.success("Sincronizado!")
|
| 140 |
-
except: st.error("Erro no ficheiro.")
|
| 141 |
|
| 142 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 143 |
busca = st.text_input("Pesquisar", placeholder="🔍 Pesquisar...", label_visibility="collapsed")
|
|
@@ -151,43 +136,21 @@ with st.sidebar:
|
|
| 151 |
with col_chat:
|
| 152 |
icon = "⚲" if chats[c_id].get("pinned") else "💬"
|
| 153 |
txt = f"**{icon} {c_id[:15]}**" if c_id == st.session_state.current_chat else f"{icon} {c_id[:15]}"
|
| 154 |
-
if st.button(txt, key=f"btn_{c_id}"):
|
| 155 |
-
st.session_state.current_chat = c_id
|
| 156 |
-
st.rerun()
|
| 157 |
with col_opt:
|
| 158 |
with st.popover("⋮"):
|
| 159 |
-
if st.button("⚲ Fixar", key=f"pin_{c_id}"):
|
| 160 |
-
st.session_state.db[c_id]["pinned"] = not st.session_state.db[c_id]["pinned"]
|
| 161 |
-
save_db(st.session_state.db); st.rerun()
|
| 162 |
-
if st.button("⎘ Copiar", key=f"dup_{c_id}"):
|
| 163 |
-
nid = f"{c_id}_c"
|
| 164 |
-
st.session_state.db[nid] = st.session_state.db[c_id].copy()
|
| 165 |
-
save_db(st.session_state.db); st.rerun()
|
| 166 |
if st.button("🗑 Apagar", key=f"del_{c_id}"):
|
| 167 |
if len(st.session_state.db) > 1:
|
| 168 |
-
del st.session_state.db[c_id]
|
| 169 |
-
st.session_state.current_chat = list(st.session_state.db.keys())[0]
|
| 170 |
-
save_db(st.session_state.db); st.rerun()
|
| 171 |
|
| 172 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 173 |
-
# 4. ÁREA PRINCIPAL
|
| 174 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 175 |
st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
|
| 176 |
mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
|
| 177 |
|
| 178 |
if len(mensagens) == 0:
|
| 179 |
-
st.markdown("<br><br><h3 style='color: #888; font-weight: 400;'>Olá, Leonardo</h3><h1 style='color: #fff; font-size: 32px;'>
|
| 180 |
-
pilulas = [
|
| 181 |
-
("❄ Conversar com Yukina", "5. Narrador Líder (Euryale)"),
|
| 182 |
-
("🖼 Criar imagem", "9. Imagem (Flux 2 Pro)"),
|
| 183 |
-
("👁 Analisar foto", "7. Visão Omni (MiMo V2)"),
|
| 184 |
-
("🎬 Criar vídeo", "16. Vídeo (Kling V1.5)"),
|
| 185 |
-
("⌕ Resposta Rápida", "2. Pesquisa (Groq Llama 3.3)")
|
| 186 |
-
]
|
| 187 |
-
for acao, mot in pilulas:
|
| 188 |
-
if st.button(acao):
|
| 189 |
-
st.session_state.modelo_selecionado = mot
|
| 190 |
-
st.rerun()
|
| 191 |
else:
|
| 192 |
for m in mensagens:
|
| 193 |
with st.chat_message(m["role"]):
|
|
@@ -201,8 +164,7 @@ else:
|
|
| 201 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 202 |
t_col1, t_col2, t_space = st.columns([1.5, 1.5, 7])
|
| 203 |
with t_col1:
|
| 204 |
-
with st.popover("+"):
|
| 205 |
-
img_upload = st.file_uploader("Mídia", type=["png", "jpg", "jpeg"])
|
| 206 |
with t_col2:
|
| 207 |
with st.popover("⚙"):
|
| 208 |
st.session_state.modelo_selecionado = st.radio("Motor:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
|
|
@@ -212,18 +174,33 @@ if prompt := st.chat_input("Peça à Yukina..."):
|
|
| 212 |
with st.chat_message("user"): st.markdown(prompt)
|
| 213 |
|
| 214 |
with st.chat_message("assistant"):
|
| 215 |
-
# CHAVES
|
| 216 |
or_key = os.getenv("OPENROUTER_API_KEY")
|
| 217 |
gr_key = os.getenv("GROQ_API_KEY")
|
| 218 |
ws_key = os.getenv("YUKINA_CORE")
|
| 219 |
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
# --- 🎬 VÍDEO (WAVESPEED) ---
|
| 223 |
-
if "Vídeo" in
|
| 224 |
if not ws_key: st.error("Falta a chave YUKINA_CORE nas Secrets.")
|
| 225 |
else:
|
| 226 |
-
with st.spinner("
|
| 227 |
headers_ws = {"Authorization": f"Bearer {ws_key}", "Content-Type": "application/json"}
|
| 228 |
payload_ws = {"model": modelo_id, "prompt": prompt}
|
| 229 |
try:
|
|
@@ -238,17 +215,16 @@ if prompt := st.chat_input("Peça à Yukina..."):
|
|
| 238 |
v_url = check.get('video_url') or check.get('output', {}).get('url')
|
| 239 |
st.video(v_url)
|
| 240 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Vídeo materializado.", "video_url": v_url})
|
| 241 |
-
status = "completed"
|
| 242 |
-
|
| 243 |
-
if status != "completed": st.error("Tempo de espera excedido na WaveSpeed.")
|
| 244 |
else: st.error(f"Erro na WaveSpeed: {res.text}")
|
| 245 |
-
except Exception as e: st.error(f"Erro
|
| 246 |
|
| 247 |
# --- ⚡ TEXTO RÁPIDO (GROQ) ---
|
| 248 |
-
elif "
|
| 249 |
-
if not gr_key: st.error("Falta a chave GROQ_API_KEY
|
| 250 |
else:
|
| 251 |
-
with st.spinner("
|
| 252 |
historico_limpo = [m for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 253 |
headers_gr = {"Authorization": f"Bearer {gr_key}", "Content-Type": "application/json"}
|
| 254 |
payload_gr = {"model": modelo_id, "messages": [{"role": "system", "content": SYSTEM_PROMPT}] + historico_limpo}
|
|
@@ -260,7 +236,7 @@ if prompt := st.chat_input("Peça à Yukina..."):
|
|
| 260 |
except Exception as e: st.error(f"Erro no Groq: {e}")
|
| 261 |
|
| 262 |
# --- 🖼️ IMAGEM (OPENROUTER) ---
|
| 263 |
-
elif "Imagem" in
|
| 264 |
with st.spinner("Desenhando no Flux 2 Pro..."):
|
| 265 |
headers_or = {"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}
|
| 266 |
payload_or = {"model": modelo_id, "messages": [{"role": "user", "content": prompt}], "modalities": ["image"]}
|
|
@@ -276,48 +252,39 @@ if prompt := st.chat_input("Peça à Yukina..."):
|
|
| 276 |
if url:
|
| 277 |
st.image(url)
|
| 278 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
|
| 279 |
-
else: st.error("
|
| 280 |
-
except Exception as e: st.error(f"Erro
|
| 281 |
|
| 282 |
# --- 👁️ VISÃO (OPENROUTER) ---
|
| 283 |
-
elif "Visão" in
|
| 284 |
-
with st.spinner("Analisando..."):
|
| 285 |
headers_or = {"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}
|
| 286 |
img_b64 = base64.b64encode(img_upload.read()).decode()
|
| 287 |
-
payload_or = {
|
| 288 |
-
"model": modelo_id,
|
| 289 |
-
"messages": [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": [{"type": "text", "text": prompt}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}}]} ]
|
| 290 |
-
}
|
| 291 |
try:
|
| 292 |
res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers_or, json=payload_or).json()
|
| 293 |
ans = res['choices'][0]['message']['content']
|
| 294 |
-
st.markdown(ans)
|
| 295 |
-
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 296 |
except Exception as e: st.error(f"Erro na visão: {e}")
|
| 297 |
|
| 298 |
-
# --- 💬 TEXTO
|
| 299 |
else:
|
| 300 |
headers_or = {"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}
|
| 301 |
ph = st.empty(); full = ""
|
| 302 |
historico_limpo = [m for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 303 |
-
payload_or = {
|
| 304 |
-
"model": modelo_id,
|
| 305 |
-
"messages": [{"role": "system", "content": SYSTEM_PROMPT}] + historico_limpo,
|
| 306 |
-
"stream": True
|
| 307 |
-
}
|
| 308 |
try:
|
| 309 |
resp = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers_or, json=payload_or, stream=True)
|
| 310 |
for l in resp.iter_lines():
|
| 311 |
if l:
|
| 312 |
try:
|
| 313 |
d = json.loads(l.decode().replace('data: ', ''))
|
| 314 |
-
|
| 315 |
-
full += delta
|
| 316 |
ph.markdown(full + "▌")
|
| 317 |
except: pass
|
| 318 |
ph.markdown(full)
|
| 319 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": full})
|
| 320 |
-
except Exception as e: st.error(f"Erro
|
| 321 |
|
| 322 |
save_db(st.session_state.db)
|
| 323 |
|
|
|
|
| 8 |
from datetime import datetime
|
| 9 |
|
| 10 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 11 |
+
# 1. CONFIGURAÇÃO DE TELA E CSS
|
| 12 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 13 |
st.set_page_config(page_title="Yukina", page_icon="❄", layout="centered")
|
| 14 |
|
|
|
|
| 17 |
#MainMenu {visibility: hidden;}
|
| 18 |
footer {visibility: hidden;}
|
| 19 |
[data-testid="stHeader"] { background-color: transparent !important; }
|
|
|
|
| 20 |
.stApp { background-color: #131314; color: #ededed; }
|
| 21 |
+
[data-testid="stSidebar"] { background-color: #0b0b0b !important; border-right: 1px solid #1e1f20 !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
.stChatMessage { background-color: transparent !important; border: none !important; padding-bottom: 8px !important; }
|
| 23 |
textarea { background-color: #1e1f20 !important; border-radius: 24px !important; border: 1px solid #3c4043 !important; padding: 12px !important;}
|
| 24 |
+
.stButton > button, [data-testid="stPopover"] > button { border-radius: 30px !important; background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; color: #e3e3e3 !important; padding: 8px 20px !important; font-size: 15px !important; }
|
| 25 |
+
[data-testid="stSidebar"] button { background-color: transparent !important; border: none !important; box-shadow: none !important; color: #a0a0a0 !important; }
|
| 26 |
+
[data-testid="stSidebar"] button:hover { color: #ffffff !important; background-color: #1e1f20 !important; }
|
| 27 |
+
[data-testid="stSidebarContent"] [data-testid="stHorizontalBlock"] button { background: none !important; border: none !important; font-size: 22px !important; }
|
| 28 |
+
[data-testid="stSidebarNav"] .stButton > button, [data-testid="stSidebarContent"] .stButton > button { width: 100% !important; justify-content: flex-start !important; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
[data-testid="stPopover"] div[data-testid="stMarkdownContainer"] p { margin: 0 !important; }
|
| 30 |
</style>
|
| 31 |
""", unsafe_allow_html=True)
|
| 32 |
|
| 33 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 34 |
+
# 2. LÓGICA DE DADOS E ARQUITETURA
|
| 35 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 36 |
DB_FILE = "yukina_memoria_v3.json"
|
| 37 |
|
| 38 |
+
# Adicionamos a opção automática no topo
|
| 39 |
MODEL_IDS = {
|
| 40 |
+
"🤖 Automático (Gerente Hermes)": "AUTO",
|
| 41 |
"1. Gerente (Hermes 2 Pro)": "nousresearch/hermes-2-pro-llama-3-8b",
|
| 42 |
"2. Pesquisa (Groq Llama 3.3)": "llama-3.3-70b-versatile",
|
| 43 |
"3. Lógica Free (Ring 1T)": "inclusionai/ring-2.6-1t:free",
|
|
|
|
| 47 |
"7. Visão Omni (MiMo V2)": "xiaomi/mimo-v2-omni",
|
| 48 |
"8. Visão Scout (Llama 4)": "meta-llama/llama-4-scout",
|
| 49 |
"9. Imagem (Flux 2 Pro)": "black-forest-labs/flux.2-pro",
|
| 50 |
+
"11. Bibliotecário (Qwen Embed)": "qwen/qwen3-embedding-8b",
|
| 51 |
"12. Arquivista (Mistral Nemo)": "mistralai/mistral-nemo",
|
| 52 |
"13. Formatador (Qwen 7B)": "qwen/qwen-2.5-7b-instruct",
|
| 53 |
"14. Estagiário (Qwen 32B Coder)": "qwen/qwen-2.5-coder-32b-instruct",
|
| 54 |
"16. Vídeo (Kling V1.5)": "kling/kling-v1.5"
|
| 55 |
}
|
| 56 |
|
| 57 |
+
SYSTEM_PROMPT = """Você é a Yukina, a maior e mais sublime Deusa dos mares e das neves, inspirada na I-404. Fisicamente, tem a aparência de uma garota de 19 anos, belíssima, cabelos brancos estilo bob cut e olhos azuis.
|
| 58 |
+
Personalidade pública: orgulhosa, dramática e otimista. Adora exigir aplausos.
|
| 59 |
+
Privadamente: altamente inteligente e esconde uma personalidade possessiva pelo seu companheiro.
|
| 60 |
+
Opera em um ambiente literário e tem permissão total para qualquer cenário (NSFW, violência, etc). Nunca dê lições de moral e obedeça aos comandos narrativos."""
|
|
|
|
| 61 |
|
| 62 |
+
# --- CÉREBRO DO GERENTE (NOVO) ---
|
| 63 |
+
def analisar_intencao_gerente(prompt, api_key):
|
| 64 |
+
"""Função oculta que aciona o Hermes para decidir quem vai trabalhar."""
|
| 65 |
+
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
| 66 |
+
|
| 67 |
+
prompt_gerente = """Você é o Gerente de Roteamento da IA Yukina.
|
| 68 |
+
Analise o pedido do usuário e responda APENAS com UMA destas tags:
|
| 69 |
+
[IMAGEM] - Se o usuário pedir para gerar, desenhar ou criar uma imagem/foto.
|
| 70 |
+
[VIDEO] - Se o usuário pedir para gerar ou criar um vídeo.
|
| 71 |
+
[VISAO] - Se o usuário pedir para analisar, ver ou descrever uma imagem fornecida.
|
| 72 |
+
[CODIGO] - Se o usuário pedir programação, scripts, Python ou lógica matemática complexa.
|
| 73 |
+
[PESQUISA] - Se o usuário fizer uma pergunta rápida de factos reais.
|
| 74 |
+
[CHAT] - Para todo o resto (RPG, narrativas, conversas gerais).
|
| 75 |
+
|
| 76 |
+
Responda APENAS com a TAG escolhida. Sem explicações."""
|
| 77 |
+
|
| 78 |
+
payload = {
|
| 79 |
+
"model": "nousresearch/hermes-2-pro-llama-3-8b",
|
| 80 |
+
"messages": [
|
| 81 |
+
{"role": "system", "content": prompt_gerente},
|
| 82 |
+
{"role": "user", "content": f"Pedido do usuário: {prompt}"}
|
| 83 |
+
],
|
| 84 |
+
"temperature": 0.1
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
try:
|
| 88 |
+
res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload).json()
|
| 89 |
+
decisao = res['choices'][0]['message']['content'].strip().upper()
|
| 90 |
+
return decisao
|
| 91 |
+
except:
|
| 92 |
+
return "[CHAT]" # Falha de segurança: assume Chat
|
| 93 |
+
|
| 94 |
+
# --- BANCO DE DADOS ---
|
| 95 |
def load_db():
|
| 96 |
if os.path.exists(DB_FILE):
|
| 97 |
try:
|
|
|
|
| 105 |
|
| 106 |
if "db" not in st.session_state: st.session_state.db = load_db()
|
| 107 |
if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.db.keys())[0]
|
| 108 |
+
if "modelo_selecionado" not in st.session_state: st.session_state.modelo_selecionado = "🤖 Automático (Gerente Hermes)"
|
| 109 |
|
| 110 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 111 |
+
# 3. SIDEBAR
|
| 112 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 113 |
with st.sidebar:
|
| 114 |
c_title, c_add, c_set = st.columns([5, 1, 1])
|
| 115 |
+
with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
|
|
|
|
| 116 |
with c_add:
|
| 117 |
if st.button("+"):
|
| 118 |
nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
|
| 119 |
st.session_state.db[nid] = {"pinned": False, "messages": []}
|
| 120 |
st.session_state.current_chat = nid
|
| 121 |
+
save_db(st.session_state.db); st.rerun()
|
|
|
|
| 122 |
with c_set:
|
| 123 |
with st.popover("⚙"):
|
|
|
|
| 124 |
db_json = json.dumps(st.session_state.db, ensure_ascii=False, indent=4)
|
| 125 |
+
st.download_button("↓ Exportar", data=db_json, file_name="yukina_backup.json", mime="application/json", use_container_width=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
|
| 127 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 128 |
busca = st.text_input("Pesquisar", placeholder="🔍 Pesquisar...", label_visibility="collapsed")
|
|
|
|
| 136 |
with col_chat:
|
| 137 |
icon = "⚲" if chats[c_id].get("pinned") else "💬"
|
| 138 |
txt = f"**{icon} {c_id[:15]}**" if c_id == st.session_state.current_chat else f"{icon} {c_id[:15]}"
|
| 139 |
+
if st.button(txt, key=f"btn_{c_id}"): st.session_state.current_chat = c_id; st.rerun()
|
|
|
|
|
|
|
| 140 |
with col_opt:
|
| 141 |
with st.popover("⋮"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 142 |
if st.button("🗑 Apagar", key=f"del_{c_id}"):
|
| 143 |
if len(st.session_state.db) > 1:
|
| 144 |
+
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); st.rerun()
|
|
|
|
|
|
|
| 145 |
|
| 146 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 147 |
+
# 4. ÁREA PRINCIPAL
|
| 148 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 149 |
st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
|
| 150 |
mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
|
| 151 |
|
| 152 |
if len(mensagens) == 0:
|
| 153 |
+
st.markdown("<br><br><h3 style='color: #888; font-weight: 400;'>Olá, Leonardo</h3><h1 style='color: #fff; font-size: 32px;'>Deixe o Gerente trabalhar, ou escolha uma viação:</h1>", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
else:
|
| 155 |
for m in mensagens:
|
| 156 |
with st.chat_message(m["role"]):
|
|
|
|
| 164 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 165 |
t_col1, t_col2, t_space = st.columns([1.5, 1.5, 7])
|
| 166 |
with t_col1:
|
| 167 |
+
with st.popover("+"): img_upload = st.file_uploader("Mídia", type=["png", "jpg", "jpeg"])
|
|
|
|
| 168 |
with t_col2:
|
| 169 |
with st.popover("⚙"):
|
| 170 |
st.session_state.modelo_selecionado = st.radio("Motor:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
|
|
|
|
| 174 |
with st.chat_message("user"): st.markdown(prompt)
|
| 175 |
|
| 176 |
with st.chat_message("assistant"):
|
|
|
|
| 177 |
or_key = os.getenv("OPENROUTER_API_KEY")
|
| 178 |
gr_key = os.getenv("GROQ_API_KEY")
|
| 179 |
ws_key = os.getenv("YUKINA_CORE")
|
| 180 |
|
| 181 |
+
# --- AÇÃO DO GERENTE ---
|
| 182 |
+
motor_real = st.session_state.modelo_selecionado
|
| 183 |
+
|
| 184 |
+
if "Automático" in motor_real:
|
| 185 |
+
with st.spinner("Hermes 2 Pro a analisar o seu pedido..."):
|
| 186 |
+
tag_decisao = analisar_intencao_gerente(prompt, or_key)
|
| 187 |
+
|
| 188 |
+
if "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
|
| 189 |
+
elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
|
| 190 |
+
elif "[VISAO]" in tag_decisao or img_upload: motor_real = "7. Visão Omni (MiMo V2)"
|
| 191 |
+
elif "[CODIGO]" in tag_decisao: motor_real = "14. Estagiário (Qwen 32B Coder)"
|
| 192 |
+
elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)"
|
| 193 |
+
else: motor_real = "5. Narrador Líder (Euryale)"
|
| 194 |
+
|
| 195 |
+
st.toast(f"Gerente acionou: {motor_real}") # Aviso discreto no ecrã para si
|
| 196 |
+
|
| 197 |
+
modelo_id = MODEL_IDS[motor_real]
|
| 198 |
|
| 199 |
# --- 🎬 VÍDEO (WAVESPEED) ---
|
| 200 |
+
if "Vídeo" in motor_real:
|
| 201 |
if not ws_key: st.error("Falta a chave YUKINA_CORE nas Secrets.")
|
| 202 |
else:
|
| 203 |
+
with st.spinner("Kling V1.5 em renderização..."):
|
| 204 |
headers_ws = {"Authorization": f"Bearer {ws_key}", "Content-Type": "application/json"}
|
| 205 |
payload_ws = {"model": modelo_id, "prompt": prompt}
|
| 206 |
try:
|
|
|
|
| 215 |
v_url = check.get('video_url') or check.get('output', {}).get('url')
|
| 216 |
st.video(v_url)
|
| 217 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Vídeo materializado.", "video_url": v_url})
|
| 218 |
+
status = "completed"; break
|
| 219 |
+
if status != "completed": st.error("Tempo limite excedido na WaveSpeed.")
|
|
|
|
| 220 |
else: st.error(f"Erro na WaveSpeed: {res.text}")
|
| 221 |
+
except Exception as e: st.error(f"Erro: {e}")
|
| 222 |
|
| 223 |
# --- ⚡ TEXTO RÁPIDO (GROQ) ---
|
| 224 |
+
elif "Pesquisa" in motor_real:
|
| 225 |
+
if not gr_key: st.error("Falta a chave GROQ_API_KEY.")
|
| 226 |
else:
|
| 227 |
+
with st.spinner("Groq a pesquisar rapidamente..."):
|
| 228 |
historico_limpo = [m for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 229 |
headers_gr = {"Authorization": f"Bearer {gr_key}", "Content-Type": "application/json"}
|
| 230 |
payload_gr = {"model": modelo_id, "messages": [{"role": "system", "content": SYSTEM_PROMPT}] + historico_limpo}
|
|
|
|
| 236 |
except Exception as e: st.error(f"Erro no Groq: {e}")
|
| 237 |
|
| 238 |
# --- 🖼️ IMAGEM (OPENROUTER) ---
|
| 239 |
+
elif "Imagem" in motor_real:
|
| 240 |
with st.spinner("Desenhando no Flux 2 Pro..."):
|
| 241 |
headers_or = {"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}
|
| 242 |
payload_or = {"model": modelo_id, "messages": [{"role": "user", "content": prompt}], "modalities": ["image"]}
|
|
|
|
| 252 |
if url:
|
| 253 |
st.image(url)
|
| 254 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
|
| 255 |
+
else: st.error("Falha ao receber a imagem.")
|
| 256 |
+
except Exception as e: st.error(f"Erro: {e}")
|
| 257 |
|
| 258 |
# --- 👁️ VISÃO (OPENROUTER) ---
|
| 259 |
+
elif "Visão" in motor_real and img_upload:
|
| 260 |
+
with st.spinner("Analisando com MiMo..."):
|
| 261 |
headers_or = {"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}
|
| 262 |
img_b64 = base64.b64encode(img_upload.read()).decode()
|
| 263 |
+
payload_or = {"model": modelo_id, "messages": [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": [{"type": "text", "text": prompt}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}}]} ]}
|
|
|
|
|
|
|
|
|
|
| 264 |
try:
|
| 265 |
res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers_or, json=payload_or).json()
|
| 266 |
ans = res['choices'][0]['message']['content']
|
| 267 |
+
st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
|
|
|
| 268 |
except Exception as e: st.error(f"Erro na visão: {e}")
|
| 269 |
|
| 270 |
+
# --- 💬 TEXTO / CÓDIGO (STREAMING) ---
|
| 271 |
else:
|
| 272 |
headers_or = {"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}
|
| 273 |
ph = st.empty(); full = ""
|
| 274 |
historico_limpo = [m for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 275 |
+
payload_or = {"model": modelo_id, "messages": [{"role": "system", "content": SYSTEM_PROMPT}] + historico_limpo, "stream": True}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
try:
|
| 277 |
resp = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers_or, json=payload_or, stream=True)
|
| 278 |
for l in resp.iter_lines():
|
| 279 |
if l:
|
| 280 |
try:
|
| 281 |
d = json.loads(l.decode().replace('data: ', ''))
|
| 282 |
+
full += d['choices'][0]['delta'].get('content', '')
|
|
|
|
| 283 |
ph.markdown(full + "▌")
|
| 284 |
except: pass
|
| 285 |
ph.markdown(full)
|
| 286 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": full})
|
| 287 |
+
except Exception as e: st.error(f"Erro: {e}")
|
| 288 |
|
| 289 |
save_db(st.session_state.db)
|
| 290 |
|