feat(voice-client): 双层配置持久化、精简手术号 UI 与 WS/服务端排查日志
- machine_config:系统级 + 用户级 voice_client.json 合并,界面失焦保存至用户目录 - 移除「当前手术号」表单项与占位文案;指派后仅在窗口标题显示手术号 - WebSocket 连接日志附带绑定/开录路径排查说明 - 开录未推送时服务端 WARNING(无站点绑定或 camera_ids 不匹配) - 测试、README、.env.example 同步 Made-with: Cursor
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
@@ -76,7 +75,12 @@ class VoiceAssignmentListener:
|
||||
try:
|
||||
|
||||
def _on_open(_ws: Any) -> None:
|
||||
logger.info("WebSocket 已连接,等待服务端 voice_assignment 消息")
|
||||
logger.info("WebSocket 已连接 terminal_id={!r}", self._terminal_id)
|
||||
logger.info(
|
||||
"若开录后仍无 voice_assignment:核对本机 ID 与 OR_SITE_CONFIG「voice_or_room_bindings」、"
|
||||
"开录请求 camera_ids 能否解析到该终端;开录须 POST /client/surgeries/start "
|
||||
"或(联调)POST /internal/demo/orchestrate-and-start。"
|
||||
)
|
||||
|
||||
def _on_close(_ws: Any, close_status_code: Any, close_msg: Any) -> None:
|
||||
logger.warning(
|
||||
@@ -137,7 +141,3 @@ class VoiceAssignmentListener:
|
||||
self._on_end(sid)
|
||||
else:
|
||||
logger.debug("忽略 voice_assignment action={!r}", action)
|
||||
|
||||
|
||||
def default_terminal_id_from_env() -> str:
|
||||
return (os.environ.get("VOICE_TERMINAL_ID") or "").strip()
|
||||
|
||||
104
voice_confirmation_client/core/machine_config.py
Normal file
104
voice_confirmation_client/core/machine_config.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""语音客户端配置:系统级(运维下发)+ 用户级(界面保存,覆盖同名字段)。
|
||||
|
||||
- ``VOICE_CLIENT_MACHINE_CONFIG_FILE``:仅改变**系统级**配置文件路径(测试或定制)。
|
||||
- ``VOICE_CLIENT_USER_CONFIG_FILE``:仅改变**用户级**配置文件路径(测试或定制)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from loguru import logger
|
||||
|
||||
_DEFAULT_HTTP_BASE = "http://127.0.0.1:38080"
|
||||
_CONFIG_FILENAME = "voice_client.json"
|
||||
|
||||
|
||||
def machine_config_file_path() -> Path:
|
||||
"""系统级配置:运维部署;只读亦可。"""
|
||||
override = (os.environ.get("VOICE_CLIENT_MACHINE_CONFIG_FILE") or "").strip()
|
||||
if override:
|
||||
return Path(override).expanduser()
|
||||
if sys.platform == "win32":
|
||||
base = os.environ.get("PROGRAMDATA", r"C:\ProgramData")
|
||||
return Path(base) / "OperationRoomMonitor" / _CONFIG_FILENAME
|
||||
if sys.platform == "darwin":
|
||||
return (
|
||||
Path("/Library/Application Support/OperationRoomMonitor")
|
||||
/ _CONFIG_FILENAME
|
||||
)
|
||||
return Path("/etc/operation-room-monitor") / _CONFIG_FILENAME
|
||||
|
||||
|
||||
def user_voice_client_config_path() -> Path:
|
||||
"""用户级配置:当前登录用户可写,界面编辑后保存到此。"""
|
||||
override = (os.environ.get("VOICE_CLIENT_USER_CONFIG_FILE") or "").strip()
|
||||
if override:
|
||||
return Path(override).expanduser()
|
||||
if sys.platform == "win32":
|
||||
base = os.environ.get("LOCALAPPDATA", str(Path.home() / "AppData/Local"))
|
||||
return Path(base) / "OperationRoomMonitor" / _CONFIG_FILENAME
|
||||
if sys.platform == "darwin":
|
||||
return (
|
||||
Path.home() / "Library/Application Support/OperationRoomMonitor" / _CONFIG_FILENAME
|
||||
)
|
||||
return Path.home() / ".config/operation-room-monitor" / _CONFIG_FILENAME
|
||||
|
||||
|
||||
def _read_json_object(path: Path) -> dict[str, Any]:
|
||||
if not path.is_file():
|
||||
return {}
|
||||
try:
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
data = json.loads(raw)
|
||||
except OSError as exc:
|
||||
logger.warning("无法读取语音客户端配置 {}: {}", path, exc)
|
||||
return {}
|
||||
except json.JSONDecodeError as exc:
|
||||
logger.warning("语音客户端配置 JSON 无效 {}: {}", path, exc)
|
||||
return {}
|
||||
if not isinstance(data, dict):
|
||||
logger.warning("语音客户端配置须为 JSON 对象: {}", path)
|
||||
return {}
|
||||
return data
|
||||
|
||||
|
||||
def load_voice_client_config() -> dict[str, Any]:
|
||||
"""合并系统配置与用户配置;同键时用户覆盖系统。"""
|
||||
system = _read_json_object(machine_config_file_path())
|
||||
user = _read_json_object(user_voice_client_config_path())
|
||||
merged: dict[str, Any] = dict(system)
|
||||
merged.update(user)
|
||||
return merged
|
||||
|
||||
|
||||
def save_user_voice_client_config(*, voice_terminal_id: str, http_base_url: str) -> None:
|
||||
"""将当前界面上的连接参数写入用户级配置文件。"""
|
||||
path = user_voice_client_config_path()
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
payload = {
|
||||
"voice_terminal_id": voice_terminal_id.strip(),
|
||||
"http_base_url": http_base_url.strip().rstrip("/"),
|
||||
}
|
||||
path.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def voice_terminal_id_from_config(file_data: dict[str, Any]) -> str:
|
||||
"""合并后 dict 中的 ``voice_terminal_id``;缺省为空串。"""
|
||||
v = file_data.get("voice_terminal_id")
|
||||
return str(v).strip() if v is not None else ""
|
||||
|
||||
|
||||
def http_base_url_from_config(file_data: dict[str, Any]) -> str:
|
||||
"""合并后 dict 中的 ``http_base_url``;缺省为 ``http://127.0.0.1:38080``。"""
|
||||
v = file_data.get("http_base_url")
|
||||
if v is not None and str(v).strip():
|
||||
return str(v).strip().rstrip("/")
|
||||
return _DEFAULT_HTTP_BASE
|
||||
Reference in New Issue
Block a user