feat:增强需求澄清与任务管理功能
更新了 .env.example,新增聊天模型配置,以提升对话处理能力。 增强了 README.md,反映了包括需求澄清、代码复用和自动重试在内的新功能。 重构了 agent.py,以支持多模型交互,并为无法在本地执行的任务新增了引导处理逻辑。 改进了 SandboxRunner,增加了任务执行成功校验,并加入了工作区清理功能。 扩展了 HistoryManager,支持任务摘要生成以及记录的批量删除。 优化了 chat_view.py 和 history_view.py 中的 UI 组件,提升用户体验,包括 Markdown 渲染和任务管理选项。
This commit is contained in:
@@ -1,32 +1,230 @@
|
||||
"""
|
||||
历史记录视图组件
|
||||
显示任务执行历史
|
||||
显示任务执行历史,支持 Markdown 渲染、代码复用、失败重试、勾选删除
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from typing import Callable, List, Optional
|
||||
from typing import Callable, List, Optional, Set
|
||||
from pathlib import Path
|
||||
|
||||
from history.manager import TaskRecord, HistoryManager
|
||||
|
||||
|
||||
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', 14, 'bold'), foreground='#ffd54f', spacing3=10)
|
||||
self.tag_configure('h2', font=('Microsoft YaHei UI', 12, 'bold'), foreground='#4fc3f7', spacing3=8)
|
||||
self.tag_configure('h3', font=('Microsoft YaHei UI', 11, 'bold'), foreground='#81c784', spacing3=6)
|
||||
|
||||
# 普通文本
|
||||
self.tag_configure('normal', font=('Microsoft YaHei UI', 10), foreground='#d4d4d4')
|
||||
|
||||
# 代码块
|
||||
self.tag_configure('code', font=('Consolas', 9), foreground='#ce93d8', background='#1a1a1a')
|
||||
self.tag_configure('code_block', font=('Consolas', 9), foreground='#ce93d8', background='#1a1a1a',
|
||||
lmargin1=20, lmargin2=20, spacing1=5, spacing3=5)
|
||||
|
||||
# 列表
|
||||
self.tag_configure('list_item', font=('Microsoft YaHei UI', 10), foreground='#d4d4d4', lmargin1=20, lmargin2=30)
|
||||
|
||||
# 强调
|
||||
self.tag_configure('bold', font=('Microsoft YaHei UI', 10, 'bold'), foreground='#ffffff')
|
||||
self.tag_configure('italic', font=('Microsoft YaHei UI', 10, 'italic'), foreground='#b0b0b0')
|
||||
|
||||
# 状态
|
||||
self.tag_configure('success', foreground='#81c784')
|
||||
self.tag_configure('error', foreground='#ef5350')
|
||||
self.tag_configure('label', font=('Microsoft YaHei UI', 10, 'bold'), foreground='#4fc3f7')
|
||||
|
||||
def render_markdown(self, text: str):
|
||||
"""渲染 Markdown 文本"""
|
||||
self.config(state=tk.NORMAL)
|
||||
self.delete(1.0, tk.END)
|
||||
|
||||
lines = text.split('\n')
|
||||
in_code_block = False
|
||||
code_block_content = []
|
||||
|
||||
for line in lines:
|
||||
# 代码块处理
|
||||
if line.strip().startswith('```'):
|
||||
if in_code_block:
|
||||
# 结束代码块
|
||||
code_text = '\n'.join(code_block_content)
|
||||
self.insert(tk.END, code_text + '\n', 'code_block')
|
||||
code_block_content = []
|
||||
in_code_block = False
|
||||
else:
|
||||
# 开始代码块
|
||||
in_code_block = True
|
||||
continue
|
||||
|
||||
if in_code_block:
|
||||
code_block_content.append(line)
|
||||
continue
|
||||
|
||||
# 标题
|
||||
if line.startswith('### '):
|
||||
self.insert(tk.END, line[4:] + '\n', 'h3')
|
||||
elif line.startswith('## '):
|
||||
self.insert(tk.END, line[3:] + '\n', 'h2')
|
||||
elif line.startswith('# '):
|
||||
self.insert(tk.END, line[2:] + '\n', 'h1')
|
||||
# 列表项
|
||||
elif line.strip().startswith('- ') or line.strip().startswith('* '):
|
||||
content = line.strip()[2:]
|
||||
self.insert(tk.END, ' • ' + content + '\n', 'list_item')
|
||||
elif re.match(r'^\d+\.\s', line.strip()):
|
||||
self.insert(tk.END, ' ' + line.strip() + '\n', 'list_item')
|
||||
# 普通行
|
||||
else:
|
||||
self._render_inline(line + '\n')
|
||||
|
||||
# 处理未闭合的代码块
|
||||
if code_block_content:
|
||||
code_text = '\n'.join(code_block_content)
|
||||
self.insert(tk.END, code_text + '\n', 'code_block')
|
||||
|
||||
self.config(state=tk.DISABLED)
|
||||
|
||||
def _render_inline(self, text: str):
|
||||
"""渲染行内元素"""
|
||||
# 简单处理:查找 `code` 和 **bold**
|
||||
pattern = r'(`[^`]+`|\*\*[^*]+\*\*)'
|
||||
parts = re.split(pattern, text)
|
||||
|
||||
for part in parts:
|
||||
if part.startswith('`') and part.endswith('`'):
|
||||
self.insert(tk.END, part[1:-1], 'code')
|
||||
elif part.startswith('**') and part.endswith('**'):
|
||||
self.insert(tk.END, part[2:-2], 'bold')
|
||||
else:
|
||||
self.insert(tk.END, part, 'normal')
|
||||
|
||||
|
||||
class CheckboxTreeview(ttk.Treeview):
|
||||
"""
|
||||
带勾选框的 Treeview
|
||||
"""
|
||||
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(parent, **kwargs)
|
||||
|
||||
# 勾选状态存储
|
||||
self._checked: Set[str] = set()
|
||||
|
||||
# 勾选变化回调
|
||||
self._on_check_changed: Optional[Callable[[Set[str]], None]] = None
|
||||
|
||||
# 绑定点击事件
|
||||
self.bind('<Button-1>', self._on_click)
|
||||
|
||||
def set_on_check_changed(self, callback: Callable[[Set[str]], None]):
|
||||
"""设置勾选变化回调"""
|
||||
self._on_check_changed = callback
|
||||
|
||||
def _on_click(self, event):
|
||||
"""处理点击事件"""
|
||||
region = self.identify_region(event.x, event.y)
|
||||
|
||||
# 点击在第一列(勾选框区域)
|
||||
if region == 'cell':
|
||||
column = self.identify_column(event.x)
|
||||
if column == '#1': # 第一列是勾选框
|
||||
item = self.identify_row(event.y)
|
||||
if item:
|
||||
self._toggle_check(item)
|
||||
|
||||
def _toggle_check(self, item: str):
|
||||
"""切换勾选状态"""
|
||||
if item in self._checked:
|
||||
self._checked.remove(item)
|
||||
else:
|
||||
self._checked.add(item)
|
||||
|
||||
# 更新显示
|
||||
self._update_check_display(item)
|
||||
|
||||
# 触发回调
|
||||
if self._on_check_changed:
|
||||
self._on_check_changed(self._checked.copy())
|
||||
|
||||
def _update_check_display(self, item: str):
|
||||
"""更新勾选框显示"""
|
||||
values = list(self.item(item, 'values'))
|
||||
if values:
|
||||
values[0] = '☑' if item in self._checked else '☐'
|
||||
self.item(item, values=values)
|
||||
|
||||
def get_checked(self) -> Set[str]:
|
||||
"""获取所有勾选的项"""
|
||||
return self._checked.copy()
|
||||
|
||||
def clear_checked(self):
|
||||
"""清除所有勾选"""
|
||||
for item in list(self._checked):
|
||||
self._checked.remove(item)
|
||||
self._update_check_display(item)
|
||||
|
||||
if self._on_check_changed:
|
||||
self._on_check_changed(set())
|
||||
|
||||
def check_all(self):
|
||||
"""全选"""
|
||||
for item in self.get_children():
|
||||
if item not in self._checked:
|
||||
self._checked.add(item)
|
||||
self._update_check_display(item)
|
||||
|
||||
if self._on_check_changed:
|
||||
self._on_check_changed(self._checked.copy())
|
||||
|
||||
def insert_with_checkbox(self, parent, index, iid=None, **kwargs):
|
||||
"""插入带勾选框的项"""
|
||||
values = list(kwargs.get('values', []))
|
||||
# 在最前面插入勾选框
|
||||
values.insert(0, '☐')
|
||||
kwargs['values'] = values
|
||||
return self.insert(parent, index, iid=iid, **kwargs)
|
||||
|
||||
|
||||
class HistoryView:
|
||||
"""
|
||||
历史记录视图
|
||||
|
||||
显示任务执行历史列表,支持查看详情
|
||||
显示任务执行历史列表,支持:
|
||||
- 查看详情(Markdown 渲染)
|
||||
- 复用成功的代码
|
||||
- 重试失败的任务
|
||||
- 勾选删除
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: tk.Widget,
|
||||
history_manager: HistoryManager,
|
||||
on_back: Callable[[], None]
|
||||
on_back: Callable[[], None],
|
||||
on_reuse_code: Optional[Callable[[TaskRecord], None]] = None,
|
||||
on_retry_task: Optional[Callable[[TaskRecord], None]] = None
|
||||
):
|
||||
self.parent = parent
|
||||
self.history = history_manager
|
||||
self.on_back = on_back
|
||||
self.on_reuse_code = on_reuse_code
|
||||
self.on_retry_task = on_retry_task
|
||||
|
||||
self._selected_record: Optional[TaskRecord] = None
|
||||
self._create_widgets()
|
||||
@@ -68,48 +266,104 @@ class HistoryView:
|
||||
# 统计信息
|
||||
stats = self.history.get_stats()
|
||||
stats_text = f"共 {stats['total']} 条 | 成功 {stats['success']} | 失败 {stats['failed']} | 成功率 {stats['success_rate']:.0%}"
|
||||
stats_label = tk.Label(
|
||||
self.stats_label = tk.Label(
|
||||
title_frame,
|
||||
text=stats_text,
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#888888',
|
||||
bg='#1e1e1e'
|
||||
)
|
||||
stats_label.pack(side=tk.RIGHT)
|
||||
self.stats_label.pack(side=tk.RIGHT)
|
||||
|
||||
# 主内容区域(左右分栏)
|
||||
content_frame = tk.Frame(self.frame, bg='#1e1e1e')
|
||||
content_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
|
||||
|
||||
# 配置列权重,让右侧详情区域更宽
|
||||
content_frame.columnconfigure(0, weight=2) # 左侧列表
|
||||
content_frame.columnconfigure(1, weight=3) # 右侧详情
|
||||
content_frame.rowconfigure(0, weight=1)
|
||||
|
||||
# 左侧:历史列表
|
||||
list_frame = tk.LabelFrame(
|
||||
content_frame,
|
||||
text=" 任务列表 ",
|
||||
text=" 任务列表",
|
||||
font=('Microsoft YaHei UI', 10, 'bold'),
|
||||
fg='#4fc3f7',
|
||||
bg='#1e1e1e',
|
||||
relief=tk.GROOVE
|
||||
)
|
||||
list_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
||||
list_frame.grid(row=0, column=0, sticky='nsew', padx=(0, 5))
|
||||
|
||||
# 列表操作栏
|
||||
list_toolbar = tk.Frame(list_frame, bg='#2d2d2d')
|
||||
list_toolbar.pack(fill=tk.X, padx=3, pady=(3, 0))
|
||||
|
||||
# 全选按钮
|
||||
self.select_all_btn = tk.Button(
|
||||
list_toolbar,
|
||||
text="☑ 全选",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#3d3d3d',
|
||||
fg='#aaaaaa',
|
||||
activebackground='#4d4d4d',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
padx=8,
|
||||
cursor='hand2',
|
||||
command=self._select_all
|
||||
)
|
||||
self.select_all_btn.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
# 取消全选按钮
|
||||
self.deselect_all_btn = tk.Button(
|
||||
list_toolbar,
|
||||
text="☐ 取消全选",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#3d3d3d',
|
||||
fg='#aaaaaa',
|
||||
activebackground='#4d4d4d',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
padx=8,
|
||||
cursor='hand2',
|
||||
command=self._deselect_all
|
||||
)
|
||||
self.deselect_all_btn.pack(side=tk.LEFT)
|
||||
|
||||
# 已选数量提示
|
||||
self.selected_count_label = tk.Label(
|
||||
list_toolbar,
|
||||
text="",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#ffd54f',
|
||||
bg='#2d2d2d'
|
||||
)
|
||||
self.selected_count_label.pack(side=tk.RIGHT, padx=5)
|
||||
|
||||
# 列表框
|
||||
list_container = tk.Frame(list_frame, bg='#2d2d2d')
|
||||
list_container.pack(fill=tk.BOTH, expand=True, padx=3, pady=3)
|
||||
|
||||
# 使用 Treeview 显示列表
|
||||
columns = ('time', 'input', 'status', 'duration')
|
||||
self.tree = ttk.Treeview(list_container, columns=columns, show='headings', height=15)
|
||||
# 使用带勾选框的 Treeview 显示列表
|
||||
columns = ('check', 'time', 'description', 'status', 'duration')
|
||||
self.tree = CheckboxTreeview(list_container, columns=columns, show='headings', height=18)
|
||||
|
||||
# 配置列
|
||||
self.tree.heading('check', text='')
|
||||
self.tree.heading('time', text='时间')
|
||||
self.tree.heading('input', text='任务描述')
|
||||
self.tree.heading('description', text='任务描述')
|
||||
self.tree.heading('status', text='状态')
|
||||
self.tree.heading('duration', text='耗时')
|
||||
|
||||
self.tree.column('time', width=120, minwidth=100)
|
||||
self.tree.column('input', width=250, minwidth=150)
|
||||
self.tree.column('status', width=60, minwidth=50)
|
||||
self.tree.column('duration', width=70, minwidth=50)
|
||||
self.tree.column('check', width=30, minwidth=30, anchor='center')
|
||||
self.tree.column('time', width=130, minwidth=110)
|
||||
self.tree.column('description', width=180, minwidth=120)
|
||||
self.tree.column('status', width=65, minwidth=55)
|
||||
self.tree.column('duration', width=65, minwidth=50)
|
||||
|
||||
# 设置勾选变化回调
|
||||
self.tree.set_on_check_changed(self._on_check_changed)
|
||||
|
||||
# 滚动条
|
||||
scrollbar = ttk.Scrollbar(list_container, orient=tk.VERTICAL, command=self.tree.yview)
|
||||
@@ -130,13 +384,13 @@ class HistoryView:
|
||||
bg='#1e1e1e',
|
||||
relief=tk.GROOVE
|
||||
)
|
||||
detail_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
|
||||
detail_frame.grid(row=0, column=1, sticky='nsew', padx=(5, 0))
|
||||
|
||||
# 详情文本框
|
||||
# 详情文本框(使用 Markdown 渲染)
|
||||
detail_container = tk.Frame(detail_frame, bg='#2d2d2d')
|
||||
detail_container.pack(fill=tk.BOTH, expand=True, padx=3, pady=3)
|
||||
|
||||
self.detail_text = tk.Text(
|
||||
self.detail_text = MarkdownText(
|
||||
detail_container,
|
||||
wrap=tk.WORD,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
@@ -154,20 +408,17 @@ class HistoryView:
|
||||
detail_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
self.detail_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# 配置详情文本样式
|
||||
self.detail_text.tag_configure('title', font=('Microsoft YaHei UI', 11, 'bold'), foreground='#ffd54f')
|
||||
self.detail_text.tag_configure('label', font=('Microsoft YaHei UI', 10, 'bold'), foreground='#4fc3f7')
|
||||
self.detail_text.tag_configure('success', foreground='#81c784')
|
||||
self.detail_text.tag_configure('error', foreground='#ef5350')
|
||||
self.detail_text.tag_configure('code', font=('Consolas', 9), foreground='#ce93d8')
|
||||
|
||||
# 底部按钮
|
||||
btn_frame = tk.Frame(self.frame, bg='#1e1e1e')
|
||||
btn_frame.pack(fill=tk.X, padx=10, pady=(0, 10))
|
||||
|
||||
# 左侧按钮组
|
||||
left_btn_frame = tk.Frame(btn_frame, bg='#1e1e1e')
|
||||
left_btn_frame.pack(side=tk.LEFT)
|
||||
|
||||
# 打开日志按钮
|
||||
self.open_log_btn = tk.Button(
|
||||
btn_frame,
|
||||
left_btn_frame,
|
||||
text="📄 打开日志",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#424242',
|
||||
@@ -180,23 +431,62 @@ class HistoryView:
|
||||
state=tk.DISABLED,
|
||||
command=self._open_log
|
||||
)
|
||||
self.open_log_btn.pack(side=tk.LEFT)
|
||||
self.open_log_btn.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
# 清空历史按钮
|
||||
clear_btn = tk.Button(
|
||||
btn_frame,
|
||||
text="🗑️ 清空历史",
|
||||
# 复用代码按钮
|
||||
self.reuse_btn = tk.Button(
|
||||
left_btn_frame,
|
||||
text="🔄 复用此代码",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#d32f2f',
|
||||
bg='#0e639c',
|
||||
fg='white',
|
||||
activebackground='#f44336',
|
||||
activebackground='#1177bb',
|
||||
activeforeground='white',
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
cursor='hand2',
|
||||
command=self._clear_history
|
||||
state=tk.DISABLED,
|
||||
command=self._reuse_code
|
||||
)
|
||||
clear_btn.pack(side=tk.RIGHT)
|
||||
self.reuse_btn.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
# 重试按钮(仅失败任务可用)
|
||||
self.retry_btn = tk.Button(
|
||||
left_btn_frame,
|
||||
text="🔧 重试(AI修复)",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#f57c00',
|
||||
fg='white',
|
||||
activebackground='#ff9800',
|
||||
activeforeground='white',
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
cursor='hand2',
|
||||
state=tk.DISABLED,
|
||||
command=self._retry_task
|
||||
)
|
||||
self.retry_btn.pack(side=tk.LEFT)
|
||||
|
||||
# 右侧按钮组
|
||||
right_btn_frame = tk.Frame(btn_frame, bg='#1e1e1e')
|
||||
right_btn_frame.pack(side=tk.RIGHT)
|
||||
|
||||
# 删除选中按钮(默认禁用)
|
||||
self.delete_btn = tk.Button(
|
||||
right_btn_frame,
|
||||
text="🗑️ 删除选中 (0)",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#5d5d5d',
|
||||
fg='#888888',
|
||||
activebackground='#5d5d5d',
|
||||
activeforeground='#888888',
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
cursor='arrow',
|
||||
state=tk.DISABLED,
|
||||
command=self._delete_selected
|
||||
)
|
||||
self.delete_btn.pack(side=tk.RIGHT)
|
||||
|
||||
# 加载数据
|
||||
self._load_data()
|
||||
@@ -207,34 +497,116 @@ class HistoryView:
|
||||
for item in self.tree.get_children():
|
||||
self.tree.delete(item)
|
||||
|
||||
# 清空勾选状态
|
||||
self.tree._checked.clear()
|
||||
|
||||
# 加载历史记录
|
||||
records = self.history.get_all()
|
||||
|
||||
for record in records:
|
||||
# 截断过长的输入
|
||||
input_text = record.user_input
|
||||
if len(input_text) > 30:
|
||||
input_text = input_text[:30] + "..."
|
||||
# 使用任务描述(如果有)或截断的用户输入
|
||||
description = getattr(record, 'task_summary', None) or record.user_input
|
||||
if len(description) > 20:
|
||||
description = description[:20] + "..."
|
||||
|
||||
status = "✓ 成功" if record.success else "✗ 失败"
|
||||
duration = f"{record.duration_ms}ms"
|
||||
|
||||
# 提取时间(只显示时分秒)
|
||||
time_parts = record.timestamp.split(' ')
|
||||
time_str = time_parts[1] if len(time_parts) > 1 else record.timestamp
|
||||
date_str = time_parts[0] if len(time_parts) > 0 else ""
|
||||
display_time = f"{date_str}\n{time_str}"
|
||||
|
||||
self.tree.insert('', tk.END, iid=record.task_id, values=(
|
||||
self.tree.insert_with_checkbox('', tk.END, iid=record.task_id, values=(
|
||||
record.timestamp,
|
||||
input_text,
|
||||
description,
|
||||
status,
|
||||
duration
|
||||
))
|
||||
|
||||
# 更新统计信息
|
||||
self._update_stats()
|
||||
|
||||
# 更新删除按钮状态
|
||||
self._update_delete_button(set())
|
||||
|
||||
# 显示空状态提示
|
||||
if not records:
|
||||
self._show_detail("暂无历史记录\n\n执行任务后,记录将显示在这里。")
|
||||
self._show_empty_state()
|
||||
|
||||
def _update_stats(self):
|
||||
"""更新统计信息"""
|
||||
stats = self.history.get_stats()
|
||||
stats_text = f"共 {stats['total']} 条 | 成功 {stats['success']} | 失败 {stats['failed']} | 成功率 {stats['success_rate']:.0%}"
|
||||
self.stats_label.config(text=stats_text)
|
||||
|
||||
def _on_check_changed(self, checked: Set[str]):
|
||||
"""勾选状态变化回调"""
|
||||
self._update_delete_button(checked)
|
||||
|
||||
# 更新已选数量提示
|
||||
count = len(checked)
|
||||
if count > 0:
|
||||
self.selected_count_label.config(text=f"已选 {count} 项")
|
||||
else:
|
||||
self.selected_count_label.config(text="")
|
||||
|
||||
def _update_delete_button(self, checked: Set[str]):
|
||||
"""更新删除按钮状态"""
|
||||
count = len(checked)
|
||||
if count > 0:
|
||||
self.delete_btn.config(
|
||||
text=f"🗑️ 删除选中 ({count})",
|
||||
state=tk.NORMAL,
|
||||
bg='#d32f2f',
|
||||
fg='white',
|
||||
activebackground='#f44336',
|
||||
activeforeground='white',
|
||||
cursor='hand2'
|
||||
)
|
||||
else:
|
||||
self.delete_btn.config(
|
||||
text="🗑️ 删除选中 (0)",
|
||||
state=tk.DISABLED,
|
||||
bg='#5d5d5d',
|
||||
fg='#888888',
|
||||
activebackground='#5d5d5d',
|
||||
activeforeground='#888888',
|
||||
cursor='arrow'
|
||||
)
|
||||
|
||||
def _select_all(self):
|
||||
"""全选"""
|
||||
self.tree.check_all()
|
||||
|
||||
def _deselect_all(self):
|
||||
"""取消全选"""
|
||||
self.tree.clear_checked()
|
||||
|
||||
def _delete_selected(self):
|
||||
"""删除选中的记录"""
|
||||
checked = self.tree.get_checked()
|
||||
if not checked:
|
||||
return
|
||||
|
||||
count = len(checked)
|
||||
result = messagebox.askyesno(
|
||||
"确认删除",
|
||||
f"确定要删除选中的 {count} 条记录吗?\n此操作不可恢复。",
|
||||
icon='warning'
|
||||
)
|
||||
|
||||
if result:
|
||||
# 删除选中的记录
|
||||
for task_id in checked:
|
||||
self.history.delete_by_id(task_id)
|
||||
|
||||
# 重新加载数据
|
||||
self._load_data()
|
||||
self._show_empty_state() if not self.history.get_all() else None
|
||||
|
||||
# 重置按钮状态
|
||||
self.open_log_btn.config(state=tk.DISABLED)
|
||||
self.reuse_btn.config(state=tk.DISABLED)
|
||||
self.retry_btn.config(state=tk.DISABLED)
|
||||
self._selected_record = None
|
||||
|
||||
messagebox.showinfo("删除成功", f"已删除 {count} 条记录")
|
||||
|
||||
def _on_select(self, event):
|
||||
"""选择记录事件"""
|
||||
@@ -248,77 +620,84 @@ class HistoryView:
|
||||
if record:
|
||||
self._selected_record = record
|
||||
self._show_record_detail(record)
|
||||
|
||||
# 更新按钮状态
|
||||
self.open_log_btn.config(state=tk.NORMAL)
|
||||
self.reuse_btn.config(state=tk.NORMAL if record.success else tk.DISABLED)
|
||||
self.retry_btn.config(state=tk.NORMAL if not record.success else tk.DISABLED)
|
||||
|
||||
def _show_record_detail(self, record: TaskRecord):
|
||||
"""显示记录详情"""
|
||||
self.detail_text.config(state=tk.NORMAL)
|
||||
self.detail_text.delete(1.0, tk.END)
|
||||
"""显示记录详情(Markdown 格式)"""
|
||||
# 构建 Markdown 内容
|
||||
status_text = "✓ 成功" if record.success else "✗ 失败"
|
||||
|
||||
# 标题
|
||||
self.detail_text.insert(tk.END, f"任务 ID: {record.task_id}\n", 'title')
|
||||
self.detail_text.insert(tk.END, f"时间: {record.timestamp}\n\n")
|
||||
md_content = f"""## 任务 ID: {record.task_id}
|
||||
|
||||
**时间:** {record.timestamp}
|
||||
**状态:** {status_text}
|
||||
**耗时:** {record.duration_ms}ms
|
||||
|
||||
---
|
||||
|
||||
### 用户输入
|
||||
{record.user_input}
|
||||
|
||||
---
|
||||
|
||||
### 执行计划
|
||||
{record.execution_plan}
|
||||
|
||||
---
|
||||
|
||||
### 生成的代码
|
||||
```python
|
||||
{record.code}
|
||||
```
|
||||
|
||||
"""
|
||||
|
||||
# 用户输入
|
||||
self.detail_text.insert(tk.END, "用户输入:\n", 'label')
|
||||
self.detail_text.insert(tk.END, f"{record.user_input}\n\n")
|
||||
|
||||
# 执行状态
|
||||
self.detail_text.insert(tk.END, "执行状态: ", 'label')
|
||||
if record.success:
|
||||
self.detail_text.insert(tk.END, "成功 ✓\n", 'success')
|
||||
else:
|
||||
self.detail_text.insert(tk.END, "失败 ✗\n", 'error')
|
||||
|
||||
self.detail_text.insert(tk.END, f"耗时: {record.duration_ms}ms\n\n")
|
||||
|
||||
# 执行计划
|
||||
self.detail_text.insert(tk.END, "执行计划:\n", 'label')
|
||||
plan_preview = record.execution_plan[:500] + "..." if len(record.execution_plan) > 500 else record.execution_plan
|
||||
self.detail_text.insert(tk.END, f"{plan_preview}\n\n")
|
||||
|
||||
# 输出
|
||||
if record.stdout:
|
||||
self.detail_text.insert(tk.END, "输出:\n", 'label')
|
||||
self.detail_text.insert(tk.END, f"{record.stdout}\n\n")
|
||||
md_content += f"""---
|
||||
|
||||
### 输出
|
||||
{record.stdout}
|
||||
|
||||
"""
|
||||
|
||||
# 错误
|
||||
if record.stderr:
|
||||
self.detail_text.insert(tk.END, "错误:\n", 'label')
|
||||
self.detail_text.insert(tk.END, f"{record.stderr}\n", 'error')
|
||||
md_content += f"""---
|
||||
|
||||
### 错误信息
|
||||
{record.stderr}
|
||||
"""
|
||||
|
||||
self.detail_text.config(state=tk.DISABLED)
|
||||
self.detail_text.render_markdown(md_content)
|
||||
|
||||
def _show_detail(self, text: str):
|
||||
"""显示详情文本"""
|
||||
def _show_empty_state(self):
|
||||
"""显示空状态"""
|
||||
self.detail_text.config(state=tk.NORMAL)
|
||||
self.detail_text.delete(1.0, tk.END)
|
||||
self.detail_text.insert(tk.END, text)
|
||||
self.detail_text.insert(tk.END, "暂无历史记录\n\n执行任务后,记录将显示在这里。", 'normal')
|
||||
self.detail_text.config(state=tk.DISABLED)
|
||||
|
||||
def _open_log(self):
|
||||
"""打开日志文件"""
|
||||
if self._selected_record and self._selected_record.log_path:
|
||||
import os
|
||||
log_path = Path(self._selected_record.log_path)
|
||||
if log_path.exists():
|
||||
os.startfile(str(log_path))
|
||||
else:
|
||||
messagebox.showwarning("提示", f"日志文件不存在:\n{log_path}")
|
||||
|
||||
def _clear_history(self):
|
||||
"""清空历史记录"""
|
||||
result = messagebox.askyesno(
|
||||
"确认清空",
|
||||
"确定要清空所有历史记录吗?\n此操作不可恢复。",
|
||||
icon='warning'
|
||||
)
|
||||
|
||||
if result:
|
||||
self.history.clear()
|
||||
self._load_data()
|
||||
self._show_detail("历史记录已清空")
|
||||
self.open_log_btn.config(state=tk.DISABLED)
|
||||
def _reuse_code(self):
|
||||
"""复用代码"""
|
||||
if self._selected_record and self.on_reuse_code:
|
||||
self.on_reuse_code(self._selected_record)
|
||||
|
||||
def _retry_task(self):
|
||||
"""重试失败的任务"""
|
||||
if self._selected_record and self.on_retry_task:
|
||||
self.on_retry_task(self._selected_record)
|
||||
|
||||
def show(self):
|
||||
"""显示视图"""
|
||||
@@ -332,4 +711,3 @@ class HistoryView:
|
||||
def get_frame(self) -> tk.Frame:
|
||||
"""获取主框架"""
|
||||
return self.frame
|
||||
|
||||
|
||||
Reference in New Issue
Block a user