feat: automate image pipeline and simplify gemini flow

This commit is contained in:
2026-03-08 23:42:18 +08:00
parent 3a78eb304a
commit 82284ce3fb
6 changed files with 660 additions and 520 deletions

View File

@@ -0,0 +1,232 @@
import asyncio
import hashlib
import json
import logging
import os
import re
from pathlib import Path
from typing import Dict, List, Optional
from urllib.parse import urlparse
import httpx
from dotenv import load_dotenv
from db.customer_db import CustomerDatabase
from db.image_tasks_db import TaskStatus, db as task_db
from services.service_gemini import GeminiExtractV2Service
from services.service_tuhui_upload import upload_to_tuhui
from services.service_wecom_bot import wecom_bot_service
load_dotenv()
logger = logging.getLogger("cs_agent")
AUTO_PROCESS_PRICE = int(os.getenv("AUTO_PROCESS_DEFAULT_PRICE", "12"))
AUTO_PROCESS_CATEGORY = os.getenv("AUTO_PROCESS_CATEGORY", "设计素材")
AUTO_PROCESS_ROOT = Path(
os.getenv("AUTO_PROCESS_ROOT", str(Path(__file__).resolve().parents[1] / "runtime" / "auto_processed"))
)
def _safe_name(text: str, fallback: str = "image") -> str:
cleaned = re.sub(r"[^0-9A-Za-z\u4e00-\u9fa5_-]+", "_", str(text or "").strip())
cleaned = cleaned.strip("_")
return cleaned[:40] or fallback
def _suffix_from_url(url: str) -> str:
path = urlparse(str(url or "")).path
suffix = Path(path).suffix.lower()
if suffix in {".png", ".jpg", ".jpeg", ".webp"}:
return suffix
return ".png"
def _build_processing_prompt(intent: str, requirement_text: str, analysis: Dict) -> str:
base_prompt = str((analysis or {}).get("gemini_prompt") or "").strip()
req = str(requirement_text or "").strip()
if base_prompt:
return base_prompt
if intent == "repair":
return f"根据客户需求“{req or '高清修复'}”,保留主体和构图,做高清修复并补足细节。"
return f"根据客户需求“{req or '找原图'}”,严格参考原图元素与构图,生成完整干净的高质量素材图。"
class AutoImagePipelineService:
def __init__(self):
self.customer_db = CustomerDatabase()
async def _download_image(self, image_url: str, dest_path: Path) -> Path:
dest_path.parent.mkdir(parents=True, exist_ok=True)
async with httpx.AsyncClient(timeout=60.0, follow_redirects=True) as client:
response = await client.get(image_url)
response.raise_for_status()
dest_path.write_bytes(response.content)
return dest_path
@staticmethod
def _format_transfer_notice(
customer_id: str,
acc_id: str,
designer_name: str,
requirement_text: str,
intent: str,
image_urls: List[str],
) -> str:
lines = [
"【AI自动转设计师】",
f"店铺:{acc_id or '-'}",
f"客户:{customer_id or '-'}",
f"设计师:{designer_name or '-'}",
f"需求:{requirement_text or '-'}",
f"类型:{'高清修复' if intent == 'repair' else '找原图'}",
f"默认价格:{AUTO_PROCESS_PRICE}",
]
if image_urls:
lines.append("原图URL")
lines.extend(image_urls[:5])
return "\n".join(lines)
@staticmethod
def _format_finish_notice(
customer_id: str,
acc_id: str,
designer_name: str,
links: List[Dict[str, str]],
failures: List[str],
) -> str:
lines = [
"【AI处理完成】",
f"店铺:{acc_id or '-'}",
f"客户:{customer_id or '-'}",
f"设计师:{designer_name or '-'}",
f"默认价格:{AUTO_PROCESS_PRICE}",
]
if links:
lines.append("处理结果:")
for idx, item in enumerate(links, 1):
lines.append(f"{idx}. 图绘链接:{item.get('download_url') or '-'}")
lines.append(f" 原图URL{item.get('source_url') or '-'}")
if failures:
lines.append("失败项:")
lines.extend(failures[:5])
return "\n".join(lines)
async def process_and_notify(
self,
*,
session_key: str,
customer_id: str,
acc_id: str,
designer_name: str,
requirement_text: str,
image_urls: List[str],
intent: str = "",
) -> Dict:
image_urls = [str(url).strip() for url in (image_urls or []) if str(url).strip()]
if not image_urls:
return {"success": False, "message": "no_images"}
image_urls = image_urls[:1]
profile = self.customer_db.get_customer(session_key)
analysis = {}
if getattr(profile, "last_image_analysis", ""):
try:
analysis = json.loads(profile.last_image_analysis)
except Exception:
analysis = {}
if not intent:
intent = "repair" if "修复" in requirement_text else "find_original"
await wecom_bot_service.send_text(
self._format_transfer_notice(
customer_id=customer_id,
acc_id=acc_id,
designer_name=designer_name,
requirement_text=requirement_text,
intent=intent,
image_urls=image_urls,
)
)
pipeline_root = AUTO_PROCESS_ROOT / _safe_name(customer_id, "customer")
pipeline_root.mkdir(parents=True, exist_ok=True)
gemini_service = GeminiExtractV2Service()
uploaded_links: List[Dict[str, str]] = []
failures: List[str] = []
for idx, image_url in enumerate(image_urls, 1):
digest = hashlib.md5(f"{customer_id}|{acc_id}|{image_url}".encode("utf-8")).hexdigest()[:10]
input_path = pipeline_root / f"{digest}_src{_suffix_from_url(image_url)}"
output_path = pipeline_root / f"{digest}_out.png"
title = f"{_safe_name(customer_id, '客户')}_{'修复' if intent == 'repair' else '原图'}_{idx}"
prompt = _build_processing_prompt(intent, requirement_text, analysis)
task_id = task_db.add_task(
customer_id=customer_id,
platform="qianniu",
original_image=image_url,
operation=intent or "auto_process",
requirements=requirement_text,
status=TaskStatus.PROCESSING.value,
)
try:
await self._download_image(image_url, input_path)
success, message, data = await gemini_service.extract_pattern(
str(input_path),
str(output_path),
custom_prompt=prompt,
aspect_ratio=str((analysis or {}).get("aspect_ratio") or "1:1"),
)
if not success or not output_path.exists():
if task_id:
task_db.update_status(task_id, TaskStatus.FAILED.value)
failures.append(f"{idx}. Gemini失败{message}")
continue
upload_result = await upload_to_tuhui(
image_path=str(output_path),
title=title,
description=requirement_text or prompt[:120],
price=AUTO_PROCESS_PRICE,
category=AUTO_PROCESS_CATEGORY,
tags="AI处理,自动转接",
designer_name=designer_name,
)
if not upload_result.success:
if task_id:
task_db.update_status(task_id, TaskStatus.FAILED.value)
failures.append(f"{idx}. 图绘上传失败:{upload_result.message}")
continue
if task_id:
task_db.update_status(task_id, TaskStatus.COMPLETED.value, upload_result.download_url)
uploaded_links.append(
{
"download_url": upload_result.download_url,
"source_url": image_url,
"work_id": str(upload_result.work_id),
}
)
except Exception as e:
if task_id:
task_db.update_status(task_id, TaskStatus.FAILED.value)
failures.append(f"{idx}. 处理异常:{e}")
await wecom_bot_service.send_text(
self._format_finish_notice(
customer_id=customer_id,
acc_id=acc_id,
designer_name=designer_name,
links=uploaded_links,
failures=failures,
)
)
return {
"success": bool(uploaded_links),
"uploaded": uploaded_links,
"failures": failures,
}
auto_image_pipeline_service = AutoImagePipelineService()