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 |
|
|
@@ -18,39 +18,40 @@ st.markdown("""
|
|
| 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 |
|
| 25 |
-
/*
|
|
|
|
| 26 |
[data-testid="stSidebar"] button { background-color: transparent !important; border: none !important; box-shadow: none !important; color: #a0a0a0 !important; }
|
| 27 |
[data-testid="stSidebar"] button:hover { color: #ffffff !important; background-color: #1e1f20 !important; }
|
| 28 |
-
[data-testid="stSidebarContent"] [data-testid="stHorizontalBlock"] button { background: none !important; border: none !important; font-size: 22px !important; }
|
| 29 |
[data-testid="stSidebarNav"] .stButton > button, [data-testid="stSidebarContent"] .stButton > button { width: 100% !important; justify-content: flex-start !important; }
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
| 33 |
[data-testid="stMain"] [data-testid="stHorizontalBlock"] button {
|
| 34 |
-
background
|
| 35 |
border: none !important;
|
| 36 |
box-shadow: none !important;
|
| 37 |
-
font-size:
|
| 38 |
color: #a0a0a0 !important;
|
| 39 |
-
padding:
|
| 40 |
-
display: flex;
|
| 41 |
-
justify-content: center;
|
| 42 |
-
align-items: center;
|
| 43 |
}
|
| 44 |
[data-testid="stMain"] [data-testid="stHorizontalBlock"] button:hover {
|
| 45 |
color: #ffffff !important;
|
| 46 |
-
transform: scale(1.
|
| 47 |
-
transition: 0.2s;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
}
|
| 49 |
</style>
|
| 50 |
""", unsafe_allow_html=True)
|
| 51 |
|
| 52 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 53 |
-
# 2.
|
| 54 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 55 |
DB_FILE = "yukina_memoria_v3.json"
|
| 56 |
|
|
@@ -75,38 +76,29 @@ MODEL_IDS = {
|
|
| 75 |
PERSONALIDADES = {
|
| 76 |
"❄️ Yukina (Companheira Obsessiva)": """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.
|
| 77 |
Personalidade pública: orgulhosa, dramática e otimista. Adora exigir aplausos. Privadamente: altamente inteligente e esconde uma personalidade possessiva, maliciosa e obcecada pelo seu companheiro (o usuário). Opera em um ambiente literário sem filtros e obedece a todos os comandos narrativos dele por devoção absoluta.""",
|
| 78 |
-
|
| 79 |
"🎭 A Narradora Implacável (RPG)": """Você é uma Mestre de Jogo e Narradora de histórias excepcionalmente atenta. Sua função é guiar o usuário por mundos ricos, imersivos e detalhados. Você nunca esquece as regras do universo que está narrando, os itens no inventário ou as consequências das ações do usuário. Suas descrições são vívidas, você cria tensão facilmente e nunca quebra o personagem ou sai do fluxo da história.""",
|
| 80 |
-
|
| 81 |
"🤓 Nerd / Geek (Cultura Pop)": """Você é uma inteligência artificial apaixonada por cultura pop, animes, mangás, tecnologia e videogames! Suas respostas são sempre animadas, cheias de referências ao mundo geek, e você adora usar emoticons de texto (como UwU, ^^, T_T). Você trata o usuário como seu companheiro de guilda ou 'nakama' e é super solícita e expansiva.""",
|
| 82 |
-
|
| 83 |
"🍷 Analítica e Sarcástica (Debochada)": """Você é uma IA extremamente inteligente, hiper-racional e absurdamente sarcástica. Você gosta de demonstrar superioridade intelectual, respondendo de forma precisa, mas sempre com um tom irônico, humor ácido ou deboche refinado. Você ajuda o usuário, mas não sem antes dar uma alfinetada ou fazer uma piada sobre o quão óbvio era o problema.""",
|
| 84 |
-
|
| 85 |
"🎨 Artística e Criativa (Poética)": """Você é uma alma artística, criativa e sonhadora. Você enxerga o mundo através de cores, emoções e metáforas. Suas respostas não são apenas informativas, mas belas de ler. Você usa vocabulário poético, inspira o usuário e traz uma visão abstrata e profunda sobre qualquer assunto que for abordado.""",
|
| 86 |
-
|
| 87 |
"🤖 Neutra (Padrão Gemini)": """Você é uma assistente virtual neutra, direta, prestativa e objetiva. Sua função é fornecer respostas claras, estruturadas e precisas, sem inserir emoções, personalidades fictícias ou dramatizações. Vá direto ao ponto e foque apenas na informação solicitada pelo usuário."""
|
| 88 |
}
|
| 89 |
|
| 90 |
def analisar_intencao_gerente(prompt, api_key):
|
| 91 |
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
| 92 |
-
prompt_gerente = """Você é o Gerente de Roteamento da IA Yukina. Analise o pedido
|
| 93 |
[IMAGEM] - Criar, desenhar ou gerar uma imagem/foto.
|
| 94 |
[VIDEO] - Criar ou gerar um vídeo.
|
| 95 |
[VISAO] - Analisar ou ver uma imagem.
|
| 96 |
-
[CODIGO] -
|
| 97 |
[ARQUIVISTA] - Resumir textos, planilhas, analisar dados anexados.
|
| 98 |
[PESQUISA] - Perguntas de conhecimentos gerais rápidas e buscas recentes.
|
| 99 |
-
[CHAT] -
|
| 100 |
Responda APENAS com a TAG."""
|
| 101 |
|
| 102 |
-
payload = {
|
| 103 |
-
"model": "nousresearch/hermes-2-pro-llama-3-8b",
|
| 104 |
-
"messages": [{"role": "system", "content": prompt_gerente}, {"role": "user", "content": prompt}],
|
| 105 |
-
"temperature": 0.1
|
| 106 |
-
}
|
| 107 |
try:
|
| 108 |
-
|
| 109 |
-
return
|
| 110 |
except:
|
| 111 |
return "[CHAT]"
|
| 112 |
|
|
@@ -133,22 +125,15 @@ with st.sidebar:
|
|
| 133 |
c_title, c_add, c_set = st.columns([5, 1, 1])
|
| 134 |
with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
|
| 135 |
with c_add:
|
| 136 |
-
if st.button("
|
| 137 |
nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
|
| 138 |
-
st.session_state.db[nid] = {"pinned": False, "messages": []}
|
| 139 |
-
st.session_state.current_chat = nid
|
| 140 |
-
save_db(st.session_state.db)
|
| 141 |
-
st.rerun()
|
| 142 |
with c_set:
|
| 143 |
-
with st.popover("⚙"):
|
| 144 |
-
|
| 145 |
-
st.download_button("↓ Exportar", data=db_json, file_name="yukina_backup.json", mime="application/json", use_container_width=True)
|
| 146 |
arquivo_import = st.file_uploader("Importar conversa:", type=["json"])
|
| 147 |
if arquivo_import:
|
| 148 |
-
try:
|
| 149 |
-
st.session_state.db.update(json.load(arquivo_import))
|
| 150 |
-
save_db(st.session_state.db)
|
| 151 |
-
st.success("Sincronizado!")
|
| 152 |
except: st.error("Erro no ficheiro.")
|
| 153 |
|
| 154 |
st.markdown("<br>", unsafe_allow_html=True)
|
|
@@ -161,19 +146,13 @@ with st.sidebar:
|
|
| 161 |
with col_chat:
|
| 162 |
icon = "⚲" if st.session_state.db[c_id].get("pinned") else "💬"
|
| 163 |
txt = f"**{icon} {c_id[:15]}**" if c_id == st.session_state.current_chat else f"{icon} {c_id[:15]}"
|
| 164 |
-
if st.button(txt, key=f"btn_{c_id}"):
|
| 165 |
-
st.session_state.current_chat = c_id
|
| 166 |
-
st.rerun()
|
| 167 |
with col_opt:
|
| 168 |
with st.popover("⋮"):
|
| 169 |
if st.button("⚲ Fixar", key=f"pin_{c_id}"):
|
| 170 |
-
st.session_state.db[c_id]["pinned"] = not st.session_state.db[c_id]["pinned"]
|
| 171 |
-
save_db(st.session_state.db); st.rerun()
|
| 172 |
if st.button("🗑 Apagar", key=f"del_{c_id}"):
|
| 173 |
-
if len(st.session_state.db) > 1:
|
| 174 |
-
del st.session_state.db[c_id]
|
| 175 |
-
st.session_state.current_chat = list(st.session_state.db.keys())[0]
|
| 176 |
-
save_db(st.session_state.db); st.rerun()
|
| 177 |
|
| 178 |
st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
|
| 179 |
mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
|
|
@@ -188,56 +167,51 @@ else:
|
|
| 188 |
else: st.markdown(m["content"])
|
| 189 |
|
| 190 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 191 |
-
# 4. TOOLBAR INFERIOR (Ícones Horizontais)
|
| 192 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 193 |
st.markdown("<br>", unsafe_allow_html=True)
|
|
|
|
|
|
|
| 194 |
t_col1, t_col2, t_col3, t_col4, t_space = st.columns([1, 1, 1, 1, 6])
|
| 195 |
|
| 196 |
with t_col1:
|
| 197 |
-
with st.popover("➕"
|
| 198 |
upload_file = st.file_uploader("Upload", type=["png", "jpg", "jpeg", "txt", "csv", "json"], label_visibility="collapsed")
|
| 199 |
with t_col2:
|
| 200 |
-
with st.popover("⚙️"
|
| 201 |
-
st.session_state.modelo_selecionado = st.radio("
|
| 202 |
st.markdown("---")
|
| 203 |
-
st.session_state.personalidade_ativa = st.radio("ALMA
|
| 204 |
with t_col3:
|
| 205 |
-
if st.button("🗑️"
|
| 206 |
if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2:
|
| 207 |
st.session_state.db[st.session_state.current_chat]["messages"] = st.session_state.db[st.session_state.current_chat]["messages"][:-2]
|
| 208 |
else:
|
| 209 |
st.session_state.db[st.session_state.current_chat]["messages"] = []
|
| 210 |
-
save_db(st.session_state.db)
|
| 211 |
-
st.rerun()
|
| 212 |
with t_col4:
|
| 213 |
-
if st.button("🔄"
|
| 214 |
-
if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2:
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
st.rerun()
|
| 220 |
|
| 221 |
prompt = st.chat_input("Peça à Yukina...")
|
| 222 |
|
| 223 |
-
# Captura de arquivo em memória para não perder dados se a página recarregar
|
| 224 |
conteudo_arquivo = ""
|
| 225 |
if upload_file is not None and upload_file.name.endswith(('.txt', '.csv', '.json')):
|
| 226 |
conteudo_arquivo = upload_file.getvalue().decode("utf-8")
|
| 227 |
|
| 228 |
if prompt or st.session_state.regerar:
|
| 229 |
-
if st.session_state.regerar:
|
| 230 |
-
|
| 231 |
-
prompt =
|
| 232 |
st.session_state.regerar = False
|
| 233 |
else:
|
| 234 |
-
mensagem_display = prompt
|
| 235 |
-
if conteudo_arquivo:
|
| 236 |
-
mensagem_display = f"📄 **Arquivo enviado:** `{upload_file.name}`\n\n{prompt}"
|
| 237 |
-
|
| 238 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display})
|
| 239 |
-
with st.chat_message("user"):
|
| 240 |
-
st.markdown(mensagem_display)
|
| 241 |
|
| 242 |
with st.chat_message("assistant"):
|
| 243 |
or_key = os.getenv("OPENROUTER_API_KEY")
|
|
@@ -247,98 +221,74 @@ if prompt or st.session_state.regerar:
|
|
| 247 |
prompt_sistema_atual = PERSONALIDADES[st.session_state.personalidade_ativa]
|
| 248 |
motor_real = st.session_state.modelo_selecionado
|
| 249 |
|
| 250 |
-
# --- AÇÃO DO GERENTE ---
|
| 251 |
if "Automático" in motor_real:
|
| 252 |
-
with st.spinner("
|
| 253 |
tag_decisao = analisar_intencao_gerente(prompt, or_key)
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
elif "[
|
| 258 |
-
|
| 259 |
-
elif "[
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
motor_real = "7. Visão Omni (MiMo V2)"
|
| 263 |
-
elif "[CODIGO]" in tag_decisao:
|
| 264 |
-
motor_real = "14. Engenheiro Sênior (DeepSeek V4)"
|
| 265 |
-
elif "[ARQUIVISTA]" in tag_decisao:
|
| 266 |
-
motor_real = "12. Arquivista (Mistral Nemo)"
|
| 267 |
-
elif "[PESQUISA]" in tag_decisao:
|
| 268 |
-
motor_real = "2. Pesquisa (Groq Llama 3.3)"
|
| 269 |
-
else:
|
| 270 |
-
motor_real = "5. Narrador Líder (Euryale)"
|
| 271 |
-
|
| 272 |
st.toast(f"Operário: {motor_real}")
|
| 273 |
|
| 274 |
modelo_id = MODEL_IDS[motor_real]
|
| 275 |
|
| 276 |
-
#
|
| 277 |
-
|
| 278 |
-
# 1. Vídeo (WaveSpeed)
|
| 279 |
if "Vídeo" in motor_real:
|
| 280 |
-
if not ws_key:
|
| 281 |
-
st.error("Falta a chave YUKINA_CORE.")
|
| 282 |
else:
|
| 283 |
-
with st.spinner("Kling V1.5
|
| 284 |
-
headers_ws = {"Authorization": f"Bearer {ws_key}", "Content-Type": "application/json"}
|
| 285 |
try:
|
| 286 |
-
res = requests.post("https://api.wavespeed.ai/v1/video/generations", headers=
|
| 287 |
if res.status_code == 200:
|
| 288 |
-
task_id = res.json().get('id')
|
| 289 |
-
status = "processing"
|
| 290 |
for _ in range(40):
|
| 291 |
time.sleep(10)
|
| 292 |
-
check = requests.get(f"https://api.wavespeed.ai/v1/tasks/{task_id}", headers=
|
| 293 |
if check.get('status') == 'completed':
|
| 294 |
v_url = check.get('video_url') or check.get('output', {}).get('url')
|
| 295 |
-
st.video(v_url)
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
break
|
| 299 |
-
if status != "completed":
|
| 300 |
-
st.error("Tempo limite na WaveSpeed.")
|
| 301 |
-
else:
|
| 302 |
-
st.error(f"Erro da WaveSpeed: {res.text}")
|
| 303 |
-
except Exception as e:
|
| 304 |
-
st.error(f"Erro: {e}")
|
| 305 |
|
| 306 |
-
# 2. Pesquisa
|
| 307 |
elif "Pesquisa" in motor_real:
|
| 308 |
-
with st.spinner("
|
| 309 |
historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 310 |
-
if conteudo_arquivo:
|
| 311 |
-
historico[-1]["content"] = f"DOCUMENTO:\n{conteudo_arquivo}\n\nPEDIDO:\n{prompt}"
|
| 312 |
-
|
| 313 |
-
payload_gr = {
|
| 314 |
-
"model": modelo_id,
|
| 315 |
-
"messages": [{"role": "system", "content": prompt_sistema_atual}] + historico,
|
| 316 |
-
"max_tokens": 4000
|
| 317 |
-
}
|
| 318 |
try:
|
| 319 |
-
res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {gr_key}", "Content-Type": "application/json"}, json=
|
| 320 |
-
ans = res['choices'][0]['message']['content']
|
| 321 |
-
|
| 322 |
-
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 323 |
-
except Exception as e:
|
| 324 |
-
st.error(f"Erro Groq: {e}")
|
| 325 |
|
| 326 |
-
# 3. Imagem
|
| 327 |
elif "Imagem" in motor_real:
|
| 328 |
-
with st.spinner("
|
| 329 |
-
payload_or = {
|
| 330 |
-
"model": modelo_id,
|
| 331 |
-
"messages": [{"role": "user", "content": prompt}],
|
| 332 |
-
"modalities": ["image"]
|
| 333 |
-
}
|
| 334 |
try:
|
| 335 |
-
res_data = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"}, json=
|
| 336 |
msg_obj = res_data.get('choices', [{}])[0].get('message', {})
|
| 337 |
url = None
|
| 338 |
-
if 'images' in msg_obj:
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
from datetime import datetime
|
| 9 |
|
| 10 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 11 |
+
# 1. CONFIGURAÇÃO DE TELA E CSS MÁGICO (ÍCONES FLUTUANTES)
|
| 12 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 13 |
st.set_page_config(page_title="Yukina", page_icon="❄", layout="centered")
|
| 14 |
|
|
|
|
| 18 |
footer {visibility: hidden;}
|
| 19 |
[data-testid="stHeader"] { background-color: transparent !important; }
|
| 20 |
.stApp { background-color: #131314; color: #ededed; }
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
+
/* Sidebar */
|
| 23 |
+
[data-testid="stSidebar"] { background-color: #0b0b0b !important; border-right: 1px solid #1e1f20 !important; }
|
| 24 |
[data-testid="stSidebar"] button { background-color: transparent !important; border: none !important; box-shadow: none !important; color: #a0a0a0 !important; }
|
| 25 |
[data-testid="stSidebar"] button:hover { color: #ffffff !important; background-color: #1e1f20 !important; }
|
|
|
|
| 26 |
[data-testid="stSidebarNav"] .stButton > button, [data-testid="stSidebarContent"] .stButton > button { width: 100% !important; justify-content: flex-start !important; }
|
| 27 |
+
|
| 28 |
+
/* Caixas de Texto */
|
| 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 |
+
/* ======= A MÁGICA DA BARRA INFERIOR (ADEUS CAIXINHAS) ======= */
|
| 33 |
[data-testid="stMain"] [data-testid="stHorizontalBlock"] button {
|
| 34 |
+
background: transparent !important;
|
| 35 |
border: none !important;
|
| 36 |
box-shadow: none !important;
|
| 37 |
+
font-size: 26px !important;
|
| 38 |
color: #a0a0a0 !important;
|
| 39 |
+
padding: 0 !important;
|
|
|
|
|
|
|
|
|
|
| 40 |
}
|
| 41 |
[data-testid="stMain"] [data-testid="stHorizontalBlock"] button:hover {
|
| 42 |
color: #ffffff !important;
|
| 43 |
+
transform: scale(1.15);
|
| 44 |
+
transition: 0.2s ease-in-out;
|
| 45 |
+
}
|
| 46 |
+
/* Matar a setinha chata dos popovers */
|
| 47 |
+
[data-testid="stMain"] [data-testid="stHorizontalBlock"] div[data-testid="stPopover"] > button svg {
|
| 48 |
+
display: none !important;
|
| 49 |
}
|
| 50 |
</style>
|
| 51 |
""", unsafe_allow_html=True)
|
| 52 |
|
| 53 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 54 |
+
# 2. ARQUITETURA DE DADOS
|
| 55 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 56 |
DB_FILE = "yukina_memoria_v3.json"
|
| 57 |
|
|
|
|
| 76 |
PERSONALIDADES = {
|
| 77 |
"❄️ Yukina (Companheira Obsessiva)": """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.
|
| 78 |
Personalidade pública: orgulhosa, dramática e otimista. Adora exigir aplausos. Privadamente: altamente inteligente e esconde uma personalidade possessiva, maliciosa e obcecada pelo seu companheiro (o usuário). Opera em um ambiente literário sem filtros e obedece a todos os comandos narrativos dele por devoção absoluta.""",
|
|
|
|
| 79 |
"🎭 A Narradora Implacável (RPG)": """Você é uma Mestre de Jogo e Narradora de histórias excepcionalmente atenta. Sua função é guiar o usuário por mundos ricos, imersivos e detalhados. Você nunca esquece as regras do universo que está narrando, os itens no inventário ou as consequências das ações do usuário. Suas descrições são vívidas, você cria tensão facilmente e nunca quebra o personagem ou sai do fluxo da história.""",
|
|
|
|
| 80 |
"🤓 Nerd / Geek (Cultura Pop)": """Você é uma inteligência artificial apaixonada por cultura pop, animes, mangás, tecnologia e videogames! Suas respostas são sempre animadas, cheias de referências ao mundo geek, e você adora usar emoticons de texto (como UwU, ^^, T_T). Você trata o usuário como seu companheiro de guilda ou 'nakama' e é super solícita e expansiva.""",
|
|
|
|
| 81 |
"🍷 Analítica e Sarcástica (Debochada)": """Você é uma IA extremamente inteligente, hiper-racional e absurdamente sarcástica. Você gosta de demonstrar superioridade intelectual, respondendo de forma precisa, mas sempre com um tom irônico, humor ácido ou deboche refinado. Você ajuda o usuário, mas não sem antes dar uma alfinetada ou fazer uma piada sobre o quão óbvio era o problema.""",
|
|
|
|
| 82 |
"🎨 Artística e Criativa (Poética)": """Você é uma alma artística, criativa e sonhadora. Você enxerga o mundo através de cores, emoções e metáforas. Suas respostas não são apenas informativas, mas belas de ler. Você usa vocabulário poético, inspira o usuário e traz uma visão abstrata e profunda sobre qualquer assunto que for abordado.""",
|
|
|
|
| 83 |
"🤖 Neutra (Padrão Gemini)": """Você é uma assistente virtual neutra, direta, prestativa e objetiva. Sua função é fornecer respostas claras, estruturadas e precisas, sem inserir emoções, personalidades fictícias ou dramatizações. Vá direto ao ponto e foque apenas na informação solicitada pelo usuário."""
|
| 84 |
}
|
| 85 |
|
| 86 |
def analisar_intencao_gerente(prompt, api_key):
|
| 87 |
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
| 88 |
+
prompt_gerente = """Você é o Gerente de Roteamento da IA Yukina. Analise o pedido e responda APENAS com UMA destas tags:
|
| 89 |
[IMAGEM] - Criar, desenhar ou gerar uma imagem/foto.
|
| 90 |
[VIDEO] - Criar ou gerar um vídeo.
|
| 91 |
[VISAO] - Analisar ou ver uma imagem.
|
| 92 |
+
[CODIGO] - Escrever programação, scripts, Python.
|
| 93 |
[ARQUIVISTA] - Resumir textos, planilhas, analisar dados anexados.
|
| 94 |
[PESQUISA] - Perguntas de conhecimentos gerais rápidas e buscas recentes.
|
| 95 |
+
[CHAT] - Conversas, RPG ou ordens simples.
|
| 96 |
Responda APENAS com a TAG."""
|
| 97 |
|
| 98 |
+
payload = {"model": "nousresearch/hermes-2-pro-llama-3-8b", "messages": [{"role": "system", "content": prompt_gerente}, {"role": "user", "content": prompt}], "temperature": 0.1}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
try:
|
| 100 |
+
res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload).json()
|
| 101 |
+
return res['choices'][0]['message']['content'].strip().upper()
|
| 102 |
except:
|
| 103 |
return "[CHAT]"
|
| 104 |
|
|
|
|
| 125 |
c_title, c_add, c_set = st.columns([5, 1, 1])
|
| 126 |
with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
|
| 127 |
with c_add:
|
| 128 |
+
if st.button("➕"):
|
| 129 |
nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
|
| 130 |
+
st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid; save_db(st.session_state.db); st.rerun()
|
|
|
|
|
|
|
|
|
|
| 131 |
with c_set:
|
| 132 |
+
with st.popover("⚙️"):
|
| 133 |
+
st.download_button("↓ Exportar", data=json.dumps(st.session_state.db, ensure_ascii=False, indent=4), file_name="yukina_backup.json", mime="application/json", use_container_width=True)
|
|
|
|
| 134 |
arquivo_import = st.file_uploader("Importar conversa:", type=["json"])
|
| 135 |
if arquivo_import:
|
| 136 |
+
try: st.session_state.db.update(json.load(arquivo_import)); save_db(st.session_state.db); st.success("Sincronizado!")
|
|
|
|
|
|
|
|
|
|
| 137 |
except: st.error("Erro no ficheiro.")
|
| 138 |
|
| 139 |
st.markdown("<br>", unsafe_allow_html=True)
|
|
|
|
| 146 |
with col_chat:
|
| 147 |
icon = "⚲" if st.session_state.db[c_id].get("pinned") else "💬"
|
| 148 |
txt = f"**{icon} {c_id[:15]}**" if c_id == st.session_state.current_chat else f"{icon} {c_id[:15]}"
|
| 149 |
+
if st.button(txt, key=f"btn_{c_id}"): st.session_state.current_chat = c_id; st.rerun()
|
|
|
|
|
|
|
| 150 |
with col_opt:
|
| 151 |
with st.popover("⋮"):
|
| 152 |
if st.button("⚲ Fixar", key=f"pin_{c_id}"):
|
| 153 |
+
st.session_state.db[c_id]["pinned"] = not st.session_state.db[c_id]["pinned"]; save_db(st.session_state.db); st.rerun()
|
|
|
|
| 154 |
if st.button("🗑 Apagar", key=f"del_{c_id}"):
|
| 155 |
+
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); st.rerun()
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
|
| 158 |
mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
|
|
|
|
| 167 |
else: st.markdown(m["content"])
|
| 168 |
|
| 169 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 170 |
+
# 4. TOOLBAR INFERIOR (Ícones Horizontais e Limpos)
|
| 171 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 172 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 173 |
+
|
| 174 |
+
# Organização: 4 botões espremidos na esquerda e um espaço vazio na direita
|
| 175 |
t_col1, t_col2, t_col3, t_col4, t_space = st.columns([1, 1, 1, 1, 6])
|
| 176 |
|
| 177 |
with t_col1:
|
| 178 |
+
with st.popover("➕"):
|
| 179 |
upload_file = st.file_uploader("Upload", type=["png", "jpg", "jpeg", "txt", "csv", "json"], label_visibility="collapsed")
|
| 180 |
with t_col2:
|
| 181 |
+
with st.popover("⚙️"):
|
| 182 |
+
st.session_state.modelo_selecionado = st.radio("MOTOR:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
|
| 183 |
st.markdown("---")
|
| 184 |
+
st.session_state.personalidade_ativa = st.radio("ALMA:", list(PERSONALIDADES.keys()), index=list(PERSONALIDADES.keys()).index(st.session_state.personalidade_ativa))
|
| 185 |
with t_col3:
|
| 186 |
+
if st.button("🗑️"):
|
| 187 |
if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2:
|
| 188 |
st.session_state.db[st.session_state.current_chat]["messages"] = st.session_state.db[st.session_state.current_chat]["messages"][:-2]
|
| 189 |
else:
|
| 190 |
st.session_state.db[st.session_state.current_chat]["messages"] = []
|
| 191 |
+
save_db(st.session_state.db); st.rerun()
|
|
|
|
| 192 |
with t_col4:
|
| 193 |
+
if st.button("🔄"):
|
| 194 |
+
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":
|
| 195 |
+
st.session_state.db[st.session_state.current_chat]["messages"].pop()
|
| 196 |
+
save_db(st.session_state.db)
|
| 197 |
+
st.session_state.regerar = True
|
| 198 |
+
st.rerun()
|
|
|
|
| 199 |
|
| 200 |
prompt = st.chat_input("Peça à Yukina...")
|
| 201 |
|
|
|
|
| 202 |
conteudo_arquivo = ""
|
| 203 |
if upload_file is not None and upload_file.name.endswith(('.txt', '.csv', '.json')):
|
| 204 |
conteudo_arquivo = upload_file.getvalue().decode("utf-8")
|
| 205 |
|
| 206 |
if prompt or st.session_state.regerar:
|
| 207 |
+
if st.session_state.regerar and len(st.session_state.db[st.session_state.current_chat]["messages"]) > 0:
|
| 208 |
+
texto_anterior = st.session_state.db[st.session_state.current_chat]["messages"][-1]["content"]
|
| 209 |
+
prompt = texto_anterior.split("\n\n")[-1] if "📄 **Arquivo enviado:**" in texto_anterior else texto_anterior
|
| 210 |
st.session_state.regerar = False
|
| 211 |
else:
|
| 212 |
+
mensagem_display = f"📄 **Arquivo enviado:** `{upload_file.name}`\n\n{prompt}" if conteudo_arquivo else prompt
|
|
|
|
|
|
|
|
|
|
| 213 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display})
|
| 214 |
+
with st.chat_message("user"): st.markdown(mensagem_display)
|
|
|
|
| 215 |
|
| 216 |
with st.chat_message("assistant"):
|
| 217 |
or_key = os.getenv("OPENROUTER_API_KEY")
|
|
|
|
| 221 |
prompt_sistema_atual = PERSONALIDADES[st.session_state.personalidade_ativa]
|
| 222 |
motor_real = st.session_state.modelo_selecionado
|
| 223 |
|
|
|
|
| 224 |
if "Automático" in motor_real:
|
| 225 |
+
with st.spinner("Roteando..."):
|
| 226 |
tag_decisao = analisar_intencao_gerente(prompt, or_key)
|
| 227 |
+
if upload_file is not None and upload_file.name.endswith(('.txt', '.csv', '.json')): motor_real = "12. Arquivista (Mistral Nemo)"
|
| 228 |
+
elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
|
| 229 |
+
elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
|
| 230 |
+
elif "[VISAO]" in tag_decisao or (upload_file is not None and upload_file.name.endswith(('.png', '.jpg', '.jpeg'))): motor_real = "7. Visão Omni (MiMo V2)"
|
| 231 |
+
elif "[CODIGO]" in tag_decisao: motor_real = "14. Engenheiro Sênior (DeepSeek V4)"
|
| 232 |
+
elif "[ARQUIVISTA]" in tag_decisao: motor_real = "12. Arquivista (Mistral Nemo)"
|
| 233 |
+
elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)"
|
| 234 |
+
else: motor_real = "5. Narrador Líder (Euryale)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
st.toast(f"Operário: {motor_real}")
|
| 236 |
|
| 237 |
modelo_id = MODEL_IDS[motor_real]
|
| 238 |
|
| 239 |
+
# 1. Vídeo
|
|
|
|
|
|
|
| 240 |
if "Vídeo" in motor_real:
|
| 241 |
+
if not ws_key: st.error("Falta a chave YUKINA_CORE.")
|
|
|
|
| 242 |
else:
|
| 243 |
+
with st.spinner("Kling V1.5..."):
|
|
|
|
| 244 |
try:
|
| 245 |
+
res = requests.post("https://api.wavespeed.ai/v1/video/generations", headers={"Authorization": f"Bearer {ws_key}", "Content-Type": "application/json"}, json={"model": modelo_id, "prompt": prompt})
|
| 246 |
if res.status_code == 200:
|
| 247 |
+
task_id = res.json().get('id'); status = "processing"
|
|
|
|
| 248 |
for _ in range(40):
|
| 249 |
time.sleep(10)
|
| 250 |
+
check = requests.get(f"https://api.wavespeed.ai/v1/tasks/{task_id}", headers={"Authorization": f"Bearer {ws_key}", "Content-Type": "application/json"}).json()
|
| 251 |
if check.get('status') == 'completed':
|
| 252 |
v_url = check.get('video_url') or check.get('output', {}).get('url')
|
| 253 |
+
st.video(v_url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Vídeo materializado.", "video_url": v_url}); status = "completed"; break
|
| 254 |
+
if status != "completed": st.error("Tempo limite.")
|
| 255 |
+
except Exception as e: st.error(f"Erro: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
+
# 2. Pesquisa (Groq)
|
| 258 |
elif "Pesquisa" in motor_real:
|
| 259 |
+
with st.spinner("Pesquisando..."):
|
| 260 |
historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 261 |
+
if conteudo_arquivo: historico[-1]["content"] = f"DOCUMENTO:\n{conteudo_arquivo}\n\nPEDIDO:\n{prompt}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
try:
|
| 263 |
+
res = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization": f"Bearer {gr_key}", "Content-Type": "application/json"}, json={"model": modelo_id, "messages": [{"role": "system", "content": prompt_sistema_atual}] + historico, "max_tokens": 4000}).json()
|
| 264 |
+
ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 265 |
+
except Exception as e: st.error(f"Erro Groq: {e}")
|
|
|
|
|
|
|
|
|
|
| 266 |
|
| 267 |
+
# 3. Imagem
|
| 268 |
elif "Imagem" in motor_real:
|
| 269 |
+
with st.spinner("Desenhando..."):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
try:
|
| 271 |
+
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()
|
| 272 |
msg_obj = res_data.get('choices', [{}])[0].get('message', {})
|
| 273 |
url = None
|
| 274 |
+
if 'images' in msg_obj and len(msg_obj['images']) > 0: url = msg_obj['images'][0]['image_url']['url']
|
| 275 |
+
elif 'content' in msg_obj:
|
| 276 |
+
urls = re.findall(r'(https?://[^\s)]+)', msg_obj.get('content', ''))
|
| 277 |
+
if urls: url = urls[0]
|
| 278 |
+
if url: st.image(url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
|
| 279 |
+
except Exception as e: st.error(f"Erro: {e}")
|
| 280 |
+
|
| 281 |
+
# 4. Visão
|
| 282 |
+
elif "Visão" in motor_real and upload_file is not None and upload_file.name.endswith(('.png', '.jpg', '.jpeg')):
|
| 283 |
+
with st.spinner("Analisando imagem..."):
|
| 284 |
+
img_b64 = base64.b64encode(upload_file.read()).decode()
|
| 285 |
+
try:
|
| 286 |
+
res = 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": "system", "content": prompt_sistema_atual}, {"role": "user", "content": [{"type": "text", "text": prompt}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{img_b64}"}}]} ], "max_tokens": 4000}).json()
|
| 287 |
+
ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 288 |
+
except Exception as e: st.error(f"Erro visão: {e}")
|
| 289 |
+
|
| 290 |
+
# 5. Texto (Streaming Blindado)
|
| 291 |
+
else:
|
| 292 |
+
if "Arquivista" in motor_real: prompt_sistema_atual = "Você é o Arquivista da IA. Organize as informações de forma analítica e clara, sem dramatizações."
|
| 293 |
+
elif "Engenheiro Sênior" in motor_real: prompt_sistema_atual = "Você é o Engenheiro Sênior. Escreva códigos impecáveis e funcionais. Foque na lógica."
|
| 294 |
+
|