""" 隐私设置视图 用于配置环境信息采集和脱敏策略 """ 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("", lambda e: canvas.configure(scrollregion=canvas.bbox("all"))) canvas.bind("", configure_scroll) # 鼠标滚轮支持 def on_mousewheel(event): canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") canvas.bind_all("", 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", "路径脱敏", "将路径中的用户名替换为 "), ("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