jean-router: streaming de messages d'attente pendant le WoL

- Quand le PC est eteint/en veille, stream immediat de messages
- 'PC de jeu se reveille...' → 'Magic packet envoye...' → 'Demarrage X%...'
- Une fois pret, forward la vraie reponse en stream
- Plus de blocage silencieux, l'utilisateur voit la progression
This commit is contained in:
Folzo
2026-08-01 19:27:54 +02:00
parent f2bcfb98e3
commit 06285b8d4f
+87 -3
View File
@@ -13,7 +13,7 @@ import socket
from typing import Optional
import requests
from flask import Flask, request, Response, jsonify
from flask import Flask, request, Response, jsonify, stream_with_context
JEAN_WEB_HOST = os.getenv("JEAN_WEB_HOST", "192.168.1.11")
JEAN_WEB_PORT = int(os.getenv("JEAN_WEB_PORT", "8090"))
@@ -34,7 +34,7 @@ OLLAMA_UNLOAD_TIMEOUT = int(os.getenv("OLLAMA_UNLOAD_TIMEOUT", "15"))
ENABLE_WOL = os.getenv("ENABLE_WOL", "false").lower() == "true"
TARGET_MAC = os.getenv("TARGET_MAC", os.getenv("PC_GAMING_MAC", ""))
BROADCAST_IP = os.getenv("BROADCAST_IP", "192.168.1.255")
WAKE_TIMEOUT = int(os.getenv("WAKE_TIMEOUT", "120"))
WAKE_TIMEOUT = int(os.getenv("WAKE_TIMEOUT", "180"))
wol_in_progress = threading.Lock()
jean_alive_cache = {"alive": False, "timestamp": 0}
@@ -152,6 +152,83 @@ def wait_for_jean(trigger_wol=True):
log.error(f"[WoL] Jean ne repond pas apres {WAKE_TIMEOUT}s")
def stream_wake_then_forward(upstream_url, data, headers):
"""Stream des messages d'attente pendant le reveil, puis forward la vraie reponse"""
import uuid
chat_id = f"chatcmpl-{uuid.uuid4().hex[:29]}"
def wake_chunk(content):
return (
f'data: {{"id":"{chat_id}","object":"chat.completion.chunk",'
f'"created":{int(time.time())},"model":"wake","choices":['
f'{{"delta":{{"content":"{content} "}},"index":0}}]}}\n\n'
).encode()
def generate():
yield wake_chunk("🖥️ Le PC de jeu se réveille...")
if not jean_alive() and ENABLE_WOL:
with wol_in_progress:
if not jean_alive():
log.info("[WoL-Stream] Envoi WoL + attente...")
send_wol()
yield wake_chunk("⚡ Magic packet envoyé, démarrage en cours...")
deadline = time.time() + WAKE_TIMEOUT
last_pct = 0
while time.time() < deadline:
if jean_alive():
jean_alive_cache["timestamp"] = 0
yield wake_chunk("✅ PC allumé, chargement du modèle...")
break
pct = int((1 - (deadline - time.time()) / WAKE_TIMEOUT) * 100)
if pct >= last_pct + 10:
yield wake_chunk(f"⏳ Démarrage... {min(pct, 95)}%")
last_pct = pct
time.sleep(CHECK_INTERVAL)
else:
yield wake_chunk("❌ Le PC n'a pas répondu, réessayez.")
yield b"data: [DONE]\n\n"
return
# Attendre que Jean soit vraiment prêt (proxy + modele)
deadline = time.time() + SWITCH_TIMEOUT
while time.time() < deadline:
try:
r = requests.get(f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/health", timeout=2)
if r.ok and r.json().get("status") == "ok":
break
except Exception:
pass
yield wake_chunk(".")
time.sleep(2)
yield wake_chunk("🚀 Modèle prêt, voici la réponse :\n\n")
# Forward la vraie requête et streamer
try:
resp = requests.request(
method="POST", url=upstream_url, headers=headers,
json=data, stream=True, timeout=None,
)
for line in resp.iter_lines():
if line:
yield line + b"\n"
yield b"data: [DONE]\n\n"
except Exception as e:
yield wake_chunk(f"❌ Erreur: {str(e)[:100]}")
yield b"data: [DONE]\n\n"
return Response(
stream_with_context(generate()),
content_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no",
},
)
def get_ollama_models(force_refresh=False):
"""Récupère la liste des modèles depuis Ollama"""
if not ENABLE_OLLAMA:
@@ -761,7 +838,14 @@ def chat_completions():
if model_id in jean_preset_ids or model_id in jean_names:
internal_id = jean_name_to_id.get(model_id, model_id)
wait_for_jean(trigger_wol=True)
if not jean_alive() and ENABLE_WOL:
log.info("[WoL-Stream] PC down, demarrage stream d'attente...")
fwd_headers = dict(request.headers)
fwd_headers.pop("Host", None)
return stream_wake_then_forward(
f"http://{JEAN_PROXY_HOST}:{JEAN_PROXY_PORT}/v1/chat/completions",
data, fwd_headers
)
if not ensure_vram_for_backend("jean"):
return jsonify({"error": "Failed to prepare VRAM for Jean"}), 503