Files
LocalAgent/ui/privacy_settings_view.py
Mimikko-zeus 8a538bb950 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.
2026-02-27 14:32:30 +08:00

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