""" 任务引导视图组件 执行任务的引导式 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_code_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_code_section(self): """创建代码预览区域(可折叠)""" # 折叠状态 self._code_expanded = False # 外层框架 self.code_section = tk.LabelFrame( self.frame, text=" 💻 生成的代码 ", font=('Microsoft YaHei UI', 10, 'bold'), fg='#64b5f6', bg='#1e1e1e', relief=tk.GROOVE ) self.code_section.pack(fill=tk.X, padx=10, pady=3) # 展开/折叠按钮 self.toggle_code_btn = tk.Button( self.code_section, text="▶ 点击展开代码预览", font=('Microsoft YaHei UI', 9), bg='#2d2d2d', fg='#64b5f6', activebackground='#3d3d3d', activeforeground='#64b5f6', relief=tk.FLAT, cursor='hand2', command=self._toggle_code_view ) self.toggle_code_btn.pack(fill=tk.X, padx=5, pady=5) # 代码显示区域(初始隐藏) self.code_frame = tk.Frame(self.code_section, bg='#1e1e1e') # 代码文本框 self.code_text = tk.Text( self.code_frame, wrap=tk.NONE, font=('Consolas', 10), bg='#1e1e1e', fg='#d4d4d4', insertbackground='white', relief=tk.FLAT, height=12, padx=8, pady=5 ) # 配置代码高亮标签 self.code_text.tag_configure('keyword', foreground='#569cd6') self.code_text.tag_configure('string', foreground='#ce9178') self.code_text.tag_configure('comment', foreground='#6a9955') self.code_text.tag_configure('function', foreground='#dcdcaa') self.code_text.tag_configure('number', foreground='#b5cea8') # 滚动条 code_scrollbar_y = ttk.Scrollbar(self.code_frame, orient=tk.VERTICAL, command=self.code_text.yview) code_scrollbar_x = ttk.Scrollbar(self.code_frame, orient=tk.HORIZONTAL, command=self.code_text.xview) self.code_text.configure(yscrollcommand=code_scrollbar_y.set, xscrollcommand=code_scrollbar_x.set) code_scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y) code_scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X) self.code_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) # 复制按钮 self.copy_code_btn = tk.Button( self.code_frame, text="📋 复制代码", font=('Microsoft YaHei UI', 9), bg='#424242', fg='white', activebackground='#616161', activeforeground='white', relief=tk.FLAT, cursor='hand2', command=self._copy_code ) def _toggle_code_view(self): """切换代码预览的展开/折叠状态""" self._code_expanded = not self._code_expanded if self._code_expanded: self.toggle_code_btn.config(text="▼ 点击折叠代码预览") self.code_frame.pack(fill=tk.BOTH, expand=True, padx=3, pady=(0, 5)) self.copy_code_btn.pack(pady=5) else: self.toggle_code_btn.config(text="▶ 点击展开代码预览") self.copy_code_btn.pack_forget() self.code_frame.pack_forget() def _copy_code(self): """复制代码到剪贴板""" code = self.code_text.get(1.0, tk.END).strip() self.frame.clipboard_clear() self.frame.clipboard_append(code) # 显示复制成功提示 original_text = self.copy_code_btn.cget('text') self.copy_code_btn.config(text="✓ 已复制!") self.frame.after(1500, lambda: self.copy_code_btn.config(text=original_text)) def _apply_syntax_highlight(self, code: str): """应用简单的语法高亮""" import re # 关键字 keywords = r'\b(import|from|def|class|if|else|elif|for|while|try|except|finally|with|as|return|yield|raise|pass|break|continue|and|or|not|in|is|None|True|False|lambda|global|nonlocal)\b' # 字符串 strings = r'(\"\"\"[\s\S]*?\"\"\"|\'\'\'[\s\S]*?\'\'\'|\"[^\"]*\"|\'[^\']*\')' # 注释 comments = r'(#.*$)' # 函数调用 functions = r'\b([a-zA-Z_][a-zA-Z0-9_]*)\s*\(' # 数字 numbers = r'\b(\d+\.?\d*)\b' # 先插入纯文本 self.code_text.delete(1.0, tk.END) self.code_text.insert(1.0, code) # 应用高亮 for match in re.finditer(keywords, code, re.MULTILINE): start = f"1.0+{match.start()}c" end = f"1.0+{match.end()}c" self.code_text.tag_add('keyword', start, end) for match in re.finditer(strings, code, re.MULTILINE): start = f"1.0+{match.start()}c" end = f"1.0+{match.end()}c" self.code_text.tag_add('string', start, end) for match in re.finditer(comments, code, re.MULTILINE): start = f"1.0+{match.start()}c" end = f"1.0+{match.end()}c" self.code_text.tag_add('comment', start, end) for match in re.finditer(numbers, code, re.MULTILINE): start = f"1.0+{match.start(1)}c" end = f"1.0+{match.end(1)}c" self.code_text.tag_add('number', start, end) 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_code(self, code: str): """设置生成的代码""" self.code_text.config(state=tk.NORMAL) self._apply_syntax_highlight(code) self.code_text.config(state=tk.DISABLED) 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