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:
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
|
||||
|
||||
Reference in New Issue
Block a user