Update app.py
Browse files
app.py
CHANGED
|
@@ -7,7 +7,6 @@ import re
|
|
| 7 |
import time
|
| 8 |
from datetime import datetime
|
| 9 |
|
| 10 |
-
# Importação do motor de Nuvem do Hugging Face
|
| 11 |
try:
|
| 12 |
from huggingface_hub import HfApi, hf_hub_download
|
| 13 |
HF_HUB_AVAILABLE = True
|
|
@@ -57,7 +56,7 @@ st.markdown("""
|
|
| 57 |
# 2. ARQUITETURA DE DADOS E MEMÓRIA EM NUVEM
|
| 58 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 59 |
DB_FILE = "yukina_memoria_v3.json"
|
| 60 |
-
DATASET_ID = "Astarok/Yukina_Memoria"
|
| 61 |
|
| 62 |
MODEL_IDS = {
|
| 63 |
"🤖 Automático (Gerente Hermes)": "AUTO",
|
|
@@ -108,47 +107,28 @@ def analisar_intencao_gerente(prompt, api_key):
|
|
| 108 |
|
| 109 |
def load_db():
|
| 110 |
hf_token = os.getenv("HF_TOKEN")
|
| 111 |
-
|
| 112 |
-
# Tenta baixar da Nuvem primeiro (Quando o servidor reinicia)
|
| 113 |
if hf_token and HF_HUB_AVAILABLE:
|
| 114 |
try:
|
| 115 |
api = HfApi(token=hf_token)
|
| 116 |
api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
|
| 117 |
path = hf_hub_download(repo_id=DATASET_ID, filename=DB_FILE, repo_type="dataset", token=hf_token)
|
| 118 |
-
with open(path, "r", encoding="utf-8") as f:
|
| 119 |
-
|
| 120 |
-
except Exception:
|
| 121 |
-
pass # Se o arquivo não existir na nuvem ainda, ignora silenciosamente
|
| 122 |
-
|
| 123 |
-
# Se falhar ou não tiver net, usa o arquivo local da máquina
|
| 124 |
if os.path.exists(DB_FILE):
|
| 125 |
try:
|
| 126 |
-
with open(DB_FILE, "r", encoding="utf-8") as f:
|
| 127 |
-
|
| 128 |
-
except:
|
| 129 |
-
pass
|
| 130 |
-
|
| 131 |
return {"Nova Conversa": {"pinned": False, "messages": []}}
|
| 132 |
|
| 133 |
def save_db(db):
|
| 134 |
-
|
| 135 |
-
with open(DB_FILE, "w", encoding="utf-8") as f:
|
| 136 |
-
json.dump(db, f, ensure_ascii=False, indent=4)
|
| 137 |
-
|
| 138 |
-
# Envia o backup pra nuvem silenciosamente no background
|
| 139 |
hf_token = os.getenv("HF_TOKEN")
|
| 140 |
if hf_token and HF_HUB_AVAILABLE:
|
| 141 |
try:
|
| 142 |
api = HfApi(token=hf_token)
|
| 143 |
api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
|
| 144 |
-
api.upload_file(
|
| 145 |
-
|
| 146 |
-
path_in_repo=DB_FILE,
|
| 147 |
-
repo_id=DATASET_ID,
|
| 148 |
-
repo_type="dataset"
|
| 149 |
-
)
|
| 150 |
-
except Exception:
|
| 151 |
-
pass # Se a internet oscilar, ele ignora para não travar o seu chat
|
| 152 |
|
| 153 |
if "db" not in st.session_state: st.session_state.db = load_db()
|
| 154 |
if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.db.keys())[0]
|
|
@@ -205,7 +185,7 @@ else:
|
|
| 205 |
else: st.markdown(m["content"])
|
| 206 |
|
| 207 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 208 |
-
# 4. TOOLBAR INFERIOR (
|
| 209 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 210 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 211 |
|
|
@@ -213,7 +193,8 @@ t_col1, t_col2, t_col3, t_col4, t_space = st.columns([1, 1, 1, 1, 6])
|
|
| 213 |
|
| 214 |
with t_col1:
|
| 215 |
with st.popover("➕"):
|
| 216 |
-
|
|
|
|
| 217 |
with t_col2:
|
| 218 |
with st.popover("⚙️"):
|
| 219 |
st.session_state.modelo_selecionado = st.radio("MOTOR:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
|
|
@@ -236,17 +217,35 @@ with t_col4:
|
|
| 236 |
|
| 237 |
prompt = st.chat_input("Peça à Yukina...")
|
| 238 |
|
|
|
|
| 239 |
conteudo_arquivo = ""
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
if prompt or st.session_state.regerar:
|
| 244 |
if st.session_state.regerar and len(st.session_state.db[st.session_state.current_chat]["messages"]) > 0:
|
| 245 |
texto_anterior = st.session_state.db[st.session_state.current_chat]["messages"][-1]["content"]
|
| 246 |
-
|
|
|
|
| 247 |
st.session_state.regerar = False
|
| 248 |
else:
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display})
|
| 251 |
with st.chat_message("user"): st.markdown(mensagem_display)
|
| 252 |
|
|
@@ -261,10 +260,10 @@ if prompt or st.session_state.regerar:
|
|
| 261 |
if "Automático" in motor_real:
|
| 262 |
with st.spinner("Roteando..."):
|
| 263 |
tag_decisao = analisar_intencao_gerente(prompt, or_key)
|
| 264 |
-
if
|
| 265 |
elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
|
| 266 |
elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
|
| 267 |
-
elif "[VISAO]" in tag_decisao or
|
| 268 |
elif "[CODIGO]" in tag_decisao: motor_real = "14. Engenheiro Sênior (DeepSeek V4)"
|
| 269 |
elif "[ARQUIVISTA]" in tag_decisao: motor_real = "12. Arquivista (Mistral Nemo)"
|
| 270 |
elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)"
|
|
@@ -295,13 +294,13 @@ if prompt or st.session_state.regerar:
|
|
| 295 |
elif "Pesquisa" in motor_real:
|
| 296 |
with st.spinner("Pesquisando..."):
|
| 297 |
historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 298 |
-
if
|
| 299 |
try:
|
| 300 |
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()
|
| 301 |
ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 302 |
except Exception as e: st.error(f"Erro Groq: {e}")
|
| 303 |
|
| 304 |
-
# 3. Imagem
|
| 305 |
elif "Imagem" in motor_real:
|
| 306 |
with st.spinner("Desenhando..."):
|
| 307 |
try:
|
|
@@ -315,22 +314,24 @@ if prompt or st.session_state.regerar:
|
|
| 315 |
if url: st.image(url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
|
| 316 |
except Exception as e: st.error(f"Erro: {e}")
|
| 317 |
|
| 318 |
-
# 4. Visão
|
| 319 |
-
elif "Visão" in motor_real and
|
| 320 |
-
with st.spinner("Analisando
|
| 321 |
-
|
|
|
|
|
|
|
| 322 |
try:
|
| 323 |
-
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":
|
| 324 |
ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 325 |
except Exception as e: st.error(f"Erro visão: {e}")
|
| 326 |
|
| 327 |
-
# 5. Texto (Streaming Blindado)
|
| 328 |
else:
|
| 329 |
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."
|
| 330 |
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."
|
| 331 |
|
| 332 |
historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 333 |
-
if
|
| 334 |
|
| 335 |
ph = st.empty(); full = ""
|
| 336 |
|
|
|
|
| 7 |
import time
|
| 8 |
from datetime import datetime
|
| 9 |
|
|
|
|
| 10 |
try:
|
| 11 |
from huggingface_hub import HfApi, hf_hub_download
|
| 12 |
HF_HUB_AVAILABLE = True
|
|
|
|
| 56 |
# 2. ARQUITETURA DE DADOS E MEMÓRIA EM NUVEM
|
| 57 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 58 |
DB_FILE = "yukina_memoria_v3.json"
|
| 59 |
+
DATASET_ID = "Astarok/Yukina_Memoria"
|
| 60 |
|
| 61 |
MODEL_IDS = {
|
| 62 |
"🤖 Automático (Gerente Hermes)": "AUTO",
|
|
|
|
| 107 |
|
| 108 |
def load_db():
|
| 109 |
hf_token = os.getenv("HF_TOKEN")
|
|
|
|
|
|
|
| 110 |
if hf_token and HF_HUB_AVAILABLE:
|
| 111 |
try:
|
| 112 |
api = HfApi(token=hf_token)
|
| 113 |
api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
|
| 114 |
path = hf_hub_download(repo_id=DATASET_ID, filename=DB_FILE, repo_type="dataset", token=hf_token)
|
| 115 |
+
with open(path, "r", encoding="utf-8") as f: return json.load(f)
|
| 116 |
+
except Exception: pass
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
if os.path.exists(DB_FILE):
|
| 118 |
try:
|
| 119 |
+
with open(DB_FILE, "r", encoding="utf-8") as f: return json.load(f)
|
| 120 |
+
except: pass
|
|
|
|
|
|
|
|
|
|
| 121 |
return {"Nova Conversa": {"pinned": False, "messages": []}}
|
| 122 |
|
| 123 |
def save_db(db):
|
| 124 |
+
with open(DB_FILE, "w", encoding="utf-8") as f: json.dump(db, f, ensure_ascii=False, indent=4)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
hf_token = os.getenv("HF_TOKEN")
|
| 126 |
if hf_token and HF_HUB_AVAILABLE:
|
| 127 |
try:
|
| 128 |
api = HfApi(token=hf_token)
|
| 129 |
api.create_repo(repo_id=DATASET_ID, repo_type="dataset", private=True, exist_ok=True)
|
| 130 |
+
api.upload_file(path_or_fileobj=DB_FILE, path_in_repo=DB_FILE, repo_id=DATASET_ID, repo_type="dataset")
|
| 131 |
+
except Exception: pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
if "db" not in st.session_state: st.session_state.db = load_db()
|
| 134 |
if "current_chat" not in st.session_state: st.session_state.current_chat = list(st.session_state.db.keys())[0]
|
|
|
|
| 185 |
else: st.markdown(m["content"])
|
| 186 |
|
| 187 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 188 |
+
# 4. TOOLBAR INFERIOR (Múltiplos Arquivos)
|
| 189 |
# ═══════════════════════════════════════════════════════════════════════════
|
| 190 |
st.markdown("<br>", unsafe_allow_html=True)
|
| 191 |
|
|
|
|
| 193 |
|
| 194 |
with t_col1:
|
| 195 |
with st.popover("➕"):
|
| 196 |
+
# A MÁGICA DOS MÚLTIPLOS ARQUIVOS (accept_multiple_files=True)
|
| 197 |
+
upload_files = st.file_uploader("Upload", type=["png", "jpg", "jpeg", "txt", "csv", "json"], accept_multiple_files=True, label_visibility="collapsed")
|
| 198 |
with t_col2:
|
| 199 |
with st.popover("⚙️"):
|
| 200 |
st.session_state.modelo_selecionado = st.radio("MOTOR:", list(MODEL_IDS.keys()), index=list(MODEL_IDS.keys()).index(st.session_state.modelo_selecionado))
|
|
|
|
| 217 |
|
| 218 |
prompt = st.chat_input("Peça à Yukina...")
|
| 219 |
|
| 220 |
+
# Processamento de Múltiplos Arquivos em Lote
|
| 221 |
conteudo_arquivo = ""
|
| 222 |
+
nomes_arquivos = []
|
| 223 |
+
imagens_b64 = []
|
| 224 |
+
|
| 225 |
+
if upload_files:
|
| 226 |
+
for f in upload_files:
|
| 227 |
+
nomes_arquivos.append(f.name)
|
| 228 |
+
if f.name.endswith(('.txt', '.csv', '.json')):
|
| 229 |
+
conteudo_arquivo += f"\n\n--- Conteúdo de: {f.name} ---\n" + f.getvalue().decode("utf-8")
|
| 230 |
+
elif f.name.endswith(('.png', '.jpg', '.jpeg')):
|
| 231 |
+
imagens_b64.append(base64.b64encode(f.read()).decode())
|
| 232 |
+
|
| 233 |
+
tem_texto = len(conteudo_arquivo) > 0
|
| 234 |
+
tem_imagem = len(imagens_b64) > 0
|
| 235 |
|
| 236 |
if prompt or st.session_state.regerar:
|
| 237 |
if st.session_state.regerar and len(st.session_state.db[st.session_state.current_chat]["messages"]) > 0:
|
| 238 |
texto_anterior = st.session_state.db[st.session_state.current_chat]["messages"][-1]["content"]
|
| 239 |
+
# Filtro inteligente para pegar só o prompt caso tenham havido arquivos na mensagem original
|
| 240 |
+
prompt = texto_anterior.split("\n\n\n", 1)[-1] if "📄 **Arquivos enviados:**" in texto_anterior else texto_anterior
|
| 241 |
st.session_state.regerar = False
|
| 242 |
else:
|
| 243 |
+
if nomes_arquivos:
|
| 244 |
+
arquivos_str = ", ".join([f"`{n}`" for n in nomes_arquivos])
|
| 245 |
+
mensagem_display = f"📄 **Arquivos enviados:** {arquivos_str}\n\n\n{prompt}"
|
| 246 |
+
else:
|
| 247 |
+
mensagem_display = prompt
|
| 248 |
+
|
| 249 |
st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "user", "content": mensagem_display})
|
| 250 |
with st.chat_message("user"): st.markdown(mensagem_display)
|
| 251 |
|
|
|
|
| 260 |
if "Automático" in motor_real:
|
| 261 |
with st.spinner("Roteando..."):
|
| 262 |
tag_decisao = analisar_intencao_gerente(prompt, or_key)
|
| 263 |
+
if tem_texto: motor_real = "12. Arquivista (Mistral Nemo)"
|
| 264 |
elif "[IMAGEM]" in tag_decisao: motor_real = "9. Imagem (Flux 2 Pro)"
|
| 265 |
elif "[VIDEO]" in tag_decisao: motor_real = "16. Vídeo (Kling V1.5)"
|
| 266 |
+
elif "[VISAO]" in tag_decisao or tem_imagem: motor_real = "7. Visão Omni (MiMo V2)"
|
| 267 |
elif "[CODIGO]" in tag_decisao: motor_real = "14. Engenheiro Sênior (DeepSeek V4)"
|
| 268 |
elif "[ARQUIVISTA]" in tag_decisao: motor_real = "12. Arquivista (Mistral Nemo)"
|
| 269 |
elif "[PESQUISA]" in tag_decisao: motor_real = "2. Pesquisa (Groq Llama 3.3)"
|
|
|
|
| 294 |
elif "Pesquisa" in motor_real:
|
| 295 |
with st.spinner("Pesquisando..."):
|
| 296 |
historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 297 |
+
if tem_texto: historico[-1]["content"] = f"DOCUMENTO(S) ANEXADOS:\n{conteudo_arquivo}\n\nPEDIDO DO USUÁRIO:\n{prompt}"
|
| 298 |
try:
|
| 299 |
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()
|
| 300 |
ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 301 |
except Exception as e: st.error(f"Erro Groq: {e}")
|
| 302 |
|
| 303 |
+
# 3. Imagem (Flux)
|
| 304 |
elif "Imagem" in motor_real:
|
| 305 |
with st.spinner("Desenhando..."):
|
| 306 |
try:
|
|
|
|
| 314 |
if url: st.image(url); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": "Arte gerada.", "image_url": url})
|
| 315 |
except Exception as e: st.error(f"Erro: {e}")
|
| 316 |
|
| 317 |
+
# 4. Visão (Análise de Múltiplas Imagens)
|
| 318 |
+
elif "Visão" in motor_real and tem_imagem:
|
| 319 |
+
with st.spinner("Analisando imagens..."):
|
| 320 |
+
content_list = [{"type": "text", "text": prompt}]
|
| 321 |
+
for img_b64 in imagens_b64:
|
| 322 |
+
content_list.append({"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}})
|
| 323 |
try:
|
| 324 |
+
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()
|
| 325 |
ans = res['choices'][0]['message']['content']; st.markdown(ans); st.session_state.db[st.session_state.current_chat]["messages"].append({"role": "assistant", "content": ans})
|
| 326 |
except Exception as e: st.error(f"Erro visão: {e}")
|
| 327 |
|
| 328 |
+
# 5. Texto (Streaming Blindado com Múltiplos Arquivos)
|
| 329 |
else:
|
| 330 |
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."
|
| 331 |
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."
|
| 332 |
|
| 333 |
historico = [{"role": m["role"], "content": m["content"]} for m in mensagens if "image_url" not in m and "video_url" not in m]
|
| 334 |
+
if tem_texto and len(historico) > 0: historico[-1]["content"] = f"DOCUMENTO(S):\n{conteudo_arquivo}\n\nO QUE FAZER:\n{prompt}"
|
| 335 |
|
| 336 |
ph = st.empty(); full = ""
|
| 337 |
|