""" LocalAgent 主应用类 管理 UI 状态切换和协调各模块工作流程 """ import os import tkinter as tk from tkinter import messagebox from pathlib import Path from typing import Optional, Dict, Any, Tuple import threading import queue from llm.client import get_client, LLMClientError from llm.prompts import ( EXECUTION_PLAN_SYSTEM, EXECUTION_PLAN_USER, CODE_GENERATION_SYSTEM, CODE_GENERATION_USER ) from intent.classifier import classify_intent, IntentResult from intent.labels import CHAT, EXECUTION from safety.rule_checker import check_code_safety from safety.llm_reviewer import review_code_safety, LLMReviewResult from executor.sandbox_runner import SandboxRunner, ExecutionResult from ui.chat_view import ChatView from ui.task_guide_view import TaskGuideView from ui.history_view import HistoryView from history.manager import get_history_manager, HistoryManager class LocalAgentApp: """ LocalAgent 主应用 职责: 1. 管理 UI 状态切换 2. 协调各模块工作流程 3. 处理用户交互 """ def __init__(self, project_root: Path): self.project_root: Path = project_root self.workspace: Path = project_root / "workspace" self.runner: SandboxRunner = SandboxRunner(str(self.workspace)) self.history: HistoryManager = get_history_manager(self.workspace) # 当前任务状态 self.current_task: Optional[Dict[str, Any]] = None # 线程通信队列 self.result_queue: queue.Queue = queue.Queue() # UI 组件 self.root: Optional[tk.Tk] = None self.main_container: Optional[tk.Frame] = None self.chat_view: Optional[ChatView] = None self.task_view: Optional[TaskGuideView] = None self.history_view: Optional[HistoryView] = None # 初始化 UI self._init_ui() def _init_ui(self) -> None: """初始化 UI""" self.root = tk.Tk() self.root.title("LocalAgent - 本地 AI 助手") self.root.geometry("800x700") self.root.configure(bg='#1e1e1e') # 设置窗口图标(如果有的话) try: self.root.iconbitmap(self.project_root / "icon.ico") except: pass # 主容器 self.main_container = tk.Frame(self.root, bg='#1e1e1e') self.main_container.pack(fill=tk.BOTH, expand=True) # 聊天视图 self.chat_view = ChatView( self.main_container, self._on_user_input, on_show_history=self._show_history ) # 定期检查后台任务结果 self._check_queue() def _check_queue(self) -> None: """检查后台任务队列""" try: while True: callback, args = self.result_queue.get_nowait() callback(*args) except queue.Empty: pass # 每 100ms 检查一次 self.root.after(100, self._check_queue) def _run_in_thread(self, func: callable, callback: callable, *args) -> None: """在后台线程运行函数,完成后回调""" def wrapper(): try: result = func(*args) self.result_queue.put((callback, (result, None))) except Exception as e: self.result_queue.put((callback, (None, e))) thread = threading.Thread(target=wrapper, daemon=True) thread.start() def _on_user_input(self, user_input: str) -> None: """处理用户输入""" # 显示用户消息 self.chat_view.add_message(user_input, 'user') self.chat_view.set_input_enabled(False) self.chat_view.show_loading("正在分析您的需求") # 在后台线程进行意图识别 self._run_in_thread( classify_intent, lambda result, error: self._on_intent_result(user_input, result, error), user_input ) def _on_intent_result(self, user_input: str, intent_result: Optional[IntentResult], error: Optional[Exception]) -> None: """意图识别完成回调""" self.chat_view.hide_loading() if error: self.chat_view.add_message(f"意图识别失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) return if intent_result.label == CHAT: # 对话模式 self._handle_chat(user_input, intent_result) else: # 执行模式 self._handle_execution(user_input, intent_result) def _handle_chat(self, user_input: str, intent_result: IntentResult) -> None: """处理对话任务""" self.chat_view.add_message( f"识别为对话模式 (原因: {intent_result.reason})", 'system' ) # 开始流式消息 self.chat_view.start_stream_message('assistant') # 在后台线程调用 LLM(流式) def do_chat_stream(): client = get_client() model = os.getenv("GENERATION_MODEL_NAME") full_response = [] for chunk in client.chat_stream( messages=[{"role": "user", "content": user_input}], model=model, temperature=0.7, max_tokens=2048, timeout=300 ): full_response.append(chunk) # 通过队列发送 chunk 到主线程更新 UI self.result_queue.put((self._on_chat_chunk, (chunk,))) return ''.join(full_response) self._run_in_thread( do_chat_stream, self._on_chat_complete ) def _on_chat_chunk(self, chunk: str): """收到对话片段回调(主线程)""" self.chat_view.append_stream_chunk(chunk) def _on_chat_complete(self, response: Optional[str], error: Optional[Exception]): """对话完成回调""" self.chat_view.end_stream_message() if error: self.chat_view.add_message(f"对话失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) def _handle_execution(self, user_input: str, intent_result: IntentResult): """处理执行任务""" self.chat_view.add_message( f"识别为执行任务 (置信度: {intent_result.confidence:.0%})\n原因: {intent_result.reason}", 'system' ) self.chat_view.show_loading("正在生成执行计划") # 保存用户输入和意图结果 self.current_task = { 'user_input': user_input, 'intent_result': intent_result } # 在后台线程生成执行计划 self._run_in_thread( self._generate_execution_plan, self._on_plan_generated, user_input ) def _on_plan_generated(self, plan: Optional[str], error: Optional[Exception]): """执行计划生成完成回调""" if error: self.chat_view.hide_loading() self.chat_view.add_message(f"生成执行计划失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return self.current_task['execution_plan'] = plan self.chat_view.update_loading_text("正在生成执行代码") # 在后台线程生成代码 self._run_in_thread( self._generate_code, self._on_code_generated, self.current_task['user_input'], plan ) def _on_code_generated(self, result: tuple, error: Optional[Exception]): """代码生成完成回调""" if error: self.chat_view.hide_loading() self.chat_view.add_message(f"生成代码失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return # result 可能是 (code, extract_error) 元组 if isinstance(result, tuple): code, extract_error = result if extract_error: self.chat_view.hide_loading() self.chat_view.add_message(f"代码提取失败: {str(extract_error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return else: code = result self.current_task['code'] = code self.chat_view.update_loading_text("正在进行安全检查") # 硬规则检查(同步,很快) rule_result = check_code_safety(code) if not rule_result.passed: self.chat_view.hide_loading() violations = "\n".join(f" • {v}" for v in rule_result.violations) self.chat_view.add_message( f"安全检查未通过,任务已取消:\n{violations}", 'error' ) self.chat_view.set_input_enabled(True) self.current_task = None return # 保存警告信息,传递给 LLM 审查 self.current_task['warnings'] = rule_result.warnings # 在后台线程进行 LLM 安全审查 self._run_in_thread( lambda: review_code_safety( self.current_task['user_input'], self.current_task['execution_plan'], code, rule_result.warnings # 传递警告给 LLM ), self._on_safety_reviewed ) def _on_safety_reviewed(self, review_result, error: Optional[Exception]): """安全审查完成回调""" self.chat_view.hide_loading() if error: self.chat_view.add_message(f"安全审查失败: {str(error)}", 'error') self.chat_view.set_input_enabled(True) self.current_task = None return if not review_result.passed: self.chat_view.add_message( f"安全审查未通过: {review_result.reason}", 'error' ) self.chat_view.set_input_enabled(True) self.current_task = None return self.chat_view.add_message("安全检查通过,请确认执行", 'system') # 显示任务引导视图 self._show_task_guide() def _generate_execution_plan(self, user_input: str) -> str: """生成执行计划(使用流式传输)""" client = get_client() model = os.getenv("GENERATION_MODEL_NAME") # 使用流式传输,避免超时 response = client.chat_stream_collect( messages=[ {"role": "system", "content": EXECUTION_PLAN_SYSTEM}, {"role": "user", "content": EXECUTION_PLAN_USER.format(user_input=user_input)} ], model=model, temperature=0.3, max_tokens=1024, timeout=300 # 5分钟超时 ) return response def _generate_code(self, user_input: str, execution_plan: str) -> tuple: """生成执行代码(使用流式传输)""" client = get_client() model = os.getenv("GENERATION_MODEL_NAME") # 使用流式传输,避免超时 response = client.chat_stream_collect( messages=[ {"role": "system", "content": CODE_GENERATION_SYSTEM}, {"role": "user", "content": CODE_GENERATION_USER.format( user_input=user_input, execution_plan=execution_plan )} ], model=model, temperature=0.2, max_tokens=4096, # 代码可能较长 timeout=300 # 5分钟超时 ) # 提取代码块,捕获可能的异常 try: code = self._extract_code(response) return (code, None) except ValueError as e: return (None, e) def _extract_code(self, response: str) -> str: """从 LLM 响应中提取代码""" import re # 尝试提取 ```python ... ``` 代码块 pattern = r'```python\s*(.*?)\s*```' matches = re.findall(pattern, response, re.DOTALL) if matches: return matches[0] # 尝试提取 ``` ... ``` 代码块 pattern = r'```\s*(.*?)\s*```' matches = re.findall(pattern, response, re.DOTALL) if matches: return matches[0] # 如果没有代码块,检查是否看起来像 Python 代码 # 简单检查:是否包含 def 或 import 语句 if 'import ' in response or 'def ' in response: return response # 无法提取代码,抛出异常 raise ValueError( "无法从 LLM 响应中提取代码块。\n" f"响应内容预览: {response[:200]}..." ) def _show_task_guide(self): """显示任务引导视图""" if not self.current_task: return # 隐藏聊天视图 self.chat_view.get_frame().pack_forget() # 创建任务引导视图 self.task_view = TaskGuideView( self.main_container, on_execute=self._on_execute_task, on_cancel=self._on_cancel_task, workspace_path=self.workspace ) # 设置内容 self.task_view.set_intent_result( self.current_task['intent_result'].reason, self.current_task['intent_result'].confidence ) self.task_view.set_execution_plan(self.current_task['execution_plan']) self.task_view.set_code(self.current_task['code']) # 显示 self.task_view.show() def _on_execute_task(self): """执行任务""" if not self.current_task: return self.task_view.set_buttons_enabled(False) # 在后台线程执行 def do_execute(): return self.runner.execute(self.current_task['code']) self._run_in_thread( do_execute, self._on_execution_complete ) def _on_execution_complete(self, result: Optional[ExecutionResult], error: Optional[Exception]): """执行完成回调""" if error: messagebox.showerror("执行错误", f"执行失败: {str(error)}") else: # 保存历史记录 if self.current_task: self.history.add_record( task_id=result.task_id, user_input=self.current_task['user_input'], intent_label=self.current_task['intent_result'].label, intent_confidence=self.current_task['intent_result'].confidence, execution_plan=self.current_task['execution_plan'], code=self.current_task['code'], success=result.success, duration_ms=result.duration_ms, stdout=result.stdout, stderr=result.stderr, log_path=result.log_path ) self._show_execution_result(result) # 刷新输出文件列表 if self.task_view: self.task_view.refresh_output() self._back_to_chat() def _show_execution_result(self, result: ExecutionResult): """显示执行结果""" if result.success: status = "执行成功" else: status = "执行失败" message = f"""{status} 任务 ID: {result.task_id} 耗时: {result.duration_ms} ms 输出: {result.stdout if result.stdout else '(无输出)'} {f'错误信息: {result.stderr}' if result.stderr else ''} """ if result.success: # 成功时显示结果并询问是否打开输出目录 open_output = messagebox.askyesno( "执行结果", message + "\n\n是否打开输出文件夹?" ) if open_output: os.startfile(str(self.workspace / "output")) else: # 失败时显示结果并询问是否打开日志 open_log = messagebox.askyesno( "执行结果", message + "\n\n是否打开日志文件查看详情?" ) if open_log and result.log_path: os.startfile(result.log_path) def _on_cancel_task(self): """取消任务""" self.current_task = None self._back_to_chat() def _back_to_chat(self): """返回聊天视图""" if self.task_view: self.task_view.hide() self.task_view = None self.chat_view.get_frame().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) self.chat_view.set_input_enabled(True) self.current_task = None def _show_history(self): """显示历史记录视图""" # 隐藏聊天视图 self.chat_view.get_frame().pack_forget() # 创建历史记录视图 self.history_view = HistoryView( self.main_container, self.history, on_back=self._hide_history ) self.history_view.show() def _hide_history(self): """隐藏历史记录视图,返回聊天""" if self.history_view: self.history_view.hide() self.history_view = None self.chat_view.get_frame().pack(fill=tk.BOTH, expand=True, padx=10, pady=10) def run(self): """运行应用""" self.root.mainloop()