feat: integrate gemini auto-draw preview via tw_terminator service
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled
This commit is contained in:
103
qingjian_cs/app/auto_draw.py
Normal file
103
qingjian_cs/app/auto_draw.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .config import AUTO_DRAW_ENDPOINT, AUTO_DRAW_TIMEOUT_SECONDS
|
||||||
|
|
||||||
|
|
||||||
|
def _add_legacy_tw_path() -> None:
|
||||||
|
root = os.getenv("LEGACY_TW_ROOT", r"D:\main\sandbox\tw_terminator").strip()
|
||||||
|
if not root:
|
||||||
|
return
|
||||||
|
p = Path(root)
|
||||||
|
if p.exists() and str(p) not in sys.path:
|
||||||
|
sys.path.insert(0, str(p))
|
||||||
|
|
||||||
|
|
||||||
|
async def _draw_via_legacy_tw(
|
||||||
|
image_url: str,
|
||||||
|
customer_id: str,
|
||||||
|
requirement: str = "",
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
_add_legacy_tw_path()
|
||||||
|
from image.image_processor import image_processor # type: ignore
|
||||||
|
from services.service_tuhui_upload import upload_to_tuhui # type: ignore
|
||||||
|
|
||||||
|
prompt = requirement.strip() or "按原图做高清处理,保留主体细节,输出清晰可用版本"
|
||||||
|
process_res = await image_processor.process_image(
|
||||||
|
image_url=image_url,
|
||||||
|
operation="enhance",
|
||||||
|
requirements="complexity:normal",
|
||||||
|
gemini_prompt=prompt,
|
||||||
|
aspect_ratio="1:1",
|
||||||
|
perspective="no",
|
||||||
|
proc_type="",
|
||||||
|
subject="",
|
||||||
|
quality="",
|
||||||
|
)
|
||||||
|
if not process_res.get("success"):
|
||||||
|
return {"ok": False, "error": str(process_res.get("message", "process_failed"))}
|
||||||
|
|
||||||
|
ok, link, _ = await upload_to_tuhui(
|
||||||
|
process_res["result_path"],
|
||||||
|
title=f"客户{customer_id[-4:]}-预览图" if customer_id else "预览图",
|
||||||
|
description="AI自动作图预览",
|
||||||
|
price=1,
|
||||||
|
)
|
||||||
|
if not ok:
|
||||||
|
return {"ok": False, "error": str(link)}
|
||||||
|
return {"ok": True, "url": str(link)}
|
||||||
|
|
||||||
|
|
||||||
|
def _draw_via_http_endpoint(image_url: str, customer_id: str, requirement: str = "") -> dict[str, Any]:
|
||||||
|
if not AUTO_DRAW_ENDPOINT:
|
||||||
|
return {"ok": False, "error": "AUTO_DRAW_ENDPOINT not configured"}
|
||||||
|
payload = {
|
||||||
|
"image_url": image_url,
|
||||||
|
"customer_id": customer_id,
|
||||||
|
"requirement": requirement,
|
||||||
|
}
|
||||||
|
resp = requests.post(AUTO_DRAW_ENDPOINT, json=payload, timeout=AUTO_DRAW_TIMEOUT_SECONDS)
|
||||||
|
if resp.status_code != 200:
|
||||||
|
return {"ok": False, "error": f"http_{resp.status_code}:{resp.text[:200]}"}
|
||||||
|
data = resp.json() if resp.text else {}
|
||||||
|
url = str(data.get("url", "") or data.get("preview_url", "") or "")
|
||||||
|
if not url:
|
||||||
|
return {"ok": False, "error": "missing_preview_url"}
|
||||||
|
return {"ok": True, "url": url}
|
||||||
|
|
||||||
|
|
||||||
|
async def auto_draw_preview(
|
||||||
|
image_url: str,
|
||||||
|
customer_id: str,
|
||||||
|
requirement: str = "",
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""
|
||||||
|
统一自动作图入口:
|
||||||
|
1) 优先走 tw_terminator 的 Gemini 作图链路
|
||||||
|
2) 失败时回退 AUTO_DRAW_ENDPOINT
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return await _draw_via_legacy_tw(image_url=image_url, customer_id=customer_id, requirement=requirement)
|
||||||
|
except Exception as e:
|
||||||
|
legacy_error = str(e)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = await asyncio.to_thread(
|
||||||
|
_draw_via_http_endpoint,
|
||||||
|
image_url,
|
||||||
|
customer_id,
|
||||||
|
requirement,
|
||||||
|
)
|
||||||
|
if data.get("ok"):
|
||||||
|
return data
|
||||||
|
return {"ok": False, "error": f"legacy:{legacy_error}; endpoint:{data.get('error','unknown')}"}
|
||||||
|
except Exception as e:
|
||||||
|
return {"ok": False, "error": f"legacy:{legacy_error}; endpoint:{e}"}
|
||||||
|
|
||||||
@@ -7,7 +7,9 @@ from collections import defaultdict
|
|||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
from .callbacks import post_tianwang_callback
|
from .callbacks import post_tianwang_callback
|
||||||
|
from .auto_draw import auto_draw_preview
|
||||||
from .config import (
|
from .config import (
|
||||||
|
AUTO_DRAW_ENABLED,
|
||||||
AUTO_QUOTE_WAIT_SECONDS,
|
AUTO_QUOTE_WAIT_SECONDS,
|
||||||
MESSAGE_DEBOUNCE_SECONDS,
|
MESSAGE_DEBOUNCE_SECONDS,
|
||||||
QINGJIAN_WS_URI,
|
QINGJIAN_WS_URI,
|
||||||
@@ -77,6 +79,28 @@ class QingjianClient:
|
|||||||
self.recent_outbound = self.recent_outbound[-200:]
|
self.recent_outbound = self.recent_outbound[-200:]
|
||||||
activity_event(self.logger, "send_reply_success", trace_id=trace_id, customer_id=data.get("from_id", "-"), msg=text)
|
activity_event(self.logger, "send_reply_success", trace_id=trace_id, customer_id=data.get("from_id", "-"), msg=text)
|
||||||
|
|
||||||
|
async def send_image(self, data: dict, image_url: str, trace_id: str = "-") -> None:
|
||||||
|
image_url = str(image_url or "").strip()
|
||||||
|
if not image_url:
|
||||||
|
return
|
||||||
|
msg = {
|
||||||
|
"msg_id": "",
|
||||||
|
"acc_id": data.get("acc_id", ""),
|
||||||
|
"msg": image_url,
|
||||||
|
"from_id": data.get("from_id", ""),
|
||||||
|
"from_name": data.get("from_name", data.get("from_id", "")),
|
||||||
|
"cy_id": data.get("from_id", ""),
|
||||||
|
"acc_type": data.get("acc_type", "AliWorkbench"),
|
||||||
|
"msg_type": 1,
|
||||||
|
"cy_name": data.get("from_name", data.get("from_id", "")),
|
||||||
|
}
|
||||||
|
activity_event(self.logger, "send_image_attempt", trace_id=trace_id, customer_id=data.get("from_id", "-"), msg=image_url)
|
||||||
|
await self.send_message(msg)
|
||||||
|
self.recent_outbound.append((str(data.get("acc_id", "")), str(data.get("from_id", "")), image_url, time.monotonic()))
|
||||||
|
if len(self.recent_outbound) > 200:
|
||||||
|
self.recent_outbound = self.recent_outbound[-200:]
|
||||||
|
activity_event(self.logger, "send_image_success", trace_id=trace_id, customer_id=data.get("from_id", "-"), msg=image_url)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _clean_text(text: str) -> str:
|
def _clean_text(text: str) -> str:
|
||||||
t = str(text or "").strip()
|
t = str(text or "").strip()
|
||||||
@@ -185,6 +209,49 @@ class QingjianClient:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if decision.action == "quote":
|
if decision.action == "quote":
|
||||||
|
if AUTO_DRAW_ENABLED and self.pending_images.get(key):
|
||||||
|
latest_image = self.pending_images[key][-1]
|
||||||
|
activity_event(
|
||||||
|
self.logger,
|
||||||
|
"auto_draw_start",
|
||||||
|
trace_id=trace_id,
|
||||||
|
customer_id=context["customer_id"],
|
||||||
|
image_url=latest_image,
|
||||||
|
)
|
||||||
|
draw_res = await auto_draw_preview(
|
||||||
|
image_url=latest_image,
|
||||||
|
customer_id=context["customer_id"],
|
||||||
|
requirement=merged_msg,
|
||||||
|
)
|
||||||
|
if draw_res.get("ok"):
|
||||||
|
preview_url = str(draw_res.get("url", "") or "")
|
||||||
|
await self.send_reply(data, "先给你做了预览图。", trace_id=trace_id)
|
||||||
|
await self.send_image(data, preview_url, trace_id=trace_id)
|
||||||
|
final_text = "看下预览,满意再拍下付款。"
|
||||||
|
await self.send_reply(data, final_text, trace_id=trace_id)
|
||||||
|
self.last_reply_key[key] = final_text
|
||||||
|
# 预览完成后清掉当前批次,避免同一图重复触发
|
||||||
|
self.pending_images[key].clear()
|
||||||
|
activity_event(
|
||||||
|
self.logger,
|
||||||
|
"auto_draw_success",
|
||||||
|
trace_id=trace_id,
|
||||||
|
customer_id=context["customer_id"],
|
||||||
|
preview_url=preview_url,
|
||||||
|
)
|
||||||
|
await post_tianwang_callback(
|
||||||
|
"message_processed",
|
||||||
|
data,
|
||||||
|
extra={"trace_id": trace_id, "route": route, "action": "quote", "reply": final_text, "auto_draw": True},
|
||||||
|
)
|
||||||
|
return
|
||||||
|
activity_event(
|
||||||
|
self.logger,
|
||||||
|
"auto_draw_fail",
|
||||||
|
trace_id=trace_id,
|
||||||
|
customer_id=context["customer_id"],
|
||||||
|
error=str(draw_res.get("error", "unknown")),
|
||||||
|
)
|
||||||
text = (decision.reply or "").strip()
|
text = (decision.reply or "").strip()
|
||||||
if self._is_invalid_ai_reply(text):
|
if self._is_invalid_ai_reply(text):
|
||||||
text = self._fallback_reply("quote")
|
text = self._fallback_reply("quote")
|
||||||
|
|||||||
@@ -31,3 +31,7 @@ MYSQL_TABLE_PREFIX = os.getenv("MYSQL_TABLE_PREFIX", "qjcs_").strip()
|
|||||||
HTTP_HOST = os.getenv("HTTP_HOST", "127.0.0.1").strip()
|
HTTP_HOST = os.getenv("HTTP_HOST", "127.0.0.1").strip()
|
||||||
HTTP_PORT = int(os.getenv("HTTP_PORT", "6060"))
|
HTTP_PORT = int(os.getenv("HTTP_PORT", "6060"))
|
||||||
TIANWANG_CALLBACK_URL = os.getenv("TIANWANG_CALLBACK_URL", "http://139.199.3.75:18789/api/callback").strip()
|
TIANWANG_CALLBACK_URL = os.getenv("TIANWANG_CALLBACK_URL", "http://139.199.3.75:18789/api/callback").strip()
|
||||||
|
|
||||||
|
AUTO_DRAW_ENABLED = os.getenv("AUTO_DRAW_ENABLED", "1").strip() in {"1", "true", "True", "yes", "on"}
|
||||||
|
AUTO_DRAW_ENDPOINT = os.getenv("AUTO_DRAW_ENDPOINT", "").strip()
|
||||||
|
AUTO_DRAW_TIMEOUT_SECONDS = int(os.getenv("AUTO_DRAW_TIMEOUT_SECONDS", "25"))
|
||||||
|
|||||||
Reference in New Issue
Block a user