haujetzhao--capswriter-offline
182 行
5.3 KiB
Python
182 行
5.3 KiB
Python
# coding: utf-8
|
|
"""
|
|
客户端状态管理模块
|
|
|
|
提供 ClientState 类用于管理客户端的全局状态。
|
|
使用 dataclass 提供类型安全和清晰的状态定义。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import time
|
|
from dataclasses import dataclass, field
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Optional, Dict, Any
|
|
|
|
if TYPE_CHECKING:
|
|
import sounddevice as sd
|
|
from websockets.legacy.client import WebSocketClientProtocol
|
|
from .app import CapsWriterClient
|
|
|
|
from rich.console import Console
|
|
from rich.theme import Theme
|
|
|
|
from . import logger
|
|
|
|
|
|
# 配置 Rich console
|
|
_theme = Theme({
|
|
'markdown.code': 'cyan',
|
|
'markdown.item.number': 'yellow'
|
|
})
|
|
console = Console(highlight=False, soft_wrap=True, theme=_theme)
|
|
|
|
|
|
@dataclass
|
|
class ClientState:
|
|
"""
|
|
客户端运行状态
|
|
|
|
管理客户端运行过程中的所有共享状态,包括事件循环、消息队列、
|
|
WebSocket 连接、音频流和录音状态等。
|
|
|
|
Attributes:
|
|
loop: asyncio 事件循环
|
|
queue_in: 音频数据输入队列
|
|
queue_out: 处理结果输出队列(保留)
|
|
websocket: WebSocket 客户端连接
|
|
stream: 音频输入流
|
|
recording: 是否正在录音
|
|
recording_start_time: 录音开始时间戳
|
|
audio_files: 任务ID到音频文件路径的映射
|
|
last_recognition_text: 最近一次识别的最终文本(热词替换后),供"添加纠错记录"使用
|
|
"""
|
|
|
|
queue_in: asyncio.Queue = field(default_factory=asyncio.Queue)
|
|
queue_out: asyncio.Queue = field(default_factory=asyncio.Queue)
|
|
websocket: Optional[WebSocketClientProtocol] = None
|
|
stream: Optional[sd.InputStream] = None
|
|
app: Optional[CapsWriterClient] = None
|
|
|
|
recording: bool = False
|
|
recording_start_time: float = 0.0
|
|
audio_files: Dict[str, Path] = field(default_factory=dict)
|
|
|
|
# 最近一次识别结果(用于手动添加纠错记录)
|
|
last_recognition_text: Optional[str] = None
|
|
|
|
# 最近一次输出内容(如果是 LLM 润色,则是润色结果;否则是原始识别结果)
|
|
last_output_text: Optional[str] = None
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
|
"""
|
|
重置状态
|
|
|
|
清理所有状态,关闭连接和流。用于重新初始化或退出时清理。
|
|
"""
|
|
logger.debug("正在重置客户端状态...")
|
|
|
|
# 关闭 WebSocket 连接
|
|
ws = self.websocket
|
|
if ws is not None:
|
|
try:
|
|
if not ws.closed and self.app and self.app.loop and self.app.loop.is_running():
|
|
asyncio.run_coroutine_threadsafe(ws.close(), self.app.loop)
|
|
except Exception:
|
|
pass
|
|
self.websocket = None
|
|
|
|
# 关闭音频流
|
|
if self.stream is not None:
|
|
try:
|
|
self.stream.close()
|
|
logger.debug("音频流已关闭")
|
|
except Exception:
|
|
pass
|
|
self.stream = None
|
|
|
|
# 重置其他状态
|
|
self.recording = False
|
|
self.recording_start_time = 0.0
|
|
self.audio_files.clear()
|
|
|
|
logger.debug("客户端状态重置完成")
|
|
|
|
def start_recording(self, start_time: float) -> None:
|
|
"""
|
|
开始录音
|
|
|
|
Args:
|
|
start_time: 录音开始的时间戳
|
|
"""
|
|
self.recording = True
|
|
self.recording_start_time = start_time
|
|
logger.debug(f"录音状态已更新: recording=True, start_time={start_time:.2f}")
|
|
|
|
def stop_recording(self) -> float:
|
|
"""
|
|
停止录音
|
|
|
|
Returns:
|
|
录音持续时间(秒)
|
|
"""
|
|
duration = 0.0
|
|
if self.recording_start_time > 0:
|
|
duration = time.time() - self.recording_start_time
|
|
|
|
self.recording = False
|
|
self.recording_start_time = 0.0
|
|
logger.debug(f"录音状态已更新: recording=False, duration={duration:.2f}s")
|
|
return duration
|
|
|
|
@property
|
|
def is_connected(self) -> bool:
|
|
"""检查 WebSocket 是否已连接"""
|
|
if self.websocket is None:
|
|
return False
|
|
try:
|
|
return not self.websocket.closed
|
|
except AttributeError:
|
|
return self.websocket is not None
|
|
|
|
def register_audio_file(self, task_id: str, file_path: Path) -> None:
|
|
"""
|
|
注册音频文件
|
|
|
|
Args:
|
|
task_id: 任务ID
|
|
file_path: 音频文件路径
|
|
"""
|
|
self.audio_files[task_id] = file_path
|
|
logger.debug(f"注册音频文件: task_id={task_id}, path={file_path}")
|
|
|
|
def pop_audio_file(self, task_id: str) -> Optional[Path]:
|
|
"""
|
|
获取并移除音频文件路径
|
|
|
|
Args:
|
|
task_id: 任务ID
|
|
|
|
Returns:
|
|
音频文件路径,如果不存在则返回 None
|
|
"""
|
|
file_path = self.audio_files.pop(task_id, None)
|
|
if file_path:
|
|
logger.debug(f"获取音频文件: task_id={task_id}, path={file_path}")
|
|
return file_path
|
|
|
|
def set_output_text(self, text: str) -> None:
|
|
"""
|
|
设置最近一次输出文本
|
|
|
|
Args:
|
|
text: 输出文本内容
|
|
"""
|
|
self.last_output_text = text
|
|
|
|
|
|
|