修改AI生图接口、修改设备连接UI、修改LocalDimming逻辑和UI

This commit is contained in:
xinzhu.yin
2026-05-29 14:40:39 +08:00
parent 21455f3916
commit 85ac47e8de
13 changed files with 811 additions and 304 deletions

View File

@@ -184,6 +184,13 @@ def create_ai_image_panel(self: "PQAutomationApp"):
self._ai_image_tooltip = None
self._ai_image_tooltip_label = None
self._ai_image_tooltip_item = ""
# 会话级参考图 URL图生图模式session_id -> upload_image_url
self._ai_image_session_refs = {}
# 本轮发送前手动上传的参考图(覆盖会话级)
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
self._ai_image_uploading = False
self.ai_image_ref_var = tk.StringVar(value="未设置参考图(文生图模式)")
container = ttk.Frame(frame, padding=10)
container.pack(fill=tk.BOTH, expand=True)
@@ -328,6 +335,29 @@ def create_ai_image_panel(self: "PQAutomationApp"):
input_frame = ttk.LabelFrame(right, text="提示输入Ctrl+Enter 发送)", padding=6)
input_frame.pack(fill=tk.X, pady=(4, 0))
# 参考图行(图生图)
ref_row = ttk.Frame(input_frame)
ref_row.pack(fill=tk.X, pady=(0, 4))
ttk.Label(
ref_row, text="参考图:",
foreground=palette["muted"], font=("微软雅黑", 9),
).pack(side=tk.LEFT)
self.ai_image_ref_label = ttk.Label(
ref_row, textvariable=self.ai_image_ref_var,
foreground=palette["fg"], font=("微软雅黑", 9),
)
self.ai_image_ref_label.pack(side=tk.LEFT, padx=(4, 0), fill=tk.X, expand=True)
self.ai_image_clear_ref_btn = ttk.Button(
ref_row, text="清除", width=6, bootstyle="secondary-outline",
command=lambda: _clear_reference_image(self),
)
self.ai_image_clear_ref_btn.pack(side=tk.RIGHT, padx=(4, 0))
self.ai_image_upload_ref_btn = ttk.Button(
ref_row, text="上传参考图…", width=12, bootstyle="info-outline",
command=lambda: _upload_reference_image(self),
)
self.ai_image_upload_ref_btn.pack(side=tk.RIGHT)
self.ai_image_input = tk.Text(
input_frame,
height=3,
@@ -435,6 +465,20 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True):
self.ai_image_records = records
sessions = _svc.group_records_by_session(self.ai_image_records)
# \u4f1a\u8bdd\u7ea7\u53c2\u8003\u56fe\u94fe\u8def\uff1a\u4ee5\u6bcf\u4e2a\u4f1a\u8bdd\u6700\u8fd1\u4e00\u5f20\u751f\u6210\u56fe\u7684 imageUrl \u4f5c\u4e3a\u53c2\u8003
refs_map = getattr(self, "_ai_image_session_refs", None)
if isinstance(refs_map, dict):
for sess in sessions:
sid = sess.get("session_id") or ""
if not sid or sid in refs_map:
continue
for r in sess.get("records") or []:
src = ""
if isinstance(r.extra, dict):
src = (r.extra.get("source_url") or "").strip()
if src:
refs_map[sid] = src
break
flat = []
current_sid = _svc.get_session_id()
for idx, sess in enumerate(sessions, start=1):
@@ -493,6 +537,10 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True):
self._ai_image_list_loaded = True
finally:
self._ai_image_reloading = False
try:
_refresh_ref_label(self)
except Exception:
pass
def _format_session_header(index: int, sess: dict, is_current: bool) -> str:
@@ -655,6 +703,10 @@ def _start_new_session(self: "PQAutomationApp"):
return
_svc.reset_session()
self.ai_image_status_var.set("已开启新对话")
# 新会话清除参考图(未设置时默认为文生图模式)
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
_refresh_ref_label(self)
# 新对话创建后不要自动选中历史图片,否则会立即把 session 切回旧会话。
reload_ai_image_list(self, auto_select_first=False)
try:
@@ -667,6 +719,103 @@ def _start_new_session(self: "PQAutomationApp"):
self.ai_image_meta_var.set("新对话已开启,等待生成图片")
def _resolve_pending_ref_url(self: "PQAutomationApp") -> str:
"""返回本次发送应使用的 upload_image_url。
优先使用用户在当前会话中手动上传的参考图;其次使用上一轮 imageUrl 自动链路。
"""
pending = (getattr(self, "_ai_image_pending_ref_url", "") or "").strip()
if pending:
return pending
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None) or {}
return (refs.get(sid) or "").strip()
def _refresh_ref_label(self: "PQAutomationApp"):
"""根据当前 pending 上传 / 会话链路状态刷新参考图提示标签。"""
var = getattr(self, "ai_image_ref_var", None)
if var is None:
return
pending = (getattr(self, "_ai_image_pending_ref_url", "") or "").strip()
if pending:
name = getattr(self, "_ai_image_pending_ref_name", "") or "参考图"
var.set(f"[图生图] {name}")
return
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None) or {}
chained = (refs.get(sid) or "").strip()
if chained:
# 自动链路:显示来源是"上一轮"
var.set("[图生图] 沿用上一轮生成图为参考")
return
var.set("未设置参考图(文生图模式)")
def _upload_reference_image(self: "PQAutomationApp"):
"""选择本地图片并上传到后端,成功后作为本次发送的参考图。"""
if getattr(self, "_ai_image_requesting", False):
messagebox.showinfo("提示", "请等待当前请求完成")
return
if getattr(self, "_ai_image_uploading", False):
return
path = filedialog.askopenfilename(
title="选择参考图PNG/JPG/JPEG超过限制将自动缩放",
filetypes=[("图片", "*.png;*.jpg;*.jpeg"), ("所有文件", "*.*")],
)
if not path:
return
self._ai_image_uploading = True
try:
self.ai_image_upload_ref_btn.configure(state=tk.DISABLED, text="上传中…")
self.ai_image_clear_ref_btn.configure(state=tk.DISABLED)
except Exception:
pass
self.ai_image_status_var.set("正在上传参考图…")
name = os.path.basename(path)
def _ok(url: str):
self.root.after(0, lambda: _on_upload_done(self, name, url, None))
def _err(exc: Exception):
self.root.after(0, lambda: _on_upload_done(self, name, "", exc))
_svc.upload_image_async(path, on_success=_ok, on_error=_err)
def _on_upload_done(self: "PQAutomationApp", name: str, url: str, exc):
self._ai_image_uploading = False
try:
self.ai_image_upload_ref_btn.configure(state=tk.NORMAL, text="上传参考图…")
self.ai_image_clear_ref_btn.configure(state=tk.NORMAL)
except Exception:
pass
if exc is not None:
self.ai_image_status_var.set(f"上传失败: {exc}")
messagebox.showerror("上传失败", str(exc))
return
self._ai_image_pending_ref_url = url
self._ai_image_pending_ref_name = name
_refresh_ref_label(self)
self.ai_image_status_var.set(f"参考图已上传:{name}")
def _clear_reference_image(self: "PQAutomationApp"):
"""清除手动上传的参考图,同时清除当前会话的自动链路参考。"""
if getattr(self, "_ai_image_requesting", False):
return
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None)
if isinstance(refs, dict):
refs.pop(sid, None)
_refresh_ref_label(self)
self.ai_image_status_var.set("已清除参考图,切换为文生图模式")
def _session_id_for_item(self: "PQAutomationApp", item_id: str) -> str:
session_map = getattr(self, "_ai_image_session_node_map", None) or {}
parent = item_id
@@ -704,6 +853,10 @@ def _switch_to_session(
if item_id:
_set_tree_selection(self, item_id)
self.ai_image_status_var.set("已切换到历史对话")
# 切换会话后刷新参考图标签pending 仅当前会话有效,故清除)
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
_refresh_ref_label(self)
if show_message:
messagebox.showinfo("提示", "已切换到所选历史对话")
@@ -720,6 +873,9 @@ def _update_request_progress(self: "PQAutomationApp"):
def _send_prompt(self: "PQAutomationApp"):
if getattr(self, "_ai_image_requesting", False):
return
if getattr(self, "_ai_image_uploading", False):
messagebox.showinfo("提示", "参考图上传中,请稍后再发送")
return
prompt = self.ai_image_input.get("1.0", tk.END).strip()
if not prompt:
messagebox.showinfo("提示", "请输入内容")
@@ -749,12 +905,17 @@ def _send_prompt(self: "PQAutomationApp"):
)
return
ref_url = _resolve_pending_ref_url(self)
if ref_url:
self.ai_image_status_var.set("后端处理中(图生图)…")
_svc.request_image_async(
prompt,
on_success=_success,
on_error=_error,
base_dir=_get_app_base_dir(self),
cancel_event=self._ai_image_cancel_event,
upload_image_url=ref_url or None,
)
@@ -764,6 +925,10 @@ def _set_requesting(self: "PQAutomationApp", flag: bool):
self.ai_image_send_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
self.ai_image_new_session_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
self.ai_image_stop_btn.configure(state=tk.NORMAL if flag else tk.DISABLED)
if hasattr(self, "ai_image_upload_ref_btn"):
self.ai_image_upload_ref_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
if hasattr(self, "ai_image_clear_ref_btn"):
self.ai_image_clear_ref_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
except Exception:
pass
if flag:
@@ -797,9 +962,24 @@ def _on_request_done(self: "PQAutomationApp", record, exc, req_seq):
return
self.ai_image_status_var.set("完成")
self.ai_image_input.delete("1.0", tk.END)
# 多轮对话链路:本轮返回的 imageUrl 作为下一轮的参考图
next_ref = ""
try:
if record is not None and isinstance(record.extra, dict):
next_ref = (record.extra.get("source_url") or "").strip()
except Exception:
next_ref = ""
sid = (record.session_id if record is not None else "") or _svc.get_session_id()
if next_ref and sid:
self._ai_image_session_refs[sid] = next_ref
# 手动上传的 pending 参考图只对本次发送生效,发完清除
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
_refresh_ref_label(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])
logger.info("[AIImagePanel] 生成完成并入列 id=%s sid=%s next_ref=%s",
record.id, (record.session_id or "")[:8], next_ref or "-")
if record is not None and self.ai_image_records:
item_id = _find_tree_item_by_record_id(self, record.id)
if item_id: