修改AI生图相关日志信息

This commit is contained in:
xinzhu.yin
2026-04-29 19:10:27 +08:00
parent afd83448ed
commit 7bc8fd8557
2 changed files with 86 additions and 13 deletions

View File

@@ -136,6 +136,18 @@ def _api_endpoint() -> str:
return base + API_PATH.lstrip("/") return base + API_PATH.lstrip("/")
def _pretty_json_text(value) -> str:
"""把对象或 JSON 字符串格式化为易读文本;失败则回退原始字符串。"""
try:
if isinstance(value, (dict, list)):
return json.dumps(value, ensure_ascii=False, indent=2)
text = "" if value is None else str(value)
parsed = json.loads(text)
return json.dumps(parsed, ensure_ascii=False, indent=2)
except Exception:
return "" if value is None else str(value)
def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = API_TIMEOUT) -> str: def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = API_TIMEOUT) -> str:
"""调用后端 ``api/v1/pqtest/generate``,返回 imageUrl。失败抛异常。""" """调用后端 ``api/v1/pqtest/generate``,返回 imageUrl。失败抛异常。"""
payload = json.dumps( payload = json.dumps(
@@ -154,11 +166,11 @@ def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = A
_mask_sid(session_id), len(user_message or ""), _truncate(user_message), _mask_sid(session_id), len(user_message or ""), _truncate(user_message),
) )
logger.info( logger.info(
"[AIImage][REQUEST]\\nendpoint=%s\\nmethod=POST\\ntimeout=%.1fs\\nheaders=%s\\nbody=%s", "[AIImage][REQUEST]\nendpoint=%s\nmethod=POST\ntimeout=%.1fs\nheaders=%s\nbody=%s",
endpoint, endpoint,
timeout, timeout,
json.dumps(request_headers, ensure_ascii=False), _pretty_json_text(request_headers),
payload.decode("utf-8", errors="replace"), _pretty_json_text(payload.decode("utf-8", errors="replace")),
) )
request = Request( request = Request(
endpoint, endpoint,
@@ -174,10 +186,10 @@ def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = A
response_headers = dict(response.headers.items()) response_headers = dict(response.headers.items())
raw_text = raw.decode("utf-8", errors="replace") raw_text = raw.decode("utf-8", errors="replace")
logger.info( logger.info(
"[AIImage][RESPONSE]\\nstatus=%s\\nheaders=%s\\nbody=%s", "[AIImage][RESPONSE]\nstatus=%s\nheaders=%s\nbody=%s",
http_status, http_status,
json.dumps(response_headers, ensure_ascii=False), _pretty_json_text(response_headers),
raw_text, _pretty_json_text(raw_text),
) )
except HTTPError as exc: except HTTPError as exc:
elapsed = time.monotonic() - t0 elapsed = time.monotonic() - t0
@@ -194,13 +206,13 @@ def _call_pqtest_generate(user_message: str, session_id: str, timeout: float = A
except Exception: except Exception:
err_headers = {} err_headers = {}
logger.error( logger.error(
"[AIImage][RESPONSE_ERROR] sid=%s elapsed=%.2fs status=%s reason=%s\\nheaders=%s\\nbody=%s", "[AIImage][RESPONSE_ERROR] sid=%s elapsed=%.2fs status=%s reason=%s\nheaders=%s\nbody=%s",
_mask_sid(session_id), _mask_sid(session_id),
elapsed, elapsed,
getattr(exc, "code", "?"), getattr(exc, "code", "?"),
str(exc), str(exc),
json.dumps(err_headers, ensure_ascii=False), _pretty_json_text(err_headers),
err_text, _pretty_json_text(err_text),
) )
raise raise
except Exception as exc: except Exception as exc:
@@ -484,6 +496,7 @@ def request_image_async(
on_error: Callable[[Exception], None], on_error: Callable[[Exception], None],
base_dir: Optional[str] = None, base_dir: Optional[str] = None,
session_id: Optional[str] = None, session_id: Optional[str] = None,
cancel_event: Optional[threading.Event] = None,
) -> threading.Thread: ) -> threading.Thread:
"""在后台线程调用后端 API → 下载图片 → 写入缓存 → 回调。 """在后台线程调用后端 API → 下载图片 → 写入缓存 → 回调。
@@ -494,22 +507,35 @@ def request_image_async(
""" """
sid = session_id or get_session_id() sid = session_id or get_session_id()
cancel = cancel_event
def _worker(): def _worker():
try: try:
if cancel is not None and cancel.is_set():
logger.info("[AIImage] 任务已取消(请求前) sid=%s", _mask_sid(sid))
return
image_url = _call_pqtest_generate(prompt, sid) image_url = _call_pqtest_generate(prompt, sid)
if cancel is not None and cancel.is_set():
logger.info("[AIImage] 任务已取消(生成后) sid=%s", _mask_sid(sid))
return
record = import_image_from_url( record = import_image_from_url(
image_url=image_url, image_url=image_url,
prompt=prompt, prompt=prompt,
extra={"source": "ai-api", "session_id": sid}, extra={"source": "ai-api", "session_id": sid},
base_dir=base_dir, base_dir=base_dir,
) )
if cancel is not None and cancel.is_set():
logger.info("[AIImage] 任务已取消(下载后) sid=%s", _mask_sid(sid))
return
logger.info( logger.info(
"[AIImage] 已写入缓存 sid=%s id=%s path=%s", "[AIImage] 已写入缓存 sid=%s id=%s path=%s",
_mask_sid(sid), record.id, record.image_path, _mask_sid(sid), record.id, record.image_path,
) )
on_success(record) on_success(record)
except Exception as exc: except Exception as exc:
if cancel is not None and cancel.is_set():
logger.info("[AIImage] 任务已取消(异常忽略) sid=%s", _mask_sid(sid))
return
logger.error( logger.error(
"[AIImage] 生成流程失败 sid=%s %s: %s", "[AIImage] 生成流程失败 sid=%s %s: %s",
_mask_sid(sid), type(exc).__name__, exc, _mask_sid(sid), type(exc).__name__, exc,
@@ -529,11 +555,15 @@ def import_image_from_url_async(
extra: Optional[dict] = None, extra: Optional[dict] = None,
base_dir: Optional[str] = None, base_dir: Optional[str] = None,
timeout: float = 20.0, timeout: float = 20.0,
cancel_event: Optional[threading.Event] = None,
) -> threading.Thread: ) -> threading.Thread:
"""在后台线程下载远程图片并写入缓存""" """在后台线程下载远程图片并写入缓存"""
def _worker(): def _worker():
try: try:
if cancel_event is not None and cancel_event.is_set():
logger.info("[AIImage] URL 导入任务已取消(请求前)")
return
record = import_image_from_url( record = import_image_from_url(
image_url=image_url, image_url=image_url,
prompt=prompt, prompt=prompt,
@@ -541,8 +571,14 @@ def import_image_from_url_async(
base_dir=base_dir, base_dir=base_dir,
timeout=timeout, timeout=timeout,
) )
if cancel_event is not None and cancel_event.is_set():
logger.info("[AIImage] URL 导入任务已取消(下载后)")
return
on_success(record) on_success(record)
except Exception as exc: except Exception as exc:
if cancel_event is not None and cancel_event.is_set():
logger.info("[AIImage] URL 导入任务已取消(异常忽略)")
return
on_error(exc) on_error(exc)
t = threading.Thread(target=_worker, daemon=True) t = threading.Thread(target=_worker, daemon=True)

View File

@@ -30,6 +30,9 @@ def create_ai_image_panel(self):
self._ai_image_requesting = False self._ai_image_requesting = False
self._ai_image_progress_job = None self._ai_image_progress_job = None
self._ai_image_progress_phase = 0 self._ai_image_progress_phase = 0
self._ai_image_cancel_event = None
self._ai_image_request_seq = 0
self._ai_image_active_seq = 0
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)
@@ -170,6 +173,12 @@ def create_ai_image_panel(self):
command=lambda: _start_new_session(self), command=lambda: _start_new_session(self),
) )
self.ai_image_new_session_btn.pack(side=tk.RIGHT, padx=(0, 6)) self.ai_image_new_session_btn.pack(side=tk.RIGHT, padx=(0, 6))
self.ai_image_stop_btn = ttk.Button(
send_row, text="停止", bootstyle="danger-outline", width=10,
command=lambda: _stop_request(self),
)
self.ai_image_stop_btn.pack(side=tk.RIGHT, padx=(0, 6))
self.ai_image_stop_btn.configure(state=tk.DISABLED)
# 注册面板 # 注册面板
self.register_panel("ai_image", frame, None, "ai_image_visible") self.register_panel("ai_image", frame, None, "ai_image_visible")
@@ -386,25 +395,35 @@ def _send_prompt(self):
messagebox.showinfo("提示", "请输入内容") messagebox.showinfo("提示", "请输入内容")
return return
self._ai_image_request_seq += 1
req_seq = self._ai_image_request_seq
self._ai_image_active_seq = req_seq
self._ai_image_cancel_event = threading.Event()
_set_requesting(self, True) _set_requesting(self, True)
is_remote_url = _svc.is_remote_image_url(prompt) is_remote_url = _svc.is_remote_image_url(prompt)
self.ai_image_status_var.set("下载中…" if is_remote_url else "后端处理中…") self.ai_image_status_var.set("下载中…" if is_remote_url else "后端处理中…")
def _success(record): def _success(record):
self.root.after(0, lambda: _on_request_done(self, record, None)) self.root.after(0, lambda: _on_request_done(self, record, None, req_seq))
def _error(exc): def _error(exc):
self.root.after(0, lambda: _on_request_done(self, None, exc)) self.root.after(0, lambda: _on_request_done(self, None, exc, req_seq))
if is_remote_url: if is_remote_url:
_svc.import_image_from_url_async( _svc.import_image_from_url_async(
prompt, prompt,
on_success=_success, on_success=_success,
on_error=_error, on_error=_error,
cancel_event=self._ai_image_cancel_event,
) )
return return
_svc.request_image_async(prompt, on_success=_success, on_error=_error) _svc.request_image_async(
prompt,
on_success=_success,
on_error=_error,
cancel_event=self._ai_image_cancel_event,
)
def _set_requesting(self, flag: bool): def _set_requesting(self, flag: bool):
@@ -412,6 +431,7 @@ def _set_requesting(self, flag: bool):
try: try:
self.ai_image_send_btn.configure(state=tk.DISABLED if flag else tk.NORMAL) 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_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)
except Exception: except Exception:
pass pass
if flag: if flag:
@@ -432,7 +452,12 @@ def _set_requesting(self, flag: bool):
pass pass
def _on_request_done(self, record, exc): def _on_request_done(self, record, exc, req_seq):
# 旧请求回调(例如用户已点击停止后)直接忽略
if req_seq != getattr(self, "_ai_image_active_seq", 0):
return
self._ai_image_active_seq = 0
self._ai_image_cancel_event = None
_set_requesting(self, False) _set_requesting(self, False)
if exc is not None: if exc is not None:
self.ai_image_status_var.set(f"失败: {exc}") self.ai_image_status_var.set(f"失败: {exc}")
@@ -455,6 +480,18 @@ def _on_request_done(self, record, exc):
break break
def _stop_request(self):
"""停止当前生成任务(协作取消:屏蔽后续回调并恢复 UI"""
if not getattr(self, "_ai_image_requesting", False):
return
event = getattr(self, "_ai_image_cancel_event", None)
if event is not None:
event.set()
self._ai_image_active_seq = 0
_set_requesting(self, False)
self.ai_image_status_var.set("已停止生成")
def _save_current(self): def _save_current(self):
rec = getattr(self, "ai_image_current", None) rec = getattr(self, "ai_image_current", None)
if rec is None: if rec is None: