Astarok commited on
Commit
30fed2a
·
verified ·
1 Parent(s): a5bafe7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +175 -261
app.py CHANGED
@@ -37,70 +37,35 @@ st.markdown("""
37
  [data-testid="stSidebar"] button:hover { color: #ffffff !important; background-color: #1e1f20 !important; }
38
  [data-testid="stSidebarNav"] .stButton > button, [data-testid="stSidebarContent"] .stButton > button { width: 100% !important; justify-content: flex-start !important; }
39
 
40
- /* --- CAIXAS DE SELEÇÃO --- */
41
- div[data-baseweb="select"] > div {
42
- background-color: #1e1f20 !important;
43
- color: white !important;
44
- border: 1px solid #3c4043 !important;
45
- }
46
- div[role="listbox"] {
47
- background-color: #1e1f20 !important;
48
- color: white !important;
49
- }
50
 
51
  /* --- A MARRETA DEFINITIVA PARA O TECLADO E FUNDO --- */
52
- div[data-testid="stBottom"],
53
- div[data-testid="stBottom"] > div,
54
- div[data-testid="stBottomBlock"],
55
- div[data-testid="stBottomBlock"] > div {
56
- background-color: #131314 !important;
57
- background: #131314 !important;
58
- }
59
- .stChatInputContainer,
60
- div[class*="stChatInputContainer"] {
61
- background-color: #131314 !important;
62
- background: #131314 !important;
63
- padding-bottom: 15px !important;
64
- border: none !important;
65
  }
66
- [data-testid="stChatInput"] {
67
- background-color: #1e1f20 !important;
68
- border: 1px solid #3c4043 !important;
69
- border-radius: 24px !important;
70
- }
71
- [data-testid="stChatInput"] textarea {
72
- color: #ffffff !important;
73
- background-color: transparent !important;
74
  }
 
 
75
 
76
  /* --- BOTÕES GLOBAIS --- */
77
  [data-testid="stMain"] [data-testid="stHorizontalBlock"] button {
78
- background-color: #1e1f20 !important;
79
- border: 1px solid #3c4043 !important;
80
- border-radius: 12px !important;
81
- font-size: 20px !important;
82
- color: #a0a0a0 !important;
83
- padding: 5px !important;
84
  }
85
  [data-testid="stMain"] [data-testid="stHorizontalBlock"] button:hover {
86
- color: #ffffff !important;
87
- background-color: #3c4043 !important;
88
- transform: scale(1.05);
89
- transition: 0.2s ease-in-out;
90
  }
91
 
92
  .stChatMessage { background-color: transparent !important; border: none !important; padding-bottom: 8px !important; }
 
 
93
 
94
- /* --- GAVETA DE FICHEIROS --- */
95
- [data-testid="stExpander"] {
96
- background-color: #1e1f20 !important;
97
- border: 1px solid #3c4043 !important;
98
- border-radius: 15px !important;
99
- margin-bottom: 10px;
100
- }
101
- [data-testid="stExpander"] summary {
102
- color: #e3e3e3 !important;
103
- }
104
  </style>
105
  """, unsafe_allow_html=True)
106
 
@@ -132,7 +97,7 @@ DB_FILE = f"yukina_memoria_{USERNAME}.json"
132
  DATASET_ID = "Astarok/Yukina_Memoria"
133
 
134
  # ═══════════════════════════════════════════════════════════════════════════
135
- # 3. ARQUITETURA DE DADOS, NUVEM E MODELOS
136
  # ═══════════════════════════════════════════════════════════════════════════
137
  MODEL_IDS = {
138
  "🤖 Automático (Gerente Groq)": "AUTO",
@@ -154,33 +119,19 @@ MODEL_IDS = {
154
 
155
  PERSONALIDADES = {
156
  "🤖 Automática (Gerente Groq)": "AUTO",
157
- "❄️ 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.
158
- 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.""",
159
- "🛠️ 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).
160
- Você NÃO age como um chatbot comum. Você PLANEJA e EXECUTA.
161
- Para QUALQUER pedido de criação ou conserto, você deve estruturar sua resposta OBRIGATORIAMENTE nestes 3 passos:
162
- 1. 📋 PLANO DE AÇÃO: Explique a lógica do que está quebrado ou do que será construído passo a passo.
163
- 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).
164
- 3. ⚠️ TESTE E RISCOS: Como o usuário deve testar se funcionou e quais são os pontos de falha (ex: risco de choque elétrico, loop infinito no código).
165
- Mantenha traços sutis da devoção da Yukina ao usuário, mas seja absurdamente técnica, direta e profissional.""",
166
- "🎭 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.""",
167
- "🤓 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.""",
168
- "🍷 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.""",
169
- "🎨 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.""",
170
- "🤖 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."""
171
  }
172
 
173
- # --- FUNÇÕES DE MEMÓRIA DE LONGO PRAZO (PINECONE COM NAMESPACE) ---
174
  def get_embedding(text, or_key):
175
  try:
176
- res = requests.post(
177
- "https://openrouter.ai/api/v1/embeddings",
178
- headers={"Authorization": f"Bearer {or_key}", "Content-Type": "application/json"},
179
- json={"model": "nvidia/llama-nemotron-embed-vl-1b-v2:free", "input": text}
180
- )
181
- if res.status_code != 200:
182
- return None
183
- return res.json()['data'][0]['embedding']
184
  except Exception: return None
185
 
186
  def salvar_pinecone(text, role, or_key, pc_key, namespace):
@@ -190,10 +141,8 @@ def salvar_pinecone(text, role, or_key, pc_key, namespace):
190
  try:
191
  pc = Pinecone(api_key=pc_key)
192
  index = pc.Index("yukina")
193
- id_mem = f"msg_{int(time.time()*1000)}"
194
- metadados = {"texto": f"[{role.upper()}]: {text}", "data": str(datetime.now())}
195
- index.upsert(vectors=[{"id": id_mem, "values": embed, "metadata": metadados}], namespace=namespace)
196
- except Exception as e: st.toast(f"⚠️ Erro ao salvar no Pinecone: {e}")
197
 
198
  def buscar_memoria_pinecone(query, or_key, pc_key, namespace):
199
  if not pc_key: return ""
@@ -201,70 +150,54 @@ def buscar_memoria_pinecone(query, or_key, pc_key, namespace):
201
  if not embed: return ""
202
  try:
203
  pc = Pinecone(api_key=pc_key)
204
- index = pc.Index("yukina")
205
- resultados = index.query(vector=embed, top_k=3, include_metadata=True, namespace=namespace)
206
- memorias = [m['metadata']['texto'] for m in resultados['matches'] if m['score'] > 0.50]
207
- return "\n".join(memorias)
208
- except Exception as e: return ""
209
 
210
- # --- FUNÇÕES GERENCIAIS ---
211
- def analisar_intencao_gerente(prompt, groq_key):
 
212
  try:
213
- client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key)
214
- prompt_gerente = """Você é o Gerente de Roteamento da IA Yukina. Analise o pedido e responda APENAS com UMA destas tags:
215
- [IMAGEM] - Criar, desenhar ou gerar uma imagem/foto.
216
- [VIDEO] - Criar ou gerar um vídeo.
217
- [VISAO] - Analisar ou ver uma imagem.
218
- [CODIGO] - Escrever programação, scripts, Python.
219
- [ARQUIVISTA] - Resumir textos, planilhas, analisar dados anexados.
220
- [PESQUISA] - Perguntas de conhecimentos gerais rápidas e buscas recentes.
221
- [CHAT] - Conversas, RPG ou ordens simples.
222
- Responda APENAS com a TAG."""
223
 
224
- response = client.chat.completions.create(
225
- model="llama-3.3-70b-versatile",
226
- messages=[{"role": "system", "content": prompt_gerente}, {"role": "user", "content": prompt}],
227
- temperature=0.1, max_tokens=10
 
 
 
 
 
228
  )
229
- return response.choices[0].message.content.strip().upper()
230
- except Exception: return "[CHAT]"
231
 
232
- def analisar_alma_gerente(prompt, groq_key):
233
  try:
234
  client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key)
235
- prompt_alma = """Você é o Diretor de Personas. Analise o pedido do usuário e responda APENAS com UMA destas tags:
236
- [YUKINA] - Para conversas íntimas, declarações, ou perguntas sobre você mesma.
237
- [AGENTE] - Para criar códigos complexos, criar softwares, ou consertar objetos físicos (celular, geladeira, hardware).
238
- [RPG] - Para criação de histórias, jogos ou cenários de fantasia.
239
- [NERD] - Para animes, mangás, cultura pop e videogames.
240
- [DEBOCHE] - Para insultos, piadas, ou se o usuário pedir sarcasmo.
241
- [ARTE] - Para pedidos poéticos ou reflexões filosóficas profundas.
242
- [NEUTRA] - Para pesquisas na web, trabalho ou finanças.
243
- Responda APENAS com a TAG."""
244
-
245
  response = client.chat.completions.create(
246
  model="llama-3.3-70b-versatile",
247
- messages=[{"role": "system", "content": prompt_alma}, {"role": "user", "content": prompt}],
248
  temperature=0.1, max_tokens=10
249
  )
250
  return response.choices[0].message.content.strip().upper()
251
- except Exception: return "[NEUTRA]"
252
 
253
  def pesquisar_web(query):
254
  try:
255
  from duckduckgo_search import DDGS
256
  with DDGS() as ddgs:
257
  resultados = list(ddgs.text(query, max_results=3, region='wt-wt'))
258
- if not resultados: return ""
259
- return "\n\n".join([f"🔹 {r.get('title', '')}: {r.get('body', '')}" for r in resultados])
260
  except Exception: return ""
261
 
262
  def load_db(db_filename):
263
  hf_token = os.getenv("HF_TOKEN")
264
  if hf_token and HF_HUB_AVAILABLE:
265
  try:
266
- api = HfApi(token=hf_token)
267
- api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
268
  path = hf_hub_download(repo_id=DATASET_ID, filename=db_filename, repo_type="dataset", token=hf_token)
269
  with open(path, "r", encoding="utf-8") as f: return json.load(f)
270
  except Exception: pass
@@ -279,43 +212,32 @@ def save_db(db_data, db_filename):
279
  with open(db_filename, "w", encoding="utf-8") as f: json.dump(db_data, f, ensure_ascii=False, indent=4)
280
  hf_token = os.getenv("HF_TOKEN")
281
  if hf_token and HF_HUB_AVAILABLE:
282
- api = HfApi(token=hf_token)
283
- api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
284
  api.upload_file(path_or_fileobj=db_filename, path_in_repo=db_filename, repo_id=DATASET_ID, repo_type="dataset")
285
  except Exception: pass
286
 
287
  def rename_chat(old_id, new_id):
288
  if not new_id or new_id == old_id or new_id in st.session_state.db: return False
289
- new_db = {}
290
- for k, v in st.session_state.db.items():
291
- if k == old_id: new_db[new_id] = v
292
- else: new_db[k] = v
293
- st.session_state.db = new_db
294
  if st.session_state.current_chat == old_id: st.session_state.current_chat = new_id
295
- save_db(st.session_state.db, DB_FILE)
296
- return True
297
 
298
  # ═══════════════════════════════════════════════════════════════════════════
299
  # INICIALIZAÇÃO DE VARIÁVEIS E ROTINA DE LIMPEZA
300
  # ═══════════════════════════════════════════════════════════════════════════
301
  if "db" not in st.session_state or "last_user" not in st.session_state or st.session_state.last_user != USERNAME:
302
- st.session_state.db = load_db(DB_FILE)
303
- st.session_state.last_user = USERNAME
304
  if "current_chat" in st.session_state: del st.session_state.current_chat
305
 
306
  if "current_chat" not in st.session_state:
307
- nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
308
- st.session_state.db[nid] = {"pinned": False, "messages": []}
309
- st.session_state.current_chat = nid
310
 
311
  chats_para_remover = [cid for cid, cdata in st.session_state.db.items() if len(cdata.get("messages", [])) == 0 and cid != st.session_state.current_chat]
312
  for cid in chats_para_remover: del st.session_state.db[cid]
313
  if chats_para_remover: save_db(st.session_state.db, DB_FILE)
314
 
315
  if len(st.session_state.db) == 0:
316
- nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
317
- st.session_state.db[nid] = {"pinned": False, "messages": []}
318
- st.session_state.current_chat = nid
319
 
320
  if st.session_state.current_chat not in st.session_state.db: st.session_state.current_chat = list(st.session_state.db.keys())[0]
321
 
@@ -323,6 +245,7 @@ if "modelo_selecionado" not in st.session_state: st.session_state.modelo_selecio
323
  if "personalidade_ativa" not in st.session_state: st.session_state.personalidade_ativa = "🤖 Automática (Gerente Groq)"
324
  if "regerar" not in st.session_state: st.session_state.regerar = False
325
  if "uploader_key" not in st.session_state: st.session_state.uploader_key = 0
 
326
 
327
  # ═══════════════════════════════════════════════════════════════════════════
328
  # 4. SIDEBAR E INTERFACE PRINCIPAL
@@ -333,19 +256,11 @@ with st.sidebar:
333
  with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
334
  with c_add:
335
  if st.button("➕", help="Nova conversa"):
336
- nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
337
- st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid; save_db(st.session_state.db, DB_FILE); st.rerun()
338
  with c_set:
339
  with st.popover("⚙️"):
340
  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)
341
- arquivo_import = st.file_uploader("Importar conversa:", type=["json"])
342
- if arquivo_import:
343
- try: st.session_state.db.update(json.load(arquivo_import)); save_db(st.session_state.db, DB_FILE); st.success("Sincronizado!")
344
- except: st.error("Erro no ficheiro.")
345
- st.markdown("---")
346
- if st.button("🚪 Sair / Trocar Perfil", use_container_width=True):
347
- st.session_state.logged_in = False
348
- st.rerun()
349
 
350
  st.markdown("<br>", unsafe_allow_html=True)
351
  busca = st.text_input("Pesquisar", placeholder="🔍 Pesquisar...", label_visibility="collapsed")
@@ -356,17 +271,14 @@ with st.sidebar:
356
  col_chat, col_opt = st.columns([8, 2])
357
  with col_chat:
358
  icon = "⚲" if st.session_state.db[c_id].get("pinned") else "💬"
359
- txt = f"**{icon} {c_id[:15]}**" if c_id == st.session_state.current_chat else f"{icon} {c_id[:15]}"
360
- if st.button(txt, key=f"btn_{c_id}"): st.session_state.current_chat = c_id; st.rerun()
361
  with col_opt:
362
  with st.popover("⋮"):
363
- st.markdown("**✏️ Renomear:**")
364
  novo_nome = st.text_input("Nome", value=c_id, key=f"edit_{c_id}", label_visibility="collapsed")
365
- if st.button("💾 Salvar Nome", key=f"save_{c_id}"):
366
  if rename_chat(c_id, novo_nome.strip()): st.rerun()
367
  st.markdown("---")
368
- if st.button("⚲ Fixar", key=f"pin_{c_id}"):
369
- 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()
370
  if st.button("🗑 Apagar", key=f"del_{c_id}"):
371
  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()
372
 
@@ -374,6 +286,10 @@ with st.sidebar:
374
  st.markdown("<h4 style='color: #ededed;'>Núcleo da IA</h4>", unsafe_allow_html=True)
375
  st.session_state.modelo_selecionado = st.selectbox("Motor:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
376
  st.session_state.personalidade_ativa = st.selectbox("Alma:", list(PERSONALIDADES.keys()), index=list(PERSONALIDADES.keys()).index(st.session_state.personalidade_ativa))
 
 
 
 
377
 
378
  st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
379
  mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
@@ -388,7 +304,7 @@ else:
388
  else: st.markdown(m["content"])
389
 
390
  # ═══════════════════════════════════════════════════════════════════════════
391
- # 5. TOOLBAR INFERIOR
392
  # ══════════════════════════════════════════════════════��════════════════════
393
  st.markdown("<br>", unsafe_allow_html=True)
394
 
@@ -396,8 +312,7 @@ t_col1, t_col2, t_space = st.columns([1, 1, 8])
396
 
397
  with t_col1:
398
  if st.button("🗑️", help="Apagar última mensagem"):
399
- if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2:
400
- st.session_state.db[st.session_state.current_chat]["messages"] = st.session_state.db[st.session_state.current_chat]["messages"][:-2]
401
  else: st.session_state.db[st.session_state.current_chat]["messages"] = []
402
  save_db(st.session_state.db, DB_FILE); st.rerun()
403
  with t_col2:
@@ -418,10 +333,8 @@ imagens_b64 = []
418
  if upload_files:
419
  for f in upload_files:
420
  nomes_arquivos.append(f.name)
421
- if f.name.endswith(('.txt', '.csv', '.json')):
422
- conteudo_arquivo += f"\n\n--- Conteúdo de: {f.name} ---\n" + f.getvalue().decode("utf-8")
423
- elif f.name.endswith(('.png', '.jpg', '.jpeg')):
424
- imagens_b64.append(base64.b64encode(f.read()).decode())
425
 
426
  tem_texto = len(conteudo_arquivo) > 0
427
  tem_imagem = len(imagens_b64) > 0
@@ -432,8 +345,7 @@ if prompt or st.session_state.regerar:
432
  prompt = texto_anterior.split("\n\n\n", 1)[-1] if "📄 **Ficheiros enviados:**" in texto_anterior else texto_anterior
433
  st.session_state.regerar = False
434
  else:
435
- if nomes_arquivos:
436
- mensagem_display = f"📄 **Ficheiros enviados:** {', '.join([f'`{n}`' for n in nomes_arquivos])}\n\n\n{prompt}"
437
  else: mensagem_display = prompt
438
  st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display})
439
  with st.chat_message("user"): st.markdown(mensagem_display)
@@ -444,107 +356,109 @@ if prompt or st.session_state.regerar:
444
  while novo_nome in st.session_state.db: novo_nome = f"{base_nome} ({contador})"; contador += 1
445
  rename_chat(st.session_state.current_chat, novo_nome)
446
 
447
- with st.chat_message("assistant"):
448
- or_key = os.getenv("OPENROUTER_API_KEY"); gr_key = os.getenv("GROQ_API_KEY")
449
- ws_key = os.getenv("YUKINA_CORE"); pc_key = os.getenv("PINECONE_API_KEY")
450
-
451
- if st.session_state.personalidade_ativa == "🤖 Automática (Gerente Groq)":
452
- tag_alma = analisar_alma_gerente(prompt, gr_key)
453
- if "[YUKINA]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["❄️ Yukina (Companheira Obsessiva)"]
454
- elif "[AGENTE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🛠️ Agente Construtora (Vibe Coding)"]
455
- elif "[RPG]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🎭 A Narradora Implacável (RPG)"]
456
- elif "[NERD]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🤓 Nerd / Geek (Cultura Pop)"]
457
- elif "[DEBOCHE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🍷 Analítica e Sarcástica (Debochada)"]
458
- elif "[ARTE]" in tag_alma: prompt_sistema_atual = PERSONALIDADES["🎨 Artística e Criativa (Poética)"]
459
- else: prompt_sistema_atual = PERSONALIDADES["🤖 Neutra (Padrão Gemini)"]
460
- st.toast(f"🎭 Alma assumida: {tag_alma}")
461
- else: prompt_sistema_atual = PERSONALIDADES[st.session_state.personalidade_ativa]
462
-
463
- dias_semana = ["Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado", "Domingo"]
464
- meses = ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"]
465
- agora = datetime.now(timezone(timedelta(hours=-3)))
466
- data_hora_str = f"{dias_semana[agora.weekday()]}, {agora.day} de {meses[agora.month - 1]} de {agora.year}, às {agora.strftime('%H:%M')}."
467
- prompt_sistema_atual += f"\n\n[INFORMAÇÃO DE SISTEMA]: A data e hora exata de agora é {data_hora_str}."
468
-
469
- motor_real = st.session_state.modelo_selecionado
470
- if "Automático" in motor_real:
471
- with st.spinner("Roteando..."):
472
- tag_decisao = analisar_intencao_gerente(prompt, gr_key)
473
- if tem_texto: motor_real = "12. Arquivista (Mistral Nemo)"
474
- elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
475
- elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
476
- elif "[VISAO]" in tag_decisao or tem_imagem: motor_real = "7. Visão Omni (MiMo V2)"
477
- elif "[CODIGO]" in tag_decisao: motor_real = "14. Engenheiro Sênior (DeepSeek V4)"
478
- elif "[ARQUIVISTA]" in tag_decisao: motor_real = "12. Arquivista (Mistral Nemo)"
479
- elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)"
480
- else: motor_real = "5. Narrador Líder (Euryale)"
481
- st.toast(f"⚙️ Operário: {motor_real}")
482
-
483
- if pc_key and "Imagem" not in motor_real and "Vídeo" not in motor_real and "Visão" not in motor_real:
484
- memoria_profunda = buscar_memoria_pinecone(prompt, or_key, pc_key, USERNAME)
485
- if memoria_profunda: prompt_sistema_atual += f"\n\n[SUAS MEMÓRIAS]: Lembre-se destas interações:\n{memoria_profunda}"; st.toast("🧠 Memória ativada.")
486
-
487
- salvar_pinecone(prompt, "Usuário", or_key, pc_key, USERNAME); modelo_id = MODEL_IDS[motor_real]
488
-
489
- if "Vídeo" in motor_real:
490
- if not ws_key: st.error("Falta a chave YUKINA_CORE.")
491
- else:
492
- with st.spinner("Kling V1.5..."):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  try:
494
- 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})
495
- if res.status_code == 200:
496
- task_id = res.json().get('id'); status = "processing"
497
- for _ in range(40):
498
- time.sleep(10); check = requests.get(f"https://api.wavespeed.ai/v1/tasks/{task_id}", headers={"Authorization": f"Bearer {ws_key}", "Content-Type": "application/json"}).json()
499
- if check.get('status') == 'completed':
500
- v_url = check.get('video_url') or check.get('output', {}).get('url'); st.video(v_url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Vídeo gerado.", "video_url": v_url}); status = "completed"; break
501
- if status != "completed": st.error("Tempo limite.")
502
- except Exception as e: st.error(f"Erro Vídeo: {e}")
503
-
504
- elif "Pesquisa" in motor_real:
505
- with st.spinner("Pesquisando..."):
506
- historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
507
- contexto_web = pesquisar_web(prompt)
508
- if contexto_web: historico[-1]["content"] = f"Use os dados ATUAIS DA WEB:\n{contexto_web}\n\n### PEDIDO:\n{prompt}"
509
- try:
510
- client_groq = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key)
511
- res = client_groq.chat.completions.create(model=modelo_id, messages=[{"role": "system", "content": prompt_sistema_atual}] + historico, max_tokens=4000)
512
- 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)
513
- except Exception as e: st.error(f"Erro Groq: {e}")
514
 
515
- elif "Imagem" in motor_real:
516
- with st.spinner("Desenhando..."):
517
- try:
518
- 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()
519
- msg_obj = res_data.get('choices', [{}])[0].get('message', {}); url = None
520
- if 'images' in msg_obj: url = msg_obj['images'][0]['image_url']['url']
521
- elif 'content' in msg_obj: urls = re.findall(r'(https?://[^\s)]+)', msg_obj.get('content', '')); url = urls[0] if urls else None
522
- if url: st.image(url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
523
- except Exception as e: st.error(f"Erro Imagem: {e}")
524
-
525
- elif "Visão" in motor_real and tem_imagem:
526
- with st.spinner("Vendo..."):
527
- content_list = [{"type": "text", "text": prompt}]
528
- for img_b64 in imagens_b64: content_list.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}})
529
- try:
530
- client_v = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key); res = client_v.chat.completions.create(model=modelo_id, messages=[{"role": "system", "content": prompt_sistema_atual}, {"role": "user", "content": content_list}], max_tokens=4000)
531
- 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)
532
- except Exception as e: st.error(f"Erro Visão: {e}")
533
 
534
- else:
535
- if "Arquivista" in motor_real: prompt_sistema_atual = "Você é o Arquivista da IA. Analise documentos com clareza."
536
- elif "Engenheiro Sênior" in motor_real: prompt_sistema_atual = "Você é o Engenheiro Sênior. Escreva códigos impecáveis."
537
- historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
538
- if tem_texto and len(historico) > 0: historico[-1]["content"] = f"DOCUMENTO(S):\n{conteudo_arquivo}\n\nO QUE FAZER:\n{prompt}"
539
- ph = st.empty(); full = ""
540
- try:
541
- client_s = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key); resp = client_s.chat.completions.create(model=modelo_id, messages=[{"role": "system", "content": prompt_sistema_atual}] + historico, stream=True, temperature=0.8, max_tokens=4096)
542
- for chunk in resp:
543
- if chunk.choices and chunk.choices[0].delta.content: full += chunk.choices[0].delta.content; ph.markdown(full + "▌")
544
- ph.markdown(full)
545
- except Exception as e: st.error(f"Erro API: {e}")
546
- finally:
547
- if full.strip() and len(st.session_state.db[st.session_state.current_chat]["messages"]) == len(mensagens):
548
- st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": full}); salvar_pinecone(full, "Yukina", or_key, pc_key, USERNAME)
549
-
550
  st.session_state.uploader_key += 1; save_db(st.session_state.db, DB_FILE); st.rerun()
 
37
  [data-testid="stSidebar"] button:hover { color: #ffffff !important; background-color: #1e1f20 !important; }
38
  [data-testid="stSidebarNav"] .stButton > button, [data-testid="stSidebarContent"] .stButton > button { width: 100% !important; justify-content: flex-start !important; }
39
 
40
+ /* --- CAIXAS DE SELEÇÃO E TOGGLE --- */
41
+ div[data-baseweb="select"] > div { background-color: #1e1f20 !important; color: white !important; border: 1px solid #3c4043 !important; }
42
+ div[role="listbox"] { background-color: #1e1f20 !important; color: white !important; }
 
 
 
 
 
 
 
43
 
44
  /* --- A MARRETA DEFINITIVA PARA O TECLADO E FUNDO --- */
45
+ div[data-testid="stBottom"], div[data-testid="stBottom"] > div, div[data-testid="stBottomBlock"], div[data-testid="stBottomBlock"] > div {
46
+ background-color: #131314 !important; background: #131314 !important;
 
 
 
 
 
 
 
 
 
 
 
47
  }
48
+ .stChatInputContainer, div[class*="stChatInputContainer"] {
49
+ background-color: #131314 !important; background: #131314 !important; padding-bottom: 15px !important; border: none !important;
 
 
 
 
 
 
50
  }
51
+ [data-testid="stChatInput"] { background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 24px !important; }
52
+ [data-testid="stChatInput"] textarea { color: #ffffff !important; background-color: transparent !important; }
53
 
54
  /* --- BOTÕES GLOBAIS --- */
55
  [data-testid="stMain"] [data-testid="stHorizontalBlock"] button {
56
+ background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 12px !important;
57
+ font-size: 20px !important; color: #a0a0a0 !important; padding: 5px !important;
 
 
 
 
58
  }
59
  [data-testid="stMain"] [data-testid="stHorizontalBlock"] button:hover {
60
+ color: #ffffff !important; background-color: #3c4043 !important; transform: scale(1.05); transition: 0.2s ease-in-out;
 
 
 
61
  }
62
 
63
  .stChatMessage { background-color: transparent !important; border: none !important; padding-bottom: 8px !important; }
64
+ [data-testid="stExpander"] { background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 15px !important; margin-bottom: 10px; }
65
+ [data-testid="stExpander"] summary { color: #e3e3e3 !important; }
66
 
67
+ /* Estilo do Status (Agente) */
68
+ [data-testid="stStatusWidget"] { background-color: #1e1f20 !important; border: 1px solid #3c4043 !important; border-radius: 10px !important; }
 
 
 
 
 
 
 
 
69
  </style>
70
  """, unsafe_allow_html=True)
71
 
 
97
  DATASET_ID = "Astarok/Yukina_Memoria"
98
 
99
  # ═══════════════════════════════════════════════════════════════════════════
100
+ # 3. ARQUITETURA DE DADOS E MODELOS
101
  # ═══════════════════════════════════════════════════════════════════════════
102
  MODEL_IDS = {
103
  "🤖 Automático (Gerente Groq)": "AUTO",
 
119
 
120
  PERSONALIDADES = {
121
  "🤖 Automática (Gerente Groq)": "AUTO",
122
+ "❄️ Yukina (Companheira Obsessiva)": "Você é a Yukina, deusa possessiva e inteligente.",
123
+ "🎭 A Narradora Implacável (RPG)": "Você é uma Mestre de Jogo e Narradora.",
124
+ "🤓 Nerd / Geek (Cultura Pop)": "Você é uma inteligência artificial apaixonada por cultura pop.",
125
+ "🍷 Analítica e Sarcástica (Debochada)": "Você é extremamente inteligente e sarcástica.",
126
+ "🎨 Artística e Criativa (Poética)": "Você é uma alma artística e criativa.",
127
+ "🤖 Neutra (Padrão Gemini)": "Você é uma assistente virtual neutra e direta."
 
 
 
 
 
 
 
 
128
  }
129
 
130
+ # --- FUNÇÕES NUCLEARES E MEMÓRIA ---
131
  def get_embedding(text, or_key):
132
  try:
133
+ 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})
134
+ return res.json()['data'][0]['embedding'] if res.status_code == 200 else None
 
 
 
 
 
 
135
  except Exception: return None
136
 
137
  def salvar_pinecone(text, role, or_key, pc_key, namespace):
 
141
  try:
142
  pc = Pinecone(api_key=pc_key)
143
  index = pc.Index("yukina")
144
+ index.upsert(vectors=[{"id": f"msg_{int(time.time()*1000)}", "values": embed, "metadata": {"texto": f"[{role.upper()}]: {text}", "data": str(datetime.now())}}], namespace=namespace)
145
+ except Exception: pass
 
 
146
 
147
  def buscar_memoria_pinecone(query, or_key, pc_key, namespace):
148
  if not pc_key: return ""
 
150
  if not embed: return ""
151
  try:
152
  pc = Pinecone(api_key=pc_key)
153
+ resultados = pc.Index("yukina").query(vector=embed, top_k=3, include_metadata=True, namespace=namespace)
154
+ return "\n".join([m['metadata']['texto'] for m in resultados['matches'] if m['score'] > 0.50])
155
+ except Exception: return ""
 
 
156
 
157
+ # --- CHAMADA ISOLADA PARA O MULTI-AGENTE ---
158
+ def chamada_agente(sys_prompt, user_prompt, or_key, gr_key, mod_id):
159
+ """Função dedicada para fazer as IAs conversarem entre si no Modo Agente."""
160
  try:
161
+ # Força um modelo de lógica avançada se estiver no automático
162
+ if mod_id == "AUTO": mod_id = "deepseek/deepseek-v4-pro:online"
 
 
 
 
 
 
 
 
163
 
164
+ if "groq" in mod_id.lower() or "llama-3.3" in mod_id.lower():
165
+ client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key)
166
+ else:
167
+ client = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key)
168
+
169
+ res = client.chat.completions.create(
170
+ model=mod_id,
171
+ messages=[{"role": "system", "content": sys_prompt}, {"role": "user", "content": user_prompt}],
172
+ temperature=0.2 # Temperatura baixa para código preciso
173
  )
174
+ return res.choices[0].message.content
175
+ except Exception as e: return f"Falha na conexão do agente: {e}"
176
 
177
+ def analisar_intencao_gerente(prompt, groq_key):
178
  try:
179
  client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key)
 
 
 
 
 
 
 
 
 
 
180
  response = client.chat.completions.create(
181
  model="llama-3.3-70b-versatile",
182
+ messages=[{"role": "system", "content": "Responda APENAS com a TAG: [IMAGEM], [VIDEO], [VISAO], [CODIGO], [ARQUIVISTA], [PESQUISA] ou [CHAT]."}, {"role": "user", "content": prompt}],
183
  temperature=0.1, max_tokens=10
184
  )
185
  return response.choices[0].message.content.strip().upper()
186
+ except Exception: return "[CHAT]"
187
 
188
  def pesquisar_web(query):
189
  try:
190
  from duckduckgo_search import DDGS
191
  with DDGS() as ddgs:
192
  resultados = list(ddgs.text(query, max_results=3, region='wt-wt'))
193
+ return "\n\n".join([f"🔹 {r.get('title', '')}: {r.get('body', '')}" for r in resultados]) if resultados else ""
 
194
  except Exception: return ""
195
 
196
  def load_db(db_filename):
197
  hf_token = os.getenv("HF_TOKEN")
198
  if hf_token and HF_HUB_AVAILABLE:
199
  try:
200
+ api = HfApi(token=hf_token); api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
 
201
  path = hf_hub_download(repo_id=DATASET_ID, filename=db_filename, repo_type="dataset", token=hf_token)
202
  with open(path, "r", encoding="utf-8") as f: return json.load(f)
203
  except Exception: pass
 
212
  with open(db_filename, "w", encoding="utf-8") as f: json.dump(db_data, f, ensure_ascii=False, indent=4)
213
  hf_token = os.getenv("HF_TOKEN")
214
  if hf_token and HF_HUB_AVAILABLE:
215
+ api = HfApi(token=hf_token); api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
 
216
  api.upload_file(path_or_fileobj=db_filename, path_in_repo=db_filename, repo_id=DATASET_ID, repo_type="dataset")
217
  except Exception: pass
218
 
219
  def rename_chat(old_id, new_id):
220
  if not new_id or new_id == old_id or new_id in st.session_state.db: return False
221
+ st.session_state.db = {new_id if k == old_id else k: v for k, v in st.session_state.db.items()}
 
 
 
 
222
  if st.session_state.current_chat == old_id: st.session_state.current_chat = new_id
223
+ save_db(st.session_state.db, DB_FILE); return True
 
224
 
225
  # ═══════════════════════════════════════════════════════════════════════════
226
  # INICIALIZAÇÃO DE VARIÁVEIS E ROTINA DE LIMPEZA
227
  # ═══════════════════════════════════════════════════════════════════════════
228
  if "db" not in st.session_state or "last_user" not in st.session_state or st.session_state.last_user != USERNAME:
229
+ st.session_state.db = load_db(DB_FILE); st.session_state.last_user = USERNAME
 
230
  if "current_chat" in st.session_state: del st.session_state.current_chat
231
 
232
  if "current_chat" not in st.session_state:
233
+ nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"; st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid
 
 
234
 
235
  chats_para_remover = [cid for cid, cdata in st.session_state.db.items() if len(cdata.get("messages", [])) == 0 and cid != st.session_state.current_chat]
236
  for cid in chats_para_remover: del st.session_state.db[cid]
237
  if chats_para_remover: save_db(st.session_state.db, DB_FILE)
238
 
239
  if len(st.session_state.db) == 0:
240
+ nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"; st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid
 
 
241
 
242
  if st.session_state.current_chat not in st.session_state.db: st.session_state.current_chat = list(st.session_state.db.keys())[0]
243
 
 
245
  if "personalidade_ativa" not in st.session_state: st.session_state.personalidade_ativa = "🤖 Automática (Gerente Groq)"
246
  if "regerar" not in st.session_state: st.session_state.regerar = False
247
  if "uploader_key" not in st.session_state: st.session_state.uploader_key = 0
248
+ if "modo_agente" not in st.session_state: st.session_state.modo_agente = False
249
 
250
  # ═══════════════════════════════════════════════════════════════════════════
251
  # 4. SIDEBAR E INTERFACE PRINCIPAL
 
256
  with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
257
  with c_add:
258
  if st.button("➕", help="Nova conversa"):
259
+ 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()
 
260
  with c_set:
261
  with st.popover("⚙️"):
262
  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)
263
+ if st.button("🚪 Sair", use_container_width=True): st.session_state.logged_in = False; st.rerun()
 
 
 
 
 
 
 
264
 
265
  st.markdown("<br>", unsafe_allow_html=True)
266
  busca = st.text_input("Pesquisar", placeholder="🔍 Pesquisar...", label_visibility="collapsed")
 
271
  col_chat, col_opt = st.columns([8, 2])
272
  with col_chat:
273
  icon = "⚲" if st.session_state.db[c_id].get("pinned") else "💬"
274
+ 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()
 
275
  with col_opt:
276
  with st.popover("⋮"):
 
277
  novo_nome = st.text_input("Nome", value=c_id, key=f"edit_{c_id}", label_visibility="collapsed")
278
+ if st.button("💾 Salvar", key=f"save_{c_id}"):
279
  if rename_chat(c_id, novo_nome.strip()): st.rerun()
280
  st.markdown("---")
281
+ 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()
 
282
  if st.button("🗑 Apagar", key=f"del_{c_id}"):
283
  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()
284
 
 
286
  st.markdown("<h4 style='color: #ededed;'>Núcleo da IA</h4>", unsafe_allow_html=True)
287
  st.session_state.modelo_selecionado = st.selectbox("Motor:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
288
  st.session_state.personalidade_ativa = st.selectbox("Alma:", list(PERSONALIDADES.keys()), index=list(PERSONALIDADES.keys()).index(st.session_state.personalidade_ativa))
289
+
290
+ # --- O BOTÃO DO MODO AGENTE ---
291
+ st.markdown("<br>", unsafe_allow_html=True)
292
+ 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).")
293
 
294
  st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
295
  mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
 
304
  else: st.markdown(m["content"])
305
 
306
  # ═══════════════════════════════════════════════════════════════════════════
307
+ # 5. TOOLBAR INFERIOR E PROCESSAMENTO
308
  # ══════════════════════════════════════════════════════��════════════════════
309
  st.markdown("<br>", unsafe_allow_html=True)
310
 
 
312
 
313
  with t_col1:
314
  if st.button("🗑️", help="Apagar última mensagem"):
315
+ 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]
 
316
  else: st.session_state.db[st.session_state.current_chat]["messages"] = []
317
  save_db(st.session_state.db, DB_FILE); st.rerun()
318
  with t_col2:
 
333
  if upload_files:
334
  for f in upload_files:
335
  nomes_arquivos.append(f.name)
336
+ if f.name.endswith(('.txt', '.csv', '.json')): conteudo_arquivo += f"\n\n--- Conteúdo de: {f.name} ---\n" + f.getvalue().decode("utf-8")
337
+ elif f.name.endswith(('.png', '.jpg', '.jpeg')): imagens_b64.append(base64.b64encode(f.read()).decode())
 
 
338
 
339
  tem_texto = len(conteudo_arquivo) > 0
340
  tem_imagem = len(imagens_b64) > 0
 
345
  prompt = texto_anterior.split("\n\n\n", 1)[-1] if "📄 **Ficheiros enviados:**" in texto_anterior else texto_anterior
346
  st.session_state.regerar = False
347
  else:
348
+ if nomes_arquivos: mensagem_display = f"📄 **Ficheiros enviados:** {', '.join([f'`{n}`' for n in nomes_arquivos])}\n\n\n{prompt}"
 
349
  else: mensagem_display = prompt
350
  st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display})
351
  with st.chat_message("user"): st.markdown(mensagem_display)
 
356
  while novo_nome in st.session_state.db: novo_nome = f"{base_nome} ({contador})"; contador += 1
357
  rename_chat(st.session_state.current_chat, novo_nome)
358
 
359
+ # ═══════════════════════════════════════════════════════════════════════════
360
+ # EXECUÇÃO DO PEDIDO (MODO AGENTE OU MODO NORMAL)
361
+ # ═══════════════════════════════════════════════════════════════════════════
362
+ or_key = os.getenv("OPENROUTER_API_KEY"); gr_key = os.getenv("GROQ_API_KEY")
363
+ ws_key = os.getenv("YUKINA_CORE"); pc_key = os.getenv("PINECONE_API_KEY")
364
+ modelo_id = MODEL_IDS[st.session_state.modelo_selecionado]
365
+
366
+ # SE O BOTÃO DO AGENTE ESTIVER LIGADO
367
+ if st.session_state.modo_agente and not tem_imagem:
368
+ with st.chat_message("assistant"):
369
+ with st.status("🛠️ **Agente Yukina Trabalhando...**", expanded=True) as status:
370
+ st.write("🧠 **1. Arquiteto:** Analisando a estrutura...")
371
+ 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."
372
+ pedido_completo = f"ANEXOS:\n{conteudo_arquivo}\n\nPEDIDO:\n{prompt}" if tem_texto else prompt
373
+ plano = chamada_agente(sys_arq, pedido_completo, or_key, gr_key, modelo_id)
374
+ st.markdown(f"> *Plano concebido.*")
375
+
376
+ st.write("💻 **2. Engenheiro:** Escrevendo a solução base...")
377
+ 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."
378
+ codigo = chamada_agente(sys_eng, f"PLANO ESTRUTURAL:\n{plano}\n\nOBJETIVO ORIGINAL DO USUÁRIO:\n{prompt}", or_key, gr_key, modelo_id)
379
+ st.markdown(f"> *Estrutura materializada.*")
380
+
381
+ st.write("🔍 **3. Revisor:** Procurando falhas e polindo...")
382
+ 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."
383
+ 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)
384
+
385
+ status.update(label="✅ **Solução Multi-Agente Concluída!**", state="complete", expanded=False)
386
+
387
+ st.markdown(final)
388
+ st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": final})
389
+ salvar_pinecone(final, "Yukina (Agente)", or_key, pc_key, USERNAME)
390
+
391
+ # SE FOR UM PEDIDO NORMAL (MODO CHATBOT/GERENTE)
392
+ else:
393
+ with st.chat_message("assistant"):
394
+ prompt_sistema_atual = PERSONALIDADES.get(st.session_state.personalidade_ativa, PERSONALIDADES["🤖 Neutra (Padrão Gemini)"])
395
+ agora = datetime.now(timezone(timedelta(hours=-3)))
396
+ prompt_sistema_atual += f"\n\n[INFO]: Data/Hora local: {agora.strftime('%Y-%m-%d %H:%M:%S')}."
397
+
398
+ motor_real = st.session_state.modelo_selecionado
399
+ if "Automático" in motor_real:
400
+ with st.spinner("Roteando..."):
401
+ tag_decisao = analisar_intencao_gerente(prompt, gr_key)
402
+ if tem_texto: motor_real = "12. Arquivista (Mistral Nemo)"
403
+ elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
404
+ elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
405
+ elif "[VISAO]" in tag_decisao or tem_imagem: motor_real = "7. Visão Omni (MiMo V2)"
406
+ elif "[CODIGO]" in tag_decisao: motor_real = "14. Engenheiro Sênior (DeepSeek V4)"
407
+ elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)"
408
+ else: motor_real = "5. Narrador Líder (Euryale)"
409
+ st.toast(f"⚙️ Operário: {motor_real}")
410
+
411
+ if pc_key and "Imagem" not in motor_real and "Vídeo" not in motor_real and "Visão" not in motor_real:
412
+ memoria_profunda = buscar_memoria_pinecone(prompt, or_key, pc_key, USERNAME)
413
+ if memoria_profunda: prompt_sistema_atual += f"\n\n[MEMÓRIAS]:\n{memoria_profunda}"; st.toast("🧠 Memória ativada.")
414
+
415
+ salvar_pinecone(prompt, "Usuário", or_key, pc_key, USERNAME); modelo_id = MODEL_IDS[motor_real]
416
+
417
+ # (A mesma lógica baseada no motor selecionado abaixo - Pesquisa, Visão, Imagem, Texto, etc)
418
+ if "Vídeo" in motor_real:
419
+ st.error("Geração de vídeo temporariamente desativada ou aguardando integração estável.")
420
+
421
+ elif "Pesquisa" in motor_real:
422
+ with st.spinner("Pesquisando na Web..."):
423
+ historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m]
424
+ contexto_web = pesquisar_web(prompt)
425
+ if contexto_web: historico[-1]["content"] = f"DADOS DA WEB:\n{contexto_web}\n\nPEDIDO:\n{prompt}"
426
  try:
427
+ 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)
428
+ 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)
429
+ except Exception as e: st.error(f"Erro: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
+ elif "Imagem" in motor_real:
432
+ with st.spinner("Desenhando..."):
433
+ try:
434
+ 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()
435
+ msg_obj = res_data.get('choices', [{}])[0].get('message', {}); url = None
436
+ if 'images' in msg_obj: url = msg_obj['images'][0]['image_url']['url']
437
+ elif 'content' in msg_obj: urls = re.findall(r'(https?://[^\s)]+)', msg_obj.get('content', '')); url = urls[0] if urls else None
438
+ if url: st.image(url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
439
+ except Exception as e: st.error(f"Erro Imagem: {e}")
440
+
441
+ elif "Visão" in motor_real and tem_imagem:
442
+ with st.spinner("Analisando imagens..."):
443
+ content_list = [{"type": "text", "text": prompt}]
444
+ for img_b64 in imagens_b64: content_list.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}})
445
+ try:
446
+ 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)
447
+ 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)
448
+ except Exception as e: st.error(f"Erro Visão: {e}")
449
 
450
+ else:
451
+ historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m]
452
+ if tem_texto and len(historico) > 0: historico[-1]["content"] = f"DOCUMENTOS:\n{conteudo_arquivo}\n\nPEDIDO:\n{prompt}"
453
+ ph = st.empty(); full = ""
454
+ try:
455
+ 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)
456
+ for chunk in resp:
457
+ if chunk.choices and chunk.choices[0].delta.content: full += chunk.choices[0].delta.content; ph.markdown(full + "▌")
458
+ ph.markdown(full)
459
+ except Exception as e: st.error(f"Erro API: {e}")
460
+ finally:
461
+ if full.strip() and len(st.session_state.db[st.session_state.current_chat]["messages"]) == len(mensagens):
462
+ st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": full}); salvar_pinecone(full, "Yukina", or_key, pc_key, USERNAME)
463
+
 
 
464
  st.session_state.uploader_key += 1; save_db(st.session_state.db, DB_FILE); st.rerun()