feat: 完整功能部署 v1.0

新增功能:
- 天网协作系统 (HTTP API 端口 6060)
- 三种工作流 (查找图片/处理图片/转人工派单)
- 图片任务数据库 (支持客户后续增加需求)
- 图绘派单系统集成 (API: 8005)
- 文字检测与加价 (60-80 元高价值订单)
- 风险评估与接单判断
- 作图失败自动转人工

新增文档:
- 项目功能汇总.md
- 三种工作流功能说明.md
- 文字加价功能说明.md
- 风险评估功能说明.md
- 图片任务数据库功能说明.md
- 图绘派单系统集成说明.md
- 作图失败转接人工说明.md
- DEPLOYMENT.md
- TIANWANG_INTEGRATION.md

核心修改:
- core/pydantic_ai_agent.py
- core/workflow.py
- core/websocket_client.py
- image/image_analyzer.py
- services/service_tuhui_dispatch.py
- db/image_tasks_db.py

版本:v1.0
日期:2026-02-28
This commit is contained in:
2026-02-28 11:20:40 +08:00
parent 5aedf1665d
commit a6c42d505a
171 changed files with 7979 additions and 328 deletions

0
services/__init__.py Normal file → Executable file
View File

0
services/__pycache__/__init__.cpython-310.pyc Normal file → Executable file
View File

0
services/__pycache__/service_gemini.cpython-310.pyc Normal file → Executable file
View File

0
services/__pycache__/service_meitu.cpython-310.pyc Normal file → Executable file
View File

View File

0
services/service_gemini.py Normal file → Executable file
View File

0
services/service_meitu.py Normal file → Executable file
View File

0
services/service_qwen.py Normal file → Executable file
View File

View File

@@ -0,0 +1,229 @@
# -*- coding: utf-8 -*-
"""
图绘派单系统 API 客户端
"""
import httpx
import logging
from typing import List, Dict, Optional
logger = logging.getLogger(__name__)
class TuhuiDispatchClient:
"""图绘派单系统客户端"""
def __init__(self):
self.base_url = "http://1.12.50.92:8005"
self.api_key = "tuhui_dispatch_key_2026"
self.headers = {"X-API-Key": self.api_key}
self.timeout = 10.0
async def get_dispatch_queue(self) -> Optional[Dict]:
"""
获取派单队列
Returns:
dict: {
"pending_tasks": {"count": 3, "tasks": [...]},
"online_designers": {"count": 2, "designers": [...]},
"suggestion": "橘子"
}
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.base_url}/dispatch/queue",
headers=self.headers
)
if response.status_code == 200:
return response.json()
else:
logger.error(f"获取派单队列失败HTTP {response.status_code}")
return None
except Exception as e:
logger.error(f"获取派单队列异常:{e}")
return None
async def get_online_designers(self) -> List[str]:
"""
获取在线设计师
Returns:
list: ["橘子", "婷婷", ...]
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.base_url}/online/designers",
headers=self.headers
)
if response.status_code == 200:
data = response.json()
return data.get("online_users", [])
else:
logger.error(f"获取在线设计师失败HTTP {response.status_code}")
return []
except Exception as e:
logger.error(f"获取在线设计师异常:{e}")
return []
async def create_task(self, task_name: str, description: str = "",
task_type: str = "design", priority: int = 1,
deadline: str = None) -> Optional[str]:
"""
创建任务
Args:
task_name: 任务名称
description: 任务描述
task_type: 任务类型
priority: 优先级1-5
deadline: 截止时间
Returns:
task_id: 任务 ID失败返回 None
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.post(
f"{self.base_url}/tasks",
headers={**self.headers, "Content-Type": "application/json"},
json={
"task_name": task_name,
"description": description,
"task_type": task_type,
"priority": priority,
"deadline": deadline
}
)
if response.status_code == 200:
data = response.json()
task_id = data.get("task_id")
logger.info(f"创建任务成功:{task_id}")
return task_id
else:
logger.error(f"创建任务失败HTTP {response.status_code}")
return None
except Exception as e:
logger.error(f"创建任务异常:{e}")
return None
async def assign_task(self, task_id: str, designer_name: str,
notes: str = "") -> bool:
"""
分配任务给设计师
Args:
task_id: 任务 ID
designer_name: 设计师姓名
notes: 备注信息
Returns:
bool: 是否成功
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.post(
f"{self.base_url}/tasks/{task_id}/assign",
headers={**self.headers, "Content-Type": "application/json"},
json={
"designer_name": designer_name,
"notes": notes or "AI 客服自动派单"
}
)
if response.status_code == 200:
data = response.json()
logger.info(f"任务分配成功:{task_id}{designer_name}")
return True
else:
logger.error(f"分配任务失败HTTP {response.status_code}")
return False
except Exception as e:
logger.error(f"分配任务异常:{e}")
return False
async def get_task_status(self, task_id: str) -> Optional[Dict]:
"""
查询任务状态
Args:
task_id: 任务 ID
Returns:
dict: 任务状态信息
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.base_url}/tasks/{task_id}",
headers=self.headers
)
if response.status_code == 200:
return response.json()
else:
logger.error(f"查询任务状态失败HTTP {response.status_code}")
return None
except Exception as e:
logger.error(f"查询任务状态异常:{e}")
return None
async def complete_task(self, task_id: str, notes: str = "") -> bool:
"""
完成任务
Args:
task_id: 任务 ID
notes: 备注信息
Returns:
bool: 是否成功
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.post(
f"{self.base_url}/tasks/{task_id}/complete",
headers={**self.headers, "Content-Type": "application/json"},
json={
"notes": notes or "客户已确认,效果满意"
}
)
if response.status_code == 200:
logger.info(f"任务完成:{task_id}")
return True
else:
logger.error(f"完成任务失败HTTP {response.status_code}")
return False
except Exception as e:
logger.error(f"完成任务异常:{e}")
return False
async def health_check(self) -> bool:
"""健康检查"""
try:
async with httpx.AsyncClient(timeout=5.0) as client:
response = await client.get(f"{self.base_url}/health")
return response.status_code == 200
except:
return False
# 单例
_client: Optional[TuhuiDispatchClient] = None
def get_tuhui_dispatch_client() -> TuhuiDispatchClient:
"""获取派单客户端单例"""
global _client
if _client is None:
_client = TuhuiDispatchClient()
return _client

View File

@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*-
"""
图绘平台上传服务
将处理好的图片上传到图绘平台,返回图片 URL
"""
import os
import httpx
import logging
from pathlib import Path
from typing import Optional, Tuple
from dotenv import load_dotenv
logger = logging.getLogger(__name__)
# 图绘平台配置
TUHUI_BASE_URL = os.getenv("TUHUI_BASE_URL", "http://127.0.0.1:8002")
TUHUI_PHONE = os.getenv("TUHUI_PHONE", "17520145271") # 图绘账号手机号
TUHUI_PASSWORD = os.getenv("TUHUI_PASSWORD", "zuowei1216") # 图绘账号密码
TUHUI_DEFAULT_PRICE = int(os.getenv("TUHUI_DEFAULT_PRICE", "20")) # 默认定价(元)
class TuhuiUploadService:
"""图绘平台上传服务"""
def __init__(self):
self.base_url = TUHUI_BASE_URL
self.phone = TUHUI_PHONE
self.password = TUHUI_PASSWORD
self.default_price = TUHUI_DEFAULT_PRICE
self.access_token = None
self.user_id = None
async def login(self) -> bool:
"""登录图绘平台获取 token"""
try:
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/api/auth/login",
json={
"phone": self.phone,
"password": self.password
},
timeout=10.0
)
if response.status_code == 200:
data = response.json()
self.access_token = data.get("access_token")
user = data.get("user", {})
self.user_id = user.get("id")
logger.info(f"图绘平台登录成功,用户 ID: {self.user_id}")
return True
else:
logger.error(f"图绘平台登录失败:{response.status_code} {response.text}")
return False
except Exception as e:
logger.error(f"图绘平台登录异常:{e}")
return False
async def upload_image(
self,
image_path: str,
title: str,
description: str = "",
price: Optional[int] = None,
category: str = "高清修复"
) -> Tuple[bool, str, int]:
"""
上传图片到图绘平台
Args:
image_path: 图片文件路径
title: 作品标题
description: 作品描述
price: 定价(元),默认使用 TUHUI_DEFAULT_PRICE
category: 分类
Returns:
(success, image_url, work_id)
- success: 是否上传成功
- image_url: 图片 URL
- work_id: 作品 ID
"""
try:
# 如果 token 过期,重新登录
if not self.access_token:
if not await self.login():
return False, "登录失败", 0
# 准备上传数据
price = price or self.default_price
# 读取图片文件
if not os.path.exists(image_path):
logger.error(f"图片文件不存在:{image_path}")
return False, "文件不存在", 0
with open(image_path, "rb") as f:
files = {
"original_image": ("image.jpg", f, "image/jpeg")
}
data = {
"title": title,
"description": description,
"price": str(price),
"category": category
}
headers = {
"Authorization": f"Bearer {self.access_token}"
}
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/api/works",
files=files,
data=data,
headers=headers,
timeout=30.0
)
if response.status_code in [200, 201]:
work_data = response.json()
work_id = work_data.get("id")
image_url = work_data.get("original_image", "")
logger.info(f"图绘平台上传成功,作品 ID: {work_id}, URL: {image_url}")
return True, image_url, work_id
else:
logger.error(f"图绘平台上传失败:{response.status_code} {response.text}")
# 如果 token 过期,尝试重新登录后再上传
if response.status_code == 401:
logger.info("Token 可能过期,尝试重新登录...")
self.access_token = None
if await self.login():
# 重新上传
return await self.upload_image(
image_path, title, description, price, category
)
return False, f"上传失败:{response.text}", 0
except Exception as e:
logger.error(f"图绘平台上传异常:{e}")
return False, f"上传异常:{e}", 0
# 单例
_tuhui_service: Optional[TuhuiUploadService] = None
def get_tuhui_service() -> TuhuiUploadService:
"""获取图绘上传服务单例"""
global _tuhui_service
if _tuhui_service is None:
_tuhui_service = TuhuiUploadService()
return _tuhui_service
# 便捷函数
async def upload_to_tuhui(
image_path: str,
title: str,
description: str = "",
price: int = 20
) -> Tuple[bool, str, int]:
"""
便捷函数:上传图片到图绘平台
Returns:
(success, image_url, work_id)
"""
service = get_tuhui_service()
return await service.upload_image(image_path, title, description, price)

0
services/service_vectorizer.py Normal file → Executable file
View File