fix: P0 fixes — VAD hint routing, session leak, YAML regex, encoding
- Add hint routing regex to VAD auto-transcribe path (was bypassed) - Clean up audio sessions on WebSocket disconnect (memory leak) - Fix YAML extraction regex (double-escaped backslashes) - Fix corrupted markdown output (literal \\n) - Add UTF-8 encoding to export_md write_text() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -310,7 +310,7 @@ def to_float(value: Any, fallback: float) -> float:
|
||||
|
||||
|
||||
def extract_yaml(payload: str) -> str:
|
||||
block_match = re.search(r"```ya?ml\\s*\\n([\\s\\S]*?)```", payload, re.IGNORECASE)
|
||||
block_match = re.search(r"```ya?ml\s*\n([\s\S]*?)```", payload, re.IGNORECASE)
|
||||
if block_match:
|
||||
return block_match.group(1).strip()
|
||||
|
||||
@@ -420,11 +420,11 @@ def build_printables_plan(scenario_id: str, title: str, selected: list[str] | No
|
||||
|
||||
yaml_text = safe_dump(manifest_out, sort_keys=False, allow_unicode=True).strip()
|
||||
markdown = (
|
||||
f"# Pack imprimables — {manifest_out['title']}\\n\\n"
|
||||
f"- Scenario: {manifest_out['scenario_id']}\\n"
|
||||
f"- Items: {len(filtered)}\\n\\n"
|
||||
+ "\\n".join(f"- {entry.get('id')} ({entry.get('category')})" for entry in filtered)
|
||||
+ "\\n\\nGénéré via gateway IA locale.\\n"
|
||||
f"# Pack imprimables — {manifest_out['title']}\n\n"
|
||||
f"- Scenario: {manifest_out['scenario_id']}\n"
|
||||
f"- Items: {len(filtered)}\n\n"
|
||||
+ "\n".join(f"- {entry.get('id')} ({entry.get('category')})" for entry in filtered)
|
||||
+ "\n\nGénéré via gateway IA locale.\n"
|
||||
)
|
||||
return yaml_text, markdown
|
||||
|
||||
|
||||
@@ -358,6 +358,7 @@ async def voice_websocket(websocket: WebSocket, token: str = ""):
|
||||
await websocket.accept()
|
||||
client = f"{websocket.client.host}:{websocket.client.port}" if websocket.client else "unknown"
|
||||
logger.info("Client connected: %s", client)
|
||||
device_id = None
|
||||
|
||||
try:
|
||||
# --- Handshake ---
|
||||
@@ -393,6 +394,8 @@ async def voice_websocket(websocket: WebSocket, token: str = ""):
|
||||
|
||||
except WebSocketDisconnect:
|
||||
logger.info("Client disconnected: %s", client)
|
||||
if device_id and device_id in _audio_sessions:
|
||||
del _audio_sessions[device_id]
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Handshake timeout: %s", client)
|
||||
if websocket.client_state == WebSocketState.CONNECTED:
|
||||
@@ -505,8 +508,15 @@ async def _handle_audio(ws: WebSocket, frame: bytes, device_id: str):
|
||||
logger.info("[%s] Auto-STT (%.1fs): %s", device_id, t_stt, transcription[:80])
|
||||
await ws.send_json({"type": "stt", "text": transcription})
|
||||
|
||||
# Feed into LLM pipeline
|
||||
response = await _query_llm(transcription)
|
||||
# Feed into LLM pipeline — detect hint routing
|
||||
hint_match = re.match(r'^\[HINT:(\w+):(\d)\]\s*(.*)', transcription)
|
||||
if hint_match:
|
||||
puzzle_id = hint_match.group(1)
|
||||
hint_level = int(hint_match.group(2))
|
||||
question = hint_match.group(3) or "Give me a hint"
|
||||
response = await _query_hints(puzzle_id, question, hint_level, device_id)
|
||||
else:
|
||||
response = await _query_llm(transcription)
|
||||
await _send_tts_response(ws, response, device_id)
|
||||
else:
|
||||
logger.warning("[%s] Auto-STT returned empty", device_id)
|
||||
|
||||
@@ -412,7 +412,7 @@ def build_brief_doc(model: dict) -> str:
|
||||
|
||||
def write_file(path: Path, content: str) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def export_files(scenario_path: Path, out_path: Path | None = None) -> None:
|
||||
|
||||
Reference in New Issue
Block a user