Astarok commited on
Commit
070f7b6
·
verified ·
1 Parent(s): f50dd12

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +109 -68
app.py CHANGED
@@ -6,6 +6,7 @@ import base64
6
  import re
7
  import time
8
  from datetime import datetime
 
9
 
10
  try:
11
  from huggingface_hub import HfApi, hf_hub_download
@@ -47,15 +48,13 @@ st.markdown("""
47
  transform: scale(1.15);
48
  transition: 0.2s ease-in-out;
49
  }
50
- [data-testid="stMain"] [data-testid="stHorizontalBlock"] div[data-testid="stPopover"] > button svg {
51
- display: none !important;
52
- }
53
 
54
  /* Estilização elegante para a Gaveta Nativa de Upload */
55
  [data-testid="stExpander"] {
56
  background-color: #1e1f20 !important;
57
  border: 1px solid #3c4043 !important;
58
  border-radius: 15px !important;
 
59
  }
60
  [data-testid="stExpander"] summary {
61
  color: #e3e3e3 !important;
@@ -70,7 +69,7 @@ DB_FILE = "yukina_memoria_v3.json"
70
  DATASET_ID = "Astarok/Yukina_Memoria"
71
 
72
  MODEL_IDS = {
73
- "🤖 Automático (Gerente Hermes)": "AUTO",
74
  "1. Gerente (Hermes 2 Pro)": "nousresearch/hermes-2-pro-llama-3-8b",
75
  "2. Pesquisa (Groq Llama 3.3)": "llama-3.3-70b-versatile",
76
  "3. Lógica Free (Ring 1T)": "inclusionai/ring-2.6-1t:free",
@@ -97,23 +96,32 @@ Personalidade pública: orgulhosa, dramática e otimista. Adora exigir aplausos.
97
  "🤖 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."""
98
  }
99
 
100
- def analisar_intencao_gerente(prompt, api_key):
101
- headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
102
- prompt_gerente = """Você é o Gerente de Roteamento da IA Yukina. Analise o pedido e responda APENAS com UMA destas tags:
103
- [IMAGEM] - Criar, desenhar ou gerar uma imagem/foto.
104
- [VIDEO] - Criar ou gerar um vídeo.
105
- [VISAO] - Analisar ou ver uma imagem.
106
- [CODIGO] - Escrever programação, scripts, Python.
107
- [ARQUIVISTA] - Resumir textos, planilhas, analisar dados anexados.
108
- [PESQUISA] - Perguntas de conhecimentos gerais rápidas e buscas recentes.
109
- [CHAT] - Conversas, RPG ou ordens simples.
110
- Responda APENAS com a TAG."""
111
-
112
- payload = {"model": "nousresearch/hermes-2-pro-llama-3-8b", "messages": [{"role": "system", "content": prompt_gerente}, {"role": "user", "content": prompt}], "temperature": 0.1}
113
- try:
114
- res = requests.post("https://openrouter.ai/api/v1/chat/completions", headers=headers, json=payload).json()
115
- return res['choices'][0]['message']['content'].strip().upper()
116
- except:
 
 
 
 
 
 
 
 
 
117
  return "[CHAT]"
118
 
119
  def load_db():
@@ -124,28 +132,33 @@ def load_db():
124
  api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
125
  path = hf_hub_download(repo_id=DATASET_ID, filename=DB_FILE, repo_type="dataset", token=hf_token)
126
  with open(path, "r", encoding="utf-8") as f: return json.load(f)
127
- except Exception: pass
 
128
  if os.path.exists(DB_FILE):
129
  try:
130
  with open(DB_FILE, "r", encoding="utf-8") as f: return json.load(f)
131
- except: pass
 
132
  return {"Nova Conversa": {"pinned": False, "messages": []}}
133
 
134
  def save_db(db):
135
- with open(DB_FILE, "w", encoding="utf-8") as f: json.dump(db, f, ensure_ascii=False, indent=4)
136
- hf_token = os.getenv("HF_TOKEN")
137
- if hf_token and HF_HUB_AVAILABLE:
138
- try:
139
  api = HfApi(token=hf_token)
140
  api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
141
  api.upload_file(path_or_fileobj=DB_FILE, path_in_repo=DB_FILE, repo_id=DATASET_ID, repo_type="dataset")
142
- except Exception: pass
 
143
 
 
144
  if "db" not in st.session_state: st.session_state.db = load_db()
145
  if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.db.keys())[0]
146
- if "modelo_selecionado" not in st.session_state: st.session_state.modelo_selecionado = "🤖 Automático (Gerente Hermes)"
147
  if "personalidade_ativa" not in st.session_state: st.session_state.personalidade_ativa = "❄️ Yukina (Companheira Obsessiva)"
148
  if "regerar" not in st.session_state: st.session_state.regerar = False
 
149
 
150
  # ═══════════════════════════════════════════════════════════════════════════
151
  # 3. SIDEBAR E INTERFACE PRINCIPAL
@@ -154,7 +167,7 @@ with st.sidebar:
154
  c_title, c_add, c_set = st.columns([5, 1, 1])
155
  with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
156
  with c_add:
157
- if st.button("➕"):
158
  nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
159
  st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid; save_db(st.session_state.db); st.rerun()
160
  with c_set:
@@ -183,11 +196,17 @@ with st.sidebar:
183
  if st.button("🗑 Apagar", key=f"del_{c_id}"):
184
  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()
185
 
 
 
 
 
 
 
186
  st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
187
  mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
188
 
189
  if len(mensagens) == 0:
190
- st.markdown("<br><br><h3 style='color: #888; font-weight: 400;'>Olá, Leonardo</h3><h1 style='color: #fff; font-size: 32px;'>Como você quer que eu aja hoje?</h1>", unsafe_allow_html=True)
191
  else:
192
  for m in mensagens:
193
  with st.chat_message(m["role"]):
@@ -196,35 +215,31 @@ else:
196
  else: st.markdown(m["content"])
197
 
198
  # ═══════════════════════════════════════════════════════════════════════════
199
- # 4. TOOLBAR INFERIOR (Gaveta Nativa Blindada)
200
  # ═══════════════════════════════════════════════════════════════════════════
201
  st.markdown("<br>", unsafe_allow_html=True)
202
 
203
- t_col1, t_col2, t_col3, t_space = st.columns([1, 1, 1, 7])
 
204
 
205
  with t_col1:
206
- with st.popover("️"):
207
- st.session_state.modelo_selecionado = st.radio("MOTOR:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
208
- st.markdown("---")
209
- st.session_state.personalidade_ativa = st.radio("ALMA:", list(PERSONALIDADES.keys()), index=list(PERSONALIDADES.keys()).index(st.session_state.personalidade_ativa))
210
- with t_col2:
211
- if st.button("🗑️"):
212
  if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2:
213
  st.session_state.db[st.session_state.current_chat]["messages"] = st.session_state.db[st.session_state.current_chat]["messages"][:-2]
214
  else:
215
  st.session_state.db[st.session_state.current_chat]["messages"] = []
216
  save_db(st.session_state.db); st.rerun()
217
- with t_col3:
218
- if st.button("🔄"):
219
  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":
220
  st.session_state.db[st.session_state.current_chat]["messages"].pop()
221
  save_db(st.session_state.db)
222
  st.session_state.regerar = True
223
  st.rerun()
224
 
225
- # GAVETA NATIVA: Não sofre "crash" ao ir para a galeria em segundo plano
226
  with st.expander("📂 Abrir Galeria / Anexar Ficheiros"):
227
- upload_files = st.file_uploader("", type=["png", "jpg", "jpeg", "txt", "csv", "json"], accept_multiple_files=True, label_visibility="collapsed")
228
 
229
  prompt = st.chat_input("Peça à Yukina...")
230
 
@@ -268,7 +283,7 @@ if prompt or st.session_state.regerar:
268
 
269
  if "Automático" in motor_real:
270
  with st.spinner("Roteando..."):
271
- tag_decisao = analisar_intencao_gerente(prompt, or_key)
272
  if tem_texto: motor_real = "12. Arquivista (Mistral Nemo)"
273
  elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
274
  elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
@@ -297,19 +312,26 @@ if prompt or st.session_state.regerar:
297
  v_url = check.get('video_url') or check.get('output', {}).get('url')
298
  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
299
  if status != "completed": st.error("Tempo limite.")
300
- except Exception as e: st.error(f"Erro: {e}")
301
 
302
- # 2. Pesquisa (Groq)
303
  elif "Pesquisa" in motor_real:
304
  with st.spinner("Pesquisando..."):
305
  historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
306
  if tem_texto: historico[-1]["content"] = f"DOCUMENTO(S) ANEXADOS:\n{conteudo_arquivo}\n\nPEDIDO DO USUÁRIO:\n{prompt}"
307
  try:
308
- 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()
309
- ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
 
 
 
 
 
 
 
310
  except Exception as e: st.error(f"Erro Groq: {e}")
311
 
312
- # 3. Imagem (Flux)
313
  elif "Imagem" in motor_real:
314
  with st.spinner("Desenhando..."):
315
  try:
@@ -320,21 +342,33 @@ if prompt or st.session_state.regerar:
320
  elif 'content' in msg_obj:
321
  urls = re.findall(r'(https?://[^\s)]+)', msg_obj.get('content', ''))
322
  if urls: url = urls[0]
323
- if url: st.image(url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
324
- except Exception as e: st.error(f"Erro: {e}")
 
 
325
 
326
- # 4. Visão (Análise de Múltiplas Imagens)
327
  elif "Visão" in motor_real and tem_imagem:
328
  with st.spinner("Analisando imagens..."):
329
  content_list = [{"type": "text", "text": prompt}]
330
  for img_b64 in imagens_b64:
331
  content_list.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}})
332
  try:
333
- 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": content_list} ], "max_tokens": 4000}).json()
334
- ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
 
 
 
 
 
 
 
 
 
 
335
  except Exception as e: st.error(f"Erro visão: {e}")
336
 
337
- # 5. Texto (Streaming Blindado com Múltiplos Arquivos)
338
  else:
339
  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."
340
  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."
@@ -345,21 +379,28 @@ if prompt or st.session_state.regerar:
345
  ph = st.empty(); full = ""
346
 
347
  try:
348
- resp = 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}] + historico, "stream": True, "temperature": 0.8, "max_tokens": 4096}, stream=True, timeout=60)
349
- if resp.status_code == 200:
350
- for l in resp.iter_lines():
351
- if l and l.startswith(b'data: '):
352
- decoded = l.decode('utf-8')[6:]
353
- if decoded == '[DONE]': break
354
- try:
355
- d = json.loads(decoded)
356
- full += d['choices'][0]['delta'].get('content', '')
357
- ph.markdown(full + "▌")
358
- except: pass
359
- ph.markdown(full)
360
- except Exception as e: st.error(f"Oscilação de rede: {e}")
 
 
 
 
361
  finally:
362
  if full.strip() and len(st.session_state.db[st.session_state.current_chat]["messages"]) == len(mensagens):
363
  st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": full})
364
 
 
 
365
  save_db(st.session_state.db)
 
 
6
  import re
7
  import time
8
  from datetime import datetime
9
+ from openai import OpenAI
10
 
11
  try:
12
  from huggingface_hub import HfApi, hf_hub_download
 
48
  transform: scale(1.15);
49
  transition: 0.2s ease-in-out;
50
  }
 
 
 
51
 
52
  /* Estilização elegante para a Gaveta Nativa de Upload */
53
  [data-testid="stExpander"] {
54
  background-color: #1e1f20 !important;
55
  border: 1px solid #3c4043 !important;
56
  border-radius: 15px !important;
57
+ margin-bottom: 10px;
58
  }
59
  [data-testid="stExpander"] summary {
60
  color: #e3e3e3 !important;
 
69
  DATASET_ID = "Astarok/Yukina_Memoria"
70
 
71
  MODEL_IDS = {
72
+ "🤖 Automático (Gerente Groq)": "AUTO",
73
  "1. Gerente (Hermes 2 Pro)": "nousresearch/hermes-2-pro-llama-3-8b",
74
  "2. Pesquisa (Groq Llama 3.3)": "llama-3.3-70b-versatile",
75
  "3. Lógica Free (Ring 1T)": "inclusionai/ring-2.6-1t:free",
 
96
  "🤖 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."""
97
  }
98
 
99
+ def analisar_intencao_gerente(prompt, groq_key):
100
+ """Utiliza a Groq para roteamento ultra-rápido da intenção."""
101
+ try:
102
+ client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=groq_key)
103
+ prompt_gerente = """Você é o Gerente de Roteamento da IA Yukina. Analise o pedido e responda APENAS com UMA destas tags:
104
+ [IMAGEM] - Criar, desenhar ou gerar uma imagem/foto.
105
+ [VIDEO] - Criar ou gerar um vídeo.
106
+ [VISAO] - Analisar ou ver uma imagem.
107
+ [CODIGO] - Escrever programação, scripts, Python.
108
+ [ARQUIVISTA] - Resumir textos, planilhas, analisar dados anexados.
109
+ [PESQUISA] - Perguntas de conhecimentos gerais rápidas e buscas recentes.
110
+ [CHAT] - Conversas, RPG ou ordens simples.
111
+ Responda APENAS com a TAG."""
112
+
113
+ response = client.chat.completions.create(
114
+ model="llama-3.3-70b-versatile",
115
+ messages=[
116
+ {"role": "system", "content": prompt_gerente},
117
+ {"role": "user", "content": prompt}
118
+ ],
119
+ temperature=0.1,
120
+ max_tokens=10
121
+ )
122
+ return response.choices[0].message.content.strip().upper()
123
+ except Exception as e:
124
+ print(f"Erro no roteamento do Gerente: {e}")
125
  return "[CHAT]"
126
 
127
  def load_db():
 
132
  api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
133
  path = hf_hub_download(repo_id=DATASET_ID, filename=DB_FILE, repo_type="dataset", token=hf_token)
134
  with open(path, "r", encoding="utf-8") as f: return json.load(f)
135
+ except Exception as e:
136
+ print(f"Aviso HuggingFace Load: {e}")
137
  if os.path.exists(DB_FILE):
138
  try:
139
  with open(DB_FILE, "r", encoding="utf-8") as f: return json.load(f)
140
+ except Exception as e:
141
+ print(f"Erro ao ler DB local: {e}")
142
  return {"Nova Conversa": {"pinned": False, "messages": []}}
143
 
144
  def save_db(db):
145
+ try:
146
+ with open(DB_FILE, "w", encoding="utf-8") as f: json.dump(db, f, ensure_ascii=False, indent=4)
147
+ hf_token = os.getenv("HF_TOKEN")
148
+ if hf_token and HF_HUB_AVAILABLE:
149
  api = HfApi(token=hf_token)
150
  api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
151
  api.upload_file(path_or_fileobj=DB_FILE, path_in_repo=DB_FILE, repo_id=DATASET_ID, repo_type="dataset")
152
+ except Exception as e:
153
+ print(f"Erro ao salvar DB: {e}")
154
 
155
+ # Inicialização de Variáveis de Estado
156
  if "db" not in st.session_state: st.session_state.db = load_db()
157
  if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.db.keys())[0]
158
+ if "modelo_selecionado" not in st.session_state: st.session_state.modelo_selecionado = "🤖 Automático (Gerente Groq)"
159
  if "personalidade_ativa" not in st.session_state: st.session_state.personalidade_ativa = "❄️ Yukina (Companheira Obsessiva)"
160
  if "regerar" not in st.session_state: st.session_state.regerar = False
161
+ if "uploader_key" not in st.session_state: st.session_state.uploader_key = 0
162
 
163
  # ═══════════════════════════════════════════════════════════════════════════
164
  # 3. SIDEBAR E INTERFACE PRINCIPAL
 
167
  c_title, c_add, c_set = st.columns([5, 1, 1])
168
  with c_title: st.markdown("<h4 style='color: #ededed; margin-bottom: 0;'>Bate-papos</h4>", unsafe_allow_html=True)
169
  with c_add:
170
+ if st.button("➕", help="Nova conversa"):
171
  nid = f"Chat {datetime.now().strftime('%H:%M:%S')}"
172
  st.session_state.db[nid] = {"pinned": False, "messages": []}; st.session_state.current_chat = nid; save_db(st.session_state.db); st.rerun()
173
  with c_set:
 
196
  if st.button("🗑 Apagar", key=f"del_{c_id}"):
197
  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()
198
 
199
+ # MUDANÇA: Configurações de IA movidas para a sidebar para melhor responsividade
200
+ st.markdown("---")
201
+ st.markdown("<h4 style='color: #ededed;'>Núcleo da IA</h4>", unsafe_allow_html=True)
202
+ st.session_state.modelo_selecionado = st.selectbox("Motor:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
203
+ st.session_state.personalidade_ativa = st.selectbox("Alma:", list(PERSONALIDADES.keys()), index=list(PERSONALIDADES.keys()).index(st.session_state.personalidade_ativa))
204
+
205
  st.markdown("<h3 style='margin-top: -10px; color: #ededed;'>❄ Yukina</h3>", unsafe_allow_html=True)
206
  mensagens = st.session_state.db[st.session_state.current_chat]["messages"]
207
 
208
  if len(mensagens) == 0:
209
+ st.markdown("<br><br><h3 style='color: #888; font-weight: 400;'>Olá!</h3><h1 style='color: #fff; font-size: 32px;'>Como você quer que eu aja hoje?</h1>", unsafe_allow_html=True)
210
  else:
211
  for m in mensagens:
212
  with st.chat_message(m["role"]):
 
215
  else: st.markdown(m["content"])
216
 
217
  # ═══════════════════════════════════════════════════════════════════════════
218
+ # 4. TOOLBAR INFERIOR
219
  # ═══════════════════════════════════════════════════════════════════════════
220
  st.markdown("<br>", unsafe_allow_html=True)
221
 
222
+ # Barra simplificada apenas com utilitários da conversa atual
223
+ t_col1, t_col2, t_space = st.columns([1, 1, 8])
224
 
225
  with t_col1:
226
+ if st.button("🗑️", help="Apagar última mensagem"):
 
 
 
 
 
227
  if len(st.session_state.db[st.session_state.current_chat]["messages"]) >= 2:
228
  st.session_state.db[st.session_state.current_chat]["messages"] = st.session_state.db[st.session_state.current_chat]["messages"][:-2]
229
  else:
230
  st.session_state.db[st.session_state.current_chat]["messages"] = []
231
  save_db(st.session_state.db); st.rerun()
232
+ with t_col2:
233
+ if st.button("🔄", help="Regerar resposta"):
234
  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":
235
  st.session_state.db[st.session_state.current_chat]["messages"].pop()
236
  save_db(st.session_state.db)
237
  st.session_state.regerar = True
238
  st.rerun()
239
 
240
+ # GAVETA NATIVA: Chave dinâmica para esvaziar arquivos após o envio
241
  with st.expander("📂 Abrir Galeria / Anexar Ficheiros"):
242
+ upload_files = st.file_uploader("", type=["png", "jpg", "jpeg", "txt", "csv", "json"], accept_multiple_files=True, label_visibility="collapsed", key=f"uploader_{st.session_state.uploader_key}")
243
 
244
  prompt = st.chat_input("Peça à Yukina...")
245
 
 
283
 
284
  if "Automático" in motor_real:
285
  with st.spinner("Roteando..."):
286
+ tag_decisao = analisar_intencao_gerente(prompt, gr_key)
287
  if tem_texto: motor_real = "12. Arquivista (Mistral Nemo)"
288
  elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
289
  elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
 
312
  v_url = check.get('video_url') or check.get('output', {}).get('url')
313
  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
314
  if status != "completed": st.error("Tempo limite.")
315
+ except Exception as e: st.error(f"Erro no Vídeo: {e}")
316
 
317
+ # 2. Pesquisa (Groq com biblioteca oficial)
318
  elif "Pesquisa" in motor_real:
319
  with st.spinner("Pesquisando..."):
320
  historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
321
  if tem_texto: historico[-1]["content"] = f"DOCUMENTO(S) ANEXADOS:\n{conteudo_arquivo}\n\nPEDIDO DO USUÁRIO:\n{prompt}"
322
  try:
323
+ client_groq = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=gr_key)
324
+ res = client_groq.chat.completions.create(
325
+ model=modelo_id,
326
+ messages=[{"role": "system", "content": prompt_sistema_atual}] + historico,
327
+ max_tokens=4000
328
+ )
329
+ ans = res.choices[0].message.content
330
+ st.markdown(ans)
331
+ st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
332
  except Exception as e: st.error(f"Erro Groq: {e}")
333
 
334
+ # 3. Imagem (Flux) - Mantido via requests para compatibilidade com "modalities" específicas do OpenRouter
335
  elif "Imagem" in motor_real:
336
  with st.spinner("Desenhando..."):
337
  try:
 
342
  elif 'content' in msg_obj:
343
  urls = re.findall(r'(https?://[^\s)]+)', msg_obj.get('content', ''))
344
  if urls: url = urls[0]
345
+ if url:
346
+ st.image(url)
347
+ st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
348
+ except Exception as e: st.error(f"Erro na Imagem: {e}")
349
 
350
+ # 4. Visão (Análise de Múltiplas Imagens via biblioteca oficial)
351
  elif "Visão" in motor_real and tem_imagem:
352
  with st.spinner("Analisando imagens..."):
353
  content_list = [{"type": "text", "text": prompt}]
354
  for img_b64 in imagens_b64:
355
  content_list.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}})
356
  try:
357
+ client_or_vision = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key)
358
+ res = client_or_vision.chat.completions.create(
359
+ model=modelo_id,
360
+ messages=[
361
+ {"role": "system", "content": prompt_sistema_atual},
362
+ {"role": "user", "content": content_list}
363
+ ],
364
+ max_tokens=4000
365
+ )
366
+ ans = res.choices[0].message.content
367
+ st.markdown(ans)
368
+ st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
369
  except Exception as e: st.error(f"Erro visão: {e}")
370
 
371
+ # 5. Texto (Streaming Blindado com biblioteca oficial)
372
  else:
373
  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."
374
  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."
 
379
  ph = st.empty(); full = ""
380
 
381
  try:
382
+ client_stream = OpenAI(base_url="https://openrouter.ai/api/v1", api_key=or_key)
383
+ resp = client_stream.chat.completions.create(
384
+ model=modelo_id,
385
+ messages=[{"role": "system", "content": prompt_sistema_atual}] + historico,
386
+ stream=True,
387
+ temperature=0.8,
388
+ max_tokens=4096
389
+ )
390
+
391
+ for chunk in resp:
392
+ if chunk.choices and chunk.choices[0].delta.content:
393
+ full += chunk.choices[0].delta.content
394
+ ph.markdown(full + "▌")
395
+ ph.markdown(full)
396
+
397
+ except Exception as e:
398
+ st.error(f"Oscilação de rede ou erro na API: {e}")
399
  finally:
400
  if full.strip() and len(st.session_state.db[st.session_state.current_chat]["messages"]) == len(mensagens):
401
  st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": full})
402
 
403
+ # Incrementa a chave do uploader para resetar a gaveta de arquivos
404
+ st.session_state.uploader_key += 1
405
  save_db(st.session_state.db)
406
+ st.rerun() # Força o rerun para limpar a gaveta visualmente