Files
LocalAgent/ui/history_view.py
Mimikko-zeus 0a92355bfb feat: enhance LocalAgent configuration and UI components
- Updated .env.example to provide clearer configuration instructions and API key setup.
- Removed debug_env.py as it was no longer needed.
- Refactored main.py to streamline application initialization and workspace setup.
- Introduced a new HistoryManager for managing task execution history.
- Enhanced UI components in chat_view.py and task_guide_view.py to improve user interaction and code preview functionality.
- Added loading indicators and improved task history display in the UI.
- Implemented unit tests for history management and intent classification.
2026-01-07 10:29:13 +08:00

336 lines
11 KiB
Python

"""
历史记录视图组件
显示任务执行历史
"""
import tkinter as tk
from tkinter import ttk, messagebox
from typing import Callable, List, Optional
from pathlib import Path
from history.manager import TaskRecord, HistoryManager
class HistoryView:
"""
历史记录视图
显示任务执行历史列表,支持查看详情
"""
def __init__(
self,
parent: tk.Widget,
history_manager: HistoryManager,
on_back: Callable[[], None]
):
self.parent = parent
self.history = history_manager
self.on_back = on_back
self._selected_record: Optional[TaskRecord] = None
self._create_widgets()
def _create_widgets(self):
"""创建 UI 组件"""
self.frame = tk.Frame(self.parent, bg='#1e1e1e')
# 标题栏
title_frame = tk.Frame(self.frame, bg='#1e1e1e')
title_frame.pack(fill=tk.X, padx=10, pady=10)
# 返回按钮
back_btn = tk.Button(
title_frame,
text="← 返回",
font=('Microsoft YaHei UI', 10),
bg='#424242',
fg='white',
activebackground='#616161',
activeforeground='white',
relief=tk.FLAT,
padx=10,
cursor='hand2',
command=self.on_back
)
back_btn.pack(side=tk.LEFT)
# 标题
title_label = tk.Label(
title_frame,
text="📜 任务历史记录",
font=('Microsoft YaHei UI', 14, 'bold'),
fg='#ce93d8',
bg='#1e1e1e'
)
title_label.pack(side=tk.LEFT, padx=20)
# 统计信息
stats = self.history.get_stats()
stats_text = f"{stats['total']} 条 | 成功 {stats['success']} | 失败 {stats['failed']} | 成功率 {stats['success_rate']:.0%}"
stats_label = tk.Label(
title_frame,
text=stats_text,
font=('Microsoft YaHei UI', 9),
fg='#888888',
bg='#1e1e1e'
)
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))
# 左侧:历史列表
list_frame = tk.LabelFrame(
content_frame,
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_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)
# 配置列
self.tree.heading('time', text='时间')
self.tree.heading('input', 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)
# 滚动条
scrollbar = ttk.Scrollbar(list_container, orient=tk.VERTICAL, command=self.tree.yview)
self.tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 绑定选择事件
self.tree.bind('<<TreeviewSelect>>', self._on_select)
# 右侧:详情面板
detail_frame = tk.LabelFrame(
content_frame,
text=" 任务详情 ",
font=('Microsoft YaHei UI', 10, 'bold'),
fg='#81c784',
bg='#1e1e1e',
relief=tk.GROOVE
)
detail_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
# 详情文本框
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(
detail_container,
wrap=tk.WORD,
font=('Microsoft YaHei UI', 10),
bg='#2d2d2d',
fg='#d4d4d4',
relief=tk.FLAT,
padx=10,
pady=10,
state=tk.DISABLED
)
detail_scrollbar = ttk.Scrollbar(detail_container, orient=tk.VERTICAL, command=self.detail_text.yview)
self.detail_text.configure(yscrollcommand=detail_scrollbar.set)
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))
# 打开日志按钮
self.open_log_btn = tk.Button(
btn_frame,
text="📄 打开日志",
font=('Microsoft YaHei UI', 10),
bg='#424242',
fg='white',
activebackground='#616161',
activeforeground='white',
relief=tk.FLAT,
padx=15,
cursor='hand2',
state=tk.DISABLED,
command=self._open_log
)
self.open_log_btn.pack(side=tk.LEFT)
# 清空历史按钮
clear_btn = tk.Button(
btn_frame,
text="🗑️ 清空历史",
font=('Microsoft YaHei UI', 10),
bg='#d32f2f',
fg='white',
activebackground='#f44336',
activeforeground='white',
relief=tk.FLAT,
padx=15,
cursor='hand2',
command=self._clear_history
)
clear_btn.pack(side=tk.RIGHT)
# 加载数据
self._load_data()
def _load_data(self):
"""加载历史数据到列表"""
# 清空现有数据
for item in self.tree.get_children():
self.tree.delete(item)
# 加载历史记录
records = self.history.get_all()
for record in records:
# 截断过长的输入
input_text = record.user_input
if len(input_text) > 30:
input_text = input_text[:30] + "..."
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=(
record.timestamp,
input_text,
status,
duration
))
# 显示空状态提示
if not records:
self._show_detail("暂无历史记录\n\n执行任务后,记录将显示在这里。")
def _on_select(self, event):
"""选择记录事件"""
selection = self.tree.selection()
if not selection:
return
task_id = selection[0]
record = self.history.get_by_id(task_id)
if record:
self._selected_record = record
self._show_record_detail(record)
self.open_log_btn.config(state=tk.NORMAL)
def _show_record_detail(self, record: TaskRecord):
"""显示记录详情"""
self.detail_text.config(state=tk.NORMAL)
self.detail_text.delete(1.0, tk.END)
# 标题
self.detail_text.insert(tk.END, f"任务 ID: {record.task_id}\n", 'title')
self.detail_text.insert(tk.END, f"时间: {record.timestamp}\n\n")
# 用户输入
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")
# 错误
if record.stderr:
self.detail_text.insert(tk.END, "错误:\n", 'label')
self.detail_text.insert(tk.END, f"{record.stderr}\n", 'error')
self.detail_text.config(state=tk.DISABLED)
def _show_detail(self, text: str):
"""显示详情文本"""
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.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 show(self):
"""显示视图"""
self._load_data() # 刷新数据
self.frame.pack(fill=tk.BOTH, expand=True)
def hide(self):
"""隐藏视图"""
self.frame.pack_forget()
def get_frame(self) -> tk.Frame:
"""获取主框架"""
return self.frame