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:
+87
-3
@@ -13,7 +13,7 @@ import socket
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import requests
|
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_HOST = os.getenv("JEAN_WEB_HOST", "192.168.1.11")
|
||||||
JEAN_WEB_PORT = int(os.getenv("JEAN_WEB_PORT", "8090"))
|
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"
|
ENABLE_WOL = os.getenv("ENABLE_WOL", "false").lower() == "true"
|
||||||
TARGET_MAC = os.getenv("TARGET_MAC", os.getenv("PC_GAMING_MAC", ""))
|
TARGET_MAC = os.getenv("TARGET_MAC", os.getenv("PC_GAMING_MAC", ""))
|
||||||
BROADCAST_IP = os.getenv("BROADCAST_IP", "192.168.1.255")
|
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()
|
wol_in_progress = threading.Lock()
|
||||||
jean_alive_cache = {"alive": False, "timestamp": 0}
|
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")
|
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):
|
def get_ollama_models(force_refresh=False):
|
||||||
"""Récupère la liste des modèles depuis Ollama"""
|
"""Récupère la liste des modèles depuis Ollama"""
|
||||||
if not ENABLE_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:
|
if model_id in jean_preset_ids or model_id in jean_names:
|
||||||
internal_id = jean_name_to_id.get(model_id, model_id)
|
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"):
|
if not ensure_vram_for_backend("jean"):
|
||||||
return jsonify({"error": "Failed to prepare VRAM for Jean"}), 503
|
return jsonify({"error": "Failed to prepare VRAM for Jean"}), 503
|
||||||
|
|||||||
Reference in New Issue
Block a user