Files
LocalAgent/ui/task_guide_view.py
Mimikko-zeus fc11ce8871 feat: update requirements and enhance task guide UI
- Added core dependencies for file handling (Pillow, openpyxl, python-docx, PyPDF2, chardet) to requirements.txt.
- Modified SandboxRunner to create a dedicated codes directory for task scripts.
- Expanded prompts.py with a list of allowed libraries for code generation.
- Simplified the task guide UI by removing drag-and-drop functionality and enhancing layout and styling for better user experience.
2026-01-07 00:47:07 +08:00

453 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
任务引导视图组件
执行任务的引导式 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