feat: refactor API key configuration and enhance application initialization
- Renamed `check_environment` to `check_api_key_configured` for clarity, simplifying the API key validation logic. - Removed the blocking behavior of the API key check during application startup, allowing the app to run while providing a prompt for configuration. - Updated `LocalAgentApp` to accept an `api_configured` parameter, enabling conditional messaging for API key setup. - Enhanced the `SandboxRunner` to support backup management and improved execution result handling with detailed metrics. - Integrated data governance strategies into the `HistoryManager`, ensuring compliance and improved data management. - Added privacy settings and metrics tracking across various components to enhance user experience and application safety.
This commit is contained in:
@@ -396,6 +396,24 @@ class ChatView:
|
||||
)
|
||||
self.settings_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
||||
|
||||
# 隐私设置按钮(将在外部设置回调)
|
||||
self.on_show_privacy = None
|
||||
self.privacy_btn = tk.Button(
|
||||
btn_container,
|
||||
text="🔒 隐私",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#424242',
|
||||
fg='#a5d6a7',
|
||||
activebackground='#616161',
|
||||
activeforeground='#a5d6a7',
|
||||
relief=tk.FLAT,
|
||||
padx=10,
|
||||
pady=3,
|
||||
cursor='hand2',
|
||||
command=lambda: self.on_show_privacy() if self.on_show_privacy else None
|
||||
)
|
||||
self.privacy_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
||||
|
||||
# 历史记录按钮
|
||||
if self.on_show_history:
|
||||
self.history_btn = tk.Button(
|
||||
|
||||
192
ui/clear_confirm_dialog.py
Normal file
192
ui/clear_confirm_dialog.py
Normal file
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
清理确认对话框
|
||||
在清空工作区前显示确认对话框,支持备份和恢复
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Callable, Optional
|
||||
|
||||
|
||||
class ClearConfirmDialog:
|
||||
"""
|
||||
清理确认对话框
|
||||
|
||||
功能:
|
||||
1. 显示当前工作区内容统计
|
||||
2. 提供"清空并备份"、"仅清空"、"取消"选项
|
||||
3. 显示最近的备份信息
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: tk.Tk,
|
||||
file_count: int,
|
||||
total_size: str,
|
||||
has_recent_backup: bool,
|
||||
on_confirm: Callable[[bool], None], # 参数:是否创建备份
|
||||
on_cancel: Callable[[], None]
|
||||
):
|
||||
self.parent = parent
|
||||
self.file_count = file_count
|
||||
self.total_size = total_size
|
||||
self.has_recent_backup = has_recent_backup
|
||||
self.on_confirm = on_confirm
|
||||
self.on_cancel = on_cancel
|
||||
|
||||
self.dialog = None
|
||||
self.result = None
|
||||
|
||||
def show(self):
|
||||
"""显示对话框"""
|
||||
self.dialog = tk.Toplevel(self.parent)
|
||||
self.dialog.title("确认清空工作区")
|
||||
self.dialog.geometry("500x300")
|
||||
self.dialog.resizable(False, False)
|
||||
|
||||
# 居中显示
|
||||
self.dialog.transient(self.parent)
|
||||
self.dialog.grab_set()
|
||||
|
||||
# 主容器
|
||||
main_frame = ttk.Frame(self.dialog, padding="20")
|
||||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# 警告图标和标题
|
||||
title_frame = ttk.Frame(main_frame)
|
||||
title_frame.pack(fill=tk.X, pady=(0, 15))
|
||||
|
||||
warning_label = ttk.Label(
|
||||
title_frame,
|
||||
text="⚠️",
|
||||
font=("Segoe UI Emoji", 24)
|
||||
)
|
||||
warning_label.pack(side=tk.LEFT, padx=(0, 10))
|
||||
|
||||
title_label = ttk.Label(
|
||||
title_frame,
|
||||
text="即将清空工作区",
|
||||
font=("Microsoft YaHei UI", 14, "bold")
|
||||
)
|
||||
title_label.pack(side=tk.LEFT)
|
||||
|
||||
# 内容统计
|
||||
info_frame = ttk.LabelFrame(main_frame, text="当前工作区内容", padding="10")
|
||||
info_frame.pack(fill=tk.X, pady=(0, 15))
|
||||
|
||||
info_text = f"• 文件数量:{self.file_count} 个\n• 总大小:{self.total_size}"
|
||||
info_label = ttk.Label(
|
||||
info_frame,
|
||||
text=info_text,
|
||||
font=("Microsoft YaHei UI", 10)
|
||||
)
|
||||
info_label.pack(anchor=tk.W)
|
||||
|
||||
# 备份提示
|
||||
if self.has_recent_backup:
|
||||
backup_hint = ttk.Label(
|
||||
main_frame,
|
||||
text="💡 提示:检测到最近的备份,您可以随时恢复",
|
||||
font=("Microsoft YaHei UI", 9),
|
||||
foreground="#666666"
|
||||
)
|
||||
backup_hint.pack(fill=tk.X, pady=(0, 15))
|
||||
|
||||
# 说明文字
|
||||
desc_label = ttk.Label(
|
||||
main_frame,
|
||||
text="清空后,input 和 output 目录中的所有文件将被删除。\n建议选择\"清空并备份\"以便后续恢复。",
|
||||
font=("Microsoft YaHei UI", 9),
|
||||
foreground="#666666",
|
||||
wraplength=450
|
||||
)
|
||||
desc_label.pack(fill=tk.X, pady=(0, 20))
|
||||
|
||||
# 按钮区域
|
||||
button_frame = ttk.Frame(main_frame)
|
||||
button_frame.pack(fill=tk.X)
|
||||
|
||||
# 取消按钮
|
||||
cancel_btn = ttk.Button(
|
||||
button_frame,
|
||||
text="取消",
|
||||
command=self._on_cancel,
|
||||
width=12
|
||||
)
|
||||
cancel_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
||||
|
||||
# 仅清空按钮
|
||||
clear_only_btn = ttk.Button(
|
||||
button_frame,
|
||||
text="仅清空(不备份)",
|
||||
command=self._on_clear_only,
|
||||
width=15
|
||||
)
|
||||
clear_only_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
||||
|
||||
# 清空并备份按钮(推荐)
|
||||
clear_backup_btn = ttk.Button(
|
||||
button_frame,
|
||||
text="清空并备份(推荐)",
|
||||
command=self._on_clear_with_backup,
|
||||
width=18
|
||||
)
|
||||
clear_backup_btn.pack(side=tk.RIGHT)
|
||||
|
||||
# 设置默认焦点
|
||||
clear_backup_btn.focus_set()
|
||||
|
||||
# 绑定 ESC 键
|
||||
self.dialog.bind("<Escape>", lambda e: self._on_cancel())
|
||||
|
||||
# 等待对话框关闭
|
||||
self.dialog.wait_window()
|
||||
|
||||
def _on_clear_with_backup(self):
|
||||
"""清空并备份"""
|
||||
self.result = "backup"
|
||||
self.dialog.destroy()
|
||||
self.on_confirm(True)
|
||||
|
||||
def _on_clear_only(self):
|
||||
"""仅清空"""
|
||||
self.result = "clear"
|
||||
self.dialog.destroy()
|
||||
self.on_confirm(False)
|
||||
|
||||
def _on_cancel(self):
|
||||
"""取消"""
|
||||
self.result = "cancel"
|
||||
self.dialog.destroy()
|
||||
self.on_cancel()
|
||||
|
||||
|
||||
def show_clear_confirm_dialog(
|
||||
parent: tk.Tk,
|
||||
file_count: int,
|
||||
total_size: str,
|
||||
has_recent_backup: bool,
|
||||
on_confirm: Callable[[bool], None],
|
||||
on_cancel: Callable[[], None]
|
||||
):
|
||||
"""
|
||||
显示清理确认对话框
|
||||
|
||||
Args:
|
||||
parent: 父窗口
|
||||
file_count: 文件数量
|
||||
total_size: 总大小(格式化字符串)
|
||||
has_recent_backup: 是否有最近的备份
|
||||
on_confirm: 确认回调(参数:是否创建备份)
|
||||
on_cancel: 取消回调
|
||||
"""
|
||||
dialog = ClearConfirmDialog(
|
||||
parent=parent,
|
||||
file_count=file_count,
|
||||
total_size=total_size,
|
||||
has_recent_backup=has_recent_backup,
|
||||
on_confirm=on_confirm,
|
||||
on_cancel=on_cancel
|
||||
)
|
||||
dialog.show()
|
||||
|
||||
338
ui/governance_panel.py
Normal file
338
ui/governance_panel.py
Normal file
@@ -0,0 +1,338 @@
|
||||
"""
|
||||
数据治理监控面板
|
||||
提供可视化的治理指标展示和管理操作
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from history.manager import HistoryManager
|
||||
from history.data_governance import GovernanceMetrics
|
||||
|
||||
|
||||
class GovernancePanel:
|
||||
"""
|
||||
数据治理监控面板
|
||||
|
||||
显示治理指标、执行清理操作、导出数据
|
||||
"""
|
||||
|
||||
def __init__(self, parent: tk.Widget, history_manager: HistoryManager):
|
||||
self.parent = parent
|
||||
self.history = history_manager
|
||||
self.frame = 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)
|
||||
|
||||
title_label = tk.Label(
|
||||
title_frame,
|
||||
text="🛡️ 数据治理监控",
|
||||
font=('Microsoft YaHei UI', 14, 'bold'),
|
||||
fg='#ffd54f',
|
||||
bg='#1e1e1e'
|
||||
)
|
||||
title_label.pack(side=tk.LEFT)
|
||||
|
||||
# 刷新按钮
|
||||
refresh_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._refresh_metrics
|
||||
)
|
||||
refresh_btn.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))
|
||||
|
||||
# 左侧:指标展示
|
||||
metrics_frame = tk.LabelFrame(
|
||||
content_frame,
|
||||
text=" 治理指标 ",
|
||||
font=('Microsoft YaHei UI', 10, 'bold'),
|
||||
fg='#4fc3f7',
|
||||
bg='#1e1e1e',
|
||||
relief=tk.GROOVE
|
||||
)
|
||||
metrics_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
||||
|
||||
# 指标显示区域
|
||||
self.metrics_text = tk.Text(
|
||||
metrics_frame,
|
||||
wrap=tk.WORD,
|
||||
font=('Consolas', 10),
|
||||
bg='#2d2d2d',
|
||||
fg='#d4d4d4',
|
||||
relief=tk.FLAT,
|
||||
padx=15,
|
||||
pady=15,
|
||||
state=tk.DISABLED,
|
||||
height=20
|
||||
)
|
||||
self.metrics_text.pack(fill=tk.BOTH, expand=True, padx=3, pady=3)
|
||||
|
||||
# 配置标签样式
|
||||
self.metrics_text.tag_configure('title', font=('Microsoft YaHei UI', 11, 'bold'), foreground='#ffd54f')
|
||||
self.metrics_text.tag_configure('label', font=('Microsoft YaHei UI', 10, 'bold'), foreground='#4fc3f7')
|
||||
self.metrics_text.tag_configure('value', font=('Consolas', 10), foreground='#81c784')
|
||||
self.metrics_text.tag_configure('warning', font=('Consolas', 10), foreground='#ef5350')
|
||||
self.metrics_text.tag_configure('normal', font=('Microsoft YaHei UI', 10), foreground='#d4d4d4')
|
||||
|
||||
# 右侧:操作面板
|
||||
action_frame = tk.LabelFrame(
|
||||
content_frame,
|
||||
text=" 管理操作 ",
|
||||
font=('Microsoft YaHei UI', 10, 'bold'),
|
||||
fg='#81c784',
|
||||
bg='#1e1e1e',
|
||||
relief=tk.GROOVE
|
||||
)
|
||||
action_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(5, 0))
|
||||
|
||||
# 操作按钮
|
||||
btn_config = {
|
||||
'font': ('Microsoft YaHei UI', 10),
|
||||
'relief': tk.FLAT,
|
||||
'cursor': 'hand2',
|
||||
'width': 18
|
||||
}
|
||||
|
||||
# 手动清理按钮
|
||||
cleanup_btn = tk.Button(
|
||||
action_frame,
|
||||
text="🧹 执行数据清理",
|
||||
bg='#f57c00',
|
||||
fg='white',
|
||||
activebackground='#ff9800',
|
||||
activeforeground='white',
|
||||
command=self._manual_cleanup,
|
||||
**btn_config
|
||||
)
|
||||
cleanup_btn.pack(padx=10, pady=(10, 5))
|
||||
|
||||
tk.Label(
|
||||
action_frame,
|
||||
text="清理过期和敏感数据",
|
||||
font=('Microsoft YaHei UI', 8),
|
||||
fg='#888888',
|
||||
bg='#1e1e1e'
|
||||
).pack(padx=10, pady=(0, 15))
|
||||
|
||||
# 导出脱敏数据按钮
|
||||
export_btn = tk.Button(
|
||||
action_frame,
|
||||
text="📤 导出脱敏数据",
|
||||
bg='#0e639c',
|
||||
fg='white',
|
||||
activebackground='#1177bb',
|
||||
activeforeground='white',
|
||||
command=self._export_sanitized,
|
||||
**btn_config
|
||||
)
|
||||
export_btn.pack(padx=10, pady=(0, 5))
|
||||
|
||||
tk.Label(
|
||||
action_frame,
|
||||
text="导出安全的历史记录",
|
||||
font=('Microsoft YaHei UI', 8),
|
||||
fg='#888888',
|
||||
bg='#1e1e1e'
|
||||
).pack(padx=10, pady=(0, 15))
|
||||
|
||||
# 查看归档按钮
|
||||
archive_btn = tk.Button(
|
||||
action_frame,
|
||||
text="📁 打开归档目录",
|
||||
bg='#424242',
|
||||
fg='white',
|
||||
activebackground='#616161',
|
||||
activeforeground='white',
|
||||
command=self._open_archive,
|
||||
**btn_config
|
||||
)
|
||||
archive_btn.pack(padx=10, pady=(0, 5))
|
||||
|
||||
tk.Label(
|
||||
action_frame,
|
||||
text="查看已归档的记录",
|
||||
font=('Microsoft YaHei UI', 8),
|
||||
fg='#888888',
|
||||
bg='#1e1e1e'
|
||||
).pack(padx=10, pady=(0, 15))
|
||||
|
||||
# 分隔线
|
||||
ttk.Separator(action_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=10, pady=15)
|
||||
|
||||
# 策略说明
|
||||
policy_label = tk.Label(
|
||||
action_frame,
|
||||
text="数据分级策略",
|
||||
font=('Microsoft YaHei UI', 10, 'bold'),
|
||||
fg='#ce93d8',
|
||||
bg='#1e1e1e'
|
||||
)
|
||||
policy_label.pack(padx=10, pady=(0, 10))
|
||||
|
||||
policy_text = """
|
||||
• 完整保存 (90天)
|
||||
敏感度 < 0.3
|
||||
|
||||
• 脱敏保存 (30天)
|
||||
0.3 ≤ 敏感度 < 0.7
|
||||
|
||||
• 最小化保存 (7天)
|
||||
敏感度 ≥ 0.7
|
||||
|
||||
• 自动归档
|
||||
过期数据自动降级或归档
|
||||
"""
|
||||
|
||||
policy_info = tk.Label(
|
||||
action_frame,
|
||||
text=policy_text,
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#b0b0b0',
|
||||
bg='#1e1e1e',
|
||||
justify=tk.LEFT
|
||||
)
|
||||
policy_info.pack(padx=10, pady=(0, 10))
|
||||
|
||||
# 加载指标
|
||||
self._refresh_metrics()
|
||||
|
||||
def _refresh_metrics(self):
|
||||
"""刷新指标显示"""
|
||||
metrics = self.history.get_governance_metrics()
|
||||
|
||||
self.metrics_text.config(state=tk.NORMAL)
|
||||
self.metrics_text.delete(1.0, tk.END)
|
||||
|
||||
if not metrics:
|
||||
self.metrics_text.insert(tk.END, "暂无治理指标数据\n\n", 'normal')
|
||||
self.metrics_text.insert(tk.END, "执行任务后将自动收集指标", 'normal')
|
||||
self.metrics_text.config(state=tk.DISABLED)
|
||||
return
|
||||
|
||||
# 显示指标
|
||||
self.metrics_text.insert(tk.END, "📊 数据统计\n\n", 'title')
|
||||
|
||||
self.metrics_text.insert(tk.END, "总记录数: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.total_records}\n", 'value')
|
||||
|
||||
self.metrics_text.insert(tk.END, "完整保存: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.full_records}\n", 'value')
|
||||
|
||||
self.metrics_text.insert(tk.END, "脱敏保存: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.sanitized_records}\n", 'value')
|
||||
|
||||
self.metrics_text.insert(tk.END, "最小化保存: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.minimal_records}\n", 'value')
|
||||
|
||||
self.metrics_text.insert(tk.END, "已归档: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.archived_records}\n\n", 'value')
|
||||
|
||||
# 存储大小
|
||||
size_mb = metrics.total_size_bytes / 1024 / 1024
|
||||
self.metrics_text.insert(tk.END, "存储占用: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{size_mb:.2f} MB\n\n", 'value')
|
||||
|
||||
# 过期记录
|
||||
if metrics.expired_records > 0:
|
||||
self.metrics_text.insert(tk.END, "⚠️ 待清理: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.expired_records} 条过期记录\n\n", 'warning')
|
||||
|
||||
# 敏感字段命中统计
|
||||
if metrics.sensitive_field_hits:
|
||||
self.metrics_text.insert(tk.END, "🔍 敏感字段命中统计\n\n", 'title')
|
||||
|
||||
for field, count in sorted(metrics.sensitive_field_hits.items(), key=lambda x: x[1], reverse=True):
|
||||
self.metrics_text.insert(tk.END, f" {field}: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{count} 次\n", 'value')
|
||||
|
||||
# 最后清理时间
|
||||
self.metrics_text.insert(tk.END, f"\n\n最后清理: ", 'label')
|
||||
self.metrics_text.insert(tk.END, f"{metrics.last_cleanup_time}\n", 'normal')
|
||||
|
||||
self.metrics_text.config(state=tk.DISABLED)
|
||||
|
||||
def _manual_cleanup(self):
|
||||
"""手动执行数据清理"""
|
||||
result = messagebox.askyesno(
|
||||
"确认清理",
|
||||
"将执行以下操作:\n\n"
|
||||
"• 完整数据过期 → 降级为脱敏\n"
|
||||
"• 脱敏数据过期 → 归档\n"
|
||||
"• 最小化数据过期 → 删除\n\n"
|
||||
"是否继续?",
|
||||
icon='question'
|
||||
)
|
||||
|
||||
if result:
|
||||
try:
|
||||
stats = self.history.manual_cleanup()
|
||||
self._refresh_metrics()
|
||||
|
||||
messagebox.showinfo(
|
||||
"清理完成",
|
||||
f"数据清理完成:\n\n"
|
||||
f"归档: {stats['archived']} 条\n"
|
||||
f"删除: {stats['deleted']} 条\n"
|
||||
f"保留: {stats['remaining']} 条"
|
||||
)
|
||||
except Exception as e:
|
||||
messagebox.showerror("清理失败", f"数据清理失败:\n{e}")
|
||||
|
||||
def _export_sanitized(self):
|
||||
"""导出脱敏数据"""
|
||||
file_path = filedialog.asksaveasfilename(
|
||||
title="导出脱敏数据",
|
||||
defaultextension=".json",
|
||||
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")]
|
||||
)
|
||||
|
||||
if file_path:
|
||||
try:
|
||||
count = self.history.export_sanitized(Path(file_path))
|
||||
messagebox.showinfo("导出成功", f"已导出 {count} 条脱敏记录到:\n{file_path}")
|
||||
except Exception as e:
|
||||
messagebox.showerror("导出失败", f"导出失败:\n{e}")
|
||||
|
||||
def _open_archive(self):
|
||||
"""打开归档目录"""
|
||||
archive_dir = self.history.workspace / "archive"
|
||||
if archive_dir.exists():
|
||||
import os
|
||||
os.startfile(str(archive_dir))
|
||||
else:
|
||||
messagebox.showinfo("提示", "归档目录不存在,暂无归档数据")
|
||||
|
||||
def show(self):
|
||||
"""显示面板"""
|
||||
self._refresh_metrics()
|
||||
self.frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def hide(self):
|
||||
"""隐藏面板"""
|
||||
self.frame.pack_forget()
|
||||
|
||||
def get_frame(self) -> tk.Frame:
|
||||
"""获取主框架"""
|
||||
return self.frame
|
||||
|
||||
@@ -503,7 +503,14 @@ class HistoryView:
|
||||
# 加载历史记录
|
||||
records = self.history.get_all()
|
||||
|
||||
# 用于跟踪已插入的ID,避免重复
|
||||
inserted_ids = set()
|
||||
|
||||
for record in records:
|
||||
# 如果task_id已存在,跳过或使用唯一ID
|
||||
if record.task_id in inserted_ids:
|
||||
continue
|
||||
|
||||
# 使用任务描述(如果有)或截断的用户输入
|
||||
description = getattr(record, 'task_summary', None) or record.user_input
|
||||
if len(description) > 20:
|
||||
@@ -512,12 +519,27 @@ class HistoryView:
|
||||
status = "✓ 成功" if record.success else "✗ 失败"
|
||||
duration = f"{record.duration_ms}ms"
|
||||
|
||||
self.tree.insert_with_checkbox('', tk.END, iid=record.task_id, values=(
|
||||
record.timestamp,
|
||||
description,
|
||||
status,
|
||||
duration
|
||||
))
|
||||
try:
|
||||
self.tree.insert_with_checkbox('', tk.END, iid=record.task_id, values=(
|
||||
record.timestamp,
|
||||
description,
|
||||
status,
|
||||
duration
|
||||
))
|
||||
inserted_ids.add(record.task_id)
|
||||
except tk.TclError as e:
|
||||
# 如果ID已存在,使用带时间戳的唯一ID
|
||||
if "already exists" in str(e):
|
||||
unique_id = f"{record.task_id}_{len(inserted_ids)}"
|
||||
self.tree.insert_with_checkbox('', tk.END, iid=unique_id, values=(
|
||||
record.timestamp,
|
||||
description,
|
||||
status,
|
||||
duration
|
||||
))
|
||||
inserted_ids.add(unique_id)
|
||||
else:
|
||||
raise
|
||||
|
||||
# 更新统计信息
|
||||
self._update_stats()
|
||||
|
||||
394
ui/privacy_settings_view.py
Normal file
394
ui/privacy_settings_view.py
Normal file
@@ -0,0 +1,394 @@
|
||||
"""
|
||||
隐私设置视图
|
||||
用于配置环境信息采集和脱敏策略
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox
|
||||
from typing import Callable, Optional
|
||||
from pathlib import Path
|
||||
|
||||
from app.privacy_config import get_privacy_manager, PrivacyManager
|
||||
|
||||
|
||||
class PrivacySettingsView:
|
||||
"""
|
||||
隐私设置视图
|
||||
|
||||
功能:
|
||||
- 配置环境信息采集开关
|
||||
- 配置脱敏策略
|
||||
- 查看隐私度量指标
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: tk.Widget,
|
||||
workspace: Path,
|
||||
on_back: Optional[Callable[[], None]] = None
|
||||
):
|
||||
self.parent = parent
|
||||
self.workspace = workspace
|
||||
self.on_back = on_back
|
||||
self.privacy_manager: PrivacyManager = get_privacy_manager(workspace)
|
||||
|
||||
# 配置变量
|
||||
self.vars = {}
|
||||
|
||||
# 创建主框架
|
||||
self.frame = tk.Frame(parent, bg='#1e1e1e')
|
||||
|
||||
self._create_ui()
|
||||
self._load_settings()
|
||||
|
||||
def _create_ui(self) -> None:
|
||||
"""创建 UI"""
|
||||
# 标题栏
|
||||
header = tk.Frame(self.frame, bg='#2d2d2d')
|
||||
header.pack(fill=tk.X, pady=(0, 20))
|
||||
|
||||
# 返回按钮
|
||||
back_btn = tk.Button(
|
||||
header,
|
||||
text="← 返回",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#3d3d3d',
|
||||
fg='#ffffff',
|
||||
activebackground='#4d4d4d',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
cursor='hand2',
|
||||
command=self._on_back_click
|
||||
)
|
||||
back_btn.pack(side=tk.LEFT, padx=10, pady=10)
|
||||
|
||||
# 标题
|
||||
title = tk.Label(
|
||||
header,
|
||||
text="🔒 隐私设置",
|
||||
font=('Microsoft YaHei UI', 16, 'bold'),
|
||||
bg='#2d2d2d',
|
||||
fg='#ffffff'
|
||||
)
|
||||
title.pack(side=tk.LEFT, padx=20, pady=10)
|
||||
|
||||
# 滚动区域
|
||||
canvas = tk.Canvas(self.frame, bg='#1e1e1e', highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(self.frame, orient=tk.VERTICAL, command=canvas.yview)
|
||||
|
||||
self.content_frame = tk.Frame(canvas, bg='#1e1e1e')
|
||||
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=20)
|
||||
|
||||
canvas_window = canvas.create_window((0, 0), window=self.content_frame, anchor=tk.NW)
|
||||
|
||||
def configure_scroll(event):
|
||||
canvas.configure(scrollregion=canvas.bbox("all"))
|
||||
canvas.itemconfig(canvas_window, width=event.width)
|
||||
|
||||
self.content_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
|
||||
canvas.bind("<Configure>", configure_scroll)
|
||||
|
||||
# 鼠标滚轮支持
|
||||
def on_mousewheel(event):
|
||||
canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
||||
canvas.bind_all("<MouseWheel>", on_mousewheel)
|
||||
|
||||
# 说明文本
|
||||
desc = tk.Label(
|
||||
self.content_frame,
|
||||
text="控制向 LLM 发送的环境信息,保护您的隐私安全",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#1e1e1e',
|
||||
fg='#808080',
|
||||
anchor=tk.W
|
||||
)
|
||||
desc.pack(fill=tk.X, pady=(10, 20))
|
||||
|
||||
# 环境信息采集区
|
||||
self._create_section("环境信息采集", [
|
||||
("send_os_info", "操作系统信息", "如 Windows 11、macOS 等"),
|
||||
("send_python_version", "Python 版本", "如 Python 3.11.0"),
|
||||
("send_architecture", "系统架构", "如 x86_64、ARM64"),
|
||||
("send_home_dir", "用户主目录", "⚠️ 敏感信息,建议关闭"),
|
||||
("send_workspace_path", "工作空间路径", "代码执行所在目录"),
|
||||
("send_current_dir", "当前工作目录", "⚠️ 敏感信息,建议关闭"),
|
||||
])
|
||||
|
||||
# 脱敏策略区
|
||||
self._create_section("脱敏策略", [
|
||||
("anonymize_paths", "路径脱敏", "将路径中的用户名替换为 <USER>"),
|
||||
("anonymize_username", "用户名脱敏", "隐藏系统用户名"),
|
||||
])
|
||||
|
||||
# 场景化策略区
|
||||
self._create_section("场景化策略", [
|
||||
("chat_minimal_info", "对话场景最小化", "对话时仅发送必要信息(推荐)"),
|
||||
("guidance_full_info", "指导场景完整信息", "操作指导时提供完整环境信息"),
|
||||
])
|
||||
|
||||
# 度量指标区
|
||||
self._create_metrics_section()
|
||||
|
||||
# 按钮区
|
||||
btn_frame = tk.Frame(self.content_frame, bg='#1e1e1e')
|
||||
btn_frame.pack(fill=tk.X, pady=30)
|
||||
|
||||
save_btn = tk.Button(
|
||||
btn_frame,
|
||||
text="💾 保存设置",
|
||||
font=('Microsoft YaHei UI', 12, 'bold'),
|
||||
bg='#0e639c',
|
||||
fg='#ffffff',
|
||||
activebackground='#1177bb',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
cursor='hand2',
|
||||
padx=30,
|
||||
pady=10,
|
||||
command=self._save_settings
|
||||
)
|
||||
save_btn.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
export_btn = tk.Button(
|
||||
btn_frame,
|
||||
text="📊 导出报告",
|
||||
font=('Microsoft YaHei UI', 12),
|
||||
bg='#3d3d3d',
|
||||
fg='#ffffff',
|
||||
activebackground='#4d4d4d',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
cursor='hand2',
|
||||
padx=30,
|
||||
pady=10,
|
||||
command=self._export_report
|
||||
)
|
||||
export_btn.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
# 提示信息
|
||||
tip = tk.Label(
|
||||
self.content_frame,
|
||||
text="💡 提示:关闭敏感信息采集可能影响 AI 回答的准确性,建议开启脱敏策略",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#1e1e1e',
|
||||
fg='#808080',
|
||||
wraplength=600,
|
||||
justify=tk.LEFT
|
||||
)
|
||||
tip.pack(pady=(0, 20))
|
||||
|
||||
def _create_section(self, title: str, fields: list) -> None:
|
||||
"""创建配置区域"""
|
||||
# 区域标题
|
||||
section_title = tk.Label(
|
||||
self.content_frame,
|
||||
text=title,
|
||||
font=('Microsoft YaHei UI', 12, 'bold'),
|
||||
bg='#1e1e1e',
|
||||
fg='#569cd6',
|
||||
anchor=tk.W
|
||||
)
|
||||
section_title.pack(fill=tk.X, pady=(20, 10))
|
||||
|
||||
# 分隔线
|
||||
separator = tk.Frame(self.content_frame, bg='#3d3d3d', height=1)
|
||||
separator.pack(fill=tk.X, pady=(0, 15))
|
||||
|
||||
# 字段
|
||||
for key, label, description in fields:
|
||||
self._create_checkbox_field(key, label, description)
|
||||
|
||||
def _create_checkbox_field(self, key: str, label: str, description: str) -> None:
|
||||
"""创建复选框字段"""
|
||||
field_frame = tk.Frame(self.content_frame, bg='#1e1e1e')
|
||||
field_frame.pack(fill=tk.X, pady=8)
|
||||
|
||||
# 复选框变量
|
||||
var = tk.BooleanVar()
|
||||
self.vars[key] = var
|
||||
|
||||
# 复选框
|
||||
checkbox = tk.Checkbutton(
|
||||
field_frame,
|
||||
text=label,
|
||||
variable=var,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#1e1e1e',
|
||||
fg='#cccccc',
|
||||
selectcolor='#2d2d2d',
|
||||
activebackground='#1e1e1e',
|
||||
activeforeground='#ffffff',
|
||||
cursor='hand2'
|
||||
)
|
||||
checkbox.pack(side=tk.LEFT, anchor=tk.W)
|
||||
|
||||
# 描述
|
||||
desc = tk.Label(
|
||||
field_frame,
|
||||
text=f" ({description})",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#1e1e1e',
|
||||
fg='#808080',
|
||||
anchor=tk.W
|
||||
)
|
||||
desc.pack(side=tk.LEFT)
|
||||
|
||||
def _create_metrics_section(self) -> None:
|
||||
"""创建度量指标区域"""
|
||||
# 区域标题
|
||||
section_title = tk.Label(
|
||||
self.content_frame,
|
||||
text="📊 隐私保护度量",
|
||||
font=('Microsoft YaHei UI', 12, 'bold'),
|
||||
bg='#1e1e1e',
|
||||
fg='#569cd6',
|
||||
anchor=tk.W
|
||||
)
|
||||
section_title.pack(fill=tk.X, pady=(30, 10))
|
||||
|
||||
# 分隔线
|
||||
separator = tk.Frame(self.content_frame, bg='#3d3d3d', height=1)
|
||||
separator.pack(fill=tk.X, pady=(0, 15))
|
||||
|
||||
# 度量指标容器
|
||||
self.metrics_frame = tk.Frame(self.content_frame, bg='#2d2d2d')
|
||||
self.metrics_frame.pack(fill=tk.X, pady=10)
|
||||
|
||||
self._update_metrics_display()
|
||||
|
||||
def _update_metrics_display(self) -> None:
|
||||
"""更新度量指标显示"""
|
||||
# 清空现有内容
|
||||
for widget in self.metrics_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
metrics = self.privacy_manager.get_metrics()
|
||||
|
||||
# 创建指标卡片
|
||||
metrics_data = [
|
||||
("总请求次数", metrics['total_requests'], "#3d3d3d"),
|
||||
("敏感字段上送", metrics['sensitive_fields_sent'], "#8b4513"),
|
||||
("脱敏处理次数", metrics['anonymized_fields'], "#2e8b57"),
|
||||
("用户关闭字段", metrics['user_disabled_fields'], "#4169e1"),
|
||||
]
|
||||
|
||||
for i, (label, value, color) in enumerate(metrics_data):
|
||||
card = tk.Frame(self.metrics_frame, bg=color)
|
||||
card.grid(row=i//2, column=i%2, padx=10, pady=10, sticky='ew')
|
||||
|
||||
value_label = tk.Label(
|
||||
card,
|
||||
text=str(value),
|
||||
font=('Microsoft YaHei UI', 20, 'bold'),
|
||||
bg=color,
|
||||
fg='#ffffff'
|
||||
)
|
||||
value_label.pack(pady=(10, 0))
|
||||
|
||||
name_label = tk.Label(
|
||||
card,
|
||||
text=label,
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg=color,
|
||||
fg='#cccccc'
|
||||
)
|
||||
name_label.pack(pady=(0, 10))
|
||||
|
||||
# 配置列权重
|
||||
self.metrics_frame.columnconfigure(0, weight=1)
|
||||
self.metrics_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# 比率显示
|
||||
if metrics['total_requests'] > 0:
|
||||
ratio_frame = tk.Frame(self.metrics_frame, bg='#2d2d2d')
|
||||
ratio_frame.grid(row=2, column=0, columnspan=2, pady=10, sticky='ew')
|
||||
|
||||
sensitive_ratio = tk.Label(
|
||||
ratio_frame,
|
||||
text=f"敏感字段上送比率: {metrics['sensitive_ratio']:.1%}",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#2d2d2d',
|
||||
fg='#cccccc'
|
||||
)
|
||||
sensitive_ratio.pack(pady=5)
|
||||
|
||||
anon_ratio = tk.Label(
|
||||
ratio_frame,
|
||||
text=f"脱敏处理比率: {metrics['anonymization_ratio']:.1%}",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#2d2d2d',
|
||||
fg='#cccccc'
|
||||
)
|
||||
anon_ratio.pack(pady=5)
|
||||
|
||||
def _load_settings(self) -> None:
|
||||
"""加载设置"""
|
||||
settings_dict = self.privacy_manager.settings.to_dict()
|
||||
for key, var in self.vars.items():
|
||||
if key in settings_dict:
|
||||
var.set(settings_dict[key])
|
||||
|
||||
def _save_settings(self) -> None:
|
||||
"""保存设置"""
|
||||
try:
|
||||
# 收集设置
|
||||
settings = {}
|
||||
for key, var in self.vars.items():
|
||||
settings[key] = var.get()
|
||||
|
||||
# 更新设置
|
||||
self.privacy_manager.update_settings(**settings)
|
||||
|
||||
# 更新度量显示
|
||||
self._update_metrics_display()
|
||||
|
||||
messagebox.showinfo("成功", "隐私设置已保存")
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"保存设置失败: {str(e)}")
|
||||
|
||||
def _export_report(self) -> None:
|
||||
"""导出隐私度量报告"""
|
||||
try:
|
||||
report = self.privacy_manager.export_metrics()
|
||||
|
||||
# 保存到文件
|
||||
report_file = self.workspace / "privacy_report.txt"
|
||||
with open(report_file, 'w', encoding='utf-8') as f:
|
||||
f.write(report)
|
||||
|
||||
messagebox.showinfo(
|
||||
"导出成功",
|
||||
f"隐私度量报告已导出到:\n{report_file}\n\n是否打开查看?"
|
||||
)
|
||||
|
||||
# 打开文件
|
||||
import os
|
||||
os.startfile(str(report_file))
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"导出报告失败: {str(e)}")
|
||||
|
||||
def _on_back_click(self) -> None:
|
||||
"""返回按钮点击"""
|
||||
if self.on_back:
|
||||
self.on_back()
|
||||
|
||||
def show(self) -> None:
|
||||
"""显示视图"""
|
||||
self._load_settings()
|
||||
self._update_metrics_display()
|
||||
self.frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def hide(self) -> None:
|
||||
"""隐藏视图"""
|
||||
self.frame.pack_forget()
|
||||
|
||||
def get_frame(self) -> tk.Frame:
|
||||
"""获取主框架"""
|
||||
return self.frame
|
||||
|
||||
321
ui/reuse_confirm_dialog.py
Normal file
321
ui/reuse_confirm_dialog.py
Normal file
@@ -0,0 +1,321 @@
|
||||
"""
|
||||
复用确认对话框
|
||||
显示任务差异并让用户确认是否复用
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import List, Callable, Optional
|
||||
from history.task_features import TaskDifference
|
||||
|
||||
|
||||
def show_reuse_confirm_dialog(
|
||||
parent: tk.Tk,
|
||||
task_summary: str,
|
||||
timestamp: str,
|
||||
similarity_score: float,
|
||||
differences: List[TaskDifference],
|
||||
on_confirm: Callable,
|
||||
on_reject: Callable
|
||||
):
|
||||
"""
|
||||
显示复用确认对话框
|
||||
|
||||
Args:
|
||||
parent: 父窗口
|
||||
task_summary: 任务摘要
|
||||
timestamp: 任务时间
|
||||
similarity_score: 相似度分数
|
||||
differences: 差异列表
|
||||
on_confirm: 确认回调
|
||||
on_reject: 拒绝回调
|
||||
"""
|
||||
dialog = tk.Toplevel(parent)
|
||||
dialog.title("发现相似任务")
|
||||
dialog.geometry("700x600")
|
||||
dialog.resizable(False, False)
|
||||
dialog.configure(bg='#2b2b2b')
|
||||
|
||||
# 居中显示
|
||||
dialog.transient(parent)
|
||||
dialog.grab_set()
|
||||
|
||||
# 主容器
|
||||
main_frame = tk.Frame(dialog, bg='#2b2b2b')
|
||||
main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=20)
|
||||
|
||||
# 标题
|
||||
title_label = tk.Label(
|
||||
main_frame,
|
||||
text="🔍 发现相似的成功任务",
|
||||
font=('Microsoft YaHei UI', 14, 'bold'),
|
||||
bg='#2b2b2b',
|
||||
fg='#ffffff'
|
||||
)
|
||||
title_label.pack(pady=(0, 15))
|
||||
|
||||
# 任务信息框
|
||||
info_frame = tk.Frame(main_frame, bg='#3c3c3c', relief=tk.FLAT, bd=0)
|
||||
info_frame.pack(fill=tk.X, pady=(0, 15))
|
||||
|
||||
# 任务摘要
|
||||
task_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"任务: {task_summary}",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#3c3c3c',
|
||||
fg='#e0e0e0',
|
||||
anchor='w',
|
||||
justify='left'
|
||||
)
|
||||
task_label.pack(fill=tk.X, padx=15, pady=(10, 5))
|
||||
|
||||
# 时间
|
||||
time_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"时间: {timestamp}",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#3c3c3c',
|
||||
fg='#a0a0a0',
|
||||
anchor='w'
|
||||
)
|
||||
time_label.pack(fill=tk.X, padx=15, pady=(0, 5))
|
||||
|
||||
# 相似度
|
||||
similarity_percent = int(similarity_score * 100)
|
||||
similarity_color = '#4caf50' if similarity_score >= 0.8 else '#ff9800' if similarity_score >= 0.6 else '#f44336'
|
||||
|
||||
similarity_label = tk.Label(
|
||||
info_frame,
|
||||
text=f"相似度: {similarity_percent}%",
|
||||
font=('Microsoft YaHei UI', 9, 'bold'),
|
||||
bg='#3c3c3c',
|
||||
fg=similarity_color,
|
||||
anchor='w'
|
||||
)
|
||||
similarity_label.pack(fill=tk.X, padx=15, pady=(0, 10))
|
||||
|
||||
# 差异部分
|
||||
if differences:
|
||||
# 统计关键差异
|
||||
critical_count = sum(1 for d in differences if d.importance == 'critical')
|
||||
high_count = sum(1 for d in differences if d.importance == 'high')
|
||||
|
||||
# 差异标题
|
||||
diff_title_frame = tk.Frame(main_frame, bg='#2b2b2b')
|
||||
diff_title_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
diff_title = tk.Label(
|
||||
diff_title_frame,
|
||||
text=f"⚠️ 发现 {len(differences)} 处差异",
|
||||
font=('Microsoft YaHei UI', 11, 'bold'),
|
||||
bg='#2b2b2b',
|
||||
fg='#ff9800'
|
||||
)
|
||||
diff_title.pack(side=tk.LEFT)
|
||||
|
||||
if critical_count > 0:
|
||||
critical_badge = tk.Label(
|
||||
diff_title_frame,
|
||||
text=f"{critical_count} 关键",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#f44336',
|
||||
fg='#ffffff',
|
||||
padx=8,
|
||||
pady=2
|
||||
)
|
||||
critical_badge.pack(side=tk.LEFT, padx=(10, 5))
|
||||
|
||||
if high_count > 0:
|
||||
high_badge = tk.Label(
|
||||
diff_title_frame,
|
||||
text=f"{high_count} 重要",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#ff9800',
|
||||
fg='#ffffff',
|
||||
padx=8,
|
||||
pady=2
|
||||
)
|
||||
high_badge.pack(side=tk.LEFT)
|
||||
|
||||
# 差异列表(可滚动)
|
||||
diff_container = tk.Frame(main_frame, bg='#2b2b2b')
|
||||
diff_container.pack(fill=tk.BOTH, expand=True, pady=(0, 15))
|
||||
|
||||
# 创建 Canvas 和 Scrollbar
|
||||
canvas = tk.Canvas(diff_container, bg='#2b2b2b', highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(diff_container, orient="vertical", command=canvas.yview)
|
||||
scrollable_frame = tk.Frame(canvas, bg='#2b2b2b')
|
||||
|
||||
scrollable_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||||
)
|
||||
|
||||
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
||||
canvas.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
# 显示差异
|
||||
importance_colors = {
|
||||
'critical': '#f44336',
|
||||
'high': '#ff9800',
|
||||
'medium': '#2196f3',
|
||||
'low': '#9e9e9e'
|
||||
}
|
||||
|
||||
importance_labels = {
|
||||
'critical': '关键',
|
||||
'high': '重要',
|
||||
'medium': '一般',
|
||||
'low': '次要'
|
||||
}
|
||||
|
||||
for i, diff in enumerate(differences):
|
||||
diff_frame = tk.Frame(scrollable_frame, bg='#3c3c3c', relief=tk.FLAT, bd=0)
|
||||
diff_frame.pack(fill=tk.X, pady=(0, 8), padx=2)
|
||||
|
||||
# 差异标题行
|
||||
header_frame = tk.Frame(diff_frame, bg='#3c3c3c')
|
||||
header_frame.pack(fill=tk.X, padx=10, pady=(8, 5))
|
||||
|
||||
category_label = tk.Label(
|
||||
header_frame,
|
||||
text=diff.category,
|
||||
font=('Microsoft YaHei UI', 9, 'bold'),
|
||||
bg='#3c3c3c',
|
||||
fg='#ffffff'
|
||||
)
|
||||
category_label.pack(side=tk.LEFT)
|
||||
|
||||
importance_badge = tk.Label(
|
||||
header_frame,
|
||||
text=importance_labels[diff.importance],
|
||||
font=('Microsoft YaHei UI', 8),
|
||||
bg=importance_colors[diff.importance],
|
||||
fg='#ffffff',
|
||||
padx=6,
|
||||
pady=1
|
||||
)
|
||||
importance_badge.pack(side=tk.LEFT, padx=(8, 0))
|
||||
|
||||
# 当前值
|
||||
current_frame = tk.Frame(diff_frame, bg='#3c3c3c')
|
||||
current_frame.pack(fill=tk.X, padx=10, pady=(0, 3))
|
||||
|
||||
current_title = tk.Label(
|
||||
current_frame,
|
||||
text="当前任务:",
|
||||
font=('Microsoft YaHei UI', 8),
|
||||
bg='#3c3c3c',
|
||||
fg='#a0a0a0'
|
||||
)
|
||||
current_title.pack(side=tk.LEFT)
|
||||
|
||||
current_value = tk.Label(
|
||||
current_frame,
|
||||
text=diff.current_value,
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#3c3c3c',
|
||||
fg='#4caf50',
|
||||
wraplength=500,
|
||||
justify='left'
|
||||
)
|
||||
current_value.pack(side=tk.LEFT, padx=(5, 0))
|
||||
|
||||
# 历史值
|
||||
history_frame = tk.Frame(diff_frame, bg='#3c3c3c')
|
||||
history_frame.pack(fill=tk.X, padx=10, pady=(0, 8))
|
||||
|
||||
history_title = tk.Label(
|
||||
history_frame,
|
||||
text="历史任务:",
|
||||
font=('Microsoft YaHei UI', 8),
|
||||
bg='#3c3c3c',
|
||||
fg='#a0a0a0'
|
||||
)
|
||||
history_title.pack(side=tk.LEFT)
|
||||
|
||||
history_value = tk.Label(
|
||||
history_frame,
|
||||
text=diff.history_value,
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#3c3c3c',
|
||||
fg='#ff9800',
|
||||
wraplength=500,
|
||||
justify='left'
|
||||
)
|
||||
history_value.pack(side=tk.LEFT, padx=(5, 0))
|
||||
|
||||
canvas.pack(side="left", fill="both", expand=True)
|
||||
scrollbar.pack(side="right", fill="y")
|
||||
else:
|
||||
# 无差异
|
||||
no_diff_label = tk.Label(
|
||||
main_frame,
|
||||
text="✅ 未发现关键差异",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#2b2b2b',
|
||||
fg='#4caf50'
|
||||
)
|
||||
no_diff_label.pack(pady=20)
|
||||
|
||||
# 提示信息
|
||||
hint_label = tk.Label(
|
||||
main_frame,
|
||||
text="是否直接复用该任务的代码?\n(选择「生成新代码」将根据当前需求重新生成)",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#2b2b2b',
|
||||
fg='#a0a0a0',
|
||||
justify='center'
|
||||
)
|
||||
hint_label.pack(pady=(10, 15))
|
||||
|
||||
# 按钮区域
|
||||
button_frame = tk.Frame(main_frame, bg='#2b2b2b')
|
||||
button_frame.pack(fill=tk.X)
|
||||
|
||||
def on_confirm_click():
|
||||
dialog.destroy()
|
||||
on_confirm()
|
||||
|
||||
def on_reject_click():
|
||||
dialog.destroy()
|
||||
on_reject()
|
||||
|
||||
# 复用按钮
|
||||
confirm_btn = tk.Button(
|
||||
button_frame,
|
||||
text="✓ 复用代码",
|
||||
font=('Microsoft YaHei UI', 10, 'bold'),
|
||||
bg='#4caf50',
|
||||
fg='#ffffff',
|
||||
activebackground='#45a049',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
cursor='hand2',
|
||||
command=on_confirm_click,
|
||||
padx=30,
|
||||
pady=10
|
||||
)
|
||||
confirm_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(0, 5))
|
||||
|
||||
# 拒绝按钮
|
||||
reject_btn = tk.Button(
|
||||
button_frame,
|
||||
text="✗ 生成新代码",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#555555',
|
||||
fg='#ffffff',
|
||||
activebackground='#666666',
|
||||
activeforeground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
cursor='hand2',
|
||||
command=on_reject_click,
|
||||
padx=30,
|
||||
pady=10
|
||||
)
|
||||
reject_btn.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=(5, 0))
|
||||
|
||||
# 等待对话框关闭
|
||||
dialog.wait_window()
|
||||
|
||||
@@ -40,7 +40,7 @@ class SettingsView:
|
||||
self,
|
||||
parent: tk.Widget,
|
||||
env_path: Path,
|
||||
on_save: Optional[Callable[[], None]] = None,
|
||||
on_save: Optional[Callable[[bool], None]] = None,
|
||||
on_back: Optional[Callable[[], None]] = None
|
||||
):
|
||||
self.parent = parent
|
||||
@@ -342,10 +342,29 @@ class SettingsView:
|
||||
# 同时更新环境变量
|
||||
os.environ[key] = value
|
||||
|
||||
messagebox.showinfo("成功", "配置已保存!")
|
||||
# 重置 LLM 客户端单例,强制使用新配置
|
||||
from llm.client import reset_client, test_connection
|
||||
reset_client()
|
||||
|
||||
# 进行连通性测试
|
||||
messagebox.showinfo("提示", "配置已保存,正在测试连接...")
|
||||
success, message = test_connection(timeout=15)
|
||||
|
||||
# 记录配置变更度量
|
||||
from llm.config_metrics import get_config_metrics
|
||||
metrics = get_config_metrics(self.env_path.parent / "workspace")
|
||||
metrics.mark_config_changed(connection_test_success=success)
|
||||
|
||||
if success:
|
||||
messagebox.showinfo("成功", f"配置已保存并生效!\n\n{message}")
|
||||
else:
|
||||
messagebox.showwarning(
|
||||
"配置已保存",
|
||||
f"配置已保存,但连接测试失败:\n\n{message}\n\n请检查配置是否正确。"
|
||||
)
|
||||
|
||||
if self.on_save:
|
||||
self.on_save()
|
||||
self.on_save(success)
|
||||
|
||||
except Exception as e:
|
||||
messagebox.showerror("错误", f"保存配置失败: {str(e)}")
|
||||
|
||||
@@ -465,7 +465,7 @@ class TaskGuideView:
|
||||
|
||||
self.risk_label = tk.Label(
|
||||
section,
|
||||
text="• 所有操作仅在 workspace 目录内进行 • 原始文件不会被修改或删除 • 执行代码已通过安全检查",
|
||||
text="• 所有操作仅在 workspace 目录内进行 • 原始文件不会被修改或删除 • 执行代码已通过当前版本安全复检",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#d4d4d4',
|
||||
bg='#1e1e1e',
|
||||
|
||||
Reference in New Issue
Block a user