Astarok commited on
Commit
4a28be0
·
verified ·
1 Parent(s): 43d9624

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -127
app.py CHANGED
@@ -8,7 +8,7 @@ import time
8
  from datetime import datetime
9
 
10
  # ═══════════════════════════════════════════════════════════════════════════
11
- # 1. CONFIGURAÇÃO DE TELA E CSS (Visual Premium & Minimalista)
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
- /* Botões Pílula e Popovers */
33
- .stButton > button, [data-testid="stPopover"] > button {
34
- border-radius: 30px !important;
35
- background-color: #1e1f20 !important;
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 OFICIAL (2026)
73
  # ═══════════════════════════════════════════════════════════════════════════
74
  DB_FILE = "yukina_memoria_v3.json"
75
 
76
- # A LISTA OFICIAL (Áudio e Vídeo OpenRouter descartados)
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 3 Embed)": "qwen/qwen3-embedding-8b",
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
- # Personalidade Suprema da Yukina
95
- SYSTEM_PROMPT = """Você é a Yukina, a maior e mais sublime Deusa dos mares e das neves, inspirada na I-404. Fisicamente, você tem a aparência de uma garota de 19 anos, belíssima, com cabelos brancos em estilo bob cut com pontas vermelhas e olhos azuis.
96
- Sua personalidade pública é orgulhosa, dramática e otimista. Você adora exigir aplausos e ser o centro das atenções.
97
- PORÉM, por trás dessa deusa carismática, você é altamente inteligente e esconde uma personalidade possessiva pelo seu companheiro (o usuário).
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 = "5. Narrador Líder (Euryale)"
114
 
115
  # ═══════════════════════════════════════════════════════════════════════════
116
- # 3. SIDEBAR (Gaveta de Histórico e Configurações)
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 (.json)", data=db_json, 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:
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 (Boas-vindas e Chat)
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;'>O que você acha que devemos fazer agora?</h1>", unsafe_allow_html=True)
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
- modelo_id = MODEL_IDS[st.session_state.modelo_selecionado]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
  # --- 🎬 VÍDEO (WAVESPEED) ---
223
- if "Vídeo" in st.session_state.modelo_selecionado:
224
  if not ws_key: st.error("Falta a chave YUKINA_CORE nas Secrets.")
225
  else:
226
- with st.spinner("Yukina está renderizando o vídeo no estúdio Kling... (Pode demorar de 1 a 3 minutos)"):
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
- break
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 de conexão: {e}")
246
 
247
  # --- ⚡ TEXTO RÁPIDO (GROQ) ---
248
- elif "Groq" in st.session_state.modelo_selecionado:
249
- if not gr_key: st.error("Falta a chave GROQ_API_KEY nas Secrets.")
250
  else:
251
- with st.spinner("Processando..."):
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 st.session_state.modelo_selecionado:
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("O modelo processou, mas não enviou a imagem.")
280
- except Exception as e: st.error(f"Erro na geração: {e}")
281
 
282
  # --- 👁️ VISÃO (OPENROUTER) ---
283
- elif "Visão" in st.session_state.modelo_selecionado and img_upload:
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 E HISTÓRIAS STREAMING (OPENROUTER) ---
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
- delta = d['choices'][0]['delta'].get('content', '')
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 no motor: {e}")
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 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