- 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.
395 lines
13 KiB
Python
395 lines
13 KiB
Python
"""
|
|
隐私设置视图
|
|
用于配置环境信息采集和脱敏策略
|
|
"""
|
|
|
|
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
|
|
|