feat:增强需求澄清与任务管理功能
更新了 .env.example,新增聊天模型配置,以提升对话处理能力。 增强了 README.md,反映了包括需求澄清、代码复用和自动重试在内的新功能。 重构了 agent.py,以支持多模型交互,并为无法在本地执行的任务新增了引导处理逻辑。 改进了 SandboxRunner,增加了任务执行成功校验,并加入了工作区清理功能。 扩展了 HistoryManager,支持任务摘要生成以及记录的批量删除。 优化了 chat_view.py 和 history_view.py 中的 UI 组件,提升用户体验,包括 Markdown 渲染和任务管理选项。
This commit is contained in:
725
ui/clarify_view.py
Normal file
725
ui/clarify_view.py
Normal file
@@ -0,0 +1,725 @@
|
||||
"""
|
||||
需求澄清视图组件
|
||||
用于通过交互式问答澄清用户的模糊需求
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import Callable, Optional, Dict, List, Any
|
||||
|
||||
|
||||
class ClarifyOption:
|
||||
"""澄清选项数据类"""
|
||||
def __init__(
|
||||
self,
|
||||
id: str,
|
||||
type: str, # radio, checkbox, input
|
||||
label: str,
|
||||
choices: List[str] = None,
|
||||
default: str = None,
|
||||
placeholder: str = None
|
||||
):
|
||||
self.id = id
|
||||
self.type = type
|
||||
self.label = label
|
||||
self.choices = choices or []
|
||||
self.default = default
|
||||
self.placeholder = placeholder or ""
|
||||
|
||||
|
||||
class ClarifyView:
|
||||
"""
|
||||
需求澄清视图
|
||||
|
||||
支持:
|
||||
- 单选按钮 (radio)
|
||||
- 复选框 (checkbox)
|
||||
- 输入框 (input)
|
||||
- 多轮对话展示
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: tk.Widget,
|
||||
on_submit: Callable[[Dict[str, Any]], None],
|
||||
on_cancel: Callable[[], None]
|
||||
):
|
||||
self.parent = parent
|
||||
self.on_submit = on_submit
|
||||
self.on_cancel = on_cancel
|
||||
|
||||
# 存储控件变量
|
||||
self._vars: Dict[str, Any] = {}
|
||||
self._option_widgets: List[tk.Widget] = []
|
||||
|
||||
# 对话历史
|
||||
self._history: List[Dict[str, Any]] = []
|
||||
|
||||
self._create_widgets()
|
||||
|
||||
def _create_widgets(self):
|
||||
"""创建 UI 组件"""
|
||||
self.frame = tk.Frame(self.parent, bg='#1e1e1e')
|
||||
|
||||
# 标题栏
|
||||
title_frame = tk.Frame(self.frame, bg='#2d2d2d')
|
||||
title_frame.pack(fill=tk.X)
|
||||
|
||||
title_label = tk.Label(
|
||||
title_frame,
|
||||
text="💬 需求澄清",
|
||||
font=('Microsoft YaHei UI', 14, 'bold'),
|
||||
fg='#4fc3f7',
|
||||
bg='#2d2d2d',
|
||||
pady=10
|
||||
)
|
||||
title_label.pack(side=tk.LEFT, padx=15)
|
||||
|
||||
# 提示信息
|
||||
tip_label = tk.Label(
|
||||
title_frame,
|
||||
text="请回答以下问题,帮助我更好地理解您的需求",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#888888',
|
||||
bg='#2d2d2d'
|
||||
)
|
||||
tip_label.pack(side=tk.RIGHT, padx=15)
|
||||
|
||||
# 主内容区域(可滚动)
|
||||
content_container = tk.Frame(self.frame, bg='#1e1e1e')
|
||||
content_container.pack(fill=tk.BOTH, expand=True, padx=15, pady=10)
|
||||
|
||||
# 创建 Canvas 和滚动条
|
||||
self.canvas = tk.Canvas(content_container, bg='#1e1e1e', highlightthickness=0)
|
||||
scrollbar = ttk.Scrollbar(content_container, orient=tk.VERTICAL, command=self.canvas.yview)
|
||||
|
||||
self.content_frame = tk.Frame(self.canvas, bg='#1e1e1e')
|
||||
|
||||
self.canvas.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
self.canvas_window = self.canvas.create_window((0, 0), window=self.content_frame, anchor=tk.NW)
|
||||
|
||||
# 绑定事件
|
||||
self.content_frame.bind("<Configure>", self._on_frame_configure)
|
||||
self.canvas.bind("<Configure>", self._on_canvas_configure)
|
||||
|
||||
# 鼠标滚轮支持
|
||||
self.canvas.bind_all("<MouseWheel>", self._on_mousewheel)
|
||||
|
||||
# 对话历史区域
|
||||
self.history_frame = tk.Frame(self.content_frame, bg='#1e1e1e')
|
||||
self.history_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
# 当前问题区域
|
||||
self.question_frame = tk.Frame(self.content_frame, bg='#252526', relief=tk.FLAT)
|
||||
self.question_frame.pack(fill=tk.X, pady=10)
|
||||
|
||||
# 问题标签
|
||||
self.question_label = tk.Label(
|
||||
self.question_frame,
|
||||
text="",
|
||||
font=('Microsoft YaHei UI', 11),
|
||||
fg='#ffffff',
|
||||
bg='#252526',
|
||||
wraplength=600,
|
||||
justify=tk.LEFT,
|
||||
padx=15,
|
||||
pady=10
|
||||
)
|
||||
self.question_label.pack(fill=tk.X)
|
||||
|
||||
# 选项区域
|
||||
self.options_frame = tk.Frame(self.question_frame, bg='#252526')
|
||||
self.options_frame.pack(fill=tk.X, padx=15, pady=(0, 15))
|
||||
|
||||
# 底部按钮区域
|
||||
btn_frame = tk.Frame(self.frame, bg='#1e1e1e')
|
||||
btn_frame.pack(fill=tk.X, padx=15, pady=15)
|
||||
|
||||
# 取消按钮
|
||||
self.cancel_btn = tk.Button(
|
||||
btn_frame,
|
||||
text="取消",
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#424242',
|
||||
fg='white',
|
||||
activebackground='#616161',
|
||||
activeforeground='white',
|
||||
relief=tk.FLAT,
|
||||
padx=20,
|
||||
pady=5,
|
||||
cursor='hand2',
|
||||
command=self._on_cancel
|
||||
)
|
||||
self.cancel_btn.pack(side=tk.LEFT)
|
||||
|
||||
# 已收集信息提示
|
||||
self.info_label = tk.Label(
|
||||
btn_frame,
|
||||
text="",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#81c784',
|
||||
bg='#1e1e1e'
|
||||
)
|
||||
self.info_label.pack(side=tk.LEFT, padx=20)
|
||||
|
||||
# 确定按钮
|
||||
self.submit_btn = tk.Button(
|
||||
btn_frame,
|
||||
text="确定 →",
|
||||
font=('Microsoft YaHei UI', 10, 'bold'),
|
||||
bg='#0e639c',
|
||||
fg='white',
|
||||
activebackground='#1177bb',
|
||||
activeforeground='white',
|
||||
relief=tk.FLAT,
|
||||
padx=20,
|
||||
pady=5,
|
||||
cursor='hand2',
|
||||
command=self._on_submit
|
||||
)
|
||||
self.submit_btn.pack(side=tk.RIGHT)
|
||||
|
||||
def _on_frame_configure(self, event):
|
||||
"""内容框架大小变化"""
|
||||
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
||||
|
||||
def _on_canvas_configure(self, event):
|
||||
"""Canvas 大小变化"""
|
||||
self.canvas.itemconfig(self.canvas_window, width=event.width)
|
||||
|
||||
def _on_mousewheel(self, event):
|
||||
"""鼠标滚轮"""
|
||||
self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
|
||||
|
||||
def set_question(self, question: str, options: List[Dict[str, Any]]):
|
||||
"""
|
||||
设置当前问题和选项
|
||||
|
||||
Args:
|
||||
question: 问题文本
|
||||
options: 选项列表,每个选项是一个字典
|
||||
"""
|
||||
# 更新问题
|
||||
self.question_label.config(text=f"❓ {question}")
|
||||
|
||||
# 清除旧选项
|
||||
for widget in self._option_widgets:
|
||||
widget.destroy()
|
||||
self._option_widgets.clear()
|
||||
self._vars.clear()
|
||||
|
||||
# 创建新选项
|
||||
for opt_data in options:
|
||||
opt = ClarifyOption(
|
||||
id=opt_data.get('id', ''),
|
||||
type=opt_data.get('type', 'input'),
|
||||
label=opt_data.get('label', ''),
|
||||
choices=opt_data.get('choices', []),
|
||||
default=opt_data.get('default'),
|
||||
placeholder=opt_data.get('placeholder', '')
|
||||
)
|
||||
self._create_option_widget(opt)
|
||||
|
||||
def _create_option_widget(self, option: ClarifyOption):
|
||||
"""创建选项控件"""
|
||||
# 选项容器
|
||||
container = tk.Frame(self.options_frame, bg='#252526')
|
||||
container.pack(fill=tk.X, pady=5)
|
||||
self._option_widgets.append(container)
|
||||
|
||||
# 标签
|
||||
if option.label:
|
||||
label = tk.Label(
|
||||
container,
|
||||
text=option.label,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
fg='#cccccc',
|
||||
bg='#252526'
|
||||
)
|
||||
label.pack(anchor=tk.W, pady=(0, 5))
|
||||
|
||||
if option.type == 'radio':
|
||||
self._create_radio_option(container, option)
|
||||
elif option.type == 'checkbox':
|
||||
self._create_checkbox_option(container, option)
|
||||
elif option.type == 'input':
|
||||
self._create_input_option(container, option)
|
||||
|
||||
def _create_radio_option(self, parent: tk.Widget, option: ClarifyOption):
|
||||
"""创建单选按钮"""
|
||||
var = tk.StringVar(value=option.default or (option.choices[0] if option.choices else ''))
|
||||
self._vars[option.id] = var
|
||||
|
||||
radio_frame = tk.Frame(parent, bg='#252526')
|
||||
radio_frame.pack(fill=tk.X)
|
||||
|
||||
# 检查是否是位置选项(需要预览)
|
||||
is_position = self._is_position_option(option)
|
||||
|
||||
if is_position:
|
||||
# 使用网格布局显示位置预览
|
||||
self._create_position_radio_with_preview(radio_frame, option, var)
|
||||
else:
|
||||
# 普通单选按钮
|
||||
for choice in option.choices:
|
||||
rb = tk.Radiobutton(
|
||||
radio_frame,
|
||||
text=choice,
|
||||
variable=var,
|
||||
value=choice,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
fg='#e0e0e0',
|
||||
bg='#252526',
|
||||
activebackground='#252526',
|
||||
activeforeground='#ffffff',
|
||||
selectcolor='#3c3c3c',
|
||||
cursor='hand2'
|
||||
)
|
||||
rb.pack(anchor=tk.W, pady=2)
|
||||
self._option_widgets.append(rb)
|
||||
|
||||
def _is_position_option(self, option: ClarifyOption) -> bool:
|
||||
"""判断是否是位置选项"""
|
||||
position_keywords = ['position', 'pos', '位置', '方位']
|
||||
|
||||
opt_id_lower = option.id.lower()
|
||||
label_lower = option.label.lower()
|
||||
|
||||
for keyword in position_keywords:
|
||||
if keyword in opt_id_lower or keyword in label_lower:
|
||||
return True
|
||||
|
||||
# 检查选项是否包含位置相关词汇
|
||||
position_values = ['左上', '右上', '左下', '右下', '居中', '中心', '顶部', '底部',
|
||||
'top', 'bottom', 'left', 'right', 'center', 'middle']
|
||||
|
||||
for choice in option.choices:
|
||||
choice_lower = choice.lower()
|
||||
for pos in position_values:
|
||||
if pos in choice_lower:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _create_position_radio_with_preview(self, parent: tk.Widget, option: ClarifyOption, var: tk.StringVar):
|
||||
"""创建带位置预览的单选按钮"""
|
||||
container = tk.Frame(parent, bg='#252526')
|
||||
container.pack(fill=tk.X, pady=5)
|
||||
|
||||
# 左侧:单选按钮列表
|
||||
radio_list = tk.Frame(container, bg='#252526')
|
||||
radio_list.pack(side=tk.LEFT, fill=tk.Y)
|
||||
|
||||
for choice in option.choices:
|
||||
rb = tk.Radiobutton(
|
||||
radio_list,
|
||||
text=choice,
|
||||
variable=var,
|
||||
value=choice,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
fg='#e0e0e0',
|
||||
bg='#252526',
|
||||
activebackground='#252526',
|
||||
activeforeground='#ffffff',
|
||||
selectcolor='#3c3c3c',
|
||||
cursor='hand2',
|
||||
command=lambda: self._update_position_preview(var, preview_canvas)
|
||||
)
|
||||
rb.pack(anchor=tk.W, pady=2)
|
||||
self._option_widgets.append(rb)
|
||||
|
||||
# 右侧:位置预览
|
||||
preview_frame = tk.Frame(container, bg='#3c3c3c', relief=tk.SOLID, borderwidth=1)
|
||||
preview_frame.pack(side=tk.LEFT, padx=(20, 0))
|
||||
|
||||
preview_canvas = tk.Canvas(
|
||||
preview_frame,
|
||||
width=120,
|
||||
height=80,
|
||||
bg='#3c3c3c',
|
||||
highlightthickness=0
|
||||
)
|
||||
preview_canvas.pack(padx=2, pady=2)
|
||||
self._option_widgets.append(preview_canvas)
|
||||
|
||||
# 绘制初始预览
|
||||
self._update_position_preview(var, preview_canvas)
|
||||
|
||||
# 绑定变量变化
|
||||
var.trace_add('write', lambda *args: self._update_position_preview(var, preview_canvas))
|
||||
|
||||
def _update_position_preview(self, var: tk.StringVar, canvas: tk.Canvas):
|
||||
"""更新位置预览"""
|
||||
canvas.delete("all")
|
||||
|
||||
# 绘制背景矩形(代表图片)
|
||||
canvas.create_rectangle(5, 5, 115, 75, outline='#666666', width=1)
|
||||
|
||||
# 获取当前选择的位置
|
||||
position = var.get().lower()
|
||||
|
||||
# 计算标记位置
|
||||
positions_map = {
|
||||
# 中文
|
||||
'左上': (20, 20),
|
||||
'右上': (100, 20),
|
||||
'左下': (20, 60),
|
||||
'右下': (100, 60),
|
||||
'居中': (60, 40),
|
||||
'中心': (60, 40),
|
||||
'顶部居中': (60, 20),
|
||||
'底部居中': (60, 60),
|
||||
'左侧居中': (20, 40),
|
||||
'右侧居中': (100, 40),
|
||||
# 英文
|
||||
'top-left': (20, 20),
|
||||
'top-right': (100, 20),
|
||||
'bottom-left': (20, 60),
|
||||
'bottom-right': (100, 60),
|
||||
'center': (60, 40),
|
||||
'top': (60, 20),
|
||||
'bottom': (60, 60),
|
||||
'left': (20, 40),
|
||||
'right': (100, 40),
|
||||
}
|
||||
|
||||
# 查找匹配的位置
|
||||
marker_pos = None
|
||||
for key, pos in positions_map.items():
|
||||
if key in position:
|
||||
marker_pos = pos
|
||||
break
|
||||
|
||||
if not marker_pos:
|
||||
# 默认居中
|
||||
marker_pos = (60, 40)
|
||||
|
||||
# 绘制位置标记
|
||||
x, y = marker_pos
|
||||
canvas.create_oval(x-8, y-8, x+8, y+8, fill='#4fc3f7', outline='#29b6f6', width=2)
|
||||
canvas.create_text(x, y, text="W", fill='white', font=('Arial', 8, 'bold'))
|
||||
|
||||
def _create_checkbox_option(self, parent: tk.Widget, option: ClarifyOption):
|
||||
"""创建复选框"""
|
||||
vars_dict = {}
|
||||
self._vars[option.id] = vars_dict
|
||||
|
||||
checkbox_frame = tk.Frame(parent, bg='#252526')
|
||||
checkbox_frame.pack(fill=tk.X)
|
||||
|
||||
# 解析默认值
|
||||
default_values = []
|
||||
if option.default:
|
||||
if isinstance(option.default, list):
|
||||
default_values = option.default
|
||||
elif isinstance(option.default, str):
|
||||
default_values = [option.default]
|
||||
|
||||
for choice in option.choices:
|
||||
var = tk.BooleanVar(value=choice in default_values)
|
||||
vars_dict[choice] = var
|
||||
|
||||
cb = tk.Checkbutton(
|
||||
checkbox_frame,
|
||||
text=choice,
|
||||
variable=var,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
fg='#e0e0e0',
|
||||
bg='#252526',
|
||||
activebackground='#252526',
|
||||
activeforeground='#ffffff',
|
||||
selectcolor='#3c3c3c',
|
||||
cursor='hand2'
|
||||
)
|
||||
cb.pack(anchor=tk.W, pady=2)
|
||||
self._option_widgets.append(cb)
|
||||
|
||||
def _create_input_option(self, parent: tk.Widget, option: ClarifyOption):
|
||||
"""创建输入框"""
|
||||
var = tk.StringVar(value=option.default or '')
|
||||
self._vars[option.id] = var
|
||||
|
||||
input_container = tk.Frame(parent, bg='#252526')
|
||||
input_container.pack(fill=tk.X, pady=2)
|
||||
|
||||
entry = tk.Entry(
|
||||
input_container,
|
||||
textvariable=var,
|
||||
font=('Microsoft YaHei UI', 10),
|
||||
bg='#3c3c3c',
|
||||
fg='#ffffff',
|
||||
insertbackground='#ffffff',
|
||||
relief=tk.FLAT,
|
||||
width=40
|
||||
)
|
||||
entry.pack(side=tk.LEFT, ipady=5)
|
||||
self._option_widgets.append(entry)
|
||||
|
||||
# 检查是否是颜色输入(通过 id 或 label 判断)
|
||||
is_color = self._is_color_option(option)
|
||||
|
||||
if is_color:
|
||||
# 添加颜色预览框
|
||||
preview_frame = tk.Frame(input_container, bg='#252526')
|
||||
preview_frame.pack(side=tk.LEFT, padx=(10, 0))
|
||||
|
||||
color_preview = tk.Label(
|
||||
preview_frame,
|
||||
text=" ",
|
||||
bg=option.default or '#000000',
|
||||
width=4,
|
||||
height=1,
|
||||
relief=tk.SOLID,
|
||||
borderwidth=1
|
||||
)
|
||||
color_preview.pack(side=tk.LEFT)
|
||||
self._option_widgets.append(color_preview)
|
||||
|
||||
# 添加颜色选择按钮
|
||||
color_btn = tk.Button(
|
||||
preview_frame,
|
||||
text="选择",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
bg='#424242',
|
||||
fg='white',
|
||||
activebackground='#616161',
|
||||
activeforeground='white',
|
||||
relief=tk.FLAT,
|
||||
padx=8,
|
||||
cursor='hand2',
|
||||
command=lambda v=var, p=color_preview: self._pick_color(v, p)
|
||||
)
|
||||
color_btn.pack(side=tk.LEFT, padx=(5, 0))
|
||||
self._option_widgets.append(color_btn)
|
||||
|
||||
# 绑定输入变化事件更新预览
|
||||
var.trace_add('write', lambda *args, v=var, p=color_preview: self._update_color_preview(v, p))
|
||||
|
||||
# 占位符提示
|
||||
if option.placeholder:
|
||||
placeholder_label = tk.Label(
|
||||
parent,
|
||||
text=f"💡 {option.placeholder}",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#666666',
|
||||
bg='#252526'
|
||||
)
|
||||
placeholder_label.pack(anchor=tk.W)
|
||||
self._option_widgets.append(placeholder_label)
|
||||
|
||||
def _is_color_option(self, option: ClarifyOption) -> bool:
|
||||
"""判断是否是颜色选项"""
|
||||
color_keywords = ['color', 'colour', '颜色', '色彩', 'rgb', 'hex']
|
||||
|
||||
# 检查 id
|
||||
opt_id_lower = option.id.lower()
|
||||
for keyword in color_keywords:
|
||||
if keyword in opt_id_lower:
|
||||
return True
|
||||
|
||||
# 检查 label
|
||||
label_lower = option.label.lower()
|
||||
for keyword in color_keywords:
|
||||
if keyword in label_lower:
|
||||
return True
|
||||
|
||||
# 检查默认值是否像颜色值
|
||||
if option.default:
|
||||
default = option.default.strip()
|
||||
if default.startswith('#') and len(default) in [4, 7, 9]:
|
||||
return True
|
||||
|
||||
# 检查 placeholder
|
||||
if option.placeholder:
|
||||
placeholder_lower = option.placeholder.lower()
|
||||
for keyword in color_keywords:
|
||||
if keyword in placeholder_lower:
|
||||
return True
|
||||
# 检查是否包含颜色格式提示
|
||||
if '#' in option.placeholder and ('rgb' in placeholder_lower or 'rrggbb' in placeholder_lower):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _update_color_preview(self, var: tk.StringVar, preview: tk.Label):
|
||||
"""更新颜色预览"""
|
||||
color = var.get().strip()
|
||||
|
||||
# 验证颜色格式
|
||||
if self._is_valid_color(color):
|
||||
try:
|
||||
preview.config(bg=color)
|
||||
except tk.TclError:
|
||||
pass # 无效颜色,忽略
|
||||
|
||||
def _is_valid_color(self, color: str) -> bool:
|
||||
"""验证颜色格式是否有效"""
|
||||
if not color:
|
||||
return False
|
||||
|
||||
# 检查十六进制颜色格式
|
||||
if color.startswith('#'):
|
||||
hex_part = color[1:]
|
||||
if len(hex_part) in [3, 6, 8]:
|
||||
try:
|
||||
int(hex_part, 16)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
# 检查常见颜色名称
|
||||
common_colors = [
|
||||
'red', 'green', 'blue', 'yellow', 'orange', 'purple', 'pink',
|
||||
'black', 'white', 'gray', 'grey', 'cyan', 'magenta', 'brown'
|
||||
]
|
||||
if color.lower() in common_colors:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _pick_color(self, var: tk.StringVar, preview: tk.Label):
|
||||
"""打开颜色选择器"""
|
||||
from tkinter import colorchooser
|
||||
|
||||
# 获取当前颜色作为初始值
|
||||
current = var.get().strip()
|
||||
initial_color = current if self._is_valid_color(current) else '#000000'
|
||||
|
||||
# 打开颜色选择对话框
|
||||
color = colorchooser.askcolor(
|
||||
color=initial_color,
|
||||
title="选择颜色"
|
||||
)
|
||||
|
||||
if color[1]: # color[1] 是十六进制颜色值
|
||||
var.set(color[1].upper())
|
||||
preview.config(bg=color[1])
|
||||
|
||||
def add_history_item(self, question: str, answer: str):
|
||||
"""
|
||||
添加历史对话项
|
||||
|
||||
Args:
|
||||
question: 问题
|
||||
answer: 用户的回答
|
||||
"""
|
||||
self._history.append({'question': question, 'answer': answer})
|
||||
|
||||
# 创建历史项 UI
|
||||
item_frame = tk.Frame(self.history_frame, bg='#2d2d2d', relief=tk.FLAT)
|
||||
item_frame.pack(fill=tk.X, pady=3)
|
||||
|
||||
# 问题
|
||||
q_label = tk.Label(
|
||||
item_frame,
|
||||
text=f"Q: {question}",
|
||||
font=('Microsoft YaHei UI', 9),
|
||||
fg='#888888',
|
||||
bg='#2d2d2d',
|
||||
anchor=tk.W,
|
||||
padx=10,
|
||||
pady=3
|
||||
)
|
||||
q_label.pack(fill=tk.X)
|
||||
|
||||
# 回答
|
||||
a_label = tk.Label(
|
||||
item_frame,
|
||||
text=f"A: {answer}",
|
||||
font=('Microsoft YaHei UI', 9, 'bold'),
|
||||
fg='#81c784',
|
||||
bg='#2d2d2d',
|
||||
anchor=tk.W,
|
||||
padx=10,
|
||||
pady=3
|
||||
)
|
||||
a_label.pack(fill=tk.X)
|
||||
|
||||
def get_current_answers(self) -> Dict[str, Any]:
|
||||
"""获取当前选项的答案"""
|
||||
answers = {}
|
||||
|
||||
for opt_id, var in self._vars.items():
|
||||
if isinstance(var, tk.StringVar):
|
||||
answers[opt_id] = var.get()
|
||||
elif isinstance(var, dict):
|
||||
# checkbox 的情况
|
||||
selected = [k for k, v in var.items() if v.get()]
|
||||
answers[opt_id] = selected
|
||||
|
||||
return answers
|
||||
|
||||
def update_info_label(self, collected_count: int, total_count: int):
|
||||
"""更新已收集信息提示"""
|
||||
if total_count > 0:
|
||||
self.info_label.config(text=f"已收集 {collected_count}/{total_count} 项信息")
|
||||
else:
|
||||
self.info_label.config(text="")
|
||||
|
||||
def set_submit_button_text(self, text: str):
|
||||
"""设置确定按钮文本"""
|
||||
self.submit_btn.config(text=text)
|
||||
|
||||
def _on_submit(self):
|
||||
"""确定按钮点击"""
|
||||
answers = self.get_current_answers()
|
||||
self.on_submit(answers)
|
||||
|
||||
def _on_cancel(self):
|
||||
"""取消按钮点击"""
|
||||
self.on_cancel()
|
||||
|
||||
def show_loading(self, text: str = "加载中..."):
|
||||
"""显示加载状态"""
|
||||
# 禁用按钮
|
||||
self.submit_btn.config(state=tk.DISABLED)
|
||||
self.cancel_btn.config(state=tk.DISABLED)
|
||||
|
||||
# 更新信息标签显示加载状态
|
||||
self._original_info_text = self.info_label.cget('text')
|
||||
self.info_label.config(text=f"⏳ {text}", fg='#ffa726')
|
||||
|
||||
def hide_loading(self):
|
||||
"""隐藏加载状态"""
|
||||
# 恢复按钮
|
||||
self.submit_btn.config(state=tk.NORMAL)
|
||||
self.cancel_btn.config(state=tk.NORMAL)
|
||||
|
||||
# 恢复信息标签
|
||||
if hasattr(self, '_original_info_text'):
|
||||
self.info_label.config(text=self._original_info_text, fg='#81c784')
|
||||
|
||||
def show(self):
|
||||
"""显示视图"""
|
||||
self.frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def hide(self):
|
||||
"""隐藏视图"""
|
||||
self.frame.pack_forget()
|
||||
|
||||
def reset(self):
|
||||
"""重置视图"""
|
||||
# 清除历史
|
||||
self._history.clear()
|
||||
for widget in self.history_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
# 清除选项
|
||||
for widget in self._option_widgets:
|
||||
widget.destroy()
|
||||
self._option_widgets.clear()
|
||||
self._vars.clear()
|
||||
|
||||
# 重置标签
|
||||
self.question_label.config(text="")
|
||||
self.info_label.config(text="")
|
||||
self.submit_btn.config(text="确定 →")
|
||||
|
||||
def get_frame(self) -> tk.Frame:
|
||||
"""获取主框架"""
|
||||
return self.frame
|
||||
|
||||
Reference in New Issue
Block a user