05-sep: [feat] menu wizard en bot OpenCode (/menu), menu IDD cierra al elegir + updates en hilos, borrar tarea NAS (0%) desde panel
This commit is contained in:
@@ -53,6 +53,7 @@ bloqueos = {} # chat_id -> True (procesando mensaje)
|
||||
sesiones = {} # chat_id -> {'repo': slug, 'session': id, 'model': obj|None}
|
||||
repos_cache = [] # lista de {'slug','dir'}
|
||||
sentidos = {} # chat_id -> [message_id...] enviados por el bot (para /limpiar)
|
||||
menu_msg = {} # chat_id -> message_id del menu abierto
|
||||
|
||||
|
||||
def log(linea):
|
||||
@@ -140,10 +141,14 @@ def teclado(filas):
|
||||
|
||||
|
||||
def tg_con_teclado(chat_id, texto, filas):
|
||||
_registrar(chat_id, tg("sendMessage",
|
||||
{"chat_id": chat_id, "text": texto,
|
||||
"parse_mode": "HTML",
|
||||
"reply_markup": json.dumps(teclado(filas))}))
|
||||
resp = tg("sendMessage",
|
||||
{"chat_id": chat_id, "text": texto, "parse_mode": "HTML",
|
||||
"reply_markup": json.dumps(teclado(filas))})
|
||||
_registrar(chat_id, resp)
|
||||
try:
|
||||
return (resp.get("result") or {}).get("message_id")
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def editar(chat_id, mid, texto, filas=None):
|
||||
@@ -443,6 +448,7 @@ def cmd_ayuda(chat):
|
||||
"<b>🤖 OpenCode por Telegram</b>\n"
|
||||
"Escribe cualquier cosa y lo hará el agente en el repo activo.\n\n"
|
||||
"<b>Comandos:</b>\n"
|
||||
"<code>/menu</code> — menú interactivo (modelo, proyecto, cerrar)\n"
|
||||
"<code>/nueva [texto]</code> — sesión nueva (olvida el contexto)\n"
|
||||
"<code>/salir</code> — cerrar la sesión actual (la próxima arranca limpia)\n"
|
||||
"<code>/proyecto</code> / <code>/defecto</code> — elegir repo (queda por defecto)\n"
|
||||
@@ -565,6 +571,154 @@ def cmd_modelo(chat):
|
||||
tg_con_teclado(chat, "🧠 Elige modelo (se aplica a partir del próximo mensaje):", filas)
|
||||
|
||||
|
||||
# ------------------------------------------------------------ menu wizard --
|
||||
|
||||
def _wiz_resumen(chat):
|
||||
slug, _ = repo_activo()
|
||||
st = sesiones.get(str(chat)) or {}
|
||||
modelo = (st.get("model") or {}).get("name") or "por defecto"
|
||||
return (f"🧭 <b>Menú OpenCode</b>\n"
|
||||
f"🧠 Modelo: <b>{modelo}</b>\n"
|
||||
f"📂 Proyecto: <b>{slug}</b>\n\n"
|
||||
"Configura y pulsa <b>Cerrar</b>.")
|
||||
|
||||
|
||||
def _wiz_raiz_filas():
|
||||
return [
|
||||
[{"text": "🧠 Elegir modelo", "callback_data": "w:modelo"},
|
||||
{"text": "📂 Elegir proyecto", "callback_data": "w:proyecto"}],
|
||||
[{"text": "✅ Cerrar menú", "callback_data": "w:cerrar"},
|
||||
{"text": "🚪 Salir", "callback_data": "w:salir"}],
|
||||
]
|
||||
|
||||
|
||||
def _quitar_teclado(chat, mid):
|
||||
try:
|
||||
requests.post(f"{API}/editMessageReplyMarkup",
|
||||
data={"chat_id": chat, "message_id": mid}, timeout=10)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def cmd_menu(chat):
|
||||
mid = tg_con_teclado(chat, _wiz_resumen(chat), _wiz_raiz_filas())
|
||||
if mid:
|
||||
menu_msg[str(chat)] = mid
|
||||
|
||||
|
||||
def _wiz_mostrar(chat, mid, texto, filas):
|
||||
try:
|
||||
requests.post(f"{API}/editMessageText",
|
||||
data={"chat_id": chat, "message_id": mid, "text": texto,
|
||||
"parse_mode": "HTML",
|
||||
"reply_markup": json.dumps(teclado(filas))},
|
||||
timeout=15)
|
||||
except Exception as e:
|
||||
log(f"wiz edit error: {e}")
|
||||
|
||||
|
||||
def _cambiar_repo_silencioso(chat, slug):
|
||||
ruta = os.path.join(GH, slug)
|
||||
if not os.path.isdir(os.path.join(ruta, ".git")):
|
||||
return "Repo no válido"
|
||||
try:
|
||||
with open(REPO_ENV, "w") as f:
|
||||
f.write(f"REPO_DIR={ruta}\n")
|
||||
subprocess.run(["systemctl", "restart", "opencode-serve"],
|
||||
timeout=60, capture_output=True)
|
||||
except Exception as e:
|
||||
log(f"cambiar_repo_silencioso error: {e}")
|
||||
return str(e)
|
||||
for _ in range(25):
|
||||
if health_ok():
|
||||
break
|
||||
time.sleep(1)
|
||||
st = sesiones.get(str(chat)) or {}
|
||||
sid, err = crear_sesion()
|
||||
if sid:
|
||||
sesiones[str(chat)] = {"repo": slug, "session": sid,
|
||||
"model": st.get("model")}
|
||||
guardar_estado()
|
||||
return None
|
||||
return str(err)
|
||||
|
||||
|
||||
def manejar_wizard(data, chat, mid):
|
||||
if data == "w:cerrar":
|
||||
_quitar_teclado(chat, mid)
|
||||
return True
|
||||
if data == "w:salir":
|
||||
_quitar_teclado(chat, mid)
|
||||
return True
|
||||
if data == "w:modelo":
|
||||
modelos = listar_modelos()
|
||||
if not modelos:
|
||||
_wiz_mostrar(chat, mid, "No hay modelos disponibles.", _wiz_raiz_filas())
|
||||
return True
|
||||
st = sesiones.get(str(chat)) or {}
|
||||
actual = (st.get("model") or {}).get("id")
|
||||
filas = []
|
||||
for m in modelos[:24]:
|
||||
marca = " ✅" if m["id"] == actual else ""
|
||||
filas.append([{"text": (m["name"] + marca)[:50],
|
||||
"callback_data": f"wm:{m['providerID']}:{m['id']}"}])
|
||||
filas.append([{"text": "↩️ Por defecto", "callback_data": "wm:def"},
|
||||
{"text": "⬅️ Atrás", "callback_data": "w:root"}])
|
||||
_wiz_mostrar(chat, mid, "🧠 Elige modelo:", filas)
|
||||
return True
|
||||
if data == "w:proyecto":
|
||||
lista = listar_repos()
|
||||
if not lista:
|
||||
_wiz_mostrar(chat, mid, "No hay repos.", _wiz_raiz_filas())
|
||||
return True
|
||||
slug_act, _ = repo_activo()
|
||||
filas = []
|
||||
for i, r in enumerate(lista[:20]):
|
||||
marca = " ✅" if r["slug"] == slug_act else ""
|
||||
filas.append([{"text": r["slug"] + marca,
|
||||
"callback_data": f"wr:{i}"}])
|
||||
filas.append([{"text": "⬅️ Atrás", "callback_data": "w:root"}])
|
||||
_wiz_mostrar(chat, mid, "📂 Elige proyecto (reinicia el serve):", filas)
|
||||
return True
|
||||
if data == "w:root":
|
||||
_wiz_mostrar(chat, mid, _wiz_resumen(chat), _wiz_raiz_filas())
|
||||
return True
|
||||
if data.startswith("wm:"):
|
||||
if data == "wm:def":
|
||||
st = sesiones.get(str(chat)) or {}
|
||||
st["model"] = None
|
||||
sesiones[str(chat)] = st
|
||||
guardar_estado()
|
||||
else:
|
||||
resto = data[3:]
|
||||
provider, mid_modelo = resto.split(":", 1)
|
||||
elegido = next((m for m in listar_modelos()
|
||||
if m["providerID"] == provider
|
||||
and m["id"] == mid_modelo), None)
|
||||
if elegido:
|
||||
st = sesiones.get(str(chat)) or {}
|
||||
st["model"] = elegido
|
||||
sesiones[str(chat)] = st
|
||||
guardar_estado()
|
||||
_wiz_mostrar(chat, mid, _wiz_resumen(chat), _wiz_raiz_filas())
|
||||
return True
|
||||
if data.startswith("wr:"):
|
||||
try:
|
||||
idx = int(data[3:])
|
||||
r = repos_cache[idx]
|
||||
except Exception:
|
||||
return True
|
||||
_wiz_mostrar(chat, mid, f"Cambiando a <b>{r['slug']}</b>…",
|
||||
[[{"text": "⏳", "callback_data": "w:none"}]])
|
||||
err = _cambiar_repo_silencioso(chat, r["slug"])
|
||||
if err:
|
||||
_wiz_mostrar(chat, mid, f"⚠️ {err}", _wiz_raiz_filas())
|
||||
else:
|
||||
_wiz_mostrar(chat, mid, _wiz_resumen(chat), _wiz_raiz_filas())
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def responder_callback(callback):
|
||||
cid = callback.get("id")
|
||||
data = callback.get("data") or ""
|
||||
@@ -572,6 +726,14 @@ def responder_callback(callback):
|
||||
chat = (msg.get("chat") or {}).get("id")
|
||||
mid = msg.get("message_id")
|
||||
tg("answerCallbackQuery", {"callback_query_id": cid})
|
||||
if ALLOWED and str(chat) not in ALLOWED.split(","):
|
||||
return
|
||||
if data.startswith(("w:", "wm:", "wr:")):
|
||||
try:
|
||||
manejar_wizard(data, chat, mid)
|
||||
except Exception as e:
|
||||
log(f"wizard error: {e}")
|
||||
return
|
||||
if not data.startswith("cb:"):
|
||||
return
|
||||
partes = data.split(":")
|
||||
@@ -625,6 +787,8 @@ def procesar(texto, chat):
|
||||
resto = partes[1].strip() if len(partes) > 1 else ""
|
||||
if comando in ("/start", "/ayuda", "/help"):
|
||||
cmd_ayuda(chat)
|
||||
elif comando == "/menu":
|
||||
cmd_menu(chat)
|
||||
elif comando in ("/nueva", "/nuevo"):
|
||||
cmd_nueva(chat, resto)
|
||||
elif comando in ("/salir", "/fin", "/exit"):
|
||||
@@ -660,7 +824,9 @@ def main():
|
||||
offset = upd["update_id"] + 1
|
||||
guardar_offset(offset)
|
||||
if upd.get("callback_query"):
|
||||
responder_callback(upd["callback_query"])
|
||||
threading.Thread(target=responder_callback,
|
||||
args=(upd["callback_query"],),
|
||||
daemon=True).start()
|
||||
continue
|
||||
msg = upd.get("message")
|
||||
if not msg:
|
||||
|
||||
Reference in New Issue
Block a user