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:
0
services/__init__.py
Normal file → Executable file
0
services/__init__.py
Normal file → Executable file
0
services/__pycache__/__init__.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/__init__.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/service_gemini.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/service_gemini.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/service_meitu.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/service_meitu.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/service_vectorizer.cpython-310.pyc
Normal file → Executable file
0
services/__pycache__/service_vectorizer.cpython-310.pyc
Normal file → Executable file
0
services/service_gemini.py
Normal file → Executable file
0
services/service_gemini.py
Normal file → Executable file
0
services/service_meitu.py
Normal file → Executable file
0
services/service_meitu.py
Normal file → Executable file
0
services/service_qwen.py
Normal file → Executable file
0
services/service_qwen.py
Normal file → Executable file
229
services/service_tuhui_dispatch.py
Normal file
229
services/service_tuhui_dispatch.py
Normal 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
|
||||
172
services/service_tuhui_upload.py
Normal file
172
services/service_tuhui_upload.py
Normal 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
0
services/service_vectorizer.py
Normal file → Executable file
Reference in New Issue
Block a user