""" 任务引导视图组件 执行任务的引导式 UI - 简化版 """ import tkinter as tk from tkinter import scrolledtext, messagebox from tkinter import ttk from typing import Callable, Optional, List from pathlib import Path import re class MarkdownText(tk.Text): """支持简单 Markdown 渲染的 Text 组件""" def __init__(self, parent, **kwargs): super().__init__(parent, **kwargs) self._setup_tags() def _setup_tags(self): """设置 Markdown 样式标签""" # 标题样式 self.tag_configure('h1', font=('Microsoft YaHei UI', 13, 'bold'), foreground='#ffd54f', spacing1=8, spacing3=4) self.tag_configure('h2', font=('Microsoft YaHei UI', 11, 'bold'), foreground='#81c784', spacing1=6, spacing3=3) self.tag_configure('h3', font=('Microsoft YaHei UI', 10, 'bold'), foreground='#4fc3f7', spacing1=4, spacing3=2) # 列表样式 self.tag_configure('bullet', foreground='#ce93d8', lmargin1=15, lmargin2=30) self.tag_configure('numbered', foreground='#ce93d8', lmargin1=15, lmargin2=30) # 普通文本 self.tag_configure('normal', font=('Microsoft YaHei UI', 10), foreground='#d4d4d4') def set_markdown(self, text: str): """设置 Markdown 内容并渲染""" self.config(state=tk.NORMAL) self.delete(1.0, tk.END) lines = text.split('\n') for line in lines: self._render_line(line) self.config(state=tk.DISABLED) def _render_line(self, line: str): """渲染单行 Markdown""" stripped = line.strip() # 标题 if stripped.startswith('### '): self.insert(tk.END, stripped[4:] + '\n', 'h3') elif stripped.startswith('## '): self.insert(tk.END, stripped[3:] + '\n', 'h2') elif stripped.startswith('# '): self.insert(tk.END, stripped[2:] + '\n', 'h1') # 无序列表 elif stripped.startswith('- ') or stripped.startswith('* '): self.insert(tk.END, '• ' + stripped[2:] + '\n', 'bullet') # 有序列表 elif re.match(r'^\d+\.\s', stripped): match = re.match(r'^(\d+\.)\s(.*)$', stripped) if match: self.insert(tk.END, match.group(1) + ' ' + match.group(2) + '\n', 'numbered') # 普通文本 else: self.insert(tk.END, line + '\n', 'normal') class FileZone(tk.Frame): """简化的文件区域 - 仅显示打开文件夹按钮""" def __init__( self, parent, title: str, target_dir: Path, is_input: bool = True, **kwargs ): super().__init__(parent, **kwargs) self.target_dir = target_dir self.is_input = is_input self.configure(bg='#2d2d2d') # 确保目录存在 self.target_dir.mkdir(parents=True, exist_ok=True) self._create_widgets(title) def _create_widgets(self, title: str): """创建组件""" # 打开文件夹按钮 color = '#4fc3f7' if self.is_input else '#81c784' self.open_btn = tk.Button( self, text=f"📂 {title}", font=('Microsoft YaHei UI', 10), bg='#424242', fg=color, activebackground='#616161', activeforeground=color, relief=tk.FLAT, padx=15, pady=8, cursor='hand2', command=self._open_folder ) self.open_btn.pack(fill=tk.X, padx=5, pady=5) # 文件计数标签 self.count_label = tk.Label( self, text="0 个文件", font=('Microsoft YaHei UI', 9), fg='#888888', bg='#2d2d2d' ) self.count_label.pack(pady=(0, 5)) self._refresh_count() def _open_folder(self): """打开目标文件夹""" import os os.startfile(str(self.target_dir)) def _refresh_count(self): """刷新文件计数""" files = list(self.target_dir.glob('*')) files = [f for f in files if f.is_file()] self.count_label.config(text=f"{len(files)} 个文件") def refresh(self): """刷新""" self._refresh_count() def get_files(self) -> List[Path]: """获取目录中的文件列表""" files = list(self.target_dir.glob('*')) return [f for f in files if f.is_file()] class TaskGuideView: """ 任务引导视图 - 简化版 """ def __init__( self, parent: tk.Widget, on_execute: Callable[[], None], on_cancel: Callable[[], None], workspace_path: Optional[Path] = None ): self.parent = parent self.on_execute = on_execute self.on_cancel = on_cancel if workspace_path: self.workspace = workspace_path else: self.workspace = Path(__file__).parent.parent / "workspace" self.input_dir = self.workspace / "input" self.output_dir = self.workspace / "output" self._create_widgets() def _create_widgets(self): """创建 UI 组件""" # 主框架 - 使用 Canvas 实现滚动 self.frame = tk.Frame(self.parent, bg='#1e1e1e') # 标题 title_label = tk.Label( self.frame, text="执行任务确认", font=('Microsoft YaHei UI', 14, 'bold'), fg='#ffd54f', bg='#1e1e1e' ) title_label.pack(pady=(5, 10)) # 文件区域(横向排列) file_section = tk.Frame(self.frame, bg='#1e1e1e') file_section.pack(fill=tk.X, padx=10, pady=5) # 输入文件区域 input_frame = tk.LabelFrame( file_section, text=" 📥 输入 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#4fc3f7', bg='#1e1e1e', relief=tk.GROOVE ) input_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5)) self.input_zone = FileZone( input_frame, title="打开输入文件夹", target_dir=self.input_dir, is_input=True, bg='#2d2d2d' ) self.input_zone.pack(fill=tk.BOTH, expand=True, padx=3, pady=3) # 箭头 arrow_label = tk.Label( file_section, text="→", font=('Microsoft YaHei UI', 16, 'bold'), fg='#ffd54f', bg='#1e1e1e' ) arrow_label.pack(side=tk.LEFT, padx=5) # 输出文件区域 output_frame = tk.LabelFrame( file_section, text=" 📤 输出 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#81c784', bg='#1e1e1e', relief=tk.GROOVE ) output_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(5, 0)) self.output_zone = FileZone( output_frame, title="打开输出文件夹", target_dir=self.output_dir, is_input=False, bg='#2d2d2d' ) self.output_zone.pack(fill=tk.BOTH, expand=True, padx=3, pady=3) # 意图识别结果区域 self._create_intent_section() # 执行计划区域(Markdown) self._create_plan_section() # 风险提示区域 self._create_risk_section() # 按钮区域 self._create_button_section() def _create_intent_section(self): """创建意图识别结果区域""" section = tk.LabelFrame( self.frame, text=" 🎯 意图识别 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#81c784', bg='#1e1e1e', relief=tk.GROOVE ) section.pack(fill=tk.X, padx=10, pady=3) self.intent_label = tk.Label( section, text="", font=('Microsoft YaHei UI', 9), fg='#d4d4d4', bg='#1e1e1e', wraplength=650, justify=tk.LEFT ) self.intent_label.pack(padx=8, pady=5, anchor=tk.W) def _create_plan_section(self): """创建执行计划区域(支持 Markdown)""" section = tk.LabelFrame( self.frame, text=" 📄 执行计划 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#ce93d8', bg='#1e1e1e', relief=tk.GROOVE ) section.pack(fill=tk.BOTH, expand=True, padx=10, pady=3) # 使用 Markdown 渲染的 Text text_frame = tk.Frame(section, bg='#2d2d2d') text_frame.pack(fill=tk.BOTH, expand=True, padx=3, pady=3) self.plan_text = MarkdownText( text_frame, wrap=tk.WORD, bg='#2d2d2d', fg='#d4d4d4', relief=tk.FLAT, height=6, padx=8, pady=5 ) # 添加滚动条 scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.plan_text.yview) self.plan_text.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.plan_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) def _create_risk_section(self): """创建风险提示区域""" section = tk.LabelFrame( self.frame, text=" ⚠️ 安全提示 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#ffb74d', bg='#1e1e1e', relief=tk.GROOVE ) section.pack(fill=tk.X, padx=10, pady=3) self.risk_label = tk.Label( section, text="• 所有操作仅在 workspace 目录内进行 • 原始文件不会被修改或删除 • 执行代码已通过安全检查", font=('Microsoft YaHei UI', 9), fg='#d4d4d4', bg='#1e1e1e', justify=tk.LEFT ) self.risk_label.pack(padx=8, pady=5, anchor=tk.W) def _create_button_section(self): """创建按钮区域""" button_frame = tk.Frame(self.frame, bg='#1e1e1e') button_frame.pack(fill=tk.X, padx=10, pady=10) # 统一按钮样式 btn_font = ('Microsoft YaHei UI', 10) btn_width = 12 btn_height = 1 # 刷新文件列表按钮 self.refresh_btn = tk.Button( button_frame, text="🔄 刷新", font=btn_font, width=btn_width, height=btn_height, bg='#424242', fg='white', activebackground='#616161', activeforeground='white', relief=tk.FLAT, cursor='hand2', command=self._refresh_all ) self.refresh_btn.pack(side=tk.LEFT) # 执行按钮 self.execute_btn = tk.Button( button_frame, text="🚀 开始执行", font=('Microsoft YaHei UI', 10, 'bold'), width=btn_width, height=btn_height, bg='#4caf50', fg='white', activebackground='#66bb6a', activeforeground='white', relief=tk.FLAT, cursor='hand2', command=self._on_execute_clicked ) self.execute_btn.pack(side=tk.RIGHT) # 取消按钮 self.cancel_btn = tk.Button( button_frame, text="取消", font=btn_font, width=btn_width, height=btn_height, bg='#616161', fg='white', activebackground='#757575', activeforeground='white', relief=tk.FLAT, cursor='hand2', command=self.on_cancel ) self.cancel_btn.pack(side=tk.RIGHT, padx=(0, 10)) def _refresh_all(self): """刷新所有文件列表""" self.input_zone.refresh() self.output_zone.refresh() def _on_execute_clicked(self): """执行按钮点击""" # 刷新文件列表 self.input_zone.refresh() # 检查 input 目录是否有文件 files = self.input_zone.get_files() if not files: result = messagebox.askyesno( "确认执行", "输入文件夹为空,确定要继续执行吗?", icon='warning' ) if not result: return self.on_execute() def set_intent_result(self, reason: str, confidence: float): """设置意图识别结果""" self.intent_label.config( text=f"识别结果: 执行任务 (置信度: {confidence:.0%}) | 原因: {reason}" ) def set_execution_plan(self, plan: str): """设置执行计划(Markdown 格式)""" self.plan_text.set_markdown(plan) def set_risk_info(self, info: str): """设置风险提示""" self.risk_label.config(text=info) def set_buttons_enabled(self, enabled: bool): """设置按钮是否可用""" state = tk.NORMAL if enabled else tk.DISABLED self.execute_btn.config(state=state) self.cancel_btn.config(state=state) self.refresh_btn.config(state=state) def refresh_output(self): """刷新输出文件列表""" self.output_zone.refresh() def show(self): """显示视图""" self.frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self._refresh_all() def hide(self): """隐藏视图""" self.frame.pack_forget() def get_frame(self) -> tk.Frame: """获取主框架""" return self.frame