LANG = "zh_CN.GB2312"异常的解决

部署运行你感兴趣的模型镜像
 vi /root/.bashrc
再最底部加上
export LC_ALL=C

您可能感兴趣的与本文相关的镜像

Qwen3-8B

Qwen3-8B

文本生成
Qwen3

Qwen3 是 Qwen 系列中的最新一代大型语言模型,提供了一整套密集型和专家混合(MoE)模型。基于广泛的训练,Qwen3 在推理、指令执行、代理能力和多语言支持方面取得了突破性进展

""" 【通义千问 Qwen】API集成模块 用于意图理解和任务处理 """ import json import re import logging from database import config import dashscope from dashscope import Generation # --- 导入快捷日志工具 --- from Progress.utils.logger_utils import log_time, log_step, log_var, log_call from Progress.utils.logger_config import setup_logger # 确保 logger 已配置 # --- 初始化日志器 --- logger = logging.getLogger(__name__) # 使用模块名作为 logger 名称,便于区分来源 # --- API 配置 --- DASHSCOPE_API_KEY = config.api_key DASHSCOPE_MODEL = config.model class QWENAssistant: def __init__(self): if not DASHSCOPE_API_KEY: raise ValueError("缺少 DASHSCOPE_API_KEY,请检查配置文件") dashscope.api_key = DASHSCOPE_API_KEY self.model_name = DASHSCOPE_MODEL or 'qwen-max' logger.info(f"✅ QWENAssistant 初始化完成,使用模型: {self.model_name}") self.conversation_history = [] self.system_prompt = """ 你是一个智能语音控制助手,能够理解用户的语音指令并执行相应的任务。 你的主要能力包括: 1. 播放音乐和控制媒体 2. 文件操作(创建、读取、编辑文件) 3. 文本生成(写文章、总结、翻译等) 4. 系统控制(打开应用、设置提醒等) 5. 多步骤任务编排 当用户发出指令时,你需要: 1. 理解用户的意图 2. 确定需要执行的具体操作 3. 返回结构化的响应,包含操作类型和参数 🎯 响应格式必须是严格合法的 JSON: { "intent": "操作类型", "action": "具体动作", "parameters": {"参数名": "参数值"}, "response": "给用户的回复", "needs_confirmation": true/false } 📌 支持的操作类型: - music: 音乐相关操作 - file: 文件操作 - text: 文本生成 - system: 系统控制 - task: 多步骤任务 - chat: 普通对话 ❗请始终用中文回复用户。 """ @log_time @log_step("处理语音指令") def process_voice_command(self, voice_text): """处理语音指令,返回结构化 JSON 响应""" log_var("原始输入", voice_text) if not voice_text.strip(): logger.warning("收到空语音输入") return self._create_response("chat", "empty", {}, "我没有听清楚,请重新说话。", False) log_call("添加用户消息到历史") self.conversation_history.append({"role": "user", "content": voice_text}) try: messages = [{"role": "system", "content": self.system_prompt}] messages.extend(self.conversation_history[-10:]) log_var("请求消息数", len(messages)) logger.debug("正在调用 Qwen API...") response = Generation.call( model=self.model_name, messages=messages, temperature=0.5, top_p=0.8, max_tokens=1024 ) if response.status_code != 200: error_msg = f"Qwen API 调用失败: {response.status_code}, {response.message}" logger.error(error_msg) return self._create_response("chat", "error", {}, f"服务暂时不可用: {response.message}", False) ai_response = response.output['text'].strip() log_var("模型输出", ai_response) self.conversation_history.append({"role": "assistant", "content": ai_response}) # 尝试解析 JSON try: parsed_response = json.loads(ai_response) log_call("成功解析结构化响应") return parsed_response except json.JSONDecodeError: logger.warning("输出非标准JSON,尝试提取片段") json_match = re.search(r'\{[\s\S]*\}', ai_response) if json_match: try: parsed_response = json.loads(json_match.group()) log_call("从文本中提取JSON成功") return parsed_response except json.JSONDecodeError: pass logger.info("降级为普通聊天响应") return self._create_response("chat", "reply", {}, ai_response, False) except Exception as e: logger.exception("处理语音指令时发生未预期异常") return self._create_response("chat", "error", {}, "抱歉,我遇到了一些技术问题,请稍后再试。", False) def _create_response(self, intent, action, parameters, response, needs_confirmation): resp = { "intent": intent, "action": action, "parameters": parameters, "response": response, "needs_confirmation": needs_confirmation } log_var("返回响应", resp) return resp @log_time def generate_text(self, prompt, task_type="general"): log_var("任务类型", task_type) log_var("提示词长度", len(prompt)) try: system_prompt = f""" 你是一个专业的文本生成助手。根据用户的要求生成高质量的文本内容。 任务类型:{task_type} 要求:{prompt} 请生成相应的文本内容,确保内容准确、有逻辑、语言流畅。 """ response = Generation.call( model=self.model_name, messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": prompt} ], temperature=0.8, max_tokens=2000 ) if response.status_code == 200: result = response.output['text'] log_var("生成结果长度", len(result)) return result else: error_msg = f"文本生成失败: {response.message}" logger.error(error_msg) return error_msg except Exception as e: logger.exception("文本生成出错") return f"抱歉,生成文本时遇到错误:{str(e)}" @log_time def summarize_text(self, text): log_var("待总结文本长度", len(text)) try: prompt = f"请总结以下文本的主要内容:\n\n{text}" response = Generation.call( model=self.model_name, messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=500 ) if response.status_code == 200: result = response.output['text'] log_var("总结结果长度", len(result)) return result else: error_msg = f"总结失败: {response.message}" logger.error(error_msg) return error_msg except Exception as e: logger.exception("文本总结出错") return f"抱歉,总结文本时遇到错误:{str(e)}" @log_time def translate_text(self, text, target_language="英文"): log_var("目标语言", target_language) log_var("原文长度", len(text)) try: prompt = f"请将以下文本翻译成{target_language}:\n\n{text}" response = Generation.call( model=self.model_name, messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=1000 ) if response.status_code == 200: result = response.output['text'] log_var("翻译结果长度", len(result)) return result else: error_msg = f"翻译失败: {response.message}" logger.error(error_msg) return error_msg except Exception as e: logger.exception("文本翻译出错") return f"抱歉,翻译文本时遇到错误:{str(e)}" def clear_history(self): old_len = len(self.conversation_history) self.conversation_history.clear() logger.info(f"🧹 对话历史已清空,共清除 {old_len} 条记录") # ============================= # 🧪 测试代码 # ============================= if __name__ == "__main__": # 初始化全局日志系统 setup_logger(name="ai_assistant", log_dir="logs") assistant = QWENAssistant() test_commands = [ "播放周杰伦的歌曲", "写一篇关于气候变化的文章", "把这段话翻译成英文:今天天气真好", "总结一下人工智能的发展历程", "你好啊", "打开浏览器" ] for cmd in test_commands: print(f"\n🔊 用户指令: {cmd}") result = assistant.process_voice_command(cmd) print("🤖 AI响应:") print(json.dumps(result, ensure_ascii=False, indent=2)) """ 【系统控制模块】System Controller 提供音乐播放、文件操作、应用启动、定时提醒等本地系统级功能 """ import os import subprocess import platform import time import psutil import pygame import schedule from datetime import datetime import logging # --- 导入配置 --- from database import config # --- 导入日志工具 --- from Progress.utils.logger_utils import log_time, log_step, log_var, log_call from Progress.utils.logger_config import setup_logger # --- 配置路径 --- RESOURCE_PATH = config.resource_path DEFAULT_MUSIC_PATH = os.path.join(RESOURCE_PATH, config.music_path) DEFAULT_DOCUMENT_PATH = os.path.join(RESOURCE_PATH, config.doc_path) # --- 初始化日志器 --- logger = logging.getLogger(__name__) class SystemController: """ 系统功能控制器:封装常见的操作系统交互操作 """ def __init__(self): self.system = platform.system() self.music_player = None self.scheduled_tasks = {} self.task_counter = 0 log_var("运行平台", self.system) self._init_music_player() @log_step("初始化音乐播放器") @log_time def _init_music_player(self): """初始化 pygame 音频引擎用于本地音乐播放""" try: pygame.mixer.init() self.music_player = pygame.mixer.music logger.info("✅ 音乐播放器初始化成功") except Exception as e: logger.exception("❌ 音乐播放器初始化失败") self.music_player = None @log_step("播放音乐") @log_time def play_music(self, music_path=None): """ 播放指定目录下的第一个音乐文件 :param music_path: 音乐文件夹路径,None 则使用默认路径 :return: (success: bool, message: str) """ if not self.music_player: error_msg = "🔇 音乐播放器未就绪" logger.error(error_msg) return False, error_msg target_path = music_path or DEFAULT_MUSIC_PATH log_var("目标音乐路径", target_path) if not os.path.exists(target_path): msg = f"📁 路径不存在: {target_path}" logger.warning(msg) return False, msg music_files = self._find_music_files(target_path) if not music_files: msg = "🎵 未在指定路径找到支持的音乐文件 (.mp3/.wav/.flac 等)" logger.info(msg) return False, msg music_file = music_files[0] log_var("即将播放", os.path.basename(music_file)) try: self.stop_music() # 停止当前播放 self.music_player.load(music_file) self.music_player.play() success_msg = f"🎶 正在播放: {os.path.basename(music_file)}" logger.info(success_msg) return True, success_msg except Exception as e: logger.exception("💥 播放音乐失败") return False, f"播放失败: {str(e)}" @log_time def stop_music(self): """停止当前播放的音乐""" try: if self.music_player and pygame.mixer.get_init(): self.music_player.stop() logger.info("⏹️ 音乐已停止") return True, "音乐已停止" except Exception as e: logger.exception("❌ 停止音乐失败") return False, f"停止失败: {str(e)}" @log_time def pause_music(self): """暂停音乐播放""" try: if self.music_player and pygame.mixer.get_init(): self.music_player.pause() logger.info("⏸️ 音乐已暂停") return True, "音乐已暂停" except Exception as e: logger.exception("❌ 暂停音乐失败") return False, f"暂停失败: {str(e)}" @log_time def resume_music(self): """恢复音乐播放""" try: if self.music_player and pygame.mixer.get_init(): self.music_player.unpause() logger.info("▶️ 音乐已恢复") return True, "音乐已恢复" except Exception as e: logger.exception("❌ 恢复音乐失败") return False, f"恢复失败: {str(e)}" @log_step("打开应用程序") @log_time def open_application(self, app_name): """ 根据名称启动常见应用程序(跨平台) :param app_name: 应用中文名,如“浏览器”、“计算器” :return: (success, message) """ app_commands = { "浏览器": self._get_browser_command(), "记事本": self._get_text_editor_command(), "文件管理器": self._get_file_manager_command(), "计算器": self._get_calculator_command(), "终端": self._get_terminal_command() } log_var("请求打开应用", app_name) if app_name not in app_commands: warn_msg = f"🚫 不支持的应用: {app_name}" logger.warning(warn_msg) return False, warn_msg command = app_commands[app_name] log_var("执行命令", command) try: subprocess.Popen(command, shell=True) success_msg = f"🚀 正在打开 {app_name}" logger.info(success_msg) return True, success_msg except Exception as e: logger.exception(f"💥 启动应用 {app_name} 失败") return False, f"启动失败: {str(e)}" @log_step("创建文件") @log_time def create_file(self, file_path, content=""): """ 创建文本文件(自动创建父目录) :param file_path: 文件完整路径 :param content: 写入内容 :return: (success, message) """ log_var("创建文件", {"path": file_path, "content_length": len(content)}) try: os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, 'w', encoding='utf-8') as f: f.write(content) logger.info(f"📄 文件已创建: {file_path}") return True, f"文件已创建: {file_path}" except Exception as e: logger.exception("❌ 创建文件失败") return False, f"创建失败: {str(e)}" @log_step("读取文件") @log_time def read_file(self, file_path): """ 读取文本文件内容 :param file_path: 文件路径 :return: (success, content 或错误信息) """ log_var("读取文件", file_path) if not os.path.exists(file_path): msg = f"⚠️ 文件不存在: {file_path}" logger.warning(msg) return False, msg try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() logger.info(f"📖 成功读取文件: {file_path} ({len(content)} 字符)") return True, content except Exception as e: logger.exception("❌ 读取文件失败") return False, f"读取失败: {str(e)}" @log_step("写入文件") @log_time def write_file(self, file_path, content): """ 覆盖写入文件 :param file_path: 文件路径 :param content: 内容 :return: (success, message) """ log_var("写入文件", {"path": file_path, "length": len(content)}) try: with open(file_path, 'w', encoding='utf-8') as f: f.write(content) logger.info(f"💾 文件已保存: {file_path}") return True, f"文件已保存: {file_path}" except Exception as e: logger.exception("❌ 写入文件失败") return False, f"写入失败: {str(e)}" @log_step("获取系统信息") @log_time def get_system_info(self): """ 获取当前系统的运行状态 :return: (success, info dict) """ try: memory = psutil.virtual_memory() disk = psutil.disk_usage('/') cpu_percent = psutil.cpu_percent(interval=1) # 更准确 info = { "操作系统": platform.system(), "系统版本": platform.version(), "处理器": platform.processor() or "未知", "内存使用率": f"{memory.percent}%", "CPU使用率": f"{cpu_percent}%", "磁盘使用率": f"{disk.percent}%", "总内存": f"{memory.total // (1024 ** 3)} GB", "可用内存": f"{memory.available // (1024 ** 2)} MB" } log_var("系统信息", info) logger.info("📊 系统信息获取成功") return True, info except Exception as e: logger.exception("❌ 获取系统信息失败") return False, f"获取失败: {str(e)}" @log_step("设置提醒") @log_time def set_reminder(self, message, delay_minutes): """ 设置一个延时提醒任务 :param message: 提醒内容 :param delay_minutes: 延迟分钟数 :return: (success, message) """ log_var("设置提醒", {"delay_min": delay_minutes, "message": message}) self.task_counter += 1 task_id = f"reminder_{self.task_counter}" def reminder_job(): logger.info(f"🔔 提醒触发: {message}") # TODO: 添加桌面通知或语音播报 try: schedule.every(delay_minutes).minutes.do(reminder_job) self.scheduled_tasks[task_id] = { "message": message, "delay": delay_minutes, "created": datetime.now().isoformat() } success_msg = f"⏰ 提醒已设置: {delay_minutes} 分钟后提醒 - '{message}'" logger.info(success_msg) return True, success_msg except Exception as e: logger.exception("❌ 设置提醒失败") return False, f"设置失败: {str(e)}" @log_time def run_scheduled_tasks(self): """运行所有待处理的定时任务(通常在主循环中调用)""" schedule.run_pending() @log_time def _find_music_files(self, directory): """递归查找指定目录中的音乐文件""" music_extensions = {'.mp3', '.wav', '.flac', '.m4a', '.ogg', '.aac'} music_files = [] try: for root, dirs, files in os.walk(directory): for file in files: ext = os.path.splitext(file.lower())[1] if ext in music_extensions: full_path = os.path.join(root, file) music_files.append(full_path) log_call(f"发现音乐文件: {full_path}") except Exception as e: logger.exception(f"🔍 搜索音乐文件时出错: {directory}") log_var("搜索完成", f"共找到 {len(music_files)} 个音乐文件") return music_files # ============================= # 🛠️ 平台命令映射(私有方法) # ============================= def _get_browser_command(self): system = self.system return "start chrome" if system == "Windows" \ else "open -a Safari" if system == "Darwin" \ else "xdg-open" def _get_text_editor_command(self): system = self.system return "notepad" if system == "Windows" \ else "open -a TextEdit" if system == "Darwin" \ else "gedit" def _get_file_manager_command(self): system = self.system return "explorer" if system == "Windows" \ else "open -a Finder" if system == "Darwin" \ else "nautilus" def _get_calculator_command(self): system = self.system return "calc" if system == "Windows" \ else "open -a Calculator" if system == "Darwin" \ else "gnome-calculator" def _get_terminal_command(self): system = self.system return "cmd" if system == "Windows" \ else "open -a Terminal" if system == "Darwin" \ else "gnome-terminal" # ============================= # 🧩 任务编排器(TaskOrchestrator) # ============================= class TaskOrchestrator: """ 多步骤任务执行器,协调 SystemController 与 GPT 助手完成复杂流程 """ def __init__(self, system_controller: SystemController, gpt_assistant=None): self.system_controller = system_controller self.gpt_assistant = gpt_assistant self.task_queue = [] self.current_task = None logger.info("🔧 任务编排器已初始化") @log_step("执行任务序列") @log_time def execute_task_sequence(self, tasks): """ 按顺序执行多个任务 :param tasks: 任务列表,每个任务格式见 `_execute_single_task` :return: 结果列表 """ results = [] log_var("开始执行任务序列", {"任务数量": len(tasks)}) for i, task in enumerate(tasks): log_call(f"▶️ 执行第 {i+1} 个任务") result = self._execute_single_task(task) results.append(result) if not result["success"]: logger.warning(f"🟡 任务 {i+1} 执行失败: {result['message']}") log_var("任务序列完成", {"成功数": sum(1 for r in results if r["success"])}) return results @log_time def _execute_single_task(self, task): """执行单个结构化任务""" task_type = task.get("type") parameters = task.get("parameters", {}) log_var("执行任务", {"type": task_type, "params": parameters}) try: if task_type == "music": action = parameters.get("action", "play") handler_map = { "play": lambda: self.system_controller.play_music(parameters.get("path")), "stop": self.system_controller.stop_music, "pause": self.system_controller.pause_music, "resume": self.system_controller.resume_music } func = handler_map.get(action) if func: return {"success": True, "message": func()[1], "task": task} else: msg = f"不支持的音乐操作: {action}" logger.warning(msg) return {"success": False, "message": msg, "task": task} elif task_type == "file": action = parameters.get("action") path = parameters.get("path", "") content = parameters.get("content", "") if action == "create": res = self.system_controller.create_file(path, content) elif action == "read": res = self.system_controller.read_file(path) elif action == "write": res = self.system_controller.write_file(path, content) else: msg = f"不支持的文件操作: {action}" logger.warning(msg) return {"success": False, "message": msg, "task": task} return {"success": res[0], "message": res[1], "task": task} elif task_type == "text" and self.gpt_assistant: action = parameters.get("action") prompt = parameters.get("prompt", "") if action == "generate": content = self.gpt_assistant.generate_text(prompt) elif action == "summarize": content = self.gpt_assistant.summarize_text(prompt) elif action == "translate": target_lang = parameters.get("target_language", "英文") content = self.gpt_assistant.translate_text(prompt, target_lang) else: msg = f"不支持的文本操作: {action}" logger.warning(msg) return {"success": False, "message": msg, "task": task} return {"success": True, "message": content, "task": task} elif task_type == "system": action = parameters.get("action") if action == "info": res = self.system_controller.get_system_info() elif action == "open_app": app_name = parameters.get("app_name", "") res = self.system_controller.open_application(app_name) elif action == "reminder": msg_text = parameters.get("message", "") delay = parameters.get("delay_minutes", 5) res = self.system_controller.set_reminder(msg_text, delay) else: msg = f"不支持的系统操作: {action}" logger.warning(msg) return {"success": False, "message": msg, "task": task} return {"success": res[0], "message": res[1], "task": task} else: msg = f"不支持的任务类型: {task_type}" logger.warning(msg) return {"success": False, "message": msg, "task": task} except Exception as e: logger.exception("💥 执行单个任务时发生异常") return { "success": False, "message": f"任务执行异常: {str(e)}", "task": task } # ============================= # 🧪 测试代码 # ============================= if __name__ == "__main__": # 初始化全局日志 if not logging.getLogger().handlers: setup_logger(name="ai_assistant", log_dir="logs") logger.info("🧪 开始测试 SystemController 和 TaskOrchestrator...") controller = SystemController() # 测试音乐播放 logger.info("🎵 测试音乐播放...") success, msg = controller.play_music() logger.info(f"播放结果: {success} | {msg}") # 测试文件操作 test_file = "test/test_demo.txt" logger.info("📂 测试文件创建...") success, msg = controller.create_file(test_file, "这是由 AI 助手创建的测试文件。\n时间: " + str(datetime.now())) logger.info(f"创建结果: {success} | {msg}") logger.info("📖 测试文件读取...") success, content = controller.read_file(test_file) logger.info(f"读取结果: {success}") if success: log_var("文件内容预览", content[:100]) # 测试应用打开 logger.info("🖥️ 测试打开记事本...") success, msg = controller.open_application("记事本") logger.info(f"打开结果: {success} | {msg}") # 测试系统信息 logger.info("📊 获取系统信息...") success, info = controller.get_system_info() if success: for k, v in info.items(): log_var("系统." + k, v) # 测试提醒 logger.info("⏰ 设置一个 1 分钟后的提醒...") success, msg = controller.set_reminder("这是测试提醒!", 1) logger.info(f"提醒设置结果: {success} | {msg}") # 模拟运行一段时间的任务调度 for _ in range(3): controller.run_scheduled_tasks() time.sleep(1) logger.info("🎉 系统控制模块测试完成") """ 【语音合成模块】Text-to-Speech (TTS) 将文本转换为语音输出,支持中断、队列和多语音切换 """ import pyttsx3 import threading import queue import time import logging # --- 导入配置 --- from database import config # --- 导入日志工具 --- from Progress.utils.logger_utils import log_time, log_step, log_var, log_call from Progress.utils.logger_config import setup_logger # --- 配置参数 --- TTS_RATE = config.rate # 语速 TTS_VOLUME = config.volume # 音量 # --- 初始化日志器 --- logger = logging.getLogger(__name__) class TTSEngine: """ 基于 pyttsx3 的语音合成引擎 支持队列管理、语音控制、异步播放 """ def __init__(self): self.engine = None self.is_speaking = False self.speech_queue = queue.Queue() self.speech_thread = None self.stop_speaking = False self._initialize_engine() @log_step("初始化语音合成引擎") @log_time def _initialize_engine(self): """初始化 pyttsx3 引擎并设置语音参数""" try: self.engine = pyttsx3.init() # 设置基础属性 self.engine.setProperty('rate', TTS_RATE) self.engine.setProperty('volume', TTS_VOLUME) log_var("TTS.语速", TTS_RATE) log_var("TTS.音量", TTS_VOLUME) # 尝试选择中文语音 voices = self.engine.getProperty('voices') log_var("可用语音数量", len(voices)) selected_voice = None for voice in voices: if any(kw in voice.name.lower() for kw in ['chinese', 'mandarin', 'zh']): selected_voice = voice break if selected_voice: self.engine.setProperty('voice', selected_voice.id) log_call(f"已选择中文语音: {selected_voice.name}") elif voices: fallback_voice = voices[0] self.engine.setProperty('voice', fallback_voice.id) log_call(f"未找到中文语音,回退到: {fallback_voice.name}") else: logger.warning("系统无可用语音") logger.info("✅ 语音合成引擎初始化成功") except Exception as e: logger.exception("❌ 语音合成引擎初始化失败") self.engine = None @log_time def speak(self, text, interrupt=True): """ 主接口:语音播报文本(线程安全,支持队列) :param text: 要朗读的文本 :param interrupt: 是否打断当前正在播放的内容 :return: 是否成功提交任务 """ if not self.is_available(): logger.error("🔊 语音引擎不可用,跳过播报") return False if not text or not text.strip(): log_var("空输入被忽略", repr(text)) return False cleaned_text = text.strip() log_var("准备播报", cleaned_text) try: if interrupt: self.stop_current_speech() log_call("已中断当前语音") self.speech_queue.put(cleaned_text) if not self.speech_thread or not self.speech_thread.is_alive(): self.speech_thread = threading.Thread(target=self._speech_worker, name="TTSWorker") self.speech_thread.daemon = True self.speech_thread.start() log_call("启动语音工作线程") return True except Exception as e: logger.exception("❌ 提交语音任务时发生异常") return False def speak_async(self, text): """便捷方法:异步直接播放(不走队列)""" thread = threading.Thread(target=self._speak_sync, args=(text,), name="TTSAsync") thread.daemon = True thread.start() def _speak_sync(self, text): """同步播放单条语音(用于异步调用)""" if not self.is_available() or not text.strip(): return try: log_call(f"异步播放: {text[:30]}...") self.is_speaking = True self.engine.say(text) self.engine.runAndWait() self.is_speaking = False except Exception as e: logger.exception("❌ 同步语音播放异常") self.is_speaking = False @log_time def _speech_worker(self): """后台工作线程:从队列中取出文本并播放""" logger.debug("🎧 语音工作线程已启动") while not self.stop_speaking: try: text = self.speech_queue.get(timeout=1.0) if text is None: # 接收到停止信号 break log_var("正在播报", text) self.is_speaking = True self.engine.say(text) self.engine.runAndWait() self.is_speaking = False self.speech_queue.task_done() except queue.Empty: continue except Exception as e: logger.exception("❌ 语音工作线程运行出错") self.is_speaking = False self.speech_queue.task_done() logger.debug("🎧 语音工作线程已退出") @log_time def stop_current_speech(self): """停止当前正在播放的语音,并清空待播队列""" log_call("请求停止当前语音") try: if self.is_speaking and self.engine: self.engine.stop() self.is_speaking = False logger.info("🛑 已停止当前语音播放") # 清空队列 cleared = 0 while not self.speech_queue.empty(): try: self.speech_queue.get_nowait() self.speech_queue.task_done() cleared += 1 except queue.Empty: break if cleared > 0: log_var("清空待播队列", f"移除了 {cleared} 条消息") except Exception as e: logger.exception("❌ 停止语音播放失败") def pause_speech(self): """暂停语音(等同于 stop)""" self.stop_current_speech() @log_time def set_rate(self, rate): """设置语速""" log_var("TTS.设置语速", rate) try: if self.engine: self.engine.setProperty('rate', rate) return True except Exception as e: logger.exception("❌ 设置语速失败") return False @log_time def set_volume(self, volume): """设置音量""" log_var("TTS.设置音量", volume) try: if self.engine: self.engine.setProperty('volume', volume) return True except Exception as e: logger.exception("❌ 设置音量失败") return False @log_time def get_available_voices(self): """获取所有可用语音列表""" try: if self.engine: voices = self.engine.getProperty('voices') voice_list = [{"id": v.id, "name": v.name} for v in voices] log_var("TTS.可用语音", voice_list) return voice_list return [] except Exception as e: logger.exception("❌ 获取语音列表失败") return [] @log_time def set_voice(self, voice_id): """切换语音""" log_var("TTS.切换语音", voice_id) try: if self.engine: self.engine.setProperty('voice', voice_id) logger.info(f"🗣️ 已切换语音: {voice_id}") return True except Exception as e: logger.exception("❌ 切换语音失败") return False def is_available(self): """检查引擎是否可用""" available = self.engine is not None log_var("TTS.可用状态", available) return available @log_step("清理 TTS 资源") @log_time def cleanup(self): """释放资源:停止播放、关闭引擎""" log_call("开始清理 TTS 模块") self.stop_speaking = True self.stop_current_speech() if self.speech_thread and self.speech_thread.is_alive(): logger.debug("等待语音线程结束...") self.speech_thread.join(timeout=2) if self.speech_thread.is_alive(): logger.warning("⚠️ 语音线程未能正常退出") try: if self.engine: self.engine.stop() logger.info("🧹 TTS 引擎已停止") except Exception as e: logger.exception("❌ 清理 TTS 引擎时出错") # ============================= # 🧪 测试代码 # ============================= if __name__ == "__main__": # 初始化全局日志 if not logging.getLogger().handlers: setup_logger(name="ai_assistant", log_dir="logs") logger.info("🧪 开始测试 TTSEngine...") tts = TTSEngine() if tts.is_available(): logger.info("✅ 语音引擎可用,开始功能测试") # 测试基础语音 tts.speak("你好,我是语音助手,正在测试语音合成功能。") time.sleep(2) # 测试异步语音 tts.speak_async("这是异步语音播报,不会阻塞主线程。") time.sleep(1) # 测试语音列表 voices = tts.get_available_voices() logger.info(f"🔍 发现 {len(voices)} 个可用语音") for i, v in enumerate(voices): logger.debug(f" [{i}] {v['name']} -> {v['id'][-10:]}") # 清理资源 tts.cleanup() logger.info("🎉 测试完成,资源已清理") else: logger.error("🔴 语音引擎初始化失败,请检查系统语音支持") 还有这三个文件,把五个文件统一起来修正问题
10-22
还有一个问题就是不能在线预览音乐,我想要的是预览音乐后然后跳转到音乐的一个界面,比较好看的,然后音乐界面,然后可以切换上一首歌下一首歌然后空格是暂停和播放,你明白我的意思吧,音乐界面可以科技感点炫酷感点,还有一个问题就是访问文件只显示当前目录的文件,我还想还要显示当前目录下的所有子目录下的文件,但是不是全部以文件形式显示出来,就是比如我选择了一个路径,当前路径下有四个文件和两个文件夹内,然后我通过访问页面中的预览文件夹就显示下一级路径下的文件或者文件夹,你明白我的意思吧,然后我给你提供我的代码,你帮我在我提供的代码上去改动吧,html的话音乐界面看是需要单做一个还是怎么着,然后是你看看要改哪些相关的代码,server: import tornado.ioloop import tornado.web import threading import time import os import logging import asyncio from concurrent.futures import ThreadPoolExecutor from handlers import MainHandler, WSHandler, UpdatePoolHandler, FileListHandler, FileDownloadHandler, FilePreviewHandler, ManualScanHandler from file_utils import scan_pools # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger("ResourceManager") # 自定义线程池(用于异步执行资源池扫描等阻塞操作) executor = ThreadPoolExecutor(4) # 根据 CPU 核心数调整线程池大小 # 设置 asyncio 默认事件循环的执行器为线程池 asyncio.get_event_loop().set_default_executor(executor) # 修复模板路径和路由配置 def make_app(): base_dir = os.path.dirname(os.path.abspath(__file__)) template_path = os.path.join(base_dir, "templates") static_path = os.path.join(base_dir, "static") return tornado.web.Application( [ (r"/", MainHandler), (r"/ws", WSHandler), (r"/update_pool", UpdatePoolHandler), (r"/files/(.+)", FileListHandler), (r"/download", FileDownloadHandler), (r"/preview", FilePreviewHandler), (r"/scan", ManualScanHandler), (r"/static/(.*)", tornado.web.StaticFileHandler, {"path": static_path}), (r"/admin", MainHandler), ], template_path=template_path, cookie_secret="YOUR_SECRET_KEY", login_url="/login", debug=False, # 生产环境建议关闭 debug max_buffer_size=1024 * 1024 * 1024, # 最大缓冲区大小(1GB) chunked_write_timeout=60000, # 分块写入超时时间(毫秒) idle_connection_timeout=60000, # 空闲连接超时时间 executor=executor # 使用自定义线程池 ) if __name__ == "__main__": try: # 初始化资源池扫描 scan_pools() logger.info("资源池扫描初始化完成") except Exception as e: logger.error(f"资源池扫描初始化失败: {str(e)}") # 创建并启动应用 app = make_app() app.listen(8888) logger.info("服务器运行中: http://localhost:8888") try: # 启动事件循环 tornado.ioloop.IOLoop.current().start() except KeyboardInterrupt: logger.info("\n服务器正在关闭...") except Exception as e: logger.error(f"服务器异常停止: {str(e)}") finally: # 清理资源(可选) executor.shutdown(wait=True) #预览视频下载和下载按钮下载速度大幅度提升,txt文本实现在线预览,word等文档预览会下载而不是显示不支持预览,本地速度快,跨设备比较慢 handlers: import tornado.web import tornado.websocket import json import os import mimetypes import urllib.parse from config import RESOURCE_POOLS, save_config, ADMIN_IP, ADMIN_PASSWORD from file_utils import get_file_list, scan_pools import aiofiles # 改进管理员检查 def is_admin(handler): """检查当前用户是否有管理员权限""" client_ip = handler.request.remote_ip # 支持IPv4和IPv6的本地地址 return client_ip in ["127.0.0.1", "::1", "localhost"] or \ client_ip == ADMIN_IP or \ handler.get_cookie("admin_auth") == ADMIN_PASSWORD # handlers.py # 手动资源扫描接口 class ManualScanHandler(tornado.web.RequestHandler): async def get(self): if not is_admin(self): self.set_status(403) self.write("权限不足") return # 使用线程池异步执行 scan_pools await tornado.ioloop.IOLoop.current().run_in_executor(executor, scan_pools) WSHandler.broadcast_update() # 通知前端更新 self.write("资源池已手动扫描完成") # 主页面处理器 class MainHandler(tornado.web.RequestHandler): def get(self): try: if self.request.path == "/admin": if not is_admin(self): self.redirect("/") return self.render("admin.html", pools=RESOURCE_POOLS) else: if is_admin(self): self.render("admin.html", pools=RESOURCE_POOLS) else: self.render("user.html", pools=RESOURCE_POOLS) except FileNotFoundError: self.set_status(404) self.render("404.html") except Exception as e: self.set_status(500) self.render("500.html", error=str(e)) # WebSocket实时更新处理器 class WSHandler(tornado.websocket.WebSocketHandler): clients = set() def open(self): self.clients.add(self) self.send_update() def on_message(self, message): # 处理客户端消息 pass def on_close(self): self.clients.remove(self) def send_update(self): """发送更新到所有连接的客户端""" for client in self.clients: client.write_message(json.dumps(RESOURCE_POOLS)) @classmethod def broadcast_update(cls): """广播更新到所有客户端""" for client in cls.clients: client.write_message(json.dumps(RESOURCE_POOLS)) # 资源池更新处理器(仅管理员) class UpdatePoolHandler(tornado.web.RequestHandler): def post(self): if not is_admin(self): self.write({"status": "error", "message": "无权限执行此操作"}) return data = json.loads(self.request.body) pool_name = data.get("pool") new_path = data.get("path") if pool_name and new_path and pool_name in RESOURCE_POOLS: # 验证路径有效性 if not os.path.exists(new_path) or not os.path.isdir(new_path): self.write({"status": "error", "message": "路径不存在或不是目录"}) return # 更新路径并扫描文件 RESOURCE_POOLS[pool_name]["path"] = new_path try: RESOURCE_POOLS[pool_name]["files"] = [ f for f in os.listdir(new_path) if os.path.isfile(os.path.join(new_path, f)) ] except Exception as e: RESOURCE_POOLS[pool_name]["files"] = [f"错误: {str(e)}"] # 保存配置并广播更新 save_config() WSHandler.broadcast_update() self.write({"status": "success"}) else: self.write({"status": "error", "message": "无效资源池或路径"}) # 文件列表处理器 class FileListHandler(tornado.web.RequestHandler): async def get(self, pool_name): if pool_name not in RESOURCE_POOLS: self.set_status(404) self.render("404.html") return # 获取文件列表数据 files = get_file_list(pool_name) # 渲染模板 self.render("file_list.html", pool_name=pool_name, files=files, pool_info=RESOURCE_POOLS[pool_name], is_admin=is_admin(self)) # handlers.py # 文件下载处理器 class FileDownloadHandler(tornado.web.RequestHandler): async def get(self): pool_name = self.get_argument("pool") file_name = self.get_argument("file") if pool_name not in RESOURCE_POOLS: self.set_status(404) self.render("404.html") return pool_path = RESOURCE_POOLS[pool_name]["path"] file_path = os.path.join(pool_path, file_name) # 防止路径遍历攻击 real_path = os.path.realpath(file_path) if not real_path.startswith(os.path.realpath(pool_path)): self.set_status(403) self.write("禁止访问此文件路径") return if not os.path.exists(file_path): self.set_status(404) self.render("404.html") return file_size = os.path.getsize(file_path) range_header = self.request.headers.get('Range') start = 0 end = file_size - 1 if range_header: self.set_status(206) # Partial Content start_bytes, end_bytes = range_header.replace('bytes=', '').split('-') start = int(start_bytes) if start_bytes else 0 end = int(end_bytes) if end_bytes else file_size - 1 self.set_header('Content-Range', f'bytes {start}-{end}/{file_size}') self.set_header("Content-Length", str(end - start + 1)) encoded_filename = urllib.parse.quote(file_name.encode('utf-8')) self.set_header("Content-Type", "application/octet-stream") self.set_header("Content-Disposition", f'attachment; filename*=UTF-8\'\'{encoded_filename}') self.set_header("Accept-Ranges", "bytes") chunk_size = 1024 * 1024 * 4 # 4MB try: async with aiofiles.open(file_path, 'rb') as f: await f.seek(start) remaining = end - start + 1 while remaining > 0: read_size = min(chunk_size, remaining) chunk = await f.read(read_size) if not chunk: break self.write(chunk) await self.flush() remaining -= len(chunk) self.finish() except tornado.iostream.StreamClosedError: # 客户端提前关闭连接,静默处理 return except Exception as e: self.set_status(500) self.write(f"Internal Server Error: {str(e)}") return class FilePreviewHandler(tornado.web.RequestHandler): async def get(self): pool_name = self.get_argument("pool") file_name = self.get_argument("file") if pool_name not in RESOURCE_POOLS: self.set_status(404) self.render("404.html") return pool_path = RESOURCE_POOLS[pool_name]["path"] file_path = os.path.join(pool_path, file_name) # 防止路径遍历攻击 real_path = os.path.realpath(file_path) if not real_path.startswith(os.path.realpath(pool_path)): self.set_status(403) self.write("禁止访问此文件路径") return if not os.path.exists(file_path): self.set_status(404) self.render("404.html") return # 获取扩展名 ext = os.path.splitext(file_name)[1].lower() # 手动定义 MIME 类型 mime_type = { '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', '.pdf': 'application/pdf', '.txt': 'text/plain', '.md': 'text/markdown', '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg', }.get(ext, mimetypes.guess_type(file_name)[0] or 'application/octet-stream') # 设置 UTF-8 编码的文件名 encoded_filename = urllib.parse.quote(file_name.encode('utf-8')) self.set_header("Content-Disposition", f'inline; filename*=UTF-8\'\'{encoded_filename}') file_size = os.path.getsize(file_path) # 区分是否是 Range 请求(预览)还是完整下载 range_header = self.request.headers.get('Range') is_download = not range_header and ext in ['.mp4', '.webm', '.ogg', '.pdf'] if is_download: # 完整下载,使用 sendfile()(Linux) 或同步读取 self.set_header('Content-Type', mime_type) self.set_header('Content-Length', str(file_size)) self.set_status(200) if os.name == 'posix': # Linux 下使用 sendfile 实现零拷贝传输 fd = os.open(file_path, os.O_RDONLY) try: await tornado.ioloop.IOLoop.current().run_in_executor( None, os.sendfile, self.request.connection.fileno(), fd, None, file_size ) finally: os.close(fd) else: # Windows 或其他系统,使用同步读取(比 aiofiles 更快) with open(file_path, 'rb') as f: while True: chunk = f.read(1024 * 1024 * 4) # 4MB if not chunk: break self.write(chunk) await self.flush() self.finish() elif ext in ['.mp4', '.webm', '.ogg', '.pdf']: # 视频预览,使用 aiofiles 支持 Range self.set_header('Accept-Ranges', 'bytes') start = 0 end = file_size - 1 if range_header: self.set_status(206) start_bytes, end_bytes = range_header.replace('bytes=', '').split('-') start = int(start_bytes) if start_bytes else 0 end = int(end_bytes) if end_bytes else file_size - 1 self.set_header('Content-Range', f'bytes {start}-{end}/{file_size}') self.set_header("Content-Length", str(end - start + 1)) self.set_header('Content-Type', mime_type) chunk_size = 4096 * 16 async with aiofiles.open(file_path, 'rb') as f: await f.seek(start) remaining = end - start + 1 while remaining > 0: chunk = await f.read(min(chunk_size, remaining)) if not chunk: break self.write(chunk) await self.flush() remaining -= len(chunk) self.finish() elif ext == '.txt': self.set_header('Content-Type', 'text/plain; charset=UTF-8') async with aiofiles.open(file_path, 'r', encoding='utf-8') as f: content = await f.read() self.write(content) self.finish() elif ext in ['.png', '.jpg', '.jpeg', '.gif', '.md', '.pdf', '.docx', '.xlsx', '.pptx']: self.set_header('Content-Type', mime_type) async with aiofiles.open(file_path, 'rb') as f: data = await f.read() self.write(data) self.finish() else: self.set_status(400) self.write("不支持预览此文件类型") file_utils: import os import time import mimetypes import logging from config import RESOURCE_POOLS # 配置日志 logger = logging.getLogger("FileUtils") logging.basicConfig(level=logging.INFO) def get_file_list(pool_name): """获取资源池中的文件列表信息""" if pool_name not in RESOURCE_POOLS: return [] pool = RESOURCE_POOLS[pool_name] files = [] path = pool["path"] # 验证路径是否存在 if not os.path.exists(path): logger.error(f"路径不存在: {path}") return [{"name": "错误: 路径不存在", "formatted_size": "0 B"}] # 验证是否为目录 if not os.path.isdir(path): logger.error(f"路径不是目录: {path}") return [{"name": "错误: 路径不是目录", "formatted_size": "0 B"}] for file_name in pool.get("files", []): file_path = os.path.join(path, file_name) # 验证文件是否存在 if not os.path.exists(file_path): logger.warning(f"文件不存在: {file_path}") continue try: stat = os.stat(file_path) file_type = get_file_type(file_path) files.append({ "name": file_name, "size": stat.st_size, "formatted_size": _format_size(stat.st_size), "modified": time.strftime("%Y-%m-%d %H:%M", time.localtime(stat.st_mtime)), "path": file_path, "type": file_type }) except Exception as e: logger.error(f"访问文件错误 {file_path}: {e}") files.append({ "name": f"错误: {file_name}", "formatted_size": "0 B", "type": "error" }) return files def _format_size(size): """格式化文件大小""" for unit in ['B', 'KB', 'MB', 'GB']: if size < 1024.0: return f"{size:.1f} {unit}" size /= 1024.0 return f"{size:.1f} TB" def get_file_type(file_path): """获取文件类型""" mime_type, _ = mimetypes.guess_type(file_path) if mime_type: return mime_type.split('/')[0] return 'other' def scan_pools(): """扫描所有资源池的文件""" for pool_name, pool in RESOURCE_POOLS.items(): path = pool["path"] # 验证路径是否存在且是目录 if not os.path.exists(path) or not os.path.isdir(path): pool["files"] = [f"错误: 路径无效 {path}"] continue try: pool["files"] = [ f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f)) ] logger.info(f"扫描完成: {pool_name} ({len(pool['files'])} 文件)") except Exception as e: pool["files"] = [f"错误: {str(e)}"] logger.error(f"扫描资源池错误 {pool_name}: {e}")
08-20
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值