继续优化AI图片列表UI显示

This commit is contained in:
xinzhu.yin
2026-05-28 17:34:51 +08:00
parent 64764524aa
commit 4498ec501e

View File

@@ -5,6 +5,8 @@ from __future__ import annotations
import os import os
import sys import sys
import threading import threading
import time
import logging
import tkinter as tk import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog from tkinter import filedialog, messagebox, simpledialog
@@ -20,6 +22,9 @@ if TYPE_CHECKING:
from pqAutomationApp import PQAutomationApp from pqAutomationApp import PQAutomationApp
logger = logging.getLogger(__name__)
def _theme_colors(): def _theme_colors():
style = ttk.Style() style = ttk.Style()
colors = style.colors colors = style.colors
@@ -35,6 +40,118 @@ def _theme_colors():
} }
def _apply_ai_image_list_style(self: "PQAutomationApp"):
"""刷新 AI 图片列表控件样式,尽量贴合当前主题色板。"""
palette = _theme_colors()
style = ttk.Style()
style.configure(
"AIImage.Treeview",
background=palette["input_bg"],
fieldbackground=palette["input_bg"],
foreground=palette["input_fg"],
bordercolor=palette["border"],
lightcolor=palette["border"],
darkcolor=palette["border"],
rowheight=24,
font=("微软雅黑", 9),
)
style.configure(
"AIImage.Treeview.Heading",
background=palette["bg"],
foreground=palette["muted"],
bordercolor=palette["border"],
font=("微软雅黑", 9, "bold"),
)
style.map(
"AIImage.Treeview",
background=[("selected", palette["select_bg"])],
foreground=[("selected", palette["select_fg"])],
)
def _hide_tree_tooltip(self: "PQAutomationApp"):
tip = getattr(self, "_ai_image_tooltip", None)
if tip is None:
return
try:
tip.withdraw()
except Exception:
pass
self._ai_image_tooltip_item = ""
def _show_tree_tooltip(self: "PQAutomationApp", text: str, x_root: int, y_root: int, item_id: str):
if not text:
_hide_tree_tooltip(self)
return
tip = getattr(self, "_ai_image_tooltip", None)
if tip is None:
tip = tk.Toplevel(self.root)
tip.withdraw()
tip.overrideredirect(True)
tip.transient(self.root)
label = tk.Label(
tip,
text="",
justify=tk.LEFT,
anchor=tk.W,
bg="#ffffff",
fg="#1f2937",
relief=tk.SOLID,
bd=1,
padx=8,
pady=6,
font=("微软雅黑", 9),
wraplength=520,
)
label.pack(fill=tk.BOTH, expand=True)
self._ai_image_tooltip = tip
self._ai_image_tooltip_label = label
else:
label = getattr(self, "_ai_image_tooltip_label", None)
if label is None:
return
self._ai_image_tooltip_item = item_id
label.configure(text=text)
tip.geometry(f"+{x_root + 14}+{y_root + 18}")
tip.deiconify()
tip.lift()
def _on_tree_motion(self: "PQAutomationApp", event):
tree = self.ai_image_tree
item_id = tree.identify_row(event.y)
col = tree.identify_column(event.x)
region = tree.identify("region", event.x, event.y)
if not item_id or col != "#0" or region not in {"tree", "cell"}:
_hide_tree_tooltip(self)
return
node_map = getattr(self, "_ai_image_node_map", None) or {}
ridx = node_map.get(item_id)
if ridx is None or ridx < 0 or ridx >= len(self.ai_image_records):
_hide_tree_tooltip(self)
return
rec = self.ai_image_records[ridx]
full_text = (rec.display_name or "").strip()
if not full_text:
_hide_tree_tooltip(self)
return
# 悬浮到任意图片标题项时都显示完整描述,避免因截断判定误差导致无响应。
if getattr(self, "_ai_image_tooltip_item", "") == item_id:
tip = getattr(self, "_ai_image_tooltip", None)
if tip is not None:
try:
tip.geometry(f"+{event.x_root + 14}+{event.y_root + 18}")
except Exception:
pass
return
_show_tree_tooltip(self, full_text, event.x_root, event.y_root, item_id)
@@ -56,6 +173,17 @@ def create_ai_image_panel(self: "PQAutomationApp"):
self._ai_image_cancel_event = None self._ai_image_cancel_event = None
self._ai_image_request_seq = 0 self._ai_image_request_seq = 0
self._ai_image_active_seq = 0 self._ai_image_active_seq = 0
self._ai_image_node_map = {}
self._ai_image_session_node_map = {}
self._ai_image_all_records = []
self._ai_image_search_var = tk.StringVar(value="")
self._ai_image_count_var = tk.StringVar(value="0 张图片")
self._ai_image_reloading = False
self._ai_image_select_guard = False
self._ai_image_list_loaded = False
self._ai_image_tooltip = None
self._ai_image_tooltip_label = None
self._ai_image_tooltip_item = ""
container = ttk.Frame(frame, padding=10) container = ttk.Frame(frame, padding=10)
container.pack(fill=tk.BOTH, expand=True) container.pack(fill=tk.BOTH, expand=True)
@@ -71,35 +199,65 @@ def create_ai_image_panel(self: "PQAutomationApp"):
left.grid(row=0, column=0, sticky=tk.NS, padx=(0, 10)) left.grid(row=0, column=0, sticky=tk.NS, padx=(0, 10))
left.grid_propagate(False) left.grid_propagate(False)
ttk.Label(left, text="历史图片", font=("微软雅黑", 10, "bold")).pack( title_row = ttk.Frame(left)
anchor=tk.W, pady=(0, 4) title_row.pack(fill=tk.X, pady=(0, 4))
ttk.Label(title_row, text="历史图片", font=("微软雅黑", 10, "bold")).pack(side=tk.LEFT)
ttk.Label(
title_row,
textvariable=self._ai_image_count_var,
foreground=palette["muted"],
font=("微软雅黑", 9),
).pack(side=tk.RIGHT)
search_row = ttk.Frame(left)
search_row.pack(fill=tk.X, pady=(0, 6))
ttk.Label(
search_row,
text="搜索",
foreground=palette["muted"],
font=("微软雅黑", 9),
).pack(side=tk.LEFT, padx=(0, 6))
self.ai_image_search_entry = ttk.Entry(
search_row,
textvariable=self._ai_image_search_var,
bootstyle="secondary",
) )
self.ai_image_search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
self.ai_image_search_entry.bind("<KeyRelease>", lambda e: reload_ai_image_list(self, auto_select_first=False))
ttk.Button(
search_row,
text="清空",
width=6,
bootstyle="secondary-outline",
command=lambda: _clear_list_search(self),
).pack(side=tk.LEFT, padx=(6, 0))
list_wrap = ttk.Frame(left, padding=2) list_wrap = ttk.Frame(left, padding=2)
list_wrap.pack(fill=tk.BOTH, expand=True) list_wrap.pack(fill=tk.BOTH, expand=True)
list_wrap.columnconfigure(0, weight=1)
list_wrap.rowconfigure(0, weight=1)
_apply_ai_image_list_style(self)
scroll = ttk.Scrollbar(list_wrap, orient=tk.VERTICAL) scroll = ttk.Scrollbar(list_wrap, orient=tk.VERTICAL)
self.ai_image_listbox = tk.Listbox( self.ai_image_tree = ttk.Treeview(
list_wrap, list_wrap,
width=34, columns=("time",),
height=1, # 由 pack fill/expand 撑满height 仅为最小保底 show="tree headings",
activestyle="none", selectmode="browse",
font=("微软雅黑", 9), style="AIImage.Treeview",
bd=1,
relief=tk.FLAT,
highlightthickness=1,
bg=palette["input_bg"],
fg=palette["input_fg"],
highlightbackground=palette["border"],
highlightcolor=palette["select_bg"],
selectbackground=palette["select_bg"],
selectforeground=palette["select_fg"],
yscrollcommand=scroll.set, yscrollcommand=scroll.set,
) )
scroll.config(command=self.ai_image_listbox.yview) self.ai_image_tree.heading("#0", text="标题")
self.ai_image_listbox.pack(side=tk.LEFT, fill=tk.Y) self.ai_image_tree.heading("time", text="时间")
scroll.pack(side=tk.RIGHT, fill=tk.Y) self.ai_image_tree.column("#0", width=210, minwidth=140, anchor=tk.W)
self.ai_image_listbox.bind("<<ListboxSelect>>", lambda e: _on_list_select(self)) self.ai_image_tree.column("time", width=105, minwidth=90, stretch=False, anchor=tk.W)
scroll.config(command=self.ai_image_tree.yview)
self.ai_image_tree.grid(row=0, column=0, sticky=tk.NSEW)
scroll.grid(row=0, column=1, sticky=tk.NS)
self.ai_image_tree.bind("<<TreeviewSelect>>", lambda e: _on_list_select(self))
self.ai_image_tree.bind("<Double-1>", lambda e: _on_tree_double_click(self, e))
self.ai_image_tree.bind("<Motion>", lambda e: _on_tree_motion(self, e))
self.ai_image_tree.bind("<Leave>", lambda e: _hide_tree_tooltip(self))
# 右键菜单:发送到 UCD / 重命名 / 另存为 / 删除 # 右键菜单:发送到 UCD / 重命名 / 另存为 / 删除
# 索引: 0=发送, 1=sep, 2=重命名, 3=另存为, 4=删除 # 索引: 0=发送, 1=sep, 2=重命名, 3=另存为, 4=删除
self.ai_image_menu = tk.Menu(self.root, tearoff=0) self.ai_image_menu = tk.Menu(self.root, tearoff=0)
@@ -120,7 +278,7 @@ def create_ai_image_panel(self: "PQAutomationApp"):
label="删除", label="删除",
command=lambda: _delete_current(self), command=lambda: _delete_current(self),
) )
self.ai_image_listbox.bind( self.ai_image_tree.bind(
"<Button-3>", "<Button-3>",
lambda e: _show_list_context_menu(self, e), lambda e: _show_list_context_menu(self, e),
) )
@@ -216,14 +374,17 @@ def create_ai_image_panel(self: "PQAutomationApp"):
# 注册面板 # 注册面板
self.register_panel("ai_image", frame, None, "ai_image_visible") self.register_panel("ai_image", frame, None, "ai_image_visible")
self.ai_image_visible = False self.ai_image_visible = False
self.ai_image_meta_var.set("首次打开 AI 图片面板时加载缓存")
# 初次加载缓存
reload_ai_image_list(self)
def toggle_ai_image_panel(self: "PQAutomationApp"): def toggle_ai_image_panel(self: "PQAutomationApp"):
"""切换 AI 图片面板显隐。""" """切换 AI 图片面板显隐。"""
self.show_panel("ai_image") self.show_panel("ai_image")
_apply_ai_image_list_style(self)
if not getattr(self, "_ai_image_list_loaded", False):
logger.info("[AIImagePanel] 首次显示面板,开始加载列表")
reload_ai_image_list(self)
self._ai_image_list_loaded = True
def _get_app_base_dir(self: "PQAutomationApp") -> str: def _get_app_base_dir(self: "PQAutomationApp") -> str:
@@ -245,58 +406,102 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True):
其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。 其下列出该轮生成的所有图片。会话按"最近使用"倒序,组内按时间倒序。
auto_select_first: 是否自动选中第一张图片(默认 True auto_select_first: 是否自动选中第一张图片(默认 True
""" """
palette = _theme_colors() t0 = time.monotonic()
self.ai_image_records = _svc.list_records(base_dir=_get_app_base_dir(self)) if getattr(self, "_ai_image_reloading", False):
self.ai_image_listbox.delete(0, tk.END) logger.debug("[AIImagePanel] 忽略重入 reload 请求")
# 维护行号 → 记录索引的映射;分隔头处为 None return
self._ai_image_row_map = [] self._ai_image_reloading = True
self._ai_image_row_session_map = [] try:
sessions = _svc.group_records_by_session(self.ai_image_records) _apply_ai_image_list_style(self)
flat = [] all_records = _svc.list_records(base_dir=_get_app_base_dir(self))
current_sid = _svc.get_session_id() self._ai_image_all_records = list(all_records)
for idx, sess in enumerate(sessions, start=1): self.ai_image_tree.delete(*self.ai_image_tree.get_children())
sid = sess["session_id"] self._ai_image_node_map = {}
is_current = sid and sid == current_sid self._ai_image_session_node_map = {}
header = _format_session_header(idx, sess, is_current=is_current)
self.ai_image_listbox.insert(tk.END, header) keyword = (self._ai_image_search_var.get() or "").strip().lower()
# 头部行:禁用选中(视觉上变灰) records = []
last = self.ai_image_listbox.size() - 1 for rec in all_records:
self.ai_image_listbox.itemconfig( if not keyword:
last, foreground=palette["muted"], selectforeground=palette["muted"], records.append(rec)
background=palette["bg"], selectbackground=palette["bg"], continue
hay = "\n".join([
rec.display_name or "",
rec.prompt or "",
os.path.basename(rec.image_path),
]).lower()
if keyword in hay:
records.append(rec)
self.ai_image_records = records
sessions = _svc.group_records_by_session(self.ai_image_records)
flat = []
current_sid = _svc.get_session_id()
for idx, sess in enumerate(sessions, start=1):
sid = sess["session_id"]
is_current = sid and sid == current_sid
header = _format_session_header(idx, sess, is_current=is_current)
parent_id = self.ai_image_tree.insert(
"",
tk.END,
text=header,
values=("",),
open=True,
)
self._ai_image_session_node_map[parent_id] = sid
for rec in sess["records"]:
label = _format_list_label(rec)
created = (rec.created_at or "").replace("T", " ")[:16]
node_id = self.ai_image_tree.insert(
parent_id,
tk.END,
text=label,
values=(created,),
)
self._ai_image_node_map[node_id] = len(flat)
flat.append(rec)
# 替换为按显示顺序展平后的列表,便于其它逻辑(前一张/后一张等)
self.ai_image_records = flat
total_count = len(all_records)
visible_count = len(self.ai_image_records)
if keyword:
self._ai_image_count_var.set(f"{visible_count}/{total_count}")
else:
self._ai_image_count_var.set(f"{visible_count} 张图片")
if self.ai_image_records and auto_select_first:
first_id = ""
for parent in self.ai_image_tree.get_children(""):
children = self.ai_image_tree.get_children(parent)
if children:
first_id = children[0]
break
if first_id:
_set_tree_selection(self, first_id)
else:
self.ai_image_current = None
self.ai_image_photo = None
self.ai_image_canvas.delete("all")
self.ai_image_meta_var.set("暂无缓存图片" if not keyword else "当前筛选无结果")
logger.info(
"[AIImagePanel] reload 完成 total=%d visible=%d sessions=%d keyword=%r elapsed=%.3fs",
total_count,
visible_count,
len(sessions),
keyword,
time.monotonic() - t0,
) )
self._ai_image_row_map.append(None) self._ai_image_list_loaded = True
self._ai_image_row_session_map.append(sid) finally:
for rec in sess["records"]: self._ai_image_reloading = False
label = " " + _format_list_label(rec)
self.ai_image_listbox.insert(tk.END, label)
self._ai_image_row_map.append(len(flat))
self._ai_image_row_session_map.append(rec.session_id)
flat.append(rec)
# 替换为按显示顺序展平后的列表,便于其它逻辑(前一张/后一张等)
self.ai_image_records = flat
if self.ai_image_records and auto_select_first:
# 选中第一张实际记录
for row, ridx in enumerate(self._ai_image_row_map):
if ridx is not None:
self.ai_image_listbox.selection_clear(0, tk.END)
self.ai_image_listbox.selection_set(row)
self.ai_image_listbox.activate(row)
_select_record(self, self.ai_image_records[ridx])
break
else:
self.ai_image_current = None
self.ai_image_photo = None
self.ai_image_canvas.delete("all")
self.ai_image_meta_var.set("暂无缓存图片")
def _format_session_header(index: int, sess: dict, is_current: bool) -> str: def _format_session_header(index: int, sess: dict, is_current: bool) -> str:
started = (sess.get("started_at") or "").replace("T", " ")[:16] started = (sess.get("started_at") or "").replace("T", " ")[:16]
tag = "(当前)" if is_current else "" tag = "(当前)" if is_current else ""
count = len(sess.get("records") or [])
if sess.get("session_id"): if sess.get("session_id"):
return f"── 会话 #{index} · {started} {tag}──" return f"会话 #{index} · {started} · {count}{tag}".strip()
return f"── 未归类 · {started} ──" return f"未归类 · {started} · {count}"
def _format_list_label(rec: _svc.AIImageRecord) -> str: def _format_list_label(rec: _svc.AIImageRecord) -> str:
@@ -305,42 +510,105 @@ def _format_list_label(rec: _svc.AIImageRecord) -> str:
extra = rec.extra or {} extra = rec.extra or {}
if isinstance(extra, dict) and extra.get("size"): if isinstance(extra, dict) and extra.get("size"):
size_tag = f"[{extra['size']}] " size_tag = f"[{extra['size']}] "
else:
try:
from PIL import Image as _Im
with _Im.open(rec.image_path) as _im:
size_tag = f"[{_im.width}×{_im.height}] "
except Exception:
pass
name_line = rec.display_name.splitlines()[0] if rec.display_name else "(未命名)" name_line = rec.display_name.splitlines()[0] if rec.display_name else "(未命名)"
# 列表宽度 width=34需要扣除两格缩进 + size_tag # Treeview 标题列可变宽,给展示名预留较长空间
max_name = 34 - 2 - len(size_tag) - 2 max_name = 40 - len(size_tag)
if max_name > 4 and len(name_line) > max_name: if max_name > 4 and len(name_line) > max_name:
name_line = name_line[:max_name] + "" name_line = name_line[:max_name] + ""
return f"{size_tag}{name_line}" return f"{size_tag}{name_line}"
def _clear_list_search(self: "PQAutomationApp"):
if (self._ai_image_search_var.get() or "").strip() == "":
return
self._ai_image_search_var.set("")
reload_ai_image_list(self, auto_select_first=False)
_hide_tree_tooltip(self)
def _set_tree_selection(self: "PQAutomationApp", item_id: str):
if not item_id:
return
_hide_tree_tooltip(self)
try:
current = self.ai_image_tree.selection()
if current and current[0] == item_id:
node_map = getattr(self, "_ai_image_node_map", None) or {}
ridx = node_map.get(item_id)
if ridx is not None and 0 <= ridx < len(self.ai_image_records):
_select_record(self, self.ai_image_records[ridx])
return
self.ai_image_tree.selection_set(item_id)
self.ai_image_tree.focus(item_id)
self.ai_image_tree.see(item_id)
except Exception:
return
node_map = getattr(self, "_ai_image_node_map", None) or {}
ridx = node_map.get(item_id)
if ridx is not None and 0 <= ridx < len(self.ai_image_records):
_select_record(self, self.ai_image_records[ridx])
def _find_tree_item_by_record_id(self: "PQAutomationApp", record_id: str) -> str:
if not record_id:
return ""
node_map = getattr(self, "_ai_image_node_map", None) or {}
for item_id, ridx in node_map.items():
if ridx is None or ridx >= len(self.ai_image_records):
continue
rec = self.ai_image_records[ridx]
if rec.id == record_id:
return item_id
return ""
def _on_list_select(self: "PQAutomationApp"): def _on_list_select(self: "PQAutomationApp"):
sel = self.ai_image_listbox.curselection() if getattr(self, "_ai_image_reloading", False):
return
if getattr(self, "_ai_image_select_guard", False):
logger.debug("[AIImagePanel] 忽略重入选择事件")
return
sel = self.ai_image_tree.selection()
if not sel: if not sel:
return return
row = sel[0] self._ai_image_select_guard = True
row_map = getattr(self, "_ai_image_row_map", None) or [] try:
if row >= len(row_map): item_id = sel[0]
node_map = getattr(self, "_ai_image_node_map", None) or {}
ridx = node_map.get(item_id)
if ridx is None:
session_id = _session_id_for_item(self, item_id)
if session_id:
logger.info("[AIImagePanel] 选中会话头 sid=%s", session_id[:8])
_switch_to_session(self, session_id, show_message=False, refresh_list=False)
return
if 0 <= ridx < len(self.ai_image_records):
rec = self.ai_image_records[ridx]
if rec.session_id:
_switch_to_session(
self,
rec.session_id,
show_message=False,
refresh_list=False,
)
_select_record(self, rec)
logger.debug("[AIImagePanel] 选中图片 id=%s sid=%s", rec.id, (rec.session_id or "")[:8])
finally:
self._ai_image_select_guard = False
def _on_tree_double_click(self: "PQAutomationApp", event):
"""双击会话头时只切换会话并展开;双击记录保持默认行为。"""
item_id = self.ai_image_tree.identify_row(event.y)
if not item_id:
return return
ridx = row_map[row] node_map = getattr(self, "_ai_image_node_map", None) or {}
if ridx is None: if item_id in node_map:
session_id = _session_id_for_row(self, row)
if session_id:
_switch_to_session(self, session_id, show_message=False)
self.ai_image_listbox.selection_clear(row)
return return
if 0 <= ridx < len(self.ai_image_records): sid = _session_id_for_item(self, item_id)
rec = self.ai_image_records[ridx] if sid:
if rec.session_id: _switch_to_session(self, sid, show_message=False, refresh_list=False)
_switch_to_session(self, rec.session_id, show_message=False, target_record_id=rec.id)
_select_record(self, rec)
def _select_record(self: "PQAutomationApp", rec: _svc.AIImageRecord): def _select_record(self: "PQAutomationApp", rec: _svc.AIImageRecord):
@@ -387,36 +655,54 @@ def _start_new_session(self: "PQAutomationApp"):
return return
_svc.reset_session() _svc.reset_session()
self.ai_image_status_var.set("已开启新对话") self.ai_image_status_var.set("已开启新对话")
# 新对话创建后不要自动选中历史图片,否则会立即把 session 切回旧会话。
reload_ai_image_list(self, auto_select_first=False) reload_ai_image_list(self, auto_select_first=False)
try:
self.ai_image_tree.selection_remove(self.ai_image_tree.selection())
except Exception:
pass
self.ai_image_current = None
self.ai_image_photo = None
self.ai_image_canvas.delete("all")
self.ai_image_meta_var.set("新对话已开启,等待生成图片")
def _session_id_for_row(self: "PQAutomationApp", row: int) -> str: def _session_id_for_item(self: "PQAutomationApp", item_id: str) -> str:
session_map = getattr(self, "_ai_image_row_session_map", None) or [] session_map = getattr(self, "_ai_image_session_node_map", None) or {}
if row < 0 or row >= len(session_map): parent = item_id
return "" if parent not in session_map:
return session_map[row] or "" try:
parent = self.ai_image_tree.parent(item_id)
except Exception:
parent = ""
return (session_map.get(parent) or "") if parent else ""
def _switch_to_session(self: "PQAutomationApp", session_id: str, show_message: bool = True, target_record_id: str = ""): def _switch_to_session(
self: "PQAutomationApp",
session_id: str,
show_message: bool = True,
target_record_id: str = "",
refresh_list: bool = True,
):
sid = (session_id or "").strip() sid = (session_id or "").strip()
if not sid: if not sid:
return return
if sid == _svc.get_session_id(): if sid == _svc.get_session_id():
return return
_svc.set_session_id(sid) _svc.set_session_id(sid)
reload_ai_image_list(self) logger.info(
"[AIImagePanel] 切换会话 sid=%s refresh=%s target=%s",
sid[:8],
refresh_list,
target_record_id[:8] if target_record_id else "",
)
if refresh_list:
reload_ai_image_list(self)
if target_record_id: if target_record_id:
for row, ridx in enumerate(getattr(self, "_ai_image_row_map", []) or []): item_id = _find_tree_item_by_record_id(self, target_record_id)
if ridx is None: if item_id:
continue _set_tree_selection(self, item_id)
rec = self.ai_image_records[ridx]
if rec.id == target_record_id:
self.ai_image_listbox.selection_clear(0, tk.END)
self.ai_image_listbox.selection_set(row)
self.ai_image_listbox.activate(row)
self.ai_image_listbox.see(row)
_select_record(self, rec)
break
self.ai_image_status_var.set("已切换到历史对话") self.ai_image_status_var.set("已切换到历史对话")
if show_message: if show_message:
messagebox.showinfo("提示", "已切换到所选历史对话") messagebox.showinfo("提示", "已切换到所选历史对话")
@@ -512,18 +798,12 @@ def _on_request_done(self: "PQAutomationApp", record, exc, req_seq):
self.ai_image_status_var.set("完成") self.ai_image_status_var.set("完成")
self.ai_image_input.delete("1.0", tk.END) self.ai_image_input.delete("1.0", tk.END)
reload_ai_image_list(self) reload_ai_image_list(self)
if record is not None:
logger.info("[AIImagePanel] 生成完成并入列 id=%s sid=%s", record.id, (record.session_id or "")[:8])
if record is not None and self.ai_image_records: if record is not None and self.ai_image_records:
for row, ridx in enumerate(getattr(self, "_ai_image_row_map", []) or []): item_id = _find_tree_item_by_record_id(self, record.id)
if ridx is None: if item_id:
continue _set_tree_selection(self, item_id)
r = self.ai_image_records[ridx]
if r.id == record.id:
self.ai_image_listbox.selection_clear(0, tk.END)
self.ai_image_listbox.selection_set(row)
self.ai_image_listbox.activate(row)
self.ai_image_listbox.see(row)
_select_record(self, r)
break
def _stop_request(self: "PQAutomationApp"): def _stop_request(self: "PQAutomationApp"):
@@ -596,18 +876,9 @@ def _rename_current(self: "PQAutomationApp"):
target_id = rec.id target_id = rec.id
reload_ai_image_list(self) reload_ai_image_list(self)
# 重新定位 item_id = _find_tree_item_by_record_id(self, target_id)
for row, ridx in enumerate(getattr(self, "_ai_image_row_map", []) or []): if item_id:
if ridx is None: _set_tree_selection(self, item_id)
continue
r = self.ai_image_records[ridx]
if r.id == target_id:
self.ai_image_listbox.selection_clear(0, tk.END)
self.ai_image_listbox.selection_set(row)
self.ai_image_listbox.activate(row)
self.ai_image_listbox.see(row)
_select_record(self, r)
break
# ---------------- 发送到 UCD ---------------- # ---------------- 发送到 UCD ----------------
@@ -615,17 +886,21 @@ def _rename_current(self: "PQAutomationApp"):
def _show_list_context_menu(self: "PQAutomationApp", event): def _show_list_context_menu(self: "PQAutomationApp", event):
"""在图片列表上显示右键菜单,并根据状态启用/禁用项。""" """在图片列表上显示右键菜单,并根据状态启用/禁用项。"""
_hide_tree_tooltip(self)
try: try:
row = self.ai_image_listbox.nearest(event.y) item_id = self.ai_image_tree.identify_row(event.y)
except Exception: except Exception:
row = -1 item_id = ""
row_map = getattr(self, "_ai_image_row_map", None) or []
ridx = row_map[row] if 0 <= row < len(row_map) else None node_map = getattr(self, "_ai_image_node_map", None) or {}
ridx = node_map.get(item_id)
if ridx is not None and 0 <= ridx < len(self.ai_image_records): if ridx is not None and 0 <= ridx < len(self.ai_image_records):
self.ai_image_listbox.selection_clear(0, tk.END) _set_tree_selection(self, item_id)
self.ai_image_listbox.selection_set(row)
self.ai_image_listbox.activate(row)
_select_record(self, self.ai_image_records[ridx]) _select_record(self, self.ai_image_records[ridx])
elif item_id:
sid = _session_id_for_item(self, item_id)
if sid:
_switch_to_session(self, sid, show_message=False, refresh_list=False)
has_selection = self.ai_image_current is not None has_selection = self.ai_image_current is not None
ucd = getattr(self, "ucd", None) ucd = getattr(self, "ucd", None)
@@ -777,7 +1052,7 @@ class AIImagePanelMixin:
_select_record = _select_record _select_record = _select_record
_redraw_preview = _redraw_preview _redraw_preview = _redraw_preview
_start_new_session = _start_new_session _start_new_session = _start_new_session
_session_id_for_row = _session_id_for_row _session_id_for_item = _session_id_for_item
_switch_to_session = _switch_to_session _switch_to_session = _switch_to_session
_update_request_progress = _update_request_progress _update_request_progress = _update_request_progress
_send_prompt = _send_prompt _send_prompt = _send_prompt