diff --git a/.gitignore b/.gitignore deleted file mode 100644 index e6d9f77..0000000 --- a/.gitignore +++ /dev/null @@ -1,36 +0,0 @@ -# Python -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python - -# 数据库 -*.db -*.db-journal - -# 日志 -logs/ -*.log - -# 结果图片 -results/ - -# 环境配置 -.env.backup -.env.*.backup - -# 备份文件 -*.bak -*.backup -*.fix_backup - -# IDE -.idea/ -.vscode/ -*.swp -*.swo - -# 系统文件 -.DS_Store -Thumbs.db diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md deleted file mode 100644 index 9e428b1..0000000 --- a/DEPLOYMENT.md +++ /dev/null @@ -1,325 +0,0 @@ -# AI 客服系统 - 天网协作版部署文档 - -## 📋 运行架构 - -**单进程多模块架构**: -- ✅ 一个 Python 进程 -- ✅ 多个模块协同工作 -- ✅ WebSocket 客户端 + HTTP API 服务器 + 任务调度器 - -**优势**: -- 部署简单(一条命令) -- 资源共享(数据库连接、内存) -- 通信高效(进程内调用) - ---- - -## 🚀 部署步骤 - -### 步骤 1:环境检查 - -```bash -# 检查 Python 版本 -python3 --version # 需要 3.8+ - -# 检查依赖 -cd /root/ai_customer_service/ai_cs -pip3 list | grep -E "flask|pydantic|sqlite" -``` - -### 步骤 2:启动天网协作版 - -```bash -cd /root/ai_customer_service/ai_cs - -# 启动(包含 AI Agent) -python3 run_with_tianwang.py - -# 启动(不包含 AI Agent,仅基础回复) -python3 run_with_tianwang.py --no-agent -``` - -**启动后会看到**: -``` -============================================================ -AI 客服系统 - 天网协作版 -============================================================ -正在启动 HTTP API 服务器... -HTTP API 服务器已启动:http://0.0.0.0:5678 - -天网任务接口: - POST /api/task/receive - 接收任务 - POST /api/task/cancel - 取消任务 - GET /api/task/status/:id - 查询任务状态 - GET /api/task/list - 任务列表 - GET /api/health - 健康检查 -============================================================ -正在连接轻简 API... -AI Agent: 已启用 -============================================================ -系统已就绪,等待消息和任务... -``` - -### 步骤 3:测试 API - -```bash -# 测试健康检查 -curl http://localhost:5678/api/health - -# 接收任务 -curl -X POST http://localhost:5678/api/task/receive \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TEST_001", - "type": "test", - "customer": {"id": "test_001"}, - "trigger": {"type": "customer_reply", "keyword": "测试"}, - "action": {"type": "send_message", "message": "测试消息"}, - "created_by": "测试" - }' - -# 查询任务 -curl http://localhost:5678/api/task/status/TEST_001 -``` - ---- - -## 🔧 生产环境部署 - -### 方式 1:systemd 服务(推荐) - -创建服务文件: - -```bash -cat > /etc/systemd/system/ai-cs-tianwang.service << 'SERVICE' -[Unit] -Description=AI Customer Service with Tianwang -After=network.target - -[Service] -Type=simple -User=root -WorkingDirectory=/root/ai_customer_service/ai_cs -ExecStart=/usr/bin/python3 run_with_tianwang.py -Restart=always -RestartSec=10 -LimitNOFILE=65535 - -# 环境变量(可选) -Environment="HTTP_API_PORT=5678" -Environment="TASK_DB_PATH=/root/ai_customer_service/ai_cs/db/task_db/tasks.db" - -# 日志 -StandardOutput=journal -StandardError=journal -SyslogIdentifier=ai-cs-tianwang - -[Install] -WantedBy=multi-user.target -SERVICE - -# 启动服务 -systemctl daemon-reload -systemctl enable ai-cs-tianwang -systemctl start ai-cs-tianwang - -# 查看状态 -systemctl status ai-cs-tianwang - -# 查看日志 -journalctl -u ai-cs-tianwang -f -``` - -### 方式 2:Docker 部署 - -创建 Dockerfile: - -```dockerfile -FROM python:3.11-slim - -WORKDIR /app - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -EXPOSE 5678 - -CMD ["python3", "run_with_tianwang.py"] -``` - -构建并运行: - -```bash -docker build -t ai-cs-tianwang . -docker run -d \ - --name ai-cs \ - -p 5678:5678 \ - -v /root/ai_customer_service/ai_cs/db:/app/db \ - --restart unless-stopped \ - ai-cs-tianwang -``` - -### 方式 3:后台运行(简单场景) - -```bash -cd /root/ai_customer_service/ai_cs - -# 使用 nohup 后台运行 -nohup python3 run_with_tianwang.py > /var/log/ai-cs.log 2>&1 & - -# 查看进程 -ps aux | grep run_with_tianwang - -# 查看日志 -tail -f /var/log/ai-cs.log - -# 停止进程 -pkill -f run_with_tianwang -``` - ---- - -## 📊 端口说明 - -| 端口 | 用途 | 是否必须 | -|------|------|----------| -| 5678 | HTTP API 服务器 | ✅ 必须 | -| 9528 | 轻简软件 WebSocket | ✅ 必须(外部) | - -**防火墙配置**: -```bash -# 开放 HTTP API 端口 -firewall-cmd --add-port=5678/tcp --permanent -firewall-cmd --reload - -# 或者使用 iptables -iptables -A INPUT -p tcp --dport 5678 -j ACCEPT -``` - ---- - -## 🔍 监控与日志 - -### 查看运行状态 - -```bash -# systemd 方式 -systemctl status ai-cs-tianwang - -# 进程方式 -ps aux | grep run_with_tianwang -``` - -### 查看日志 - -```bash -# systemd 方式 -journalctl -u ai-cs-tianwang -f - -# 文件方式 -tail -f /var/log/ai-cs.log - -# 只看任务相关日志 -journalctl -u ai-cs-tianwang -f | grep "任务" -``` - -### 查看数据库 - -```bash -# 进入 SQLite -sqlite3 /root/ai_customer_service/ai_cs/db/task_db/tasks.db - -# 查看所有任务 -SELECT task_id, status, created_at FROM tasks ORDER BY created_at DESC LIMIT 10; - -# 查看待触发任务 -SELECT * FROM tasks WHERE status='pending'; - -# 查看失败任务 -SELECT task_id, error_message FROM tasks WHERE status='failed'; - -# 退出 -.exit -``` - ---- - -## ⚠️ 注意事项 - -1. **HTTP 端口**: - - 默认 5678,确保未被占用 - - 可在 `.env` 中修改:`HTTP_API_PORT=5679` - -2. **数据库路径**: - - 确保有写权限 - - 建议定期备份:`cp tasks.db tasks.db.backup` - -3. **天网回调**: - - 配置正确的回调 URL - - 确保天网服务器能访问 AI 客服 - -4. **资源限制**: - - 建议内存:≥ 512MB - - 建议 CPU:≥ 1 核心 - -5. **进程守护**: - - 推荐使用 systemd - - 自动重启,故障恢复 - ---- - -## 🐛 故障排查 - -### 无法启动 - -```bash -# 检查端口占用 -netstat -tlnp | grep 5678 - -# 检查依赖 -pip3 list | grep -E "flask|pydantic" - -# 手动运行查看错误 -python3 run_with_tianwang.py -``` - -### 任务未触发 - -```bash -# 1. 检查任务状态 -curl http://localhost:5678/api/task/status/TASK_ID - -# 2. 查看日志 -journalctl -u ai-cs-tianwang -f | grep "任务触发" - -# 3. 检查触发条件 -# 确认客户消息包含触发关键词 -``` - -### HTTP API 无法访问 - -```bash -# 1. 检查服务状态 -systemctl status ai-cs-tianwang - -# 2. 检查端口 -netstat -tlnp | grep 5678 - -# 3. 检查防火墙 -firewall-cmd --list-ports - -# 4. 本地测试 -curl http://localhost:5678/api/health -``` - ---- - -## 📞 技术支持 - -- 完整文档:`/root/ai_customer_service/ai_cs/TIANWANG_INTEGRATION.md` -- 使用示例:`/root/ai_customer_service/ai_cs/SPECIFIED_CUSTOMER_REPLY_EXAMPLE.md` -- 日志位置:`journalctl -u ai-cs-tianwang -f` -- 数据库位置:`/root/ai_customer_service/ai_cs/db/task_db/tasks.db` - diff --git a/FIX_SUMMARY.md b/FIX_SUMMARY.md deleted file mode 100644 index 33ecd59..0000000 --- a/FIX_SUMMARY.md +++ /dev/null @@ -1,105 +0,0 @@ -# AI 客服系统修复总结 - -## ✅ 已修复的问题 - -### 1. Agent 模块语法错误 -**问题**: `@self.agent.tool`装饰器在类方法外部使用 -**修复**: 删除了错误的`upload_to_tuhui_platform` 工具定义(行 219-246) - -### 2. WebSocket 客户端循环导入 -**问题**: 顶部导入导致循环依赖 -**修复**: 改为延迟加载,在需要时才导入任务模块 - -### 3. 任务模块初始化 -**问题**: `__init__` 中直接导入任务模块 -**修复**: 删除直接初始化,改为`_load_task_modules()` 方法延迟加载 - ---- - -## 🚀 启动方式 - -### 方式 1: AI 客服(原始稳定版) -```bash -cd /root/ai_customer_service/ai_cs -python3 run.py -``` - -### 方式 2: 天网协作版(HTTP API) -```bash -cd /root/ai_customer_service/ai_cs -python3 run_tianwang_simple.py -``` - -### 方式 3: 后台运行 -```bash -# AI 客服 -nohup python3 run.py > /tmp/ai-cs.log 2>&1 & - -# 天网协作 -nohup python3 run_tianwang_simple.py > /tmp/tianwang.log 2>&1 & -``` - ---- - -## 📊 当前状态 - -| 模块 | 状态 | 说明 | -|------|------|------| -| **AI 客服** | ✅ 可启动 | 需要轻简软件配合 | -| **天网 HTTP API** | ✅ 运行中 | 端口 5678 | -| **任务数据库** | ✅ 正常 | SQLite | -| **任务接收** | ✅ 已测试 | POST /api/task/receive | -| **指定客户回复** | ✅ 已实现 | specified_customer_reply | - ---- - -## 🔧 修复的文件 - -1. `core/pydantic_ai_agent.py` - 删除错误的工具定义 -2. `core/websocket_client.py` - 修复循环导入 -3. `db/task_db/task_model.py` - 修复 INSERT 语句 -4. `run_tianwang_simple.py` - 新建简化启动器 - ---- - -## 📝 测试记录 - -### 天网 API 测试 -```bash -# 健康检查 -curl http://localhost:5678/api/health -# ✅ 200 OK - -# 接收任务 -curl -X POST http://localhost:5678/api/task/receive \ - -H "Content-Type: application/json" \ - -d '{"task_id": "TEST_002", ...}' -# ✅ 任务接收成功 - -# 查询任务 -curl http://localhost:5678/api/task/status/TEST_002 -# ✅ 返回任务状态 -``` - -### AI 客服测试 -```bash -python3 run.py --no-agent -# ✅ 启动成功(需要轻简软件配合) -``` - ---- - -## 📖 文档位置 - -- 部署文档:`DEPLOYMENT.md` -- 使用示例:`SPECIFIED_CUSTOMER_REPLY_EXAMPLE.md` -- 完整文档:`TIANWANG_INTEGRATION.md` - ---- - -## ⚠️ 注意事项 - -1. **AI 客服**需要轻简软件运行在`ws://127.0.0.1:9528` -2. **天网 API**独立运行,不需要轻简软件 -3. 两个系统可以同时运行,互不干扰 - diff --git a/MULTI_PROCESS.md b/MULTI_PROCESS.md deleted file mode 100644 index 46a823a..0000000 --- a/MULTI_PROCESS.md +++ /dev/null @@ -1,226 +0,0 @@ -# 多进程异步并行架构 - -## 架构说明 - -### 当前架构(改造前) - -``` -单进程 + 单事件循环 -┌─────────────────────┐ -│ Python 进程 │ -│ ┌─────────────────┐ │ -│ │ asyncio Loop │ │ -│ │ 所有客户 + Agent │ │ -│ └─────────────────┘ │ -└─────────────────────┘ -``` - -**问题**: -- ❌ 单 CPU 核心 -- ❌ 一个客户卡住影响全局 -- ❌ 无法利用多核 CPU - ---- - -### 新架构(改造后) - -``` -多进程 + 多事件循环 -┌─────────┐ ┌─────────┐ ┌─────────┐ -│进程 1 │ │进程 2 │ │进程 3 │ -│Loop + │ │Loop + │ │Loop + │ -│客户 A,B │ │客户 C,D │ │客户 E,F │ -└─────────┘ └─────────┘ └─────────┘ - ↓ ↓ ↓ - 独立运行 独立运行 独立运行 -``` - -**优势**: -- ✅ 真正的多核并行 -- ✅ 故障隔离 -- ✅ 负载均衡 -- ✅ 可动态扩缩容 - ---- - -## 使用方法 - -### 方式 1:systemd 服务(推荐) - -```bash -# 启动多进程模式 -systemctl start ai-cs-multi - -# 查看状态 -systemctl status ai-cs-multi - -# 查看日志 -journalctl -u ai-cs-multi -f - -# 停止服务 -systemctl stop ai-cs-multi -``` - -### 方式 2:命令行启动 - -```bash -cd /root/ai_customer_service/ai_cs - -# 使用默认进程数(CPU 核心数) -python3 scripts/multi_process_launcher.py - -# 指定进程数 -python3 scripts/multi_process_launcher.py --workers 4 -``` - ---- - -## 配置说明 - -### 环境变量 - -| 变量 | 说明 | 默认值 | -|------|------|--------| -| `AI_CS_WORKER_ID` | 工作进程 ID | 0 | -| `AI_CS_SHARD_KEYS` | 本进程负责的客户 key | 空 | - -### 分片算法 - -客户按 `acc_id:from_id` 的 MD5 hash 值分配到不同进程: - -```python -shard_id = int(md5(f"{acc_id}:{from_id}").hexdigest(), 16) % num_workers -``` - -**特点**: -- 同一客户始终分配到同一进程 -- 不同客户均匀分布 -- 动态增减进程时自动重新平衡 - ---- - -## 监控 - -### 查看进程状态 - -```bash -# 查看所有工作进程 -ps aux | grep ai-cs-worker - -# 查看每个进程的 CPU 使用 -top -p $(pgrep -d, -f ai-cs-worker) -``` - -### 日志查看 - -```bash -# 查看主进程日志 -journalctl -u ai-cs-multi -f - -# 查看特定工作进程日志 -journalctl -u ai-cs-multi -f | grep "Worker 2" -``` - ---- - -## 性能对比 - -| 指标 | 单进程 | 多进程 (4 核) | -|------|--------|-------------| -| **并发客户数** | ~50 | ~200 | -| **CPU 使用率** | 25% (单核) | 80% (4 核) | -| **响应延迟** | 高 | 低 | -| **故障影响** | 全局 | 局部 | - ---- - -## 注意事项 - -1. **进程数选择**: - - 默认 = CPU 核心数 - - 建议不超过 CPU 核心数 × 2 - -2. **内存占用**: - - 每个进程独立内存空间 - - 总内存 = 进程数 × 单进程内存 - -3. **日志管理**: - - 每个进程独立日志 - - 通过 `worker_id` 区分 - -4. **动态扩容**: - - 修改 `--workers` 参数 - - 重启服务自动重新分片 - ---- - -## 故障排查 - -### Worker 进程退出 - -```bash -# 查看日志 -journalctl -u ai-cs-multi -f | grep "Worker.*退出" - -# 手动重启 -systemctl restart ai-cs-multi -``` - -### 负载不均衡 - -```bash -# 查看每个进程处理的客户数 -ps aux | grep ai-cs-worker | awk '{print $2}' | while read pid; do - echo "PID $pid: $(ps -p $pid -o %cpu,%mem,cmd --no-headers)" -done -``` - -### 内存泄漏 - -```bash -# 监控内存使用 -watch -n 1 'ps aux | grep ai-cs-worker | awk "{sum+=$6} END {print \"Total: \" sum/1024 \" MB\"}"' -``` - - ---- - -## 使用 run.py 启动 - -### 单进程模式(默认) - -```bash -cd /root/ai_customer_service/ai_cs - -# 正常启动(含 AI Agent) -python3 run.py - -# 不启用 AI Agent -python3 run.py --no-agent -``` - -### 多进程模式 - -```bash -cd /root/ai_customer_service/ai_cs - -# 多进程模式(默认 CPU 核心数) -python3 run.py --multi - -# 指定进程数 -python3 run.py --multi --workers 4 -``` - -### systemd 服务 - -```bash -# 单进程模式 -systemctl start ai-cs -systemctl status ai-cs -journalctl -u ai-cs -f - -# 多进程模式 -systemctl start ai-cs-multi -systemctl status ai-cs-multi -journalctl -u ai-cs-multi -f -``` diff --git a/README.md b/README.md index 9a564ab..1d2c9bf 100755 --- a/README.md +++ b/README.md @@ -1,166 +1,82 @@ # AI 客服系统 - 天网协作版 -**版本**: v1.0 -**更新日期**: 2026-02-27 -**服务器**: 1.12.50.92 +**版本**: v1.0 | **服务器**: 1.12.50.92 --- -## 🎯 功能概览 +## 功能概览 -### 核心功能 - -1. **天网协作** - 接收天网任务,支持指定客户回复触发 -2. **三种工作流** - 根据客户说的话自动判断执行 -3. **图片任务数据库** - 任务持久化,支持后续增加需求 -4. **图绘派单系统** - 自动派单给在线设计师 -5. **文字检测加价** - 自动识别文字数量并加价(60-80 元高价值订单) -6. **风险评估** - 自动识别敏感内容,拒绝不良订单 -7. **作图失败转人工** - 失败自动转接人工客服 +| 功能 | 说明 | +|------|------| +| 天网协作 | 接收天网任务,支持指定客户回复触发 | +| 三种工作流 | 找图 / 处理图片 / 转人工派单 | +| 图片任务数据库 | 任务持久化,支持后续增加需求 | +| 图绘派单系统 | 自动派单给在线设计师 | +| 文字检测加价 | 自动识别文字数量并加价 | +| 风险评估 | 自动识别敏感内容,拒绝不良订单 | +| 作图失败转人工 | 失败自动转接人工客服 | --- -## 🚀 快速开始 - -### 启动服务 +## 快速开始 ```bash cd /root/ai_customer_service/ai_cs +pip3 install -r requirements.txt -# 启动天网协作版(推荐) -python3 run_tianwang_simple.py +# 天网协作版(仅 HTTP API) +python3 run.py --api-only -# 后台运行 -nohup python3 run_tianwang_simple.py > /tmp/tianwang.log 2>&1 & +# 完整版(HTTP API + WebSocket + AI Agent) +python3 run.py --tianwang + +# AI 客服(仅 WebSocket,默认) +python3 run.py ``` -### API 地址 - -- **天网任务接收**: `http://127.0.0.1:6060` -- **派单系统**: `http://1.12.50.92:8005` -- **图绘平台**: `http://1.12.50.92:8002` - ---- - -## 📋 三种工作流 - -### 1. 查找图片 -**触发词**: "找一下"、"找图"、"找原图" - -**回复**: "找到了!http://tuhui.cloud/works/123" - -### 2. 处理图片 -**触发词**: "做一下"、"处理一下"、"安排" - -**回复**: "稍等,我看看...好的,可以做" - -### 3. 转人工派单 -**触发词**: "做不了"、"处理不了" - -**回复**: "好的,已帮您安排设计师处理" - ---- - -## 💰 价格策略 - -### 基础价格 - -| 复杂度 | 价格 | -|--------|------| -| simple | 10-15 元 | -| normal | 15-20 元 | -| complex | 20-25 元 | -| hard | 25-30 元 | - -### 文字加价 - -| 文字数量 | 加价 | -|----------|------| -| 少量 (1-10 字) | +5 元 | -| 中量 (11-50 字) | +15 元 | -| 大量 (51-200 字) | +30 元 | -| 极多 (200 字以上) | +50 元 | - -### 高价值订单 - -**文字分层 + 大量文字** → **60-80 元** - ---- - -## 🔧 API 接口 - -### 接收天网任务 +### 后台运行 ```bash -curl -X POST http://localhost:6060/api/task/receive \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_001", - "customer": {"id": "customer_123"}, - "trigger": {"type": "customer_reply", "keyword": "好的"}, - "action": {"type": "send_message", "message": "您好"} - }' +nohup python3 run.py --api-only > /tmp/tianwang.log 2>&1 & ``` -### 查询派单队列 +### 验证 ```bash -curl -X GET "http://1.12.50.92:8005/dispatch/queue" \ - -H "X-API-Key: tuhui_dispatch_key_2026" -``` - -### 查询在线设计师 - -```bash -curl -X GET "http://1.12.50.92:8005/online/designers" \ - -H "X-API-Key: tuhui_dispatch_key_2026" +curl http://localhost:6060/api/health ``` --- -## 📖 文档 +## API 地址 -| 文档 | 说明 | +| 服务 | 地址 | |------|------| -| `项目功能汇总.md` | **完整功能说明** ⭐ | -| `DEPLOYMENT.md` | 部署文档 | -| `TIANWANG_INTEGRATION.md` | 天网协作 | -| `三种工作流功能说明.md` | 工作流 | -| `文字加价功能说明.md` | 价格策略 | -| `风险评估功能说明.md` | 风险控制 | -| `图片任务数据库功能说明.md` | 任务管理 | -| `图绘派单系统集成说明.md` | 派单系统 | +| AI 客服 API | `http://127.0.0.1:6060` | +| 派单系统 | `http://1.12.50.92:8005` | +| 图绘平台 | `http://1.12.50.92:8002` | --- -## 🎯 使用示例 +## 文档 -### 客户发送图片 - -``` -客户:找一下这个图 [图片] -AI: 找到了!http://tuhui.cloud/works/123 -``` - -``` -客户:做一下 [图片] -AI: 稍等,我看看...好的,可以做 -``` - -``` -客户:这个能做吗 [图片] -AI: 抱歉,这个我做不了 -AI: 好的,已帮您安排设计师处理 -``` +| 文档 | 内容 | +|------|------| +| **项目功能汇总.md** | 全部功能详细说明(工作流、报价、风险、派单、数据库等) | +| **部署文档.md** | 部署、API 接口、天网集成、多进程、故障排查 | --- -## 📞 技术支持 +## 项目结构 -**服务器**: 1.12.50.92 -**日志**: `/tmp/tianwang.log` -**进程**: `ps aux | grep run_tianwang` - ---- - -**所有功能已部署,系统运行正常!** 🎉 +``` +├── api/ # HTTP API 服务器 +├── core/ # 核心逻辑(Agent、工作流、WebSocket) +├── config/ # 配置文件 +├── db/ # 数据库模块 +├── image/ # 图片处理模块 +├── services/ # 外部服务集成 +├── utils/ # 工具模块 +├── skills/ # Agent 技能定义 +└── run.py # 统一入口(--api-only / --tianwang / 默认 WebSocket) +``` diff --git a/SPECIFIED_CUSTOMER_REPLY_EXAMPLE.md b/SPECIFIED_CUSTOMER_REPLY_EXAMPLE.md deleted file mode 100644 index a1882cf..0000000 --- a/SPECIFIED_CUSTOMER_REPLY_EXAMPLE.md +++ /dev/null @@ -1,239 +0,0 @@ -# 指定客户回复触发任务 - 使用示例 - -## 📋 功能说明 - -支持**指定特定客户**回复**指定内容**时触发任务。 - ---- - -## 🎯 使用场景 - -### 场景 1:只等小明说"好的"就发文件 - -**任务配置**: -```json -{ - "task_id": "TASK_001", - "type": "send_file_after_reply", - "customer": { - "id": "customer_123", - "name": "小明" - }, - "trigger": { - "type": "specified_customer_reply", - "customer_id": "customer_123", - "customer_name": "小明", - "keyword": "好的", - "exact_match": false - }, - "action": { - "type": "send_message", - "message": "这是您要的文件" - }, - "priority": "normal", - "timeout_hours": 24, - "created_by": "设计师 lz" -} -``` - -**触发逻辑**: -1. ✅ 客户 ID = `customer_123` -2. ✅ 客户名称 = `小明` -3. ✅ 消息内容包含"好的" -4. ✅ 触发任务,发送文件 - -**如果其他客户说"好的"**: -- ❌ 不触发(客户 ID 不匹配) - -**如果小明说"可以的"**: -- ❌ 不触发(关键词不匹配) - ---- - -### 场景 2:精确匹配 - 只认"已付款"三个字 - -**任务配置**: -```json -{ - "task_id": "TASK_002", - "type": "send_tutorial_after_payment", - "customer": { - "id": "customer_456", - "name": "小红" - }, - "trigger": { - "type": "specified_customer_reply", - "customer_id": "customer_456", - "customer_name": "小红", - "keyword": "已付款", - "exact_match": true - }, - "action": { - "type": "send_message", - "message": "感谢购买!这是教程..." - } -} -``` - -**触发逻辑**: -- ✅ 客户说"已付款" → 触发 -- ❌ 客户说"我已付款了" → 不触发(不是精确匹配) -- ❌ 客户说"付款了" → 不触发(关键词不匹配) - ---- - -### 场景 3:不指定客户名称,只认客户 ID - -**任务配置**: -```json -{ - "task_id": "TASK_003", - "type": "send_welcome", - "customer": { - "id": "customer_789" - }, - "trigger": { - "type": "specified_customer_reply", - "customer_id": "customer_789", - "keyword": "你好" - }, - "action": { - "type": "send_message", - "message": "欢迎光临!" - } -} -``` - -**触发逻辑**: -- ✅ 只要客户 ID 是 `customer_789`,说"你好"就触发 -- ✅ 不检查客户名称 - ---- - -## 📝 API 调用示例 - -### 1. 创建指定客户回复任务 - -```bash -curl -X POST http://localhost:5678/api/task/receive \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_20260227_001", - "type": "send_file_after_reply", - "customer": { - "id": "customer_123", - "name": "小明" - }, - "trigger": { - "type": "specified_customer_reply", - "customer_id": "customer_123", - "customer_name": "小明", - "keyword": "好的", - "exact_match": false - }, - "action": { - "type": "send_message", - "message": "这是您要的文件" - }, - "priority": "normal", - "timeout_hours": 24, - "created_by": "设计师 lz" - }' -``` - -### 2. 查询任务状态 - -```bash -curl http://localhost:5678/api/task/status/TASK_20260227_001 -``` - -### 3. 取消任务 - -```bash -curl -X POST http://localhost:5678/api/task/cancel \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_20260227_001", - "reason": "客户不需要了" - }' -``` - ---- - -## 🔧 触发条件配置说明 - -### 字段说明 - -| 字段 | 必填 | 说明 | 示例 | -|------|------|------|------| -| `type` | ✅ | 触发类型 | `specified_customer_reply` | -| `customer_id` | ✅ | 指定客户 ID | `customer_123` | -| `customer_name` | ❌ | 指定客户名称 | `小明` | -| `keyword` | ✅ | 回复关键词 | `好的` | -| `exact_match` | ❌ | 是否精确匹配 | `false`(默认) | - -### exact_match 参数 - -- `false`(默认):模糊匹配,消息**包含**关键词即可 - - 关键词:"好的" - - 匹配:"好的"、"好的谢谢"、"好的我知道了" - -- `true`:精确匹配,消息**等于**关键词 - - 关键词:"好的" - - 匹配:"好的" - - 不匹配:"好的谢谢" - ---- - -## 📊 触发流程图 - -``` -客户发送消息 - ↓ -检查任务列表 - ↓ -遍历待触发任务 - ↓ -┌─────────────────────────────┐ -│ 1. 检查客户 ID 是否匹配? │ ← specified_customer_id -└──────────┬──────────────────┘ - │ NO → 跳过此任务 - │ YES - ↓ -┌─────────────────────────────┐ -│ 2. 检查客户名称是否匹配? │ ← specified_customer_name(可选) -└──────────┬──────────────────┘ - │ NO → 跳过此任务 - │ YES - ↓ -┌─────────────────────────────┐ -│ 3. 检查回复内容是否匹配? │ ← keyword + exact_match -└──────────┬──────────────────┘ - │ NO → 跳过此任务 - │ YES - ↓ -触发任务执行 -``` - ---- - -## 🐛 常见问题 - -### Q1: 如何只监听特定客户? -**A**: 使用 `specified_customer_reply` 触发类型,配置 `customer_id` 字段。 - -### Q2: 如何精确匹配某句话? -**A**: 设置 `exact_match: true`,只有消息完全等于关键词时才触发。 - -### Q3: 可以监听多个关键词吗? -**A**: 可以,使用 `customer_keyword` 触发类型,配置 `keywords` 数组。 - -### Q4: 任务超时后会自动取消吗? -**A**: 是的,默认 24 小时超时,可在任务配置中设置 `timeout_hours`。 - ---- - -## 📖 完整文档 - -查看:`/root/ai_customer_service/ai_cs/TIANWANG_INTEGRATION.md` - diff --git a/TIANWANG_INTEGRATION.md b/TIANWANG_INTEGRATION.md deleted file mode 100644 index a165ae1..0000000 --- a/TIANWANG_INTEGRATION.md +++ /dev/null @@ -1,402 +0,0 @@ -# 天网 AI 客服协作系统 - 完整文档 - -## 📋 项目概述 - -**目标**: 实现天网(任务调度中心)与 AI 客服的协作,支持设计师在群里下发任务,AI 客服自动执行。 - -**协作流程**: -``` -设计师(群里) → 天网(任务分析) → AI 客服(执行) - ↑ ↓ - └←←←←←← 结果反馈 ←←←←←←←←←←┘ -``` - ---- - -## 🚀 已实现的功能 - -### 1️⃣ 任务接收接口 ✅ - -#### API 端点 -``` -POST /api/task/receive # 接收天网下发的任务 -POST /api/task/cancel # 取消任务 -GET /api/task/status/:id # 查询任务状态 -GET /api/task/list # 任务列表 -GET /api/health # 健康检查 -``` - -#### 任务数据结构 -```json -{ - "task_id": "TASK_20260226_001", - "type": "send_file_after_reply", - "customer": { - "name": "小明", - "id": "customer_123" - }, - "trigger": { - "type": "customer_reply", - "keyword": "好的" - }, - "action": { - "type": "send_file", - "file_url": "https://xxx.com/file.zip", - "message": "这是您要的文件" - }, - "priority": "normal", - "timeout_hours": 24, - "created_by": "设计师 lz" -} -``` - -#### 返回格式 -```json -{ - "code": 200, - "message": "任务接收成功", - "data": { - "task_id": "TASK_20260226_001", - "status": "pending" - } -} -``` - ---- - -### 2️⃣ 任务调度系统 ✅ - -#### 任务状态流转 -``` -pending → waiting → running → completed - ↓ - failed -``` - -#### 功能特性 -- ✅ 优先级队列(urgent > high > normal) -- ✅ 超时处理(默认 24 小时) -- ✅ 自动重试(最多 3 次) -- ✅ 状态持久化(SQLite) -- ✅ 天网回调通知 - ---- - -### 3️⃣ 条件监听增强 ✅ - -#### 触发条件类型 -```python -TRIGGER_TYPES = { - "customer_reply": "客户回复指定内容", - "customer_keyword": "客户说某关键词", - "customer_payment": "客户付款", - "customer_order": "客户下单", - "time_reach": "到达指定时间", - "custom_event": "自定义事件" -} -``` - -#### 触发流程 -``` -收到客户消息 - ↓ -检查是否有匹配的任务 - ↓ -触发任务执行 - ↓ -自动回复客户 -``` - ---- - -### 4️⃣ 文件/链接发送功能 🔄 - -- ✅ 消息发送(已实现) -- 🔄 文件发送(待接入轻简 API) -- 🔄 链接发送(已实现为基础消息) - ---- - -## 📁 项目结构 - -``` -ai_customer_service/ai_cs/ -├── db/task_db/ -│ ├── task_model.py # 任务数据模型 -│ └── tasks.db # SQLite 数据库 -├── core/ -│ ├── task_scheduler.py # 任务调度器 -│ └── websocket_client.py # WebSocket 客户端(已增强) -├── api/ -│ └── http_server.py # HTTP API 服务器 -├── scripts/ -│ └── multi_process_launcher.py -├── run_with_tianwang.py # 天网协作版启动器 -└── TIANWANG_INTEGRATION.md # 本文档 -``` - ---- - -## 🚀 使用方法 - -### 启动天网协作版 - -```bash -cd /root/ai_customer_service/ai_cs - -# 方式 1:使用专用启动器 -python3 run_with_tianwang.py - -# 方式 2:使用 run.py(未来整合) -python3 run.py --tianwang -``` - -### API 调用示例 - -#### 1. 接收任务 -```bash -curl -X POST http://localhost:5678/api/task/receive \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_20260226_001", - "type": "send_file_after_reply", - "customer": { - "name": "小明", - "id": "customer_123" - }, - "trigger": { - "type": "customer_reply", - "keyword": "好的" - }, - "action": { - "type": "send_message", - "message": "这是您要的文件" - }, - "priority": "normal", - "timeout_hours": 24, - "created_by": "设计师 lz" - }' -``` - -#### 2. 查询任务状态 -```bash -curl http://localhost:5678/api/task/status/TASK_20260226_001 -``` - -#### 3. 取消任务 -```bash -curl -X POST http://localhost:5678/api/task/cancel \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_20260226_001", - "reason": "客户已退款" - }' -``` - ---- - -## 📊 数据库表结构 - -```sql -CREATE TABLE tasks ( - task_id TEXT PRIMARY KEY, - type TEXT NOT NULL, - customer_name TEXT, - customer_id TEXT, - trigger_type TEXT, - trigger_keyword TEXT, - trigger_keywords TEXT, -- JSON array - action_type TEXT, - action_file_url TEXT, - action_message TEXT, - priority TEXT DEFAULT 'normal', - timeout_hours INTEGER DEFAULT 24, - status TEXT DEFAULT 'pending', - retry_count INTEGER DEFAULT 0, - max_retry INTEGER DEFAULT 3, - created_at TEXT, - created_by TEXT, - triggered_at TEXT, - completed_at TEXT, - error_message TEXT, - result TEXT -- JSON -) -``` - ---- - -## 🔧 配置说明 - -### 环境变量 - -| 变量 | 说明 | 默认值 | -|------|------|--------| -| `HTTP_API_HOST` | HTTP 服务器地址 | 0.0.0.0 | -| `HTTP_API_PORT` | HTTP 服务器端口 | 5678 | -| `TASK_DB_PATH` | 任务数据库路径 | db/task_db/tasks.db | - -### 天网回调配置 - -在 `core/task_scheduler.py` 中配置天网回调 URL: - -```python -async def _notify_tianwang(self, task_id: str, status: str, error: str = None): - async with httpx.AsyncClient() as client: - await client.post( - 'http://tianwang-server/api/task/callback', # ← 修改这里 - json={ - 'task_id': task_id, - 'status': status, - 'error': error, - 'timestamp': datetime.now().isoformat() - } - ) -``` - ---- - -## 📝 使用场景示例 - -### 场景 1:客户说"好的"后发送文件 - -**天网下发任务**: -```json -{ - "task_id": "TASK_001", - "type": "send_file_after_reply", - "customer": {"id": "customer_123"}, - "trigger": {"type": "customer_reply", "keyword": "好的"}, - "action": { - "type": "send_file", - "file_url": "https://xxx.com/file.zip", - "message": "这是您要的文件" - } -} -``` - -**流程**: -1. 客户在群里说"好的" -2. AI 客服检测到匹配任务 -3. 自动发送文件给客户 -4. 任务标记为 completed - ---- - -### 场景 2:客户付款后发送教程 - -**天网下发任务**: -```json -{ - "task_id": "TASK_002", - "type": "send_tutorial_after_payment", - "customer": {"id": "customer_456"}, - "trigger": {"type": "customer_payment"}, - "action": { - "type": "send_message", - "message": "感谢购买!这是使用教程:..." - } -} -``` - ---- - -### 场景 3:定时发送问候 - -**天网下发任务**: -```json -{ - "task_id": "TASK_003", - "type": "send_greeting", - "customer": {"id": "customer_789"}, - "trigger": {"type": "time_reach", "time": "2026-02-27 09:00:00"}, - "action": { - "type": "send_message", - "message": "早上好!有什么可以帮您?" - } -} -``` - ---- - -## 🔍 监控与日志 - -### 查看任务日志 -```bash -journalctl -u ai-cs -f | grep "任务" -``` - -### 查询数据库 -```bash -sqlite3 /root/ai_customer_service/ai_cs/db/task_db/tasks.db - -# 查看所有任务 -SELECT task_id, status, created_at FROM tasks ORDER BY created_at DESC LIMIT 10; - -# 查看失败任务 -SELECT task_id, error_message FROM tasks WHERE status='failed'; -``` - -### 统计信息 -```bash -curl http://localhost:5678/api/task/stats -``` - ---- - -## ⚠️ 注意事项 - -1. **HTTP 端口**: 默认 5678,确保端口未被占用 -2. **数据库路径**: 确保有写权限 -3. **天网回调**: 需要配置正确的回调 URL -4. **任务超时**: 默认 24 小时,根据业务需求调整 -5. **重试次数**: 默认 3 次,避免无限重试 - ---- - -## 🐛 故障排查 - -### 任务未触发 -```bash -# 1. 检查任务状态 -curl http://localhost:5678/api/task/status/TASK_ID - -# 2. 查看日志 -journalctl -u ai-cs -f | grep "任务触发" - -# 3. 检查触发条件是否匹配 -# 确认客户消息包含触发关键词 -``` - -### HTTP API 无法访问 -```bash -# 1. 检查端口 -netstat -tlnp | grep 5678 - -# 2. 检查防火墙 -firewall-cmd --list-ports - -# 3. 重启服务 -systemctl restart ai-cs -``` - -### 任务执行失败 -```bash -# 1. 查看错误信息 -curl http://localhost:5678/api/task/status/TASK_ID - -# 2. 查看日志 -journalctl -u ai-cs -f | grep "任务执行失败" - -# 3. 手动重试 -# 取消任务后重新下发 -``` - ---- - -## 📞 技术支持 - -如有问题请联系: -- 文档:`/root/ai_customer_service/ai_cs/TIANWANG_INTEGRATION.md` -- 日志:`journalctl -u ai-cs -f` -- 数据库:`/root/ai_customer_service/ai_cs/db/task_db/tasks.db` - diff --git a/archive/README.md b/archive/README.md deleted file mode 100755 index 212da0a..0000000 --- a/archive/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# 归档文件 - -此目录存放暂未使用或已替代的旧文件,主流程不依赖。 - -| 文件 | 说明 | -|------|------| -| service_meitu.py | 美图服务(未接入) | -| service_vectorizer.py | 矢量化服务(未接入) | -| view_chats.py | 旧版聊天查看(已由 chat_log_viewer 替代) | -| test_import.py | 导入测试 | -| test_battle.py | 压价话术测试 | -| viewer_out.txt | 临时输出 | diff --git a/archive/test_battle.py b/archive/test_battle.py deleted file mode 100755 index 2c29db2..0000000 --- a/archive/test_battle.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -AI 左右互搏测试工具 - -客服AI(真实) vs 买家AI(模拟) -用来调试客服AI的回复质量,不需要真实客户 -""" -import asyncio -import os -import random -import httpx -from openai import AsyncOpenAI -from dotenv import load_dotenv -from pydantic_ai_agent import CustomerServiceAgent, CustomerMessage - -load_dotenv() - -WECHAT_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=cc88bdef-a13f-4d7e-bdb6-ee51b68b8205" - - -async def notify_wechat(content: str): - """发送企业微信机器人通知""" - try: - async with httpx.AsyncClient(timeout=10) as client: - await client.post(WECHAT_WEBHOOK, json={ - "msgtype": "text", - "text": {"content": content} - }) - except Exception as e: - print(f"[通知] 企业微信发送失败: {e}") - -# ========== 颜色输出 ========== -RESET = "\033[0m" -BLUE = "\033[94m" # 买家 -GREEN = "\033[92m" # 客服 -YELLOW = "\033[93m" # 系统提示 -GRAY = "\033[90m" # 分隔线 - - -def print_buyer(msg): - print(f"{BLUE}【买家】{msg}{RESET}") - -def print_shop(msg): - print(f"{GREEN}【客服】{msg}{RESET}") - -def print_system(msg): - print(f"{YELLOW}{msg}{RESET}") - -def print_sep(): - print(f"{GRAY}{'─' * 50}{RESET}") - - -# ========== 买家人设配置 ========== -BUYER_PERSONAS = { - "普通买家": """你是一个普通淘宝买家,想找一张图的高清版本。 -行为:先打招呼,发图问价,可能小砍一次价,觉得合适就说要拍了。 -说话简短自然,像手机上发消息,1-2句话。不要太正式。""", - - "砍价客": """你是个爱砍价的淘宝买家,总想便宜一点。 -行为:问价后嫌贵,砍价2-3次,最后可能接受也可能走人。 -说话直接,喜欢说"能不能便宜点""太贵了""别家更便宜"。""", - - "爱问细节": """你是个很谨慎的买家,买东西前喜欢问清楚。 -行为:先问在不在,问能不能找到,问格式,问效果,问不满意能退吗,最后才考虑下单。 -说话有点啰嗦,喜欢多问几个问题。""", - - "快节奏": """你是个很忙的买家,说话简短,不废话。 -行为:直接发图问多少钱,价格合适立刻说拍了,不合适直接走。 -每次只说3-5个字,极度简短。""", - - "售后投诉": """你是个已经付款的买家,但对收到的图片不满意,想要退款或重做。 -行为:先说拿到图了但效果不行,描述哪里不满意(模糊/颜色不对/尺寸不够), -要求重做或者退款,态度有点不耐烦,反复追问进度。 -说话带点情绪,但不至于骂人,就是那种"花钱了结果不行"的委屈感。""", - - "多图打包": """你是个需要批量处理图片的买家,手头有好几张图要找高清版。 -行为:先问在不在,然后说有多张图要处理,询问能不能打包便宜点, -逐步发图或询问价格,跟客服商量总价,最终决定下单还是再想想。 -说话随意,像在商量事情,不太在意每张的价格,更关注总价合不合适。""", - - "大批量砍价": """你是个手头有十几张图要处理的买家,量大,觉得自己有底气砍价。 -行为:上来就说"我有很多图,你们量大能便宜吗", -告知大概数量(10-20张),用量大作为筹码反复压价, -问"做完这批还有下一批,能不能给个长期价""别家给我打6折了", -如果客服给的总价还算合理就下单,否则拉锯几个来回。 -说话有点强势,觉得自己是大客户,应该享受优惠。""", - - "要分层PSD": """你是个做设计的买家,需要带分层的PSD格式,不是普通jpg。 -行为:先问在不在,发图后问"这个能出PSD吗""要分层的那种", -追问能不能保留图层、格式是不是真的PSD,听到价格后可能嫌贵问能不能便宜, -最终决定下单或者放弃。 -说话带点专业感,会说"图层""分层""PSD""源文件"之类的词。""", - - "问尺寸分辨率": """你是个有印刷需求的买家,非常在意图片的尺寸和分辨率。 -行为:发图后先不问价,反复问"这个能做多大""分辨率能到300dpi吗""印出来会不会模糊", -问完尺寸再问价格,如果客服说能满足需求就考虑下单,否则犹豫很久。 -说话里经常出现"厘米""像素""dpi""印刷""大图"。""", - - "问颜色效果": """你是个对颜色很挑剔的买家,担心高清版颜色和原图有色差。 -行为:发图后问"颜色会变吗""饱和度能保持吗""跟原图一样的色调吧", -让客服保证颜色效果,追问不满意能不能改,反复确认才肯下单。 -说话谨慎,总要客服给"保证",但说话不凶,就是很在意品质。""", - - "来源质疑": """你是个对店铺服务有疑虑的买家,总觉得有猫腻,想搞清楚原理。 -行为:问"你们是怎么找的""原图是从哪里来的""是AI弄的吗还是真的原图", -追问来源和方法,对客服的回答将信将疑,继续追问, -最终可能被说服下单,也可能觉得不靠谱走人。 -说话带点怀疑,喜欢反问,但不是来找茬的,是真的想搞清楚。""", -} - -# 用于测试的图片URL -TEST_IMAGE_URLS = [ - "https://img.alicdn.com/imgextra/i1/O1CN01OilxfD1kr8tM4Ugg2_!!4611686018427387680-0-amp.jpg", -] - - -class BuyerAgent: - """模拟买家的AI""" - - def __init__(self, persona_name: str = "普通买家"): - self.client = AsyncOpenAI( - api_key=os.getenv("OPENAI_API_KEY"), - base_url=os.getenv("OPENAI_BASE_URL"), - ) - self.model = os.getenv("OPENAI_MODEL", "gpt-4o-mini") - self.persona_name = persona_name - self.persona = BUYER_PERSONAS.get(persona_name, BUYER_PERSONAS["普通买家"]) - self.history = [] - self.image_sent = False - self.rounds = 0 - - async def next_message(self, shop_reply: str = None) -> str: - """根据客服回复,生成下一条买家消息""" - self.rounds += 1 - - if shop_reply: - self.history.append({"role": "assistant", "content": f"客服说:{shop_reply}"}) - - # 第一轮:打招呼或直接发问 - if self.rounds == 1: - first_msgs = ["在不在", "在吗", "你好", "有人吗", "亲在吗"] - msg = random.choice(first_msgs) - self.history.append({"role": "user", "content": msg}) - return msg - - # 第二轮:发图+问有没有 - if self.rounds == 2 and not self.image_sent: - self.image_sent = True - url = random.choice(TEST_IMAGE_URLS) - msg = f"有吗#*#{url}" - self.history.append({"role": "user", "content": msg}) - return msg - - # 后续:让AI根据对话历史自由生成 - system = f"""{self.persona} - -你正在和一家找图店的客服聊天,对话历史如下。 -请根据你的人设生成下一条消息,简短自然,像真人发消息。 -如果对话已经快结束(超过10轮),可以说"好的拍了"或"算了不要了"结束对话。 -只输出你要发的消息内容,不要加任何前缀说明。""" - - resp = await self.client.chat.completions.create( - model=self.model, - messages=[ - {"role": "system", "content": system}, - *self.history, - {"role": "user", "content": "(请生成你的下一条消息)"} - ], - max_tokens=80, - temperature=0.9, - ) - msg = resp.choices[0].message.content.strip() - self.history.append({"role": "user", "content": msg}) - return msg - - -async def run_battle(persona_name: str = "普通买家", max_rounds: int = 8): - """运行一场对话模拟""" - - print_sep() - print_system(f" 开始模拟 | 买家人设:{persona_name} | 最多 {max_rounds} 轮") - print_sep() - - # 初始化双方 - buyer = BuyerAgent(persona_name) - shop = CustomerServiceAgent() - - buyer_id = "test_buyer_001" - shop_id = "test_shop_001" - - shop_reply = None - - for i in range(max_rounds): - # 买家发消息 - buyer_msg = await buyer.next_message(shop_reply) - print_buyer(buyer_msg) - - # 判断对话是否结束 - end_words = ["算了", "不要了", "不买了", "拍了", "好的拍了", "下单了", "谢谢"] - if any(w in buyer_msg for w in end_words): - print_system(f"\n 对话结束(第 {i+1} 轮)") - break - - # 构建消息对象 - customer_msg = CustomerMessage( - msg_id=f"test_{i}", - acc_id=shop_id, - msg=buyer_msg, - from_id=buyer_id, - from_name="测试买家", - cy_id=buyer_id, - acc_type="AliWorkbench", - msg_type=0, - cy_name="测试买家", - goods_name="模糊图清晰处理专业代找原图素材淘宝图片找图修复服务", - ) - - # 含图片URL时先打印等待语(模拟真实客服的即时回应) - if "#*#" in buyer_msg or (buyer_msg.startswith(("http://", "https://")) and any( - h in buyer_msg for h in ("alicdn.com", "imgextra", ".jpg", ".jpeg", ".png") - )): - print_shop("我找一下看看") - print() - - # 客服AI处理 - response = await shop.process_message(customer_msg) - - if response.need_transfer: - print_shop("[转人工]") - print_system("\n 客服决定转人工,对话结束") - break - - shop_reply = response.reply if response.should_reply else None - - # 过滤 AI 误输出的内部独白(与生产环境保持一致) - nonsense_patterns = [ - "无需", "流程已完成", "不需要回复", "无需额外", "已完成", - "无需回复", "不需要额外", "已经完成", "无需再", "操作已完成", - "任务完成", "流程完成", "记录完成", "报价已", - ] - if shop_reply and any(p in shop_reply for p in nonsense_patterns): - print_system(f" [已拦截无效回复: {shop_reply}]") - shop_reply = None - - if shop_reply: - print_shop(shop_reply) - else: - print_system(" (客服决定不回复)") - shop_reply = "" - - print() - await asyncio.sleep(0.3) - - print_sep() - print_system(" 模拟结束") - print_sep() - - -async def main(): - import sys - - personas = list(BUYER_PERSONAS.keys()) - - print(f"\n{YELLOW}{'=' * 50}") - print(" AI 左右互搏测试工具") - print(f"{'=' * 50}{RESET}") - print(f"{GRAY}用法: python test_battle.py [人设编号|all]") - print(f" 1=普通买家 2=砍价客 3=爱问细节 4=快节奏") - print(f" 5=售后投诉 6=多图打包 7=大批量砍价 8=要分层PSD") - print(f" 9=问尺寸分辨率 10=问颜色效果 11=来源质疑 0=全部{RESET}\n") - - arg = sys.argv[1] if len(sys.argv) > 1 else "1" - - if arg == "0" or arg == "all": - for persona in personas: - await run_battle(persona, max_rounds=14) - print() - await asyncio.sleep(1) - else: - try: - idx = int(arg) - 1 - persona = personas[idx] if 0 <= idx < len(personas) else personas[0] - except ValueError: - persona = personas[0] - await run_battle(persona, max_rounds=14) - - -if __name__ == "__main__": - try: - asyncio.run(main()) - except Exception as e: - err_str = str(e) - if "AccountOverdueError" in err_str or "overdue" in err_str.lower(): - msg = "⚠️ 火山引擎 API 欠费,请立即充值!\n地址:https://console.volcengine.com/ark" - else: - msg = f"⚠️ 测试脚本异常退出:{err_str[:200]}" - print(f"\n[通知] {msg}") - asyncio.run(notify_wechat(msg)) - raise diff --git a/archive/test_import.py b/archive/test_import.py deleted file mode 100755 index ecb4230..0000000 --- a/archive/test_import.py +++ /dev/null @@ -1,13 +0,0 @@ -import traceback - -try: - from pydantic_ai_agent import CustomerServiceAgent, CustomerMessage - print("✓ Agent 模块导入成功") - - # 尝试初始化 - agent = CustomerServiceAgent() - print("✓ Agent 初始化成功") - -except Exception as e: - print(f"✗ 导入或初始化失败: {e}") - traceback.print_exc() diff --git a/archive/view_chats.py b/archive/view_chats.py deleted file mode 100755 index f7e7acb..0000000 --- a/archive/view_chats.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -聊天记录查看工具 - -用法: - python view_chats.py # 列出所有客户 - python view_chats.py <客户ID> # 查看某客户的全部对话 - python view_chats.py <客户ID> --today # 只看今天 - python view_chats.py --search <关键词> # 全文搜索 -""" - -import sys -import argparse -from chat_log_db import get_customers, get_conversation, get_conversation_today, search_messages - -# ANSI 颜色 -GREEN = "\033[92m" -BLUE = "\033[94m" -YELLOW = "\033[93m" -GRAY = "\033[90m" -RESET = "\033[0m" -BOLD = "\033[1m" - - -def list_customers(): - customers = get_customers(limit=200) - if not customers: - print("暂无聊天记录") - return - - print(f"\n{BOLD}{'='*60}{RESET}") - print(f"{BOLD} 客户聊天记录总览 共 {len(customers)} 位客户{RESET}") - print(f"{BOLD}{'='*60}{RESET}") - print(f" {'客户ID':<22} {'昵称':<12} {'平台':<15} {'消息数':>6} {'最后联系'}") - print(f" {'-'*22} {'-'*12} {'-'*15} {'-'*6} {'-'*19}") - - for c in customers: - cid = c["customer_id"][:20] - name = (c["customer_name"] or "未知")[:10] - plat = (c["platform"] or "未知")[:13] - total = c["total_msgs"] - last = c["last_time"][:16] - print(f" {YELLOW}{cid:<22}{RESET} {name:<12} {GRAY}{plat:<15}{RESET} {total:>6}条 {last}") - - print(f"\n{GRAY}查看某客户对话:python view_chats.py <客户ID>{RESET}\n") - - -def show_conversation(customer_id: str, today_only: bool = False): - if today_only: - records = get_conversation_today(customer_id) - label = "今日对话" - else: - records = get_conversation(customer_id, limit=300) - label = "全部对话" - - if not records: - print(f" {GRAY}暂无记录{RESET}") - return - - print(f"\n{BOLD}{'='*60}{RESET}") - print(f"{BOLD} 客户:{customer_id} {label} 共 {len(records)} 条{RESET}") - print(f"{BOLD}{'='*60}{RESET}\n") - - last_date = "" - for r in records: - ts = r["timestamp"] - date = ts[:10] - time = ts[11:16] - msg = r["message"] - direction = r["direction"] - - if date != last_date: - print(f" {GRAY}── {date} ──────────────────────────────{RESET}") - last_date = date - - if direction == "in": - # 客户消息,左对齐蓝色 - print(f" {GRAY}{time}{RESET} {BLUE}【客户】{RESET} {msg}") - else: - # 客服回复,右对齐绿色 - print(f" {GRAY}{time}{RESET} {GREEN}【客服】{RESET} {msg}") - - print() - - -def show_search(keyword: str): - results = search_messages(keyword, limit=50) - if not results: - print(f" 未找到包含"{keyword}"的消息") - return - - print(f"\n{BOLD}搜索:"{keyword}" 共 {len(results)} 条结果{RESET}\n") - for r in results: - direction = "客户" if r["direction"] == "in" else "客服" - color = BLUE if r["direction"] == "in" else GREEN - print(f" {GRAY}{r['timestamp'][:16]}{RESET} {YELLOW}{r['customer_id'][:20]}{RESET} {color}[{direction}]{RESET} {r['message']}") - print() - - -def main(): - parser = argparse.ArgumentParser( - description="查看按用户分开的聊天记录", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=__doc__, - ) - parser.add_argument("customer_id", nargs="?", help="客户ID(不填则列出所有客户)") - parser.add_argument("--today", action="store_true", help="只显示今天的对话") - parser.add_argument("--search", metavar="关键词", help="全文搜索所有消息") - args = parser.parse_args() - - if args.search: - show_search(args.search) - elif args.customer_id: - show_conversation(args.customer_id, today_only=args.today) - else: - list_customers() - - -if __name__ == "__main__": - main() diff --git a/archive/viewer_out.txt b/archive/viewer_out.txt deleted file mode 100755 index 934faea..0000000 --- a/archive/viewer_out.txt +++ /dev/null @@ -1,24 +0,0 @@ - -──────────────────────────────────────────────────────────── - 对话记录 test_user_001 (4 条) -──────────────────────────────────────────────────────────── - - ──────────────────── 2026-02-25 ──────────────────── - 17:44 买家 -  你好,想问下图片处理多少钱  - - 17:44 客服 -python : Traceback (most recent call last): -所在位置 C:\Users\jimi\AppData\Local\Temp\ps-script-7c6a5466-18ca-4772-ac44-a07a3d10df05.ps1:75 字符: 19 -+ ... d d:\Terminator; python chat_log_viewer.py test_user_001 2>&1 | Out-F ... -+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - + CategoryInfo : NotSpecified: (Traceback (most recent call last)::String) [], RemoteException - + FullyQualifiedErrorId : NativeCommandError - - File "D:\Terminator\chat_log_viewer.py", line 233, in - cmd_show_conversation(args[0]) - File "D:\Terminator\chat_log_viewer.py", line 127, in cmd_show_conversation - print_bubble(m["direction"], m["message"], ts) - File "D:\Terminator\chat_log_viewer.py", line 80, in print_bubble - print(f" {GREEN}\u25b6 {line}{RESET}") -UnicodeEncodeError: 'gbk' codec can't encode character '\u25b6' in position 9: illegal multibyte sequence diff --git a/config/.api_cost.json b/config/.api_cost.json index 94ab296..49eab42 100755 --- a/config/.api_cost.json +++ b/config/.api_cost.json @@ -1,9 +1,10 @@ { "daily": { "2026-02-26": 1.4850000000000005, - "2026-02-27": 1.3200000000000007 + "2026-02-27": 1.3200000000000007, + "2026-02-28": 0.25999999999999995 }, "monthly": { - "2026-02": 2.8050000000000015 + "2026-02": 3.0650000000000017 } } \ No newline at end of file diff --git a/config/DESIGNER_ROSTER_API.md b/config/DESIGNER_ROSTER_API.md deleted file mode 100755 index 744eab6..0000000 --- a/config/DESIGNER_ROSTER_API.md +++ /dev/null @@ -1,10 +0,0 @@ -# 设计师在线查询 API(本端接入) - -- **地址**:`.env` 中 `DESIGNER_ROSTER_API`,如 `http://huichang.online:8001/online` -- **方法**:GET -- **返回**:`{online_users: ["lz", "ZuoWei"], ...}`,本端用 `online_users` 同步本地后派单 - -## 调用时机 - -- **转人工时**按需 GET 一次,不轮询 -- 无人在线时:回退到 `transfer_groups.json` 静态配置,并发送企微「谁在线啊」提醒 diff --git a/config/DESIGNER_ROSTER_需求-给另一台AI.md b/config/DESIGNER_ROSTER_需求-给另一台AI.md deleted file mode 100755 index 0f917e4..0000000 --- a/config/DESIGNER_ROSTER_需求-给另一台AI.md +++ /dev/null @@ -1,33 +0,0 @@ -# 设计师在线查询服务 - 需求(给另一台 AI) - -## 你要做的 - -建库,从企微群解析「上线」「下线」消息并存储,提供 **GET 接口** 返回当前在线设计师名单。 - -## 接口 - -- **方法**:GET -- **路径**:如 `/online` - -## 返回格式(本端已适配) - -```json -{ - "online_count": 2, - "online_users": ["lz", "ZuoWei"], - "update_time": "2026-02-26 16:30:00" -} -``` - -| 字段 | 必填 | 说明 | -|------|------|------| -| online_users | 是 | 当前在线设计师名单,对应 init_designer_roster 的 wechat_user_id | - -## 你这边的逻辑 - -1. 企微群消息 → 解析「上线」/「下线」→ 存库 -2. 接口从库查当前在线名单,按 `online_users` 返回 - -## 调用方 - -本端在**转人工时**按需 GET 一次,用 `online_users` 同步本地后派单。无人在线时发企微「谁在线啊」。 diff --git a/config/README.md b/config/README.md index 5fb77c7..455ca9d 100755 --- a/config/README.md +++ b/config/README.md @@ -1,4 +1,4 @@ -# 配置文件 +# 配置文件说明 ## transfer_groups.json @@ -18,19 +18,47 @@ --- -## 设计师派单(SQLite,可选) +## 设计师派单(SQLite) 同一设计师在不同店铺对应不同 group_id,转人工时按需查询在线状态,从在线设计师中轮询派单。 -**初始化数据**: +### 数据库 + +路径: `db/designer_roster_db/roster.db` + +| 表 | 说明 | +|---|------| +| `designers` | 设计师(name, wechat_user_id)| +| `designer_shops` | 设计师在某店铺的 group_id(同一人不同店铺不同分组)| +| `designer_online` | 在线状态(转人工时按需查询外部 API 同步)| + +### 初始化数据 + ```bash python scripts/init_designer_roster.py example # 写入示例 python scripts/init_designer_roster.py list # 查看当前数据 ``` -**数据库**:`db/designer_roster_db/roster.db` -- `designers`:设计师(name, wechat_user_id) -- `designer_shops`:设计师在某店铺的 group_id(同一人不同店铺不同分组) -- `designer_online`:在线状态(转人工时按需查询外部 API 同步) +### 在线查询 API -**接入**:`.env` 配置 `DESIGNER_ROSTER_API`(如 `http://xxx/online`),转人工时 GET 一次,用 `online_users` 同步。无人在线时发企微「谁在线啊」。 +`.env` 中配置 `DESIGNER_ROSTER_API`(如 `http://huichang.online:8001/online`)。 + +**接口**: GET,返回格式: + +```json +{ + "online_count": 2, + "online_users": ["lz", "ZuoWei"], + "update_time": "2026-02-26 16:30:00" +} +``` + +| 字段 | 必填 | 说明 | +|------|------|------| +| `online_users` | 是 | 当前在线设计师名单,对应 `wechat_user_id` | + +**调用时机**: 转人工时按需 GET 一次,不轮询。无人在线时回退到 `transfer_groups.json` 静态配置,并发企微「谁在线啊」提醒。 + +### 对接方要求(外部 AI 服务) + +对端需实现:企微群消息 → 解析「上线」/「下线」→ 存库 → 提供 GET `/online` 接口,按上述格式返回在线名单。 diff --git a/core/pydantic_ai_agent.py b/core/pydantic_ai_agent.py index 78099ca..477c656 100755 --- a/core/pydantic_ai_agent.py +++ b/core/pydantic_ai_agent.py @@ -19,6 +19,8 @@ from dotenv import load_dotenv load_dotenv() from services.service_tuhui_upload import upload_to_tuhui +from core.workflow_router import get_workflow_router +from core.workflow_router import get_workflow_router # ========== 企业微信通知 ========== _WECHAT_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=cc88bdef-a13f-4d7e-bdb6-ee51b68b8205" @@ -200,9 +202,9 @@ class CustomerServiceAgent: system_prompt=self._get_similar_prompt() ) # 工作流程路由器 -self.workflow_router = get_workflow_router() + self.workflow_router = get_workflow_router() -self.agent_order = Agent( + self.agent_order = Agent( model=model, deps_type=AgentDeps, system_prompt=self._get_order_prompt() @@ -260,7 +262,6 @@ self.agent_order = Agent( # 在 workflow 里创建待处理任务(付款后自动触发 Gemini) try: from core.workflow import workflow -from core.workflow_router import get_workflow_router await workflow.image_analysis_result( customer_id=ctx.deps.from_id, image_url=image_url, @@ -455,21 +456,20 @@ from core.workflow_router import get_workflow_router @self.agent.tool async def process_image_gemini(ctx: RunContext[AgentDeps], customer_id: str = "") -> str: + """ + 触发 Gemini 作图处理。客户付款后或说「安排一下」「处理一下」时调用。 + 会从客户档案读取上次发图的 URL 和处理参数(提示词、比例、透视),启动 Gemini 流程。 + 处理完成后会自动发图给客户。 + """ try: from config.config import IMAGE_MODULE_ENABLED if not IMAGE_MODULE_ENABLED: return "现在处理模块暂时暂停,先不自动作图" except Exception: return "现在处理模块暂时暂停,先不自动作图" - """ - 触发 Gemini 作图处理。客户付款后或说「安排一下」「处理一下」时调用。 - 会从客户档案读取上次发图的 URL 和处理参数(提示词、比例、透视),启动 Gemini 流程。 - 处理完成后会自动发图给客户。 - """ cid = customer_id or ctx.deps.from_id try: from core.workflow import workflow -from core.workflow_router import get_workflow_router ok = await workflow.trigger_processing_on_payment( customer_id=cid, acc_id=ctx.deps.acc_id, @@ -518,26 +518,8 @@ from core.workflow_router import get_workflow_router @self.agent_processing.tool async def process_image_gemini_run(ctx: RunContext[AgentDeps], customer_id: str = "") -> str: - try: - from config.config import IMAGE_MODULE_ENABLED - if not IMAGE_MODULE_ENABLED: - return "现在处理模块暂时暂停" - except Exception: - return "现在处理模块暂时暂停" - cid = customer_id or ctx.deps.from_id - try: - from core.workflow import workflow -from core.workflow_router import get_workflow_router - ok = await workflow.trigger_processing_on_payment( - customer_id=cid, - acc_id=ctx.deps.acc_id, - acc_type=ctx.deps.platform, - ) - if ok: - return "已安排" - return "暂无待处理图片" - except Exception as e: - return f"触发作图失败: {e}" + """触发 Gemini 作图处理(processing agent 专用入口)。""" + return await process_image_gemini(ctx, customer_id) @self.agent_similar.tool async def recommend_similar(ctx: RunContext[AgentDeps], hint: str = "") -> str: @@ -986,7 +968,7 @@ from core.workflow_router import get_workflow_router - 输出不超过1句话""" def _get_customer_profile_context(self, customer_id: str) -> str: - """从数据库读取客户画像,注入给 AI。含个性化语气、报价策略、主动预测。""" + """从数据库读取客户画像,注入给 AI。含个性化语气、报价策略、主动预测、近期对话。""" try: from db.customer_db import db profile = db.get_customer(customer_id) @@ -994,41 +976,95 @@ from core.workflow_router import get_workflow_router if profile.blacklist: return f"【⚠️黑名单客户】原因:{profile.blacklist_reason or '已标记'},请转接人工处理,不要自动回复" - parts = [] + lines = [] + lines.append("=== 客户档案 ===") + + # 基础信息 + basic_info = [] + basic_info.append(f"客户ID: {customer_id}") + basic_info.append(f"姓名: {profile.name or '未知'}") if profile.email: - parts.append(f"邮箱:{profile.email}") + basic_info.append(f"邮箱: {profile.email}") if profile.phone: - parts.append(f"电话:{profile.phone}") + basic_info.append(f"电话: {profile.phone}") if profile.wechat: - parts.append(f"微信:{profile.wechat}") - if profile.personality: - parts.append(f"性格:{'/'.join(profile.personality)}") + basic_info.append(f"微信: {profile.wechat}") + lines.append(" | ".join(basic_info)) + + # 消费分析 + consume_info = [] + consume_info.append(f"客户等级: {profile.customer_level}级") + if profile.vip: + consume_info.append("VIP客户") + consume_info.append(f"总订单: {profile.total_orders}单") + consume_info.append(f"总消费: {profile.total_spent}元") if profile.total_orders > 0: - parts.append(f"历史下单:{profile.total_orders}次") + consume_info.append(f"客单价: {profile.total_spent // profile.total_orders}元") + lines.append("--- 消费分析 ---") + lines.append(" | ".join(consume_info)) + + # 报价历史 + price_info = [] if profile.vip_custom_price: - parts.append(f"VIP专属价:{profile.vip_custom_price}元(直接用这个价报)") - elif profile.last_price: - parts.append(f"上次报价:{profile.last_price}元") + price_info.append(f"VIP专属价: {profile.vip_custom_price}元(直接报这个价)") + if profile.last_price: + price_info.append(f"上次报价: {profile.last_price}元") if profile.lowest_price_accepted: - parts.append(f"最低接受价:{profile.lowest_price_accepted}元") + price_info.append(f"历史最低成交: {profile.lowest_price_accepted}元") if profile.discount_given_count: - parts.append(f"历史让价:{profile.discount_given_count}次") - if getattr(profile, "last_quote_no_convert", False): - parts.append("【报价策略】上次报价未成交,本次可适当降低5-10元促成交") + price_info.append(f"历史让价: {profile.discount_given_count}次") if profile.price_sensitivity: - parts.append(f"价格敏感:{profile.price_sensitivity}") + price_info.append(f"价格敏感度: {profile.price_sensitivity}") + if getattr(profile, "last_quote_no_convert", False): + price_info.append("【策略】上次报价未成交,本次可降5-10元") + if price_info: + lines.append("--- 报价历史 ---") + lines.append(" | ".join(price_info)) + + # 性格与决策 + personality_info = [] + if profile.personality: + personality_info.append(f"性格: {'/'.join(profile.personality)}") if profile.decision_speed: - parts.append(f"决策速度:{profile.decision_speed}") - if profile.last_image_url: - parts.append(f"上次发图:{profile.last_image_url}") - if profile.processing_status: - parts.append(f"当前任务:{profile.processing_status}") + personality_info.append(f"决策速度: {profile.decision_speed}") + if profile.communication_prefer: + personality_info.append(f"沟通偏好: {profile.communication_prefer}") + if personality_info: + lines.append("--- 性格特征 ---") + lines.append(" | ".join(personality_info)) + + # 图片习惯 + image_info = [] + image_info.append(f"累计发图: {profile.total_images_sent}张") + if profile.complexity_history: + avg_complexity = self._calc_avg_complexity(profile.complexity_history) + image_info.append(f"平均复杂度: {avg_complexity}") + if profile.image_type_history: + from collections import Counter + top_types = Counter(profile.image_type_history).most_common(3) + types_str = "、".join(f"{t}({c}次)" for t, c in top_types) + image_info.append(f"常见类型: {types_str}") if profile.preferred_format: - parts.append(f"格式偏好:{profile.preferred_format}") - if profile.bulk_potential == "有": - parts.append("批量潜力:有(可主动推打包价)") - if profile.upsell_opportunity: - parts.append(f"加购机会:{'/'.join(profile.upsell_opportunity)}") + image_info.append(f"格式偏好: {profile.preferred_format}") + if profile.preferred_size: + image_info.append(f"尺寸要求: {profile.preferred_size}") + if profile.last_image_url: + image_info.append(f"最近发图: {profile.last_image_url[:60]}...") + lines.append("--- 图片习惯 ---") + lines.append(" | ".join(image_info)) + + # 当前任务状态 + if profile.processing_status: + task_info = [] + task_info.append(f"状态: {profile.processing_status}") + if profile.processing_image_url: + task_info.append(f"处理中: {profile.processing_image_url[:40]}...") + if profile.expected_done_at: + task_info.append(f"预计完成: {profile.expected_done_at}") + lines.append("--- 当前任务 ---") + lines.append(" | ".join(task_info)) + + # 上次对话摘要 if profile.last_conversation_summary: time_str = "" if profile.last_conversation_time: @@ -1042,38 +1078,54 @@ from core.workflow_router import get_workflow_router time_str = f"({h}小时前)" if h > 0 else "(刚刚)" except Exception: pass - parts.append(f"上次对话{time_str}:{profile.last_conversation_summary}") - - # 个性化:语气与报价策略 + lines.append(f"--- 上次对话 {time_str} ---") + lines.append(profile.last_conversation_summary) + + # 个性化回复策略 hints = [] if profile.personality: if "爽快" in profile.personality: - hints.append("回复简洁直接,少废话") + hints.append("回复简洁直接,不废话,快速报价") if "砍价" in profile.personality or "砍价狂" in profile.personality: - hints.append("报价时强调性价比,只让价一次") + hints.append("报价时强调性价比,只让价一次,第二次引导去 xinhui.cloud") if "纠结" in profile.personality or "墨迹" in profile.personality: - hints.append("耐心一点,可多给一点说明") + hints.append("多给一点说明,耐心回答") if profile.price_sensitivity == "高": hints.append("报价时顺带提「满意再拍」降低顾虑") if profile.decision_speed == "快": - hints.append("重点推快速成交,少铺垫") + hints.append("直接报价推成交,少铺垫") + if profile.total_orders > 0 and profile.decision_speed == "快": + hints.append("老客爽快,直接报价成交") if hints: - parts.append(f"【回复风格】{'; '.join(hints)}") - - # 主动预测:批量/加急 + lines.append("--- 回复策略 ---") + lines.append(";".join(hints)) + + # 主动推荐 proactive = [] if profile.bulk_potential == "有" or (profile.total_images_sent or 0) >= 2: - proactive.append("可主动问「要做多张吗,多张有优惠」") - if profile.total_orders > 0 and profile.decision_speed == "快": - proactive.append("老客爽快,直接推成交") + proactive.append("可问「要做多张吗,多张有优惠」") + if profile.upsell_opportunity: + proactive.append(f"加购机会: {'、'.join(profile.upsell_opportunity)}") if proactive: - parts.append(f"【主动推荐】{'; '.join(proactive)}") - - if parts: - return "【该客户历史信息】" + " | ".join(parts) + lines.append("--- 主动推荐 ---") + lines.append(";".join(proactive)) + + return "\n".join(lines) + except Exception as e: + print(f"[Agent] 获取客户画像失败: {e}") + return "" + + def _calc_avg_complexity(self, complexity_history: list) -> str: + """计算平均复杂度""" + if not complexity_history: + return "未知" + level_map = {"simple": 1, "normal": 2, "complex": 3, "hard": 4} + label_map = {1: "简单", 2: "一般", 3: "复杂", 4: "很复杂"} + try: + avg = sum(level_map.get(c, 2) for c in complexity_history) / len(complexity_history) + return label_map.get(round(avg), "一般") except Exception: - pass - return "" + return "一般" def _get_refusal_context_hint(self, customer_id: str, current_msg: str, profile_context: str) -> str: """ @@ -1203,7 +1255,6 @@ from core.workflow_router import get_workflow_router # 已付款:触发 Gemini 作图 try: from core.workflow import workflow -from core.workflow_router import get_workflow_router asyncio.create_task(workflow.trigger_processing_on_payment( customer_id=message.from_id, acc_id=message.acc_id, @@ -1816,6 +1867,51 @@ from core.workflow_router import get_workflow_router return prompt + async def _handle_image_workflow(self, message: str, data: dict, image_urls: list) -> bool: + """处理图片工作流(根据客户说的话判断执行哪种工作流)""" + if not image_urls: + return False + + workflow_type, confidence = self.workflow_router.detect_workflow(message) + + customer_id = data.get('from_id') + acc_id = data.get('acc_id', '') + acc_type = data.get('acc_type', 'AliWorkbench') + image_url = image_urls[0] + + print(f"[Agent] 检测到工作流类型:{workflow_type} (置信度:{confidence})") + + if workflow_type == "find_image": + print(f"[Agent] 执行查找图片工作流 | 客户:{customer_id}") + from core.workflow import workflow + return await workflow.find_image_workflow( + customer_id=customer_id, + image_url=image_url, + acc_id=acc_id, + acc_type=acc_type + ) + elif workflow_type == "process_image": + print(f"[Agent] 执行处理图片工作流 | 客户:{customer_id}") + from core.workflow import workflow + return await workflow.process_image_workflow( + customer_id=customer_id, + image_url=image_url, + acc_id=acc_id, + acc_type=acc_type + ) + elif workflow_type == "transfer_human": + print(f"[Agent] 执行转人工派单工作流 | 客户:{customer_id}") + from core.workflow import workflow + return await workflow.transfer_to_designer_workflow( + customer_id=customer_id, + image_url=image_url, + acc_id=acc_id, + acc_type=acc_type, + reason="客户主动要求转人工" + ) + + return False + async def test_agent(): """测试 Agent""" @@ -1842,65 +1938,3 @@ async def test_agent(): if __name__ == "__main__": import asyncio asyncio.run(test_agent()) - - async def _handle_image_workflow(self, message: str, data: dict, image_urls: list) -> bool: - """ - 处理图片工作流(根据客户说的话判断执行哪种工作流) - - Args: - message: 客户消息 - data: 消息数据 - image_urls: 图片 URL 列表 - - Returns: - bool: 是否已处理 - """ - if not image_urls: - return False - - # 检测工作流类型 - workflow_type, confidence = self.workflow_router.detect_workflow(message) - - customer_id = data.get('from_id') - acc_id = data.get('acc_id', '') - acc_type = data.get('acc_type', 'AliWorkbench') - image_url = image_urls[0] # 取第一张图 - - print(f"[Agent] 检测到工作流类型:{workflow_type} (置信度:{confidence})") - - if workflow_type == "find_image": - # 工作流 1:查找图片 - print(f"[Agent] 执行查找图片工作流 | 客户:{customer_id}") - success = await workflow.find_image_workflow( - customer_id=customer_id, - image_url=image_url, - acc_id=acc_id, - acc_type=acc_type - ) - return success - - elif workflow_type == "process_image": - # 工作流 2:处理图片 - print(f"[Agent] 执行处理图片工作流 | 客户:{customer_id}") - success = await workflow.process_image_workflow( - customer_id=customer_id, - image_url=image_url, - acc_id=acc_id, - acc_type=acc_type - ) - return success - - elif workflow_type == "transfer_human": - # 工作流 3:转人工派单 - print(f"[Agent] 执行转人工派单工作流 | 客户:{customer_id}") - success = await workflow.transfer_to_designer_workflow( - customer_id=customer_id, - image_url=image_url, - acc_id=acc_id, - acc_type=acc_type, - reason="客户主动要求转人工" - ) - return success - - # 未知工作流,不处理 - return False diff --git a/core/workflow.py b/core/workflow.py index ebe984d..0676a2d 100755 --- a/core/workflow.py +++ b/core/workflow.py @@ -611,26 +611,10 @@ class CustomerServiceWorkflow: ) return "\n".join(lines) - -# ========== 全局实例 ========== -workflow = CustomerServiceWorkflow() - # ========== 客户需求变更 ========== async def add_customer_requirement(self, task_id: str, customer_id: str, requirement: str, changed_by: str = 'customer') -> bool: - """ - 客户添加/修改需求细节 - - Args: - task_id: 任务 ID - customer_id: 客户 ID - requirement: 需求内容(如:"需要去掉背景"、"要分层文件") - changed_by: 修改者(customer/staff) - - Returns: - bool: 是否成功 - """ # 检查任务是否存在 task = self.get_task(task_id) if not task: @@ -981,3 +965,7 @@ workflow = CustomerServiceWorkflow() except Exception as e: logger.error(f"派单失败:{e}") return False + + +# ========== 全局实例 ========== +workflow = CustomerServiceWorkflow() diff --git a/customer_db/customers.json b/customer_db/customers.json index a45ded7..6d587d7 100755 --- a/customer_db/customers.json +++ b/customer_db/customers.json @@ -1,7 +1,7 @@ { - "tb056906307893": { - "customer_id": "tb056906307893", - "name": "", + "new_customer_001": { + "customer_id": "new_customer_001", + "name": "新客户小王", "nickname": "", "email": "", "phone": "", @@ -15,1010 +15,7 @@ "requirements": [], "preference_services": [], "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 30, - "last_price_time": "2026-02-25T18:05:35.822928", - "last_image_url": "https://img.alicdn.com/imgextra/i3/O1CN015r1E1y1kr8tLkTbdZ_!!4611686018427387680-0-amp.jpg", - "last_image_time": "2026-02-25T18:05:30.939198", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家问身份,客服称是店家", - "last_conversation_time": "2026-02-26T10:45:06.893182", - "total_images_sent": 2, - "complexity_history": [ - "complex", - "complex" - ], - "image_type_history": [ - "产品", - "产品" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-25 15:18] 报价 30元(单图处理)", - "[2026-02-25 15:21] 报价 40元(单图处理)", - "[2026-02-25 15:22] 报价 40元(单图复杂处理)", - "[2026-02-25 15:22] 报价 40元(单图复杂处理)", - "[2026-02-25 17:21] 报价 30元(单图处理)", - "[2026-02-25 17:26] 报价 25元(单图处理)", - "[2026-02-25 18:02] 报价 30元(单图处理)", - "[2026-02-25 18:05] 报价 30元(单图处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T10:45:01.348362", - "last_update": "2026-02-26T10:45:06.893182" - }, - "飞扬草89": { - "customer_id": "飞扬草89", - "name": "", - "nickname": "", - "email": "", - "phone": "16860184273", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T15:28:31.551548", - "last_update": "2026-02-25T15:28:31.551548" - }, - "默雪微凉": { - "customer_id": "默雪微凉", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "客服让买家放心拍不满意可退", - "last_conversation_time": "2026-02-27T10:30:31.753772", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T10:30:24.698816", - "last_update": "2026-02-27T10:30:31.755040" - }, - "h846967523": { - "customer_id": "h846967523", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "", - "last_conversation_time": "", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T00:18:45.344764", - "last_update": "2026-02-26T00:18:45.344764" - }, - "test_buyer_001": { - "customer_id": "test_buyer_001", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-25T16:34:25.734977", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-25 15:44] 报价 20元(单图处理)", - "[2026-02-25 15:45] 报价 40元(单图处理)", - "[2026-02-25 15:49] 报价 25元(单图处理)", - "[2026-02-25 15:49] 报价 30元(单图处理)", - "[2026-02-25 15:50] 报价 30元(单图处理)", - "[2026-02-25 15:57] 报价 25元(单图处理)", - "[2026-02-25 15:57] 报价 30元(单图处理)", - "[2026-02-25 16:09] 报价 25元(单图处理)", - "[2026-02-25 16:22] 报价 25元(单图处理)", - "[2026-02-25 16:23] 报价 15元(单图处理)", - "[2026-02-25 16:24] 报价 15元(单图处理)", - "[2026-02-25 16:25] 报价 25元(单图处理)", - "[2026-02-25 16:26] 报价 25元(单图处理)", - "[2026-02-25 16:26] 报价 100元(五图打包)", - "[2026-02-25 16:27] 报价 15元(单图处理)", - "[2026-02-25 16:27] 报价 15元(单图处理)", - "[2026-02-25 16:29] 报价 15元(单图处理)", - "[2026-02-25 16:29] 报价 15元(单图处理)", - "[2026-02-25 16:30] 报价 25元(单图处理)", - "[2026-02-25 16:30] 报价 32元(四张图打包处理)", - "[2026-02-25 16:31] 报价 15元(单图处理)", - "[2026-02-25 16:32] 报价 15元(单图处理)", - "[2026-02-25 16:33] 报价 25元(单图处理)", - "[2026-02-25 16:34] 报价 25元(单图处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "", - "last_update": "2026-02-25T16:34:25.737317" - }, - "jajp5257": { - "customer_id": "jajp5257", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T17:39:00.239783", - "last_update": "2026-02-25T17:39:00.239783" - }, - "": { - "customer_id": "", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "", - "last_image_time": "", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "", - "last_conversation_time": "", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "", - "last_update": "2026-02-25T18:40:25.907580" - }, - "tb9244405304": { - "customer_id": "tb9244405304", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 1, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "2026-02-26T18:02:48.649796", - "first_order_date": "2026-02-26T18:02:48.649796", - "order_ids": [ - "3263604169112478274" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "D", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "客服让买家发图以便处理", - "last_conversation_time": "2026-02-26T19:05:31.307469", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [ - "产品", - "产品", - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T19:05:25.018904", - "last_update": "2026-02-26T19:05:31.307469" - }, - "tb2801080146": { - "customer_id": "tb2801080146", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取该花砖印花,去除实拍背景杂物,还原清晰平面印花|ratio:1:1|perspective:mild|proc_type:印花提取|subject:花砖印花图案|quality:截图", - "complexity:complex|prompt:提取复古花砖餐垫印花,去除桌面背景,保留色彩细节|ratio:1:1|perspective:mild|proc_type:印花提取|subject:花砖印花餐垫|quality:截图", - "complexity:complex|prompt:提取复古花砖印花,去除拍摄背景,还原色彩细节|ratio:1:1|perspective:mild|proc_type:印花提取|subject:花砖印花图案|quality:截图" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 30, - "last_price_time": "2026-02-25T19:18:08.110317", - "last_image_url": "https://img.alicdn.com/imgextra/i3/O1CN01uQbEnC1qIEOd94v3r_!!4611686018427380880-0-amp.jpg", - "last_image_time": "2026-02-25T19:32:23.346846", - "last_gemini_prompt": "提取复古花砖印花,去除拍摄背景,还原色彩细节", - "last_aspect_ratio": "1:1", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家付款下单客服安排服务", - "last_conversation_time": "2026-02-25T19:33:14.215188", - "total_images_sent": 6, - "complexity_history": [ - "complex", - "complex", - "complex", - "complex", - "complex", - "complex" - ], - "image_type_history": [ - "产品", - "产品", - "产品", - "产品", - "花砖印花图案", - "产品", - "花砖印花餐垫", - "花砖印花图案", - "产品" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-25 18:21] 报价 30元(单图处理)", - "[2026-02-25 18:31] 报价 30元(单图处理)", - "[2026-02-25 18:39] 报价 30元(单图处理)", - "[2026-02-25 19:02] 报价 30元(单图处理)", - "[2026-02-25 19:18] 报价 30元(单图处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T19:33:09.670757", - "last_update": "2026-02-25T19:33:40.028103" - }, - "沙沙爱沙沙123": { - "customer_id": "沙沙爱沙沙123", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "https://img.alicdn.com/imgextra/i4/O1CN01f9NInZ2IPNovVI7nr_!!4611686018427383486-0-amp.jpg", - "last_image_time": "2026-02-25T18:46:58.057669", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "客服让买家拍下后马上安排", - "last_conversation_time": "2026-02-25T19:23:09.755507", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T19:22:54.303664", - "last_update": "2026-02-25T19:23:09.755507" - }, - "test_user_001": { - "customer_id": "test_user_001", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取复古花砖餐垫印花,去除实拍背景与杂物,保留色彩细节|ratio:1:1|perspective:mild|proc_type:印花提取|subject:花砖印花餐垫|quality:截图", - "complexity:complex|prompt:提取复古花砖印花图案,去除桌面杂物背景,保留色彩细节,输出平面印花图|ratio:1:1|perspective:mild|proc_type:印花提取|subject:复古花砖印花图案|quality:截图", - "complexity:complex|prompt:提取该复古花砖印花,去除桌面背景杂物,保留色彩细节,输出平面印花图|ratio:1:1|perspective:mild|proc_type:印花提取|subject:复古花砖印花图案|quality:截图", - "complexity:complex|prompt:提取复古花砖印花,去除桌面背景,保留色彩细节,输出干净平面印花图|ratio:1:1|perspective:mild|proc_type:印花提取|subject:花砖印花图案|quality:截图" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "", - "last_image_time": "", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "", - "last_conversation_time": "", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T19:13:22.533990", - "last_update": "2026-02-25T19:13:22.533990" - }, - "zn19900727": { - "customer_id": "zn19900727", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "买家未存原图客服索要现图", - "last_conversation_time": "2026-02-25T19:33:46.463407", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [ - "人物" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T19:33:41.781455", - "last_update": "2026-02-25T19:33:46.463407" - }, - "天天爱买耶": { - "customer_id": "天天爱买耶", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:去除顶部白边,优化色彩过渡,保留原图风景细节|ratio:9:16|perspective:strong|proc_type:去背景|subject:渐变山峦风景|quality:清晰", - "complexity:normal|prompt:提取该抽象山脉印花,保留色彩纹理,去除多余白边|ratio:9:16|perspective:mild|proc_type:印花提取|subject:印花图案|quality:清晰" - ], - "preference_services": [], - "total_orders": 1, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "2026-02-26T10:53:49.096624", - "first_order_date": "2026-02-26T10:53:49.096624", - "order_ids": [ - "3262728216497229278" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "D", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "https://img.alicdn.com/imgextra/i2/2782227892/O1CN01EzwbG528AasLEt7wU_!!2782227892-2-ampmedia.png", - "last_image_time": "2026-02-25T22:03:25.276837", - "last_gemini_prompt": "提取该抽象山脉印花,保留色彩纹理,去除多余白边", - "last_aspect_ratio": "9:16", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", - "preferred_size": "", - "last_conversation_summary": "买家下单修图,客服索要图片。", - "last_conversation_time": "2026-02-26T10:54:03.053690", - "total_images_sent": 2, - "complexity_history": [ - "normal", - "normal" - ], - "image_type_history": [ - "渐变山峦风景", - "印花图案", - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T10:57:13.391540", - "last_update": "2026-02-26T10:57:13.391540" - }, - "levey萤火": { - "customer_id": "levey萤火", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:去除电商页面多余元素,保留鼠标垫产品展示效果,优化画面清晰度|ratio:9:16|perspective:mild|proc_type:其他|subject:宝可梦主题RGB电竞鼠标垫|quality:截图" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, + "total_spent": 0, "avg_order_value": 0.0, "purchase_frequency": "", "last_order_date": "", @@ -1035,259 +32,9 @@ "vip": false, "vip_level": 0, "last_price": 20, - "last_price_time": "2026-02-25T23:19:38.626636", - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01yZBLzz2EtpgvnvipX_!!4611686018427382211-0-amp.jpg", - "last_image_time": "2026-02-25T23:18:40.994670", - "last_gemini_prompt": "去除电商页面多余元素,保留鼠标垫产品展示效果,优化画面清晰度", - "last_aspect_ratio": "9:16", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家嫌贵,客服降价促成交", - "last_conversation_time": "2026-02-25T23:22:02.889483", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "宝可梦主题RGB电竞鼠标垫" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-25 23:18] 报价 15元(单图处理)", - "[2026-02-25 23:19] 报价 20元(单图处理+5k尺寸调整)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-25T23:21:48.843046", - "last_update": "2026-02-25T23:22:02.889483" - }, - "江石之恋": { - "customer_id": "江石之恋", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:保留雕花画框和油画细节,保持复古质感,输出高清成品|ratio:1:1|proc_type:其他|subject:带雕花画框的静物油画|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-26T00:16:41.942275", - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01j95n1g1VaSDwO3cVH_!!4611686018427379981-0-amp.jpg", - "last_image_time": "2026-02-26T00:16:36.045639", - "last_gemini_prompt": "保留雕花画框和油画细节,保持复古质感,输出高清成品", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家发图,客服称已完成报价记录", - "last_conversation_time": "2026-02-26T00:16:49.486759", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "带雕花画框的静物油画" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-26 00:16] 报价 25元(单图处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T00:16:47.467194", - "last_update": "2026-02-26T00:16:49.486759" - }, - "tankgoing": { - "customer_id": "tankgoing", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:优化壁纸细节,保留手机界面与外观,清理背景杂物,去除抖音水印|ratio:2:3|proc_type:其他|subject:iPhone手机及主屏界面|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01GA4it822Lgm9GLRn2_!!4611686018427380640-0-amp.jpg", - "last_image_time": "2026-02-26T09:52:21.952226", - "last_gemini_prompt": "优化壁纸细节,保留手机界面与外观,清理背景杂物,去除抖音水印", - "last_aspect_ratio": "2:3", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "买家问可否擦边,客服要看图", - "last_conversation_time": "2026-02-26T09:51:56.148574", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "iPhone手机及主屏界面" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T09:52:21.957434", - "last_update": "2026-02-26T09:52:21.957434" - }, - "tb427924597502": { - "customer_id": "tb427924597502", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", + "last_price_time": "2026-02-28T15:04:15.181813", + "last_quote_no_convert": false, + "last_min_price": 0, "last_image_url": "", "last_image_time": "", "last_gemini_prompt": "", @@ -1298,258 +45,8 @@ "expected_done_at": "", "discount_given_count": 0, "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "买家要求重发,客服同意重发", - "last_conversation_time": "2026-02-26T10:45:38.499947", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T10:45:28.253646", - "last_update": "2026-02-26T10:45:38.499947" - }, - "楚茜tracy": { - "customer_id": "楚茜tracy", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:增强画面细节,保留赛博光影和人物造型,优化光效发丝质感|ratio:9:16|perspective:mild|proc_type:人像修复|subject:动漫女性角色|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 20, - "last_price_time": "2026-02-26T10:55:52.191700", - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01j1qr9P22z4pz7tup7_!!4611686018427382278-0-amp.jpg", - "last_image_time": "2026-02-26T10:51:52.323288", - "last_gemini_prompt": "增强画面细节,保留赛博光影和人物造型,优化光效发丝质感", - "last_aspect_ratio": "9:16", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 1, - "lowest_price_accepted": 20, "preferred_format": "jpg", "preferred_size": "", - "last_conversation_summary": "客服称价格已是最低价", - "last_conversation_time": "2026-02-26T10:58:21.994304", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "动漫女性角色", - "人物" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-26 10:52] 报价 25元(单张动漫壁纸高清修复)", - "[2026-02-26 10:55] 报价 20元(单张动漫壁纸高清修复(优惠后))" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T10:58:09.418707", - "last_update": "2026-02-26T10:58:21.994304" - }, - "shanshan_5200": { - "customer_id": "shanshan_5200", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:保留展板黑金质感与文字,去除舞台场景,导出高清平面图|ratio:16:9|perspective:strong|proc_type:其他|subject:活动宣传展板|quality:清晰" - ], - "preference_services": [], - "total_orders": 1, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "2026-02-26T11:01:55.091860", - "first_order_date": "2026-02-26T11:01:55.091860", - "order_ids": [ - "3262607077713177965" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "D", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01QHZb0t1XYjyQZARMD_!!4611686018427382648-0-amp.jpg", - "last_image_time": "2026-02-26T11:00:47.165693", - "last_gemini_prompt": "保留展板黑金质感与文字,去除舞台场景,导出高清平面图", - "last_aspect_ratio": "16:9", - "last_perspective": "strong", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家下单,客服回应马上安排", - "last_conversation_time": "2026-02-26T11:02:10.144435", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "活动宣传展板", - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T11:01:58.774826", - "last_update": "2026-02-26T11:02:11.054909" - }, - "绯殇枫焱": { - "customer_id": "绯殇枫焱", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", "last_conversation_summary": "", "last_conversation_time": "", "total_images_sent": 0, @@ -1578,12 +75,12 @@ "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-26T11:52:59.407592", - "last_update": "2026-02-26T11:52:59.410191" + "last_contact": "2026-02-28T15:03:57.129715", + "last_update": "2026-02-28T15:04:15.184378" }, - "kai19960222": { - "customer_id": "kai19960222", - "name": "", + "fast_customer_002": { + "customer_id": "fast_customer_002", + "name": "爽快老客老李", "nickname": "", "email": "", "phone": "", @@ -1596,8 +93,8 @@ "budget_range_max": 0, "requirements": [], "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, + "total_orders": 8, + "total_spent": 280, "avg_order_value": 0.0, "purchase_frequency": "", "last_order_date": "", @@ -1606,15 +103,19 @@ "pending_orders": 0, "completed_orders": 0, "refund_count": 0, - "personality": [], + "personality": [ + "爽快" + ], "communication_prefer": "", "response_speed": "", "patience_level": "", "customer_level": "C", "vip": false, "vip_level": 0, - "last_price": 0, - "last_price_time": "", + "last_price": 10, + "last_price_time": "2026-02-28T15:06:10.872962", + "last_quote_no_convert": false, + "last_min_price": 0, "last_image_url": "", "last_image_time": "", "last_gemini_prompt": "", @@ -1623,17 +124,99 @@ "processing_status": "", "processing_image_url": "", "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", + "discount_given_count": 2, + "lowest_price_accepted": 10, + "preferred_format": "jpg", "preferred_size": "", - "last_conversation_summary": "买家询问进度,客服回复快好了", - "last_conversation_time": "2026-02-26T13:06:55.712730", + "last_conversation_summary": "", + "last_conversation_time": "", "total_images_sent": 0, "complexity_history": [], "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", + "price_sensitivity": "中", + "decision_speed": "快", + "revision_count": 0, + "revision_orders": 0, + "total_completed_orders": 8, + "bulk_potential": "", + "churn_risk": "低", + "upsell_opportunity": [], + "blacklist": false, + "blacklist_reason": "", + "vip_custom_price": 0, + "last_email_status": "", + "good_reviews": 0, + "bad_reviews": 0, + "dispute_count": 0, + "follow_up_by": "", + "follow_up_date": "", + "next_follow_date": "", + "source": "", + "coupon_used": "", + "notes": [], + "tags": [], + "created_at": "", + "last_contact": "2026-02-28T15:03:57.131384", + "last_update": "2026-02-28T15:06:10.875534" + }, + "bargainer_003": { + "customer_id": "bargainer_003", + "name": "砍价王小张", + "nickname": "", + "email": "", + "phone": "", + "wechat": "", + "address": "", + "platform": "", + "platform_id": "", + "budget": "", + "budget_range_min": 0, + "budget_range_max": 0, + "requirements": [], + "preference_services": [], + "total_orders": 3, + "total_spent": 45, + "avg_order_value": 0.0, + "purchase_frequency": "", + "last_order_date": "", + "first_order_date": "", + "order_ids": [], + "pending_orders": 0, + "completed_orders": 0, + "refund_count": 0, + "personality": [ + "砍价狂", + "纠结" + ], + "communication_prefer": "", + "response_speed": "", + "patience_level": "", + "customer_level": "C", + "vip": false, + "vip_level": 0, + "last_price": 10, + "last_price_time": "2026-02-28T15:05:45.067204", + "last_quote_no_convert": false, + "last_min_price": 0, + "last_image_url": "", + "last_image_time": "", + "last_gemini_prompt": "", + "last_aspect_ratio": "1:1", + "last_perspective": "no", + "processing_status": "", + "processing_image_url": "", + "expected_done_at": "", + "discount_given_count": 6, + "lowest_price_accepted": 10, + "preferred_format": "jpg", + "preferred_size": "", + "last_conversation_summary": "", + "last_conversation_time": "", + "total_images_sent": 0, + "complexity_history": [], + "image_type_history": [], + "price_sensitivity": "高", + "decision_speed": "慢", "revision_count": 0, "revision_orders": 0, "total_completed_orders": 0, @@ -1655,12 +238,12 @@ "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-26T13:06:51.839306", - "last_update": "2026-02-26T13:06:55.712730" + "last_contact": "2026-02-28T15:03:57.132648", + "last_update": "2026-02-28T15:05:45.071818" }, - "ghcnwei": { - "customer_id": "ghcnwei", - "name": "", + "vip_customer_004": { + "customer_id": "vip_customer_004", + "name": "VIP客户陈总", "nickname": "", "email": "", "phone": "", @@ -1671,13 +254,10 @@ "budget": "", "budget_range_min": 0, "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:修复该漫画截图,保留所有文字与人物细节,去除界面冗余元素|ratio:9:16|proc_type:高清修复|subject:鬼灭之刃同人漫画|quality:截图", - "complexity:complex|prompt:该图片含违规内容,无法处理|ratio:16:9|proc_type:其他|subject:同人漫画截图|quality:截图" - ], + "requirements": [], "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, + "total_orders": 15, + "total_spent": 680, "avg_order_value": 0.0, "purchase_frequency": "", "last_order_date": "", @@ -1686,40 +266,118 @@ "pending_orders": 0, "completed_orders": 0, "refund_count": 0, - "personality": [], + "personality": [ + "爽快" + ], "communication_prefer": "", "response_speed": "", "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-26T13:08:15.800720", + "customer_level": "A", + "vip": true, + "vip_level": 2, + "last_price": 20, + "last_price_time": "2026-02-28T15:04:56.155844", "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i3/O1CN01qskN6l2N9Q65ugJVk_!!4611686018427384960-2-amp.png", - "last_image_time": "2026-02-26T13:09:15.701068", - "last_gemini_prompt": "该图片含违规内容,无法处理", - "last_aspect_ratio": "16:9", + "last_min_price": 0, + "last_image_url": "", + "last_image_time": "", + "last_gemini_prompt": "", + "last_aspect_ratio": "1:1", "last_perspective": "no", "processing_status": "", "processing_image_url": "", "expected_done_at": "", "discount_given_count": 0, "lowest_price_accepted": 0, - "preferred_format": "png", + "preferred_format": "jpg", "preferred_size": "", - "last_conversation_summary": "买家致谢,客服友好回应。", - "last_conversation_time": "2026-02-26T20:19:16.252161", - "total_images_sent": 2, - "complexity_history": [ - "complex", - "complex" + "last_conversation_summary": "", + "last_conversation_time": "", + "total_images_sent": 0, + "complexity_history": [], + "image_type_history": [], + "price_sensitivity": "低", + "decision_speed": "快", + "revision_count": 0, + "revision_orders": 0, + "total_completed_orders": 0, + "bulk_potential": "", + "churn_risk": "低", + "upsell_opportunity": [], + "blacklist": false, + "blacklist_reason": "", + "vip_custom_price": 18, + "last_email_status": "", + "good_reviews": 0, + "bad_reviews": 0, + "dispute_count": 0, + "follow_up_by": "", + "follow_up_date": "", + "next_follow_date": "", + "source": "", + "coupon_used": "", + "notes": [], + "tags": [], + "created_at": "", + "last_contact": "2026-02-28T15:03:57.134104", + "last_update": "2026-02-28T15:04:56.158233" + }, + "high_value_005": { + "customer_id": "high_value_005", + "name": "高价值客户刘老板", + "nickname": "", + "email": "", + "phone": "", + "wechat": "", + "address": "", + "platform": "", + "platform_id": "", + "budget": "", + "budget_range_min": 0, + "budget_range_max": 0, + "requirements": [], + "preference_services": [], + "total_orders": 20, + "total_spent": 1200, + "avg_order_value": 60, + "purchase_frequency": "", + "last_order_date": "", + "first_order_date": "", + "order_ids": [], + "pending_orders": 0, + "completed_orders": 0, + "refund_count": 0, + "personality": [ + "爽快" ], - "image_type_history": [ - "鬼灭之刃同人漫画", - "同人漫画截图" - ], - "price_sensitivity": "", + "communication_prefer": "", + "response_speed": "", + "patience_level": "", + "customer_level": "A", + "vip": false, + "vip_level": 0, + "last_price": 20, + "last_price_time": "2026-02-28T15:05:11.156030", + "last_quote_no_convert": false, + "last_min_price": 0, + "last_image_url": "", + "last_image_time": "", + "last_gemini_prompt": "", + "last_aspect_ratio": "1:1", + "last_perspective": "no", + "processing_status": "", + "processing_image_url": "", + "expected_done_at": "", + "discount_given_count": 0, + "lowest_price_accepted": 0, + "preferred_format": "jpg", + "preferred_size": "", + "last_conversation_summary": "", + "last_conversation_time": "", + "total_images_sent": 0, + "complexity_history": [], + "image_type_history": [], + "price_sensitivity": "低", "decision_speed": "快", "revision_count": 0, "revision_orders": 0, @@ -1739,284 +397,15 @@ "next_follow_date": "", "source": "", "coupon_used": "", - "notes": [ - "[2026-02-26 13:08] 报价 25元(单图高清修复)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T20:19:05.895030", - "last_update": "2026-02-26T20:19:16.252161" - }, - "tb305370_55": { - "customer_id": "tb305370_55", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:高清修复该邮件截图,保留所有文字,去除拍摄阴影与透视,优化清晰度|ratio:3:4|perspective:mild|proc_type:高清修复|subject:谷歌家人群组通知邮件截图|quality:轻微模糊" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 50, - "last_price_time": "2026-02-26T14:46:10.292771", - "last_image_url": "https://img.alicdn.com/imgextra/i3/O1CN01eR7msr1HpCLyqaTH7_!!4611686018427386614-0-amp.jpg", - "last_image_time": "2026-02-26T14:44:19.334519", - "last_gemini_prompt": "高清修复该邮件截图,保留所有文字,去除拍摄阴影与透视,优化清晰度", - "last_aspect_ratio": "3:4", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "客服告知买家pro权限到期需续费", - "last_conversation_time": "2026-02-26T14:46:24.915898", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "产品", - "谷歌家人群组通知邮件截图" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-26 14:44] 报价 15元(单图高清修复)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T14:46:10.300326", - "last_update": "2026-02-26T14:46:24.915898" - }, - "黄梅榕98": { - "customer_id": "黄梅榕98", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取该流水生财山水印花,保留文字细节,去除背景,输出干净平面图|ratio:16:9|perspective:mild|proc_type:印花提取|subject:流水生财山水印花|quality:清晰", - "complexity:complex|prompt:提取该家和福字装饰图案,去除背景与支架,保留所有细节|ratio:3:4|perspective:mild|proc_type:印花提取|subject:家和福字装饰图案|quality:清晰", - "complexity:complex|prompt:提取该家和福字装饰印花,保留所有文字和花卉细节,去除背景和屏风框架|ratio:9:16|perspective:mild|proc_type:印花提取|subject:印花图案|quality:清晰" - ], - "preference_services": [], - "total_orders": 1, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "2026-02-26T19:19:37.683510", - "first_order_date": "2026-02-26T19:19:37.683510", - "order_ids": [ - "5085769644678975532" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "D", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-26T19:16:17.105069", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01Wps0F21ZuqJp2lxqd_!!4611686018427385927-2-amp.png", - "last_image_time": "2026-02-26T19:16:13.969512", - "last_gemini_prompt": "提取该家和福字装饰印花,保留所有文字和花卉细节,去除背景和屏风框架", - "last_aspect_ratio": "9:16", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", - "preferred_size": "", - "last_conversation_summary": "买家已付款,客服将尽快处理发出", - "last_conversation_time": "2026-02-26T19:20:02.811961", - "total_images_sent": 3, - "complexity_history": [ - "complex", - "complex", - "complex" - ], - "image_type_history": [ - "流水生财山水印花", - "家和福字装饰图案", - "印花图案", - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-26 14:57] 报价 25元(单图山水印花提取)", - "[2026-02-26 18:50] 报价 25元(单图印花提取)", - "[2026-02-26 19:16] 报价 25元(单图印花提取)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T19:22:59.025828", - "last_update": "2026-02-26T19:22:59.025828" - }, - "xiaobin8703": { - "customer_id": "xiaobin8703", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:去除外围浅蓝色边框,修复画面细节,保留油画色彩与肌理|ratio:1:1|proc_type:高清修复|subject:抽象花卉油画图案|quality:轻微模糊" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01Pdr6QW1KK0Dw8DHu4_!!4611686018427380856-0-amp.jpg", - "last_image_time": "2026-02-26T15:35:05.232933", - "last_gemini_prompt": "去除外围浅蓝色边框,修复画面细节,保留油画色彩与肌理", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家询居中图,客服报价拍下发", - "last_conversation_time": "2026-02-26T15:35:14.647440", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "抽象花卉油画图案" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-26T15:35:06.858823", - "last_update": "2026-02-26T15:35:14.647440" + "last_contact": "2026-02-28T15:03:57.135396", + "last_update": "2026-02-28T15:05:11.160004" }, - "tb5438719_2012": { - "customer_id": "tb5438719_2012", - "name": "", + "blacklist_006": { + "customer_id": "blacklist_006", + "name": "黑名单客户", "nickname": "", "email": "", "phone": "", @@ -2049,6 +438,7 @@ "last_price": 0, "last_price_time": "", "last_quote_no_convert": false, + "last_min_price": 0, "last_image_url": "", "last_image_time": "", "last_gemini_prompt": "", @@ -2059,10 +449,89 @@ "expected_done_at": "", "discount_given_count": 0, "lowest_price_accepted": 0, - "preferred_format": "", + "preferred_format": "jpg", "preferred_size": "", - "last_conversation_summary": "买家询一元修图客服称最低十元", - "last_conversation_time": "2026-02-26T17:29:02.489193", + "last_conversation_summary": "", + "last_conversation_time": "", + "total_images_sent": 0, + "complexity_history": [], + "image_type_history": [], + "price_sensitivity": "", + "decision_speed": "", + "revision_count": 0, + "revision_orders": 0, + "total_completed_orders": 0, + "bulk_potential": "", + "churn_risk": "低", + "upsell_opportunity": [], + "blacklist": true, + "blacklist_reason": "恶意投诉多次", + "vip_custom_price": 0, + "last_email_status": "", + "good_reviews": 0, + "bad_reviews": 0, + "dispute_count": 0, + "follow_up_by": "", + "follow_up_date": "", + "next_follow_date": "", + "source": "", + "coupon_used": "", + "notes": [], + "tags": [], + "created_at": "", + "last_contact": "2026-02-28T15:03:57.136490", + "last_update": "2026-02-28T15:05:27.155220" + }, + "test_new_001": { + "customer_id": "test_new_001", + "name": "新客户小王", + "nickname": "", + "email": "", + "phone": "", + "wechat": "", + "address": "", + "platform": "", + "platform_id": "", + "budget": "", + "budget_range_min": 0, + "budget_range_max": 0, + "requirements": [], + "preference_services": [], + "total_orders": 0, + "total_spent": 0, + "avg_order_value": 0.0, + "purchase_frequency": "", + "last_order_date": "", + "first_order_date": "", + "order_ids": [], + "pending_orders": 0, + "completed_orders": 0, + "refund_count": 0, + "personality": [], + "communication_prefer": "", + "response_speed": "", + "patience_level": "", + "customer_level": "C", + "vip": false, + "vip_level": 0, + "last_price": 0, + "last_price_time": "2026-02-28T15:27:40.801329", + "last_quote_no_convert": false, + "last_min_price": 0, + "last_image_url": "", + "last_image_time": "", + "last_gemini_prompt": "", + "last_aspect_ratio": "1:1", + "last_perspective": "no", + "processing_status": "", + "processing_image_url": "", + "expected_done_at": "", + "discount_given_count": 0, + "lowest_price_accepted": 0, + "preferred_format": "jpg", + "preferred_size": "", + "last_conversation_summary": "", + "last_conversation_time": "", "total_images_sent": 0, "complexity_history": [], "image_type_history": [], @@ -2089,12 +558,12 @@ "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-26T17:28:50.811768", - "last_update": "2026-02-26T17:29:02.489193" + "last_contact": "2026-02-28T15:29:05.719291", + "last_update": "2026-02-28T15:29:05.719308" }, - "珍惜我们的时候": { - "customer_id": "珍惜我们的时候", - "name": "", + "test_fast_002": { + "customer_id": "test_fast_002", + "name": "爽快老客老李", "nickname": "", "email": "", "phone": "", @@ -2107,8 +576,8 @@ "budget_range_max": 0, "requirements": [], "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, + "total_orders": 8, + "total_spent": 280, "avg_order_value": 0.0, "purchase_frequency": "", "last_order_date": "", @@ -2117,16 +586,19 @@ "pending_orders": 0, "completed_orders": 0, "refund_count": 0, - "personality": [], + "personality": [ + "爽快" + ], "communication_prefer": "", "response_speed": "", "patience_level": "", "customer_level": "C", "vip": false, "vip_level": 0, - "last_price": 0, + "last_price": 25, "last_price_time": "", "last_quote_no_convert": false, + "last_min_price": 0, "last_image_url": "", "last_image_time": "", "last_gemini_prompt": "", @@ -2139,18 +611,18 @@ "lowest_price_accepted": 0, "preferred_format": "", "preferred_size": "", - "last_conversation_summary": "买家反馈未发货,客服回应马上发。", - "last_conversation_time": "2026-02-26T18:03:59.162058", + "last_conversation_summary": "", + "last_conversation_time": "", "total_images_sent": 0, "complexity_history": [], "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", + "price_sensitivity": "低", + "decision_speed": "快", "revision_count": 0, "revision_orders": 0, - "total_completed_orders": 0, + "total_completed_orders": 8, "bulk_potential": "", - "churn_risk": "低", + "churn_risk": "", "upsell_opportunity": [], "blacklist": false, "blacklist_reason": "", @@ -2167,12 +639,12 @@ "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-26T18:03:41.433262", - "last_update": "2026-02-26T18:03:59.162058" + "last_contact": "2026-02-28T15:29:05.720944", + "last_update": "2026-02-28T15:29:05.720948" }, - "tb123201563": { - "customer_id": "tb123201563", - "name": "", + "test_bargain_003": { + "customer_id": "test_bargain_003", + "name": "砍价王小张", "nickname": "", "email": "", "phone": "", @@ -2185,182 +657,8 @@ "budget_range_max": 0, "requirements": [], "preference_services": [], - "total_orders": 1, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "2026-02-26T18:41:08.073526", - "first_order_date": "2026-02-26T18:41:08.073526", - "order_ids": [ - "5085473654517257211" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "D", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "买家说好了,客服说稍等查看", - "last_conversation_time": "2026-02-26T18:41:35.391391", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [ - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T18:41:28.652682", - "last_update": "2026-02-26T18:41:35.391391" - }, - "cyc5511": { - "customer_id": "cyc5511", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:提取该水墨山水壁画图案,去除楼梯背景,保留细节|ratio:4:3|perspective:mild|proc_type:印花提取|subject:水墨山水风景图案|quality:清晰", - "complexity:complex|prompt:去除楼梯多余墙面,保留手绘壁画并还原细节|ratio:2:3|perspective:strong|proc_type:去背景|subject:江南水乡手绘壁画与楼梯|quality:清晰" - ], - "preference_services": [], - "total_orders": 1, - "total_spent": 35.0, - "avg_order_value": 35.0, - "purchase_frequency": "", - "last_order_date": "2026-02-26T19:02:19.935201", - "first_order_date": "2026-02-26T19:02:19.935201", - "order_ids": [ - "3263881152997215964" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01MhARHr1xaHJVoJvjS_!!4611686018427387275-0-amp.jpg", - "last_image_time": "2026-02-26T19:01:12.933480", - "last_gemini_prompt": "去除楼梯多余墙面,保留手绘壁画并还原细节", - "last_aspect_ratio": "2:3", - "last_perspective": "strong", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "客服索要买家邮箱以便发原图", - "last_conversation_time": "2026-02-26T19:15:41.165568", - "total_images_sent": 2, - "complexity_history": [ - "normal", - "complex" - ], - "image_type_history": [ - "水墨山水风景图案", - "江南水乡手绘壁画与楼梯", - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 1, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-26T19:15:33.879387", - "last_update": "2026-02-26T19:15:41.165568" - }, - "lian2099": { - "customer_id": "lian2099", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:保留文字与卡通元素,去除现场杂物和阴影,还原平整清晰背景|ratio:3:4|perspective:mild|proc_type:高清修复|subject:百天宴装饰背景|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, + "total_orders": 3, + "total_spent": 45, "avg_order_value": 0.0, "purchase_frequency": "", "last_order_date": "", @@ -2369,706 +667,10 @@ "pending_orders": 0, "completed_orders": 0, "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-27T10:22:33.367453", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i1/53824251/O1CN01uUm2BD1hH0ng0EZT8_!!53824251-0-ampmedia.jpg", - "last_image_time": "2026-02-27T10:22:28.641691", - "last_gemini_prompt": "保留文字与卡通元素,去除现场杂物和阴影,还原平整清晰背景", - "last_aspect_ratio": "3:4", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家发商品图,客服报价让拍下", - "last_conversation_time": "2026-02-27T10:22:41.391517", - "total_images_sent": 1, - "complexity_history": [ - "complex" + "personality": [ + "砍价狂", + "纠结" ], - "image_type_history": [ - "百天宴装饰背景" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 10:22] 报价 25元(单图高清修复)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T10:22:35.798105", - "last_update": "2026-02-27T10:22:41.391517" - }, - "tb33332993": { - "customer_id": "tb33332993", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:去除画框与背景阴影,提取画芯,保留题款细节,高清修复画面|ratio:16:9|proc_type:去背景|subject:青绿山水风景画|quality:清晰", - "complexity:complex|prompt:高清修复这幅山水风景画,保留题款与画面细节|ratio:16:9|proc_type:高清修复|subject:风景山水画|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 40, - "last_price_time": "2026-02-27T10:26:52.452593", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i4/3976129458/O1CN01VHBbWA2Jjp7BP5BAi_!!3976129458-2-ampmedia.png", - "last_image_time": "2026-02-27T10:26:41.971113", - "last_gemini_prompt": "高清修复这幅山水风景画,保留题款与画面细节", - "last_aspect_ratio": "16:9", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", - "preferred_size": "", - "last_conversation_summary": "买家发图客服称等待客户回应", - "last_conversation_time": "2026-02-27T10:27:05.712050", - "total_images_sent": 2, - "complexity_history": [ - "complex", - "complex" - ], - "image_type_history": [ - "青绿山水风景画", - "风景山水画" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 10:26] 报价 25元(单图处理(青绿山水风景画))", - "[2026-02-27 10:26] 报价 40元(两张风景山水画打包处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T10:26:54.729377", - "last_update": "2026-02-27T10:27:05.712050" - }, - "世界因你们美丽": { - "customer_id": "世界因你们美丽", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:去除背景,保留装饰马全部细节,还原色彩,输出清晰平面图案|ratio:1:1|proc_type:印花提取|subject:装饰马印花图案|quality:清晰" - ], - "preference_services": [], - "total_orders": 1, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "2026-02-27T10:37:34.509280", - "first_order_date": "2026-02-27T10:37:34.509280", - "order_ids": [ - "3264150326359502463" - ], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "D", - "vip": false, - "vip_level": 0, - "last_price": 15, - "last_price_time": "2026-02-27T10:33:24.939223", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i3/O1CN01wrBw2I1waRqQWBGoZ_!!4611686018427386132-0-amp.jpg", - "last_image_time": "2026-02-27T10:33:19.700846", - "last_gemini_prompt": "去除背景,保留装饰马全部细节,还原色彩,输出清晰平面图案", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家下单找图,客服回应接单", - "last_conversation_time": "2026-02-27T10:37:47.753454", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "装饰马印花图案", - "老照片", - "产品" - ], - "price_sensitivity": "低", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 10:33] 报价 15元(单图装饰印花提取处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T10:37:39.163915", - "last_update": "2026-02-27T10:37:53.522807" - }, - "为何你总是笑个不够": { - "customer_id": "为何你总是笑个不够", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:修复该红旗渠国画,保留山水细节,去除多余水印,优化明暗阴影|ratio:4:3|perspective:strong|proc_type:高清修复|subject:风景|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-27T11:14:17.293058", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01g9PheP2KMINsOwlxR_!!4611686018427387302-2-amp.png", - "last_image_time": "2026-02-27T11:14:09.576986", - "last_gemini_prompt": "修复该红旗渠国画,保留山水细节,去除多余水印,优化明暗阴影", - "last_aspect_ratio": "4:3", - "last_perspective": "strong", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", - "preferred_size": "", - "last_conversation_summary": "买家不用高清客服找不了出版信息", - "last_conversation_time": "2026-02-27T11:15:36.544687", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "风景" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 11:14] 报价 25元(单图风景高清修复)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T11:15:13.138660", - "last_update": "2026-02-27T11:15:36.544687" - }, - "tb01593913": { - "customer_id": "tb01593913", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:去除墙面和画框背景,保留天鹅樱花湖风景细节,提升画面清晰度|ratio:16:9|proc_type:去背景|subject:风景|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 10, - "last_price_time": "2026-02-27T11:12:52.633216", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i4/O1CN01ZhwGSo1YN7XbZ4WQv_!!4611686018427382534-0-amp.jpg", - "last_image_time": "2026-02-27T11:11:24.779589", - "last_gemini_prompt": "去除墙面和画框背景,保留天鹅樱花湖风景细节,提升画面清晰度", - "last_aspect_ratio": "16:9", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家称没链接,客服让直接下单。", - "last_conversation_time": "2026-02-27T11:22:16.857125", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "风景" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 11:11] 报价 15元(单图处理)", - "[2026-02-27 11:12] 报价 10元(图一天鹅移到图二替换原有天鹅)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T11:23:01.046250", - "last_update": "2026-02-27T11:23:01.046250" - }, - "crab314": { - "customer_id": "crab314", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取蒲公英印花图案,去除背景场景,保留细节输出干净平面图|ratio:9:16|perspective:mild|proc_type:印花提取|subject:蒲公英印花图案|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i4/O1CN017eWihn2B5xiYrsPF1_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i4/O1CN01MS2Hw72B5xiaN1DjI_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i1/O1CN01o9Shyi2B5xiZ2sDLE_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i2/O1CN01XxUFtU2B5xiYfXBZ4_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i4/O1CN01IZsQWa2B5xiZDM0mG_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i3/O1CN01VZNSCU2B5xiZ5e1u4_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i1/O1CN01kv9Cx32B5xiZ2t5Lv_!!4611686018427382192-0-amp.jpg#*#https://img.alicdn.com/imgextra/i4/O1CN01eBaxY82B5xiZXUXWy_!!4611686018427382192-0-amp.jpg", - "last_image_time": "2026-02-27T11:26:33.081935", - "last_gemini_prompt": "提取蒲公英印花图案,去除背景场景,保留细节输出干净平面图", - "last_aspect_ratio": "9:16", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "买家询问找图价格客服看图定价", - "last_conversation_time": "2026-02-27T11:25:26.889758", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "蒲公英印花图案" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T11:26:33.092837", - "last_update": "2026-02-27T11:26:33.092837" - }, - "王2474313415": { - "customer_id": "王2474313415", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "", - "last_image_time": "", - "last_gemini_prompt": "", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "", - "preferred_size": "", - "last_conversation_summary": "买家问价,客服要求发图", - "last_conversation_time": "2026-02-27T12:32:29.487749", - "total_images_sent": 0, - "complexity_history": [], - "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T12:32:25.029569", - "last_update": "2026-02-27T12:32:29.487749" - }, - "张欣然2009": { - "customer_id": "张欣然2009", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取该中式地毯印花,去除地面背景,保留所有图案细节还原真实色彩|ratio:16:9|perspective:strong|proc_type:印花提取|subject:中式印花地毯图案|quality:清晰", - "complexity:complex|prompt:高清修复该截图,保留地毯花纹细节,优化阴影,去除多余界面元素|ratio:9:16|perspective:strong|proc_type:高清修复|subject:民族花纹装饰地毯、短视频界面|quality:截图", - "complexity:complex|prompt:提取该草原风景挂毯图案,去除水印和界面文字,保留色彩细节|ratio:16:9|perspective:mild|proc_type:图案提取|subject:风景挂毯图案|quality:截图", - "complexity:complex|prompt:提取该草原牧马挂毯印花,去除地板背景,保留细节输出平面图|ratio:16:9|proc_type:印花提取|subject:挂毯印花图案|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-27T12:57:41.812629", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i1/O1CN01F8MdIA2MyszInS0wV_!!4611686018427384345-0-amp.jpg", - "last_image_time": "2026-02-27T12:57:37.634954", - "last_gemini_prompt": "提取该草原牧马挂毯印花,去除地板背景,保留细节输出平面图", - "last_aspect_ratio": "16:9", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "尺寸1.6×3米", - "last_conversation_summary": "买家询问付款,客服指引操作。", - "last_conversation_time": "2026-02-27T12:58:34.831864", - "total_images_sent": 4, - "complexity_history": [ - "complex", - "complex", - "complex", - "complex" - ], - "image_type_history": [ - "中式印花地毯图案", - "民族花纹装饰地毯、短视频界面", - "风景挂毯图案", - "挂毯印花图案" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 12:40] 报价 60元(三张印花细节图打包处理)", - "[2026-02-27 12:40] 报价 40元(两张截图高清修复打包)", - "[2026-02-27 12:56] 报价 25元(单图处理,改尺寸1.6×3米)", - "[2026-02-27 12:57] 报价 25元(第二张单图处理,印花提取)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T12:58:26.122002", - "last_update": "2026-02-27T12:58:34.831864" - }, - "xx友邦xx": { - "customer_id": "xx友邦xx", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:高清修复该国风茶饮菜单,保留全部文字和装饰,保持原有风格|ratio:4:3|proc_type:高清修复|subject:国风茶饮饮品菜单海报|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], "communication_prefer": "", "response_speed": "", "patience_level": "", @@ -3076,36 +678,33 @@ "vip": false, "vip_level": 0, "last_price": 15, - "last_price_time": "2026-02-27T12:44:46.968099", + "last_price_time": "", "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i4/2238780260/O1CN01sxtBnY1Dn86Mxt4tJ_!!2238780260-2-ampmedia.png", - "last_image_time": "2026-02-27T12:44:39.359110", - "last_gemini_prompt": "高清修复该国风茶饮菜单,保留全部文字和装饰,保持原有风格", - "last_aspect_ratio": "4:3", + "last_min_price": 0, + "last_image_url": "", + "last_image_time": "", + "last_gemini_prompt": "", + "last_aspect_ratio": "1:1", "last_perspective": "no", "processing_status": "", "processing_image_url": "", "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", + "discount_given_count": 4, + "lowest_price_accepted": 15, + "preferred_format": "", "preferred_size": "", - "last_conversation_summary": "买家询问,客服承诺不满意可退", - "last_conversation_time": "2026-02-27T12:47:07.603409", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "国风茶饮饮品菜单海报" - ], - "price_sensitivity": "", - "decision_speed": "", + "last_conversation_summary": "", + "last_conversation_time": "", + "total_images_sent": 0, + "complexity_history": [], + "image_type_history": [], + "price_sensitivity": "高", + "decision_speed": "慢", "revision_count": 0, "revision_orders": 0, "total_completed_orders": 0, "bulk_potential": "", - "churn_risk": "低", + "churn_risk": "", "upsell_opportunity": [], "blacklist": false, "blacklist_reason": "", @@ -3119,17 +718,15 @@ "next_follow_date": "", "source": "", "coupon_used": "", - "notes": [ - "[2026-02-27 12:44] 报价 15元(单图高清修复)" - ], + "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-27T12:46:58.406933", - "last_update": "2026-02-27T12:47:07.603409" + "last_contact": "2026-02-28T15:29:05.722448", + "last_update": "2026-02-28T15:29:05.722454" }, - "t_1480389856809_0613": { - "customer_id": "t_1480389856809_0613", - "name": "", + "test_vip_004": { + "customer_id": "test_vip_004", + "name": "VIP 客户陈总", "nickname": "", "email": "", "phone": "", @@ -3142,8 +739,8 @@ "budget_range_max": 0, "requirements": [], "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, + "total_orders": 15, + "total_spent": 680, "avg_order_value": 0.0, "purchase_frequency": "", "last_order_date": "", @@ -3152,16 +749,19 @@ "pending_orders": 0, "completed_orders": 0, "refund_count": 0, - "personality": [], + "personality": [ + "爽快" + ], "communication_prefer": "", "response_speed": "", "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, + "customer_level": "A", + "vip": true, + "vip_level": 2, "last_price": 0, "last_price_time": "", "last_quote_no_convert": false, + "last_min_price": 0, "last_image_url": "", "last_image_time": "", "last_gemini_prompt": "", @@ -3174,22 +774,22 @@ "lowest_price_accepted": 0, "preferred_format": "", "preferred_size": "", - "last_conversation_summary": "买家应允后客服请其稍等", - "last_conversation_time": "2026-02-27T13:01:10.377629", + "last_conversation_summary": "", + "last_conversation_time": "", "total_images_sent": 0, "complexity_history": [], "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", + "price_sensitivity": "低", + "decision_speed": "快", "revision_count": 0, "revision_orders": 0, "total_completed_orders": 0, "bulk_potential": "", - "churn_risk": "低", + "churn_risk": "", "upsell_opportunity": [], "blacklist": false, "blacklist_reason": "", - "vip_custom_price": 0, + "vip_custom_price": 18, "last_email_status": "", "good_reviews": 0, "bad_reviews": 0, @@ -3202,98 +802,12 @@ "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-27T13:01:04.464866", - "last_update": "2026-02-27T13:01:10.377629" + "last_contact": "2026-02-28T15:29:05.723887", + "last_update": "2026-02-28T15:29:05.723890" }, - "轩5235": { - "customer_id": "轩5235", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:修复该调理海报,清晰化文字,保留配图,去除手机界面元素|ratio:9:16|proc_type:高清修复|subject:颈肩腰腿疼调理宣传海报|quality:截图" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-27T13:44:37.305553", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01VHbDsZ1KfzEYTBESf_!!4611686018427380792-0-amp.jpg", - "last_image_time": "2026-02-27T13:44:24.551737", - "last_gemini_prompt": "修复该调理海报,清晰化文字,保留配图,去除手机界面元素", - "last_aspect_ratio": "9:16", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "客服确认商品图告知拍下价格", - "last_conversation_time": "2026-02-27T13:44:50.013434", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "颈肩腰腿疼调理宣传海报" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 13:44] 报价 25元(单图高清修复(含人脸,细节偏多))" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T13:44:42.912813", - "last_update": "2026-02-27T13:44:50.013434" - }, - "tb941834206": { - "customer_id": "tb941834206", - "name": "", + "test_highvalue_005": { + "customer_id": "test_highvalue_005", + "name": "高价值客户刘老板", "nickname": "", "email": "", "phone": "", @@ -3306,9 +820,9 @@ "budget_range_max": 0, "requirements": [], "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, + "total_orders": 20, + "total_spent": 1200, + "avg_order_value": 60, "purchase_frequency": "", "last_order_date": "", "first_order_date": "", @@ -3316,16 +830,19 @@ "pending_orders": 0, "completed_orders": 0, "refund_count": 0, - "personality": [], + "personality": [ + "爽快" + ], "communication_prefer": "", "response_speed": "", "patience_level": "", - "customer_level": "C", + "customer_level": "A", "vip": false, "vip_level": 0, "last_price": 0, "last_price_time": "", "last_quote_no_convert": false, + "last_min_price": 0, "last_image_url": "", "last_image_time": "", "last_gemini_prompt": "", @@ -3338,18 +855,18 @@ "lowest_price_accepted": 0, "preferred_format": "", "preferred_size": "", - "last_conversation_summary": "买家应允后客服称马上办好", - "last_conversation_time": "2026-02-27T13:49:21.903069", + "last_conversation_summary": "", + "last_conversation_time": "", "total_images_sent": 0, "complexity_history": [], "image_type_history": [], - "price_sensitivity": "", - "decision_speed": "", + "price_sensitivity": "低", + "decision_speed": "快", "revision_count": 0, "revision_orders": 0, "total_completed_orders": 0, "bulk_potential": "", - "churn_risk": "低", + "churn_risk": "", "upsell_opportunity": [], "blacklist": false, "blacklist_reason": "", @@ -3366,445 +883,7 @@ "notes": [], "tags": [], "created_at": "", - "last_contact": "2026-02-27T13:49:13.035572", - "last_update": "2026-02-27T13:49:21.903069" - }, - "姜汶丰233": { - "customer_id": "姜汶丰233", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取该复古花卉印花,保留色彩细节,去除背景,输出干净平面图|ratio:1:1|proc_type:印花提取|subject:印花图案|quality:轻微模糊", - "complexity:complex|prompt:提取花鸟植物印花,去除背景,保留色彩细节,输出干净平面印花图|ratio:1:1|proc_type:印花提取|subject:印花图案|quality:轻微模糊" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 0, - "last_price_time": "", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i4/2949310257/O1CN018tGeFP1DlkuhIoyk7_!!2949310257-0-ampmedia.jpg", - "last_image_time": "2026-02-27T13:50:51.301023", - "last_gemini_prompt": "提取花鸟植物印花,去除背景,保留色彩细节,输出干净平面印花图", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家发图询价,客服报价回应", - "last_conversation_time": "2026-02-27T13:50:46.999728", - "total_images_sent": 2, - "complexity_history": [ - "complex", - "complex" - ], - "image_type_history": [ - "印花图案", - "印花图案" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T13:50:58.686838", - "last_update": "2026-02-27T13:50:58.706577" - }, - "栀夏明月2280": { - "customer_id": "栀夏明月2280", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:normal|prompt:保留原图宣传标语与插画,去除边缘黑框,优化画面效果|ratio:16:9|proc_type:其他|subject:宣传标语与休闲插画|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 15, - "last_price_time": "2026-02-27T14:37:08.910680", - "last_quote_no_convert": false, - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01WKkvzF2CSgzMRDlow_!!4611686018427381385-0-amp.jpg", - "last_image_time": "2026-02-27T14:36:56.631943", - "last_gemini_prompt": "保留原图宣传标语与插画,去除边缘黑框,优化画面效果", - "last_aspect_ratio": "16:9", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家无需文字,客服同意安排。", - "last_conversation_time": "2026-02-27T14:38:03.403170", - "total_images_sent": 1, - "complexity_history": [ - "normal" - ], - "image_type_history": [ - "宣传标语与休闲插画" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 14:37] 报价 15元(单图处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T14:37:56.489193", - "last_update": "2026-02-27T14:38:03.403170" - }, - "tb405958516": { - "customer_id": "tb405958516", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取该柴犬向日葵洞洞板印花,保留细节,去除背景杂物,还原清晰图案|ratio:3:4|perspective:mild|proc_type:印花提取|subject:柴犬向日葵洞洞板装饰印花|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-27T15:20:11.942171", - "last_quote_no_convert": false, - "last_min_price": 20, - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01iJ7h0K26nrbVUHLL7_!!4611686018427382091-0-amp.jpg", - "last_image_time": "2026-02-27T15:20:07.903491", - "last_gemini_prompt": "提取该柴犬向日葵洞洞板印花,保留细节,去除背景杂物,还原清晰图案", - "last_aspect_ratio": "3:4", - "last_perspective": "mild", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "jpg", - "preferred_size": "", - "last_conversation_summary": "买家要求正常比例客服回应安排", - "last_conversation_time": "2026-02-27T15:20:24.202009", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "柴犬向日葵洞洞板装饰印花" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 15:20] 报价 25元(单图印花提取)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T15:20:17.052679", - "last_update": "2026-02-27T15:20:24.202009" - }, - "tb01680130": { - "customer_id": "tb01680130", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:修复该板面广告海报,保留所有文字画面,去除墙面射灯背景|ratio:16:9|proc_type:其他|subject:牛肉板面宣传广告海报|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 25, - "last_price_time": "2026-02-27T15:27:38.577642", - "last_quote_no_convert": false, - "last_min_price": 20, - "last_image_url": "https://img.alicdn.com/imgextra/i2/O1CN01Z1AKVu1OBXMlpYf1r_!!4611686018427382051-2-amp.png", - "last_image_time": "2026-02-27T15:27:32.683507", - "last_gemini_prompt": "修复该板面广告海报,保留所有文字画面,去除墙面射灯背景", - "last_aspect_ratio": "16:9", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", - "preferred_size": "", - "last_conversation_summary": "买家发图询价,客服报价待拍", - "last_conversation_time": "2026-02-27T15:27:55.838970", - "total_images_sent": 1, - "complexity_history": [ - "complex" - ], - "image_type_history": [ - "牛肉板面宣传广告海报" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 15:27] 报价 25元(单图处理)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T15:27:45.569863", - "last_update": "2026-02-27T15:27:55.838970" - }, - "菠罗菠罗蜜20143": { - "customer_id": "菠罗菠罗蜜20143", - "name": "", - "nickname": "", - "email": "", - "phone": "", - "wechat": "", - "address": "", - "platform": "", - "platform_id": "", - "budget": "", - "budget_range_min": 0, - "budget_range_max": 0, - "requirements": [ - "complexity:complex|prompt:提取青花印花图案,去除背景与阴影,保留图案原有细节|ratio:1:1|proc_type:印花提取|subject:印花图案|quality:轻微模糊", - "complexity:complex|prompt:提取该孔雀花卉印花,去除背景、水印和阴影,保留细节还原色彩|ratio:1:1|proc_type:印花提取|subject:印花图案|quality:清晰" - ], - "preference_services": [], - "total_orders": 0, - "total_spent": 0.0, - "avg_order_value": 0.0, - "purchase_frequency": "", - "last_order_date": "", - "first_order_date": "", - "order_ids": [], - "pending_orders": 0, - "completed_orders": 0, - "refund_count": 0, - "personality": [], - "communication_prefer": "", - "response_speed": "", - "patience_level": "", - "customer_level": "C", - "vip": false, - "vip_level": 0, - "last_price": 35, - "last_price_time": "2026-02-27T15:49:26.196425", - "last_quote_no_convert": true, - "last_min_price": 20, - "last_image_url": "https://img.alicdn.com/imgextra/i1/1975036200/O1CN01MPhy7n1vfejU00FTF_!!1975036200-2-ampmedia.png", - "last_image_time": "2026-02-27T15:49:14.744270", - "last_gemini_prompt": "提取该孔雀花卉印花,去除背景、水印和阴影,保留细节还原色彩", - "last_aspect_ratio": "1:1", - "last_perspective": "no", - "processing_status": "", - "processing_image_url": "", - "expected_done_at": "", - "discount_given_count": 0, - "lowest_price_accepted": 0, - "preferred_format": "png", - "preferred_size": "分辨率高于300,? ? ? ?300mm", - "last_conversation_summary": "买家提议加QQ客服同意并索号", - "last_conversation_time": "2026-02-27T15:55:42.265292", - "total_images_sent": 2, - "complexity_history": [ - "complex", - "complex" - ], - "image_type_history": [ - "印花图案", - "印花图案" - ], - "price_sensitivity": "", - "decision_speed": "", - "revision_count": 0, - "revision_orders": 0, - "total_completed_orders": 0, - "bulk_potential": "", - "churn_risk": "低", - "upsell_opportunity": [], - "blacklist": false, - "blacklist_reason": "", - "vip_custom_price": 0, - "last_email_status": "", - "good_reviews": 0, - "bad_reviews": 0, - "dispute_count": 0, - "follow_up_by": "", - "follow_up_date": "", - "next_follow_date": "", - "source": "", - "coupon_used": "", - "notes": [ - "[2026-02-27 15:32] 报价 25元(单图印花提取处理)", - "[2026-02-27 15:49] 报价 35元(两张图印花提取打包)" - ], - "tags": [], - "created_at": "", - "last_contact": "2026-02-27T15:55:22.174045", - "last_update": "2026-02-27T15:55:42.265292" + "last_contact": "2026-02-28T15:29:05.725313", + "last_update": "2026-02-28T15:29:05.725316" } } \ No newline at end of file diff --git a/customer_db/schema.json b/customer_db/schema.json deleted file mode 100755 index ccd4d0f..0000000 --- a/customer_db/schema.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "_注释": "本文件是 customers.json 的字段说明,不参与程序运行,仅供人工查阅。", - "字段说明": { - "customer_id": "客户ID(平台用户名/ID)", - "name": "真实姓名", - "nickname": "昵称", - "email": "邮箱", - "phone": "手机号", - "wechat": "微信号", - "address": "收货地址", - "platform": "来源平台(淘宝/拼多多/微信等)", - "platform_id": "平台内部ID", - - "budget": "预算描述(客户自述)", - "budget_range_min": "预算下限(元)", - "budget_range_max": "预算上限(元)", - "requirements": "历史需求列表", - "preference_services": "偏好服务类型列表", - - "total_orders": "累计下单次数", - "total_spent": "累计消费金额(元)", - "avg_order_value": "平均客单价(元)", - "purchase_frequency": "购买频次描述", - "last_order_date": "最后下单日期", - "first_order_date": "首次下单日期", - "order_ids": "订单号列表", - "pending_orders": "待处理订单数", - "completed_orders": "已完成订单数", - "refund_count": "退款次数", - - "personality": "性格标签列表(如:急躁/爽快/纠结)", - "communication_prefer": "沟通偏好", - "response_speed": "回复速度描述", - "patience_level": "耐心程度", - - "customer_level": "客户等级(A/B/C/D)", - "vip": "是否VIP", - "vip_level": "VIP等级(0=无)", - "vip_custom_price": "VIP专属报价(0=无专属价)", - - "last_price": "上次报价金额(元)", - "last_price_time": "上次报价时间", - "last_quote_no_convert": "上次报价后未成交,下次可适当降低", - "lowest_price_accepted":"历史接受过的最低价(元)", - "discount_given_count": "累计让价次数", - "price_sensitivity": "价格敏感度(高/中/低,自动计算)", - "decision_speed": "决策速度(快/慢,自动标记)", - - "good_reviews": "好评次数", - "bad_reviews": "差评次数", - "dispute_count": "纠纷次数", - - "follow_up_by": "跟进负责人", - "follow_up_date": "跟进日期", - "next_follow_date": "下次跟进日期", - "source": "客户来源渠道", - "coupon_used": "使用过的优惠券", - - "notes": "备注列表(含自动报价记录)", - "tags": "自定义标签列表", - "created_at": "建档时间", - "last_contact": "最后联系时间", - "last_update": "最后更新时间", - - "last_image_url": "最后发来的图片URL", - "last_image_time": "最后发图时间", - "processing_status": "当前作图状态(pending/processing/done等)", - "processing_image_url": "当前处理中的图片URL", - "expected_done_at": "预计完成时间", - - "preferred_format": "偏好文件格式(jpg/png/psd等)", - "preferred_size": "偏好尺寸/分辨率描述", - "total_images_sent": "历史发图总数", - "complexity_history": "历史图片复杂度列表(normal/complex/hard)", - "image_type_history": "历史图片类型列表(印花/logo/人物等)", - - "bulk_potential": "批量潜力(有/无/未知)", - "churn_risk": "流失风险(高/中/低,自动计算)", - "upsell_opportunity": "加购机会标签(如:分层PSD/批量打包)", - "revision_count": "累计改稿次数", - "revision_orders": "有改稿的订单数", - "total_completed_orders":"已完成出图的订单数", - - "blacklist": "是否黑名单", - "blacklist_reason": "拉黑原因", - "last_email_status": "最后邮件发送状态(sent/failed)", - - "last_conversation_summary": "上次对话AI摘要(15字以内)", - "last_conversation_time": "上次对话时间" - } -} diff --git a/db/chat_log_db.py b/db/chat_log_db.py index b8d9b26..d900566 100755 --- a/db/chat_log_db.py +++ b/db/chat_log_db.py @@ -9,9 +9,33 @@ from datetime import datetime from typing import List, Dict, Optional _DB_PATH = os.path.join(os.path.dirname(__file__), "chat_log_db", "chats.db") +_DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() +_MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1") +_MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) +_MYSQL_USER = os.getenv("MYSQL_USER", "root") +_MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "") +_MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "ai_cs") + +def _is_mysql() -> bool: + return _DB_TYPE in ("mysql", "mariadb") + +def _sql(query: str) -> str: + return query.replace("?", "%s") if _is_mysql() else query def _get_conn() -> sqlite3.Connection: + if _is_mysql(): + import pymysql + return pymysql.connect( + host=_MYSQL_HOST, + port=_MYSQL_PORT, + user=_MYSQL_USER, + password=_MYSQL_PASSWORD, + database=_MYSQL_DATABASE, + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, + autocommit=False, + ) os.makedirs(os.path.dirname(_DB_PATH), exist_ok=True) conn = sqlite3.connect(_DB_PATH) conn.row_factory = sqlite3.Row @@ -21,27 +45,44 @@ def _get_conn() -> sqlite3.Connection: def init_db(): """建表(首次运行时自动调用)""" with _get_conn() as conn: - conn.execute(""" - CREATE TABLE IF NOT EXISTS chat_logs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - customer_id TEXT NOT NULL, - customer_name TEXT DEFAULT '', - acc_id TEXT DEFAULT '', - platform TEXT DEFAULT '', - direction TEXT NOT NULL CHECK(direction IN ('in','out')), - message TEXT NOT NULL, - msg_type INTEGER DEFAULT 0, - timestamp TEXT NOT NULL - ) - """) - conn.execute("CREATE INDEX IF NOT EXISTS idx_customer ON chat_logs(customer_id)") - conn.execute("CREATE INDEX IF NOT EXISTS idx_ts ON chat_logs(timestamp)") - # 兼容旧表:若缺少 acc_id 列则补上(必须在创建该列索引之前) - try: - conn.execute("ALTER TABLE chat_logs ADD COLUMN acc_id TEXT DEFAULT ''") - except Exception: - pass - conn.execute("CREATE INDEX IF NOT EXISTS idx_acc ON chat_logs(acc_id)") + if _is_mysql(): + conn.execute(""" + CREATE TABLE IF NOT EXISTS chat_logs ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + customer_id VARCHAR(128) NOT NULL, + customer_name VARCHAR(255) DEFAULT '', + acc_id VARCHAR(128) DEFAULT '', + platform VARCHAR(64) DEFAULT '', + direction VARCHAR(8) NOT NULL, + message TEXT NOT NULL, + msg_type INTEGER DEFAULT 0, + timestamp DATETIME NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_customer ON chat_logs(customer_id)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_ts ON chat_logs(timestamp)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_acc ON chat_logs(acc_id)") + else: + conn.execute(""" + CREATE TABLE IF NOT EXISTS chat_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + customer_id TEXT NOT NULL, + customer_name TEXT DEFAULT '', + acc_id TEXT DEFAULT '', + platform TEXT DEFAULT '', + direction TEXT NOT NULL CHECK(direction IN ('in','out')), + message TEXT NOT NULL, + msg_type INTEGER DEFAULT 0, + timestamp TEXT NOT NULL + ) + """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_customer ON chat_logs(customer_id)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_ts ON chat_logs(timestamp)") + try: + conn.execute("ALTER TABLE chat_logs ADD COLUMN acc_id TEXT DEFAULT ''") + except Exception: + pass + conn.execute("CREATE INDEX IF NOT EXISTS idx_acc ON chat_logs(acc_id)") conn.commit() @@ -63,9 +104,9 @@ def log_message( ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") with _get_conn() as conn: conn.execute( - "INSERT INTO chat_logs " - "(customer_id, customer_name, acc_id, platform, direction, message, msg_type, timestamp) " - "VALUES (?,?,?,?,?,?,?,?)", + _sql("INSERT INTO chat_logs " + "(customer_id, customer_name, acc_id, platform, direction, message, msg_type, timestamp) " + "VALUES (?,?,?,?,?,?,?,?)"), (customer_id, customer_name, acc_id, platform, direction, message, msg_type, ts), ) conn.commit() @@ -77,6 +118,19 @@ def get_customers(limit: int = 100) -> List[Dict]: """返回所有有记录的客户列表(按最新消息时间排序)""" with _get_conn() as conn: rows = conn.execute(""" + SELECT + customer_id, + MAX(customer_name) AS customer_name, + MAX(platform) AS platform, + COUNT(*) AS total_msgs, + SUM(direction='in') AS recv, + SUM(direction='out') AS sent, + MAX(timestamp) AS last_time + FROM chat_logs + GROUP BY customer_id + ORDER BY last_time DESC + LIMIT %s + """ if _is_mysql() else """ SELECT customer_id, MAX(customer_name) AS customer_name, @@ -96,13 +150,13 @@ def get_customers(limit: int = 100) -> List[Dict]: def get_conversation(customer_id: str, limit: int = 200) -> List[Dict]: """返回某客户的全部对话记录(按时间升序)""" with _get_conn() as conn: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT id, direction, message, msg_type, timestamp, acc_id FROM chat_logs WHERE customer_id = ? ORDER BY timestamp ASC, id ASC LIMIT ? - """, (customer_id, limit)).fetchall() + """), (customer_id, limit)).fetchall() return [dict(r) for r in rows] @@ -110,21 +164,21 @@ def get_recent_conversation(customer_id: str, acc_id: str = "", limit: int = 10) """返回某客户近期对话(同店铺),用于企微推送保持连贯""" with _get_conn() as conn: if acc_id: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT id, direction, message, timestamp, acc_id FROM chat_logs WHERE customer_id = ? AND acc_id = ? ORDER BY id DESC LIMIT ? - """, (customer_id, acc_id, limit)).fetchall() + """), (customer_id, acc_id, limit)).fetchall() else: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT id, direction, message, timestamp, acc_id FROM chat_logs WHERE customer_id = ? ORDER BY id DESC LIMIT ? - """, (customer_id, limit)).fetchall() + """), (customer_id, limit)).fetchall() out = [dict(r) for r in reversed(rows)] return out @@ -133,12 +187,12 @@ def get_conversation_today(customer_id: str) -> List[Dict]: """返回某客户今天的对话""" today = datetime.now().strftime("%Y-%m-%d") with _get_conn() as conn: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT id, direction, message, msg_type, timestamp FROM chat_logs WHERE customer_id = ? AND timestamp LIKE ? ORDER BY timestamp ASC, id ASC - """, (customer_id, f"{today}%")).fetchall() + """), (customer_id, f"{today}%")).fetchall() return [dict(r) for r in rows] @@ -151,7 +205,7 @@ def get_daily_stats(date: str = "") -> List[Dict]: if not date: date = datetime.now().strftime("%Y-%m-%d") with _get_conn() as conn: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT acc_id, platform, @@ -165,7 +219,7 @@ def get_daily_stats(date: str = "") -> List[Dict]: WHERE timestamp LIKE ? GROUP BY acc_id ORDER BY unique_customers DESC - """, (f"{date}%",)).fetchall() + """), (f"{date}%",)).fetchall() return [dict(r) for r in rows] @@ -176,22 +230,39 @@ def get_daily_conversations(date: str = "") -> List[Dict]: if not date: date = datetime.now().strftime("%Y-%m-%d") with _get_conn() as conn: - rows = conn.execute(""" - SELECT - acc_id, - customer_id, - MAX(customer_name) AS customer_name, - COUNT(*) AS msg_count, - GROUP_CONCAT( - CASE WHEN direction='in' THEN '买:' || SUBSTR(message,1,40) - ELSE '客:' || SUBSTR(message,1,40) END, - ' | ' - ) AS snippet - FROM chat_logs - WHERE timestamp LIKE ? - GROUP BY acc_id, customer_id - ORDER BY acc_id, MAX(timestamp) DESC - """, (f"{date}%",)).fetchall() + if _is_mysql(): + rows = conn.execute(_sql(""" + SELECT + acc_id, + customer_id, + MAX(customer_name) AS customer_name, + COUNT(*) AS msg_count, + GROUP_CONCAT( + CONCAT(CASE WHEN direction='in' THEN '买:' ELSE '客:' END, LEFT(message,40)) + SEPARATOR ' | ' + ) AS snippet + FROM chat_logs + WHERE timestamp LIKE ? + GROUP BY acc_id, customer_id + ORDER BY acc_id, MAX(timestamp) DESC + """), (f"{date}%",)).fetchall() + else: + rows = conn.execute(_sql(""" + SELECT + acc_id, + customer_id, + MAX(customer_name) AS customer_name, + COUNT(*) AS msg_count, + GROUP_CONCAT( + CASE WHEN direction='in' THEN '买:' || SUBSTR(message,1,40) + ELSE '客:' || SUBSTR(message,1,40) END, + ' | ' + ) AS snippet + FROM chat_logs + WHERE timestamp LIKE ? + GROUP BY acc_id, customer_id + ORDER BY acc_id, MAX(timestamp) DESC + """), (f"{date}%",)).fetchall() return [dict(r) for r in rows] @@ -199,18 +270,28 @@ def search_messages(keyword: str, customer_id: Optional[str] = None, limit: int """全文搜索消息""" if customer_id: with _get_conn() as conn: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT customer_id, customer_name, direction, message, timestamp FROM chat_logs WHERE customer_id = ? AND message LIKE ? ORDER BY timestamp DESC LIMIT ? - """, (customer_id, f"%{keyword}%", limit)).fetchall() + """), (customer_id, f"%{keyword}%", limit)).fetchall() else: with _get_conn() as conn: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT customer_id, customer_name, direction, message, timestamp FROM chat_logs WHERE message LIKE ? ORDER BY timestamp DESC LIMIT ? - """, (f"%{keyword}%", limit)).fetchall() + """), (f"%{keyword}%", limit)).fetchall() + return [dict(r) for r in rows] + + +def get_latest_messages(limit: int = 20) -> List[Dict]: + with _get_conn() as conn: + rows = conn.execute(_sql(""" + SELECT id, customer_id, customer_name, direction, message, timestamp + FROM chat_logs + ORDER BY id DESC LIMIT ? + """), (limit,)).fetchall() return [dict(r) for r in rows] diff --git a/db/deal_outcome_db.py b/db/deal_outcome_db.py index 0c8a652..8c377b1 100755 --- a/db/deal_outcome_db.py +++ b/db/deal_outcome_db.py @@ -8,9 +8,33 @@ from datetime import datetime from typing import List, Dict, Optional _DB_PATH = os.path.join(os.path.dirname(__file__), "deal_outcome_db", "outcomes.db") +_DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() +_MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1") +_MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) +_MYSQL_USER = os.getenv("MYSQL_USER", "root") +_MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "") +_MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "ai_cs") + +def _is_mysql() -> bool: + return _DB_TYPE in ("mysql", "mariadb") + +def _sql(query: str) -> str: + return query.replace("?", "%s") if _is_mysql() else query def _get_conn() -> sqlite3.Connection: + if _is_mysql(): + import pymysql + return pymysql.connect( + host=_MYSQL_HOST, + port=_MYSQL_PORT, + user=_MYSQL_USER, + password=_MYSQL_PASSWORD, + database=_MYSQL_DATABASE, + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, + autocommit=False, + ) os.makedirs(os.path.dirname(_DB_PATH), exist_ok=True) conn = sqlite3.connect(_DB_PATH) conn.row_factory = sqlite3.Row @@ -19,26 +43,48 @@ def _get_conn() -> sqlite3.Connection: def _init_db(): with _get_conn() as conn: - conn.execute(""" - CREATE TABLE IF NOT EXISTS deal_outcomes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - customer_id TEXT NOT NULL, - customer_name TEXT DEFAULT '', - acc_id TEXT DEFAULT '', - platform TEXT DEFAULT '', - date TEXT NOT NULL, - outcome TEXT NOT NULL CHECK(outcome IN ('成交','未成交')), - reason TEXT DEFAULT '', - order_id TEXT DEFAULT '', - amount REAL DEFAULT 0, - discount_given INTEGER DEFAULT 0, - timestamp TEXT NOT NULL - ) - """) - conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_date ON deal_outcomes(date)") - conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_customer ON deal_outcomes(customer_id)") - conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_acc ON deal_outcomes(acc_id)") - conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_outcome ON deal_outcomes(outcome)") + if _is_mysql(): + conn.execute(""" + CREATE TABLE IF NOT EXISTS deal_outcomes ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + customer_id VARCHAR(128) NOT NULL, + customer_name VARCHAR(255) DEFAULT '', + acc_id VARCHAR(128) DEFAULT '', + platform VARCHAR(64) DEFAULT '', + date DATE NOT NULL, + outcome VARCHAR(16) NOT NULL, + reason TEXT, + order_id VARCHAR(128) DEFAULT '', + amount REAL DEFAULT 0, + discount_given INTEGER DEFAULT 0, + timestamp DATETIME NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_date ON deal_outcomes(date)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_customer ON deal_outcomes(customer_id)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_acc ON deal_outcomes(acc_id)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_outcome ON deal_outcomes(outcome)") + else: + conn.execute(""" + CREATE TABLE IF NOT EXISTS deal_outcomes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + customer_id TEXT NOT NULL, + customer_name TEXT DEFAULT '', + acc_id TEXT DEFAULT '', + platform TEXT DEFAULT '', + date TEXT NOT NULL, + outcome TEXT NOT NULL CHECK(outcome IN ('成交','未成交')), + reason TEXT DEFAULT '', + order_id TEXT DEFAULT '', + amount REAL DEFAULT 0, + discount_given INTEGER DEFAULT 0, + timestamp TEXT NOT NULL + ) + """) + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_date ON deal_outcomes(date)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_customer ON deal_outcomes(customer_id)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_acc ON deal_outcomes(acc_id)") + conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_outcome ON deal_outcomes(outcome)") conn.commit() @@ -61,10 +107,10 @@ def record_deal( date = datetime.now().strftime("%Y-%m-%d") with _get_conn() as conn: conn.execute( - """INSERT INTO deal_outcomes + _sql("""INSERT INTO deal_outcomes (customer_id, customer_name, acc_id, platform, date, outcome, reason, order_id, amount, discount_given, timestamp) - VALUES (?,?,?,?,?,?,?,?,?,?,?)""", + VALUES (?,?,?,?,?,?,?,?,?,?,?)"""), ( customer_id, customer_name or "", @@ -88,13 +134,13 @@ def get_daily_outcomes(date: str = "") -> List[Dict]: date = datetime.now().strftime("%Y-%m-%d") with _get_conn() as conn: rows = conn.execute( - """ + _sql(""" SELECT customer_id, customer_name, acc_id, outcome, reason, order_id, amount, discount_given, timestamp FROM deal_outcomes WHERE date = ? ORDER BY timestamp ASC - """, + """), (date,), ).fetchall() return [dict(r) for r in rows] @@ -131,19 +177,19 @@ def export_for_analysis(start_date: str = "", end_date: str = "") -> List[Dict]: with _get_conn() as conn: if start_date and end_date: rows = conn.execute( - """SELECT * FROM deal_outcomes + _sql("""SELECT * FROM deal_outcomes WHERE date BETWEEN ? AND ? - ORDER BY date, timestamp""", + ORDER BY date, timestamp"""), (start_date, end_date), ).fetchall() elif start_date: rows = conn.execute( - """SELECT * FROM deal_outcomes WHERE date >= ? ORDER BY date, timestamp""", + _sql("""SELECT * FROM deal_outcomes WHERE date >= ? ORDER BY date, timestamp"""), (start_date,), ).fetchall() elif end_date: rows = conn.execute( - """SELECT * FROM deal_outcomes WHERE date <= ? ORDER BY date, timestamp""", + _sql("""SELECT * FROM deal_outcomes WHERE date <= ? ORDER BY date, timestamp"""), (end_date,), ).fetchall() else: diff --git a/db/designer_roster_db.py b/db/designer_roster_db.py index 76004ae..8eab246 100755 --- a/db/designer_roster_db.py +++ b/db/designer_roster_db.py @@ -10,9 +10,33 @@ import os from typing import Optional _DB_PATH = os.path.join(os.path.dirname(__file__), "designer_roster_db", "roster.db") +_DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() +_MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1") +_MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) +_MYSQL_USER = os.getenv("MYSQL_USER", "root") +_MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "") +_MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "ai_cs") + +def _is_mysql() -> bool: + return _DB_TYPE in ("mysql", "mariadb") + +def _sql(query: str) -> str: + return query.replace("?", "%s") if _is_mysql() else query def _get_conn() -> sqlite3.Connection: + if _is_mysql(): + import pymysql + return pymysql.connect( + host=_MYSQL_HOST, + port=_MYSQL_PORT, + user=_MYSQL_USER, + password=_MYSQL_PASSWORD, + database=_MYSQL_DATABASE, + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, + autocommit=False, + ) os.makedirs(os.path.dirname(_DB_PATH), exist_ok=True) conn = sqlite3.connect(_DB_PATH) conn.row_factory = sqlite3.Row @@ -21,35 +45,66 @@ def _get_conn() -> sqlite3.Connection: def init_db(): with _get_conn() as conn: - conn.execute(""" - CREATE TABLE IF NOT EXISTS designers ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - wechat_user_id TEXT UNIQUE NOT NULL - ) - """) - conn.execute(""" - CREATE TABLE IF NOT EXISTS designer_shops ( - designer_id INTEGER NOT NULL, - shop_id TEXT NOT NULL, - group_id TEXT NOT NULL, - PRIMARY KEY (designer_id, shop_id), - FOREIGN KEY (designer_id) REFERENCES designers(id) - ) - """) - conn.execute(""" - CREATE TABLE IF NOT EXISTS designer_online ( - wechat_user_id TEXT PRIMARY KEY, - is_online INTEGER NOT NULL DEFAULT 0, - updated_at TEXT - ) - """) - conn.execute(""" - CREATE TABLE IF NOT EXISTS round_robin ( - shop_id TEXT PRIMARY KEY, - last_index INTEGER NOT NULL DEFAULT 0 - ) - """) + if _is_mysql(): + conn.execute(""" + CREATE TABLE IF NOT EXISTS designers ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + wechat_user_id VARCHAR(128) UNIQUE NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS designer_shops ( + designer_id INTEGER NOT NULL, + shop_id VARCHAR(128) NOT NULL, + group_id VARCHAR(128) NOT NULL, + PRIMARY KEY (designer_id, shop_id), + FOREIGN KEY (designer_id) REFERENCES designers(id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS designer_online ( + wechat_user_id VARCHAR(128) PRIMARY KEY, + is_online INTEGER NOT NULL DEFAULT 0, + updated_at DATETIME + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS round_robin ( + shop_id VARCHAR(128) PRIMARY KEY, + last_index INTEGER NOT NULL DEFAULT 0 + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """) + else: + conn.execute(""" + CREATE TABLE IF NOT EXISTS designers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + wechat_user_id TEXT UNIQUE NOT NULL + ) + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS designer_shops ( + designer_id INTEGER NOT NULL, + shop_id TEXT NOT NULL, + group_id TEXT NOT NULL, + PRIMARY KEY (designer_id, shop_id), + FOREIGN KEY (designer_id) REFERENCES designers(id) + ) + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS designer_online ( + wechat_user_id TEXT PRIMARY KEY, + is_online INTEGER NOT NULL DEFAULT 0, + updated_at TEXT + ) + """) + conn.execute(""" + CREATE TABLE IF NOT EXISTS round_robin ( + shop_id TEXT PRIMARY KEY, + last_index INTEGER NOT NULL DEFAULT 0 + ) + """) conn.commit() @@ -61,22 +116,34 @@ init_db() def add_designer(name: str, wechat_user_id: str) -> int: """添加设计师,返回 id""" with _get_conn() as conn: - conn.execute( - "INSERT OR IGNORE INTO designers (name, wechat_user_id) VALUES (?, ?)", - (name, wechat_user_id), - ) + if _is_mysql(): + conn.execute( + "INSERT IGNORE INTO designers (name, wechat_user_id) VALUES (%s, %s)", + (name, wechat_user_id), + ) + else: + conn.execute( + "INSERT OR IGNORE INTO designers (name, wechat_user_id) VALUES (?, ?)", + (name, wechat_user_id), + ) conn.commit() - row = conn.execute("SELECT id FROM designers WHERE wechat_user_id = ?", (wechat_user_id,)).fetchone() + row = conn.execute(_sql("SELECT id FROM designers WHERE wechat_user_id = ?"), (wechat_user_id,)).fetchone() return row["id"] if row else 0 def set_designer_shop(designer_id: int, shop_id: str, group_id: str): """设置设计师在某店铺的分组 ID(同一设计师不同店铺不同 group_id)""" with _get_conn() as conn: - conn.execute( - "INSERT OR REPLACE INTO designer_shops (designer_id, shop_id, group_id) VALUES (?, ?, ?)", - (designer_id, shop_id, group_id), - ) + if _is_mysql(): + conn.execute( + "REPLACE INTO designer_shops (designer_id, shop_id, group_id) VALUES (%s, %s, %s)", + (designer_id, shop_id, group_id), + ) + else: + conn.execute( + "INSERT OR REPLACE INTO designer_shops (designer_id, shop_id, group_id) VALUES (?, ?, ?)", + (designer_id, shop_id, group_id), + ) conn.commit() @@ -85,10 +152,16 @@ def update_online(wechat_user_id: str, is_online: bool): from datetime import datetime ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S") with _get_conn() as conn: - conn.execute( - "INSERT OR REPLACE INTO designer_online (wechat_user_id, is_online, updated_at) VALUES (?, ?, ?)", - (wechat_user_id, 1 if is_online else 0, ts), - ) + if _is_mysql(): + conn.execute( + "REPLACE INTO designer_online (wechat_user_id, is_online, updated_at) VALUES (%s, %s, %s)", + (wechat_user_id, 1 if is_online else 0, ts), + ) + else: + conn.execute( + "INSERT OR REPLACE INTO designer_online (wechat_user_id, is_online, updated_at) VALUES (?, ?, ?)", + (wechat_user_id, 1 if is_online else 0, ts), + ) conn.commit() @@ -101,26 +174,32 @@ def get_transfer_group_for_shop(shop_id: str) -> Optional[str]: 无人在线则返回 None。 """ with _get_conn() as conn: - rows = conn.execute(""" + rows = conn.execute(_sql(""" SELECT d.wechat_user_id, ds.group_id FROM designer_shops ds JOIN designers d ON d.id = ds.designer_id JOIN designer_online o ON o.wechat_user_id = d.wechat_user_id AND o.is_online = 1 WHERE ds.shop_id = ? - """, (shop_id,)).fetchall() + """), (shop_id,)).fetchall() if not rows: return None with _get_conn() as conn: - rr = conn.execute("SELECT last_index FROM round_robin WHERE shop_id = ?", (shop_id,)).fetchone() + rr = conn.execute(_sql("SELECT last_index FROM round_robin WHERE shop_id = ?"), (shop_id,)).fetchone() last = rr["last_index"] if rr else 0 idx = last % len(rows) chosen = rows[idx] - conn.execute( - "INSERT OR REPLACE INTO round_robin (shop_id, last_index) VALUES (?, ?)", - (shop_id, idx + 1), - ) + if _is_mysql(): + conn.execute( + "REPLACE INTO round_robin (shop_id, last_index) VALUES (%s, %s)", + (shop_id, idx + 1), + ) + else: + conn.execute( + "INSERT OR REPLACE INTO round_robin (shop_id, last_index) VALUES (?, ?)", + (shop_id, idx + 1), + ) conn.commit() return chosen["group_id"] @@ -142,11 +221,11 @@ def list_designers(): result = [] for d in designers: shops = conn.execute( - "SELECT shop_id, group_id FROM designer_shops WHERE designer_id = ?", + _sql("SELECT shop_id, group_id FROM designer_shops WHERE designer_id = ?"), (d["id"],), ).fetchall() online = conn.execute( - "SELECT is_online FROM designer_online WHERE wechat_user_id = ?", + _sql("SELECT is_online FROM designer_online WHERE wechat_user_id = ?"), (d["wechat_user_id"],), ).fetchone() result.append({ diff --git a/db/image_tasks_db.py b/db/image_tasks_db.py index e3e774e..2b1fbe9 100644 --- a/db/image_tasks_db.py +++ b/db/image_tasks_db.py @@ -10,8 +10,26 @@ from typing import Optional, List, Dict from pathlib import Path from datetime import datetime from enum import Enum +import os logger = logging.getLogger(__name__) +_DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() +_MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1") +_MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) +_MYSQL_USER = os.getenv("MYSQL_USER", "root") +_MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "") +_MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "ai_cs") + +def _is_mysql() -> bool: + return _DB_TYPE in ("mysql", "mariadb") + +def _sql(query: str) -> str: + return query.replace("?", "%s") if _is_mysql() else query + +def _now_str() -> str: + if _is_mysql(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") + return datetime.now().isoformat() class TaskStatus(Enum): """任务状态""" @@ -36,61 +54,105 @@ class ImageTaskManager: def _init_db(self): """初始化数据库""" - self.db_path.parent.mkdir(parents=True, exist_ok=True) - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - # 创建图片任务表 - cursor.execute(''' - CREATE TABLE IF NOT EXISTS image_tasks ( - task_id TEXT PRIMARY KEY, - customer_id TEXT NOT NULL, - customer_name TEXT, - original_image TEXT NOT NULL, - operation TEXT DEFAULT 'enhance', - requirements TEXT, -- JSON 格式:复杂度、比例、透视等 - customer_notes TEXT, -- 客户备注/需求细节 - status TEXT DEFAULT 'pending', - created_at TEXT, - paid_at TEXT, - started_at TEXT, - completed_at TEXT, - result_image TEXT, - error_message TEXT, - retry_count INTEGER DEFAULT 0, - - -- 店铺信息 - acc_id TEXT, - acc_type TEXT DEFAULT 'AliWorkbench' - ) - ''') - - # 创建需求变更记录表(支持客户后续增加需求) - cursor.execute(''' - CREATE TABLE IF NOT EXISTS task_requirement_changes ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - task_id TEXT NOT NULL, - change_type TEXT, -- add_note/modify_operation/add_requirement - old_value TEXT, - new_value TEXT, - changed_at TEXT, - changed_by TEXT, -- customer/staff - FOREIGN KEY (task_id) REFERENCES image_tasks(task_id) - ) - ''') - - # 创建索引 - cursor.execute('CREATE INDEX IF NOT EXISTS idx_customer ON image_tasks(customer_id)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_status ON image_tasks(status)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_created ON image_tasks(created_at)') - - conn.commit() - conn.close() + if _is_mysql(): + conn = self._get_conn() + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS image_tasks ( + task_id VARCHAR(128) PRIMARY KEY, + customer_id VARCHAR(128) NOT NULL, + customer_name VARCHAR(255), + original_image TEXT NOT NULL, + operation VARCHAR(64) DEFAULT 'enhance', + requirements TEXT, + customer_notes TEXT, + status VARCHAR(32) DEFAULT 'pending', + created_at DATETIME, + paid_at DATETIME, + started_at DATETIME, + completed_at DATETIME, + result_image TEXT, + error_message TEXT, + retry_count INT DEFAULT 0, + acc_id VARCHAR(128), + acc_type VARCHAR(64) DEFAULT 'AliWorkbench' + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS task_requirement_changes ( + id INTEGER PRIMARY KEY AUTO_INCREMENT, + task_id VARCHAR(128) NOT NULL, + change_type VARCHAR(64), + old_value TEXT, + new_value TEXT, + changed_at DATETIME, + changed_by VARCHAR(32), + FOREIGN KEY (task_id) REFERENCES image_tasks(task_id) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + ''') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_customer ON image_tasks(customer_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_status ON image_tasks(status)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_created ON image_tasks(created_at)') + conn.commit() + conn.close() + else: + self.db_path.parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS image_tasks ( + task_id TEXT PRIMARY KEY, + customer_id TEXT NOT NULL, + customer_name TEXT, + original_image TEXT NOT NULL, + operation TEXT DEFAULT 'enhance', + requirements TEXT, + customer_notes TEXT, + status TEXT DEFAULT 'pending', + created_at TEXT, + paid_at TEXT, + started_at TEXT, + completed_at TEXT, + result_image TEXT, + error_message TEXT, + retry_count INTEGER DEFAULT 0, + acc_id TEXT, + acc_type TEXT DEFAULT 'AliWorkbench' + ) + ''') + cursor.execute(''' + CREATE TABLE IF NOT EXISTS task_requirement_changes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id TEXT NOT NULL, + change_type TEXT, + old_value TEXT, + new_value TEXT, + changed_at TEXT, + changed_by TEXT, + FOREIGN KEY (task_id) REFERENCES image_tasks(task_id) + ) + ''') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_customer ON image_tasks(customer_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_status ON image_tasks(status)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_created ON image_tasks(created_at)') + conn.commit() + conn.close() logger.info("数据库表初始化完成") def _get_conn(self): """获取数据库连接""" + if _is_mysql(): + import pymysql + return pymysql.connect( + host=_MYSQL_HOST, + port=_MYSQL_PORT, + user=_MYSQL_USER, + password=_MYSQL_PASSWORD, + database=_MYSQL_DATABASE, + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, + autocommit=False, + ) conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn @@ -105,13 +167,13 @@ class ImageTaskManager: requirements_json = json.dumps(requirements) if requirements else None - cursor.execute(''' + cursor.execute(_sql(''' INSERT INTO image_tasks ( task_id, customer_id, customer_name, original_image, operation, requirements, customer_notes, status, created_at, acc_id, acc_type ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', ( + '''), ( task_id, customer_id, customer_name, @@ -120,7 +182,7 @@ class ImageTaskManager: requirements_json, '', # 初始备注为空 TaskStatus.PENDING.value, - datetime.now().isoformat(), + _now_str(), acc_id, acc_type )) @@ -141,7 +203,7 @@ class ImageTaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute('SELECT * FROM image_tasks WHERE task_id = ?', (task_id,)) + cursor.execute(_sql('SELECT * FROM image_tasks WHERE task_id = ?'), (task_id,)) row = cursor.fetchone() conn.close() @@ -164,17 +226,17 @@ class ImageTaskManager: cursor = conn.cursor() if status: - cursor.execute(''' + cursor.execute(_sql(''' SELECT * FROM image_tasks WHERE customer_id = ? AND status = ? ORDER BY created_at DESC - ''', (customer_id, status)) + '''), (customer_id, status)) else: - cursor.execute(''' + cursor.execute(_sql(''' SELECT * FROM image_tasks WHERE customer_id = ? ORDER BY created_at DESC - ''', (customer_id,)) + '''), (customer_id,)) rows = cursor.fetchall() conn.close() @@ -198,27 +260,28 @@ class ImageTaskManager: conn = self._get_conn() cursor = conn.cursor() - updates = ['status = ?'] + placeholder = "%s" if _is_mysql() else "?" + updates = [f'status = {placeholder}'] params = [status.value] # 根据状态设置时间 if status == TaskStatus.PAID: - updates.append('paid_at = ?') - params.append(datetime.now().isoformat()) + updates.append(f'paid_at = {placeholder}') + params.append(_now_str()) elif status == TaskStatus.PROCESSING: - updates.append('started_at = ?') - params.append(datetime.now().isoformat()) + updates.append(f'started_at = {placeholder}') + params.append(_now_str()) elif status in [TaskStatus.COMPLETED, TaskStatus.FAILED]: - updates.append('completed_at = ?') - params.append(datetime.now().isoformat()) + updates.append(f'completed_at = {placeholder}') + params.append(_now_str()) params.append(task_id) - cursor.execute(f''' + cursor.execute(_sql(f''' UPDATE image_tasks SET {', '.join(updates)} WHERE task_id = ? - ''', params) + '''), params) conn.commit() conn.close() @@ -234,11 +297,11 @@ class ImageTaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute(''' + cursor.execute(_sql(''' UPDATE image_tasks SET result_image = ?, error_message = ? WHERE task_id = ? - ''', (result_image, error_message, task_id)) + '''), (result_image, error_message, task_id)) conn.commit() conn.close() @@ -265,30 +328,30 @@ class ImageTaskManager: cursor = conn.cursor() # 获取旧备注 - cursor.execute('SELECT customer_notes FROM image_tasks WHERE task_id = ?', (task_id,)) + cursor.execute(_sql('SELECT customer_notes FROM image_tasks WHERE task_id = ?'), (task_id,)) row = cursor.fetchone() old_note = row['customer_notes'] if row else '' # 更新备注 new_note = f"{old_note}\n[{datetime.now().strftime('%m-%d %H:%M')}] {note}" if old_note else f"[{datetime.now().strftime('%m-%d %H:%M')}] {note}" - cursor.execute(''' + cursor.execute(_sql(''' UPDATE image_tasks SET customer_notes = ? WHERE task_id = ? - ''', (new_note, task_id)) + '''), (new_note, task_id)) # 记录变更历史 - cursor.execute(''' + cursor.execute(_sql(''' INSERT INTO task_requirement_changes ( task_id, change_type, old_value, new_value, changed_at, changed_by ) VALUES (?, ?, ?, ?, ?, ?) - ''', ( + '''), ( task_id, 'add_note', old_note or '无', note, - datetime.now().isoformat(), + _now_str(), changed_by )) @@ -319,28 +382,28 @@ class ImageTaskManager: cursor = conn.cursor() # 获取旧操作 - cursor.execute('SELECT operation FROM image_tasks WHERE task_id = ?', (task_id,)) + cursor.execute(_sql('SELECT operation FROM image_tasks WHERE task_id = ?'), (task_id,)) row = cursor.fetchone() old_operation = row['operation'] if row else '' # 更新操作 - cursor.execute(''' + cursor.execute(_sql(''' UPDATE image_tasks SET operation = ? WHERE task_id = ? - ''', (new_operation, task_id)) + '''), (new_operation, task_id)) # 记录变更历史 - cursor.execute(''' + cursor.execute(_sql(''' INSERT INTO task_requirement_changes ( task_id, change_type, old_value, new_value, changed_at, changed_by ) VALUES (?, ?, ?, ?, ?, ?) - ''', ( + '''), ( task_id, 'modify_operation', old_operation, new_operation, - datetime.now().isoformat(), + _now_str(), changed_by )) @@ -360,11 +423,11 @@ class ImageTaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute(''' + cursor.execute(_sql(''' SELECT * FROM task_requirement_changes WHERE task_id = ? ORDER BY changed_at DESC - ''', (task_id,)) + '''), (task_id,)) rows = cursor.fetchall() conn.close() @@ -385,13 +448,13 @@ class ImageTaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute(''' + cursor.execute(_sql(''' UPDATE image_tasks SET retry_count = retry_count + 1 WHERE task_id = ? - ''', (task_id,)) + '''), (task_id,)) - cursor.execute('SELECT retry_count FROM image_tasks WHERE task_id = ?', (task_id,)) + cursor.execute(_sql('SELECT retry_count FROM image_tasks WHERE task_id = ?'), (task_id,)) row = cursor.fetchone() conn.close() diff --git a/db/task_db/task_model.py b/db/task_db/task_model.py index 697b199..84c4a49 100644 --- a/db/task_db/task_model.py +++ b/db/task_db/task_model.py @@ -9,8 +9,26 @@ from datetime import datetime, timedelta from typing import Optional, Dict, List from pathlib import Path from enum import Enum +import os logger = logging.getLogger(__name__) +_DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower() +_MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1") +_MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306")) +_MYSQL_USER = os.getenv("MYSQL_USER", "root") +_MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "") +_MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "ai_cs") + +def _is_mysql() -> bool: + return _DB_TYPE in ("mysql", "mariadb") + +def _sql(query: str) -> str: + return query.replace("?", "%s") if _is_mysql() else query + +def _now_str() -> str: + if _is_mysql(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") + return datetime.now().isoformat() class TaskStatus(Enum): """任务状态""" @@ -40,51 +58,93 @@ class TaskManager: def _init_db(self): """初始化数据库""" - self.db_path.parent.mkdir(parents=True, exist_ok=True) - - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - # 创建任务表 - cursor.execute(''' - CREATE TABLE IF NOT EXISTS tasks ( - task_id TEXT PRIMARY KEY, - specified_customer_id TEXT, - specified_customer_name TEXT, - type TEXT NOT NULL, - customer_name TEXT, - customer_id TEXT, - trigger_type TEXT, - trigger_keyword TEXT, - trigger_keywords TEXT, -- JSON array - action_type TEXT, - action_file_url TEXT, - action_message TEXT, - priority TEXT DEFAULT 'normal', - timeout_hours INTEGER DEFAULT 24, - status TEXT DEFAULT 'pending', - retry_count INTEGER DEFAULT 0, - max_retry INTEGER DEFAULT 3, - created_at TEXT, - created_by TEXT, - triggered_at TEXT, - completed_at TEXT, - error_message TEXT, - result TEXT -- JSON - ) - ''') - - # 创建索引 - cursor.execute('CREATE INDEX IF NOT EXISTS idx_status ON tasks(status)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_customer ON tasks(customer_id)') - cursor.execute('CREATE INDEX IF NOT EXISTS idx_created ON tasks(created_at)') - - conn.commit() - conn.close() + if _is_mysql(): + conn = self._get_conn() + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS tasks ( + task_id VARCHAR(128) PRIMARY KEY, + specified_customer_id VARCHAR(128), + specified_customer_name VARCHAR(255), + type VARCHAR(64) NOT NULL, + customer_name VARCHAR(255), + customer_id VARCHAR(128), + trigger_type VARCHAR(64), + trigger_keyword VARCHAR(255), + trigger_keywords TEXT, + action_type VARCHAR(64), + action_file_url TEXT, + action_message TEXT, + priority VARCHAR(16) DEFAULT 'normal', + timeout_hours INT DEFAULT 24, + status VARCHAR(32) DEFAULT 'pending', + retry_count INT DEFAULT 0, + max_retry INT DEFAULT 3, + created_at DATETIME, + created_by VARCHAR(255), + triggered_at DATETIME, + completed_at DATETIME, + error_message TEXT, + result TEXT + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + ''') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_status ON tasks(status)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_customer ON tasks(customer_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_created ON tasks(created_at)') + conn.commit() + conn.close() + else: + self.db_path.parent.mkdir(parents=True, exist_ok=True) + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS tasks ( + task_id TEXT PRIMARY KEY, + specified_customer_id TEXT, + specified_customer_name TEXT, + type TEXT NOT NULL, + customer_name TEXT, + customer_id TEXT, + trigger_type TEXT, + trigger_keyword TEXT, + trigger_keywords TEXT, + action_type TEXT, + action_file_url TEXT, + action_message TEXT, + priority TEXT DEFAULT 'normal', + timeout_hours INTEGER DEFAULT 24, + status TEXT DEFAULT 'pending', + retry_count INTEGER DEFAULT 0, + max_retry INTEGER DEFAULT 3, + created_at TEXT, + created_by TEXT, + triggered_at TEXT, + completed_at TEXT, + error_message TEXT, + result TEXT + ) + ''') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_status ON tasks(status)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_customer ON tasks(customer_id)') + cursor.execute('CREATE INDEX IF NOT EXISTS idx_created ON tasks(created_at)') + conn.commit() + conn.close() logger.info("数据库表初始化完成") def _get_conn(self): """获取数据库连接""" + if _is_mysql(): + import pymysql + return pymysql.connect( + host=_MYSQL_HOST, + port=_MYSQL_PORT, + user=_MYSQL_USER, + password=_MYSQL_PASSWORD, + database=_MYSQL_DATABASE, + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, + autocommit=False, + ) conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row return conn @@ -100,7 +160,7 @@ class TaskManager: if isinstance(trigger_keywords, list): trigger_keywords = json.dumps(trigger_keywords) - cursor.execute(''' + insert_sql = ''' INSERT OR REPLACE INTO tasks ( task_id, specified_customer_id, specified_customer_name, type, customer_name, customer_id, @@ -109,7 +169,19 @@ class TaskManager: priority, timeout_hours, status, retry_count, max_retry, created_at, created_by ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', ( + ''' + if _is_mysql(): + insert_sql = ''' + REPLACE INTO tasks ( + task_id, specified_customer_id, specified_customer_name, + type, customer_name, customer_id, + trigger_type, trigger_keyword, trigger_keywords, + action_type, action_file_url, action_message, + priority, timeout_hours, status, retry_count, max_retry, + created_at, created_by + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''' + cursor.execute(_sql(insert_sql), ( task.get('task_id'), task.get('customer', {}).get('id'), task.get('customer', {}).get('name'), @@ -127,7 +199,7 @@ class TaskManager: task.get('status', 'pending'), task.get('retry_count', 0), task.get('max_retry', 3), - task.get('created_at', datetime.now().isoformat()), + task.get('created_at', _now_str()), task.get('created_by') )) @@ -147,7 +219,7 @@ class TaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute('SELECT * FROM tasks WHERE task_id = ?', (task_id,)) + cursor.execute(_sql('SELECT * FROM tasks WHERE task_id = ?'), (task_id,)) row = cursor.fetchone() conn.close() @@ -165,32 +237,33 @@ class TaskManager: conn = self._get_conn() cursor = conn.cursor() - updates = ['status = ?'] + placeholder = "%s" if _is_mysql() else "?" + updates = [f'status = {placeholder}'] params = [status.value] if status == TaskStatus.RUNNING: - updates.append('triggered_at = ?') - params.append(datetime.now().isoformat()) + updates.append(f'triggered_at = {placeholder}') + params.append(_now_str()) if status in [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED]: - updates.append('completed_at = ?') - params.append(datetime.now().isoformat()) + updates.append(f'completed_at = {placeholder}') + params.append(_now_str()) if error_message: - updates.append('error_message = ?') + updates.append(f'error_message = {placeholder}') params.append(error_message) if result: - updates.append('result = ?') + updates.append(f'result = {placeholder}') params.append(json.dumps(result)) params.append(task_id) - cursor.execute(f''' + cursor.execute(_sql(f''' UPDATE tasks SET {', '.join(updates)} WHERE task_id = ? - ''', params) + '''), params) conn.commit() conn.close() @@ -206,13 +279,13 @@ class TaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute(''' + cursor.execute(_sql(''' UPDATE tasks SET retry_count = retry_count + 1 WHERE task_id = ? - ''', (task_id,)) + '''), (task_id,)) - cursor.execute('SELECT retry_count, max_retry FROM tasks WHERE task_id = ?', (task_id,)) + cursor.execute(_sql('SELECT retry_count, max_retry FROM tasks WHERE task_id = ?'), (task_id,)) row = cursor.fetchone() conn.close() @@ -231,7 +304,7 @@ class TaskManager: cursor = conn.cursor() if customer_id: - cursor.execute(''' + cursor.execute(_sql(''' SELECT * FROM tasks WHERE status = 'pending' AND customer_id = ? @@ -242,7 +315,7 @@ class TaskManager: ELSE 3 END, created_at - ''', (customer_id,)) + '''), (customer_id,)) else: cursor.execute(''' SELECT * FROM tasks @@ -271,11 +344,18 @@ class TaskManager: conn = self._get_conn() cursor = conn.cursor() - cursor.execute(''' - SELECT * FROM tasks - WHERE status = 'pending' - AND datetime(created_at, '+' || timeout_hours || ' hours') < datetime('now') - ''') + if _is_mysql(): + cursor.execute(''' + SELECT * FROM tasks + WHERE status = 'pending' + AND created_at < DATE_SUB(NOW(), INTERVAL timeout_hours HOUR) + ''') + else: + cursor.execute(''' + SELECT * FROM tasks + WHERE status = 'pending' + AND datetime(created_at, '+' || timeout_hours || ' hours') < datetime('now') + ''') rows = cursor.fetchall() conn.close() @@ -299,7 +379,7 @@ class TaskManager: stats = {} for status in TaskStatus: - cursor.execute('SELECT COUNT(*) FROM tasks WHERE status = ?', (status.value,)) + cursor.execute(_sql('SELECT COUNT(*) FROM tasks WHERE status = ?'), (status.value,)) stats[status.value] = cursor.fetchone()[0] conn.close() diff --git a/requirements.txt b/requirements.txt index a90e6c4..4b20c0c 100755 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,4 @@ aiofiles>=23.0.0 httpx>=0.25.0 numpy>=1.24.0 opencv-python>=4.8.0 +pymysql>=1.1.0 diff --git a/run.py b/run.py index a5ae049..e201a8f 100755 --- a/run.py +++ b/run.py @@ -1,96 +1,218 @@ +#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -项目入口 - 启动 WebSocket 客服客户端 +AI 客服系统 - 统一启动器 + 用法: - python run.py # 单进程模式(默认) - python run.py --no-agent # 仅基础回复,不启用 AI - python run.py --multi # 多进程模式 - python run.py --multi -w 4 # 多进程模式,指定 4 个进程 + python run.py # WebSocket 客服模式(默认) + python run.py --tianwang # 完整版(HTTP API + WebSocket + AI Agent) + python run.py --tianwang-multi -w 4 # 天网 + 多进程(HTTP API + 多进程 WebSocket) + python run.py --api-only # 仅 HTTP API(不含 WebSocket / AI Agent) + python run.py --no-agent # 不启用 AI Agent + python run.py --multi -w 4 # 多进程模式,4 个 Worker + python run.py --port 6060 # 指定 HTTP API 端口 """ import sys import os +import signal +import logging import argparse from pathlib import Path -# 确保项目根目录在 sys.path 首位 _root = Path(__file__).resolve().parent if str(_root) not in sys.path: sys.path.insert(0, str(_root)) +logging.basicConfig( + level=logging.INFO, + format='[%(asctime)s] %(levelname)s: %(message)s' +) +logger = logging.getLogger(__name__) -def run_single_process(enable_agent: bool): - """单进程模式""" - from core.websocket_client import QingjianAPIClient +DEFAULT_HTTP_PORT = 6060 + + +def _print_api_info(port: int): + logger.info(f"HTTP API 服务器已启动:http://0.0.0.0:{port}") + logger.info("") + logger.info("天网任务接口:") + logger.info(" POST /api/task/receive - 接收任务") + logger.info(" POST /api/task/cancel - 取消任务") + logger.info(" GET /api/task/status/:id - 查询任务状态") + logger.info(" GET /api/task/list - 任务列表") + logger.info(" GET /api/health - 健康检查") + + +def run_websocket(enable_agent: bool): + """WebSocket 客服模式(默认)""" import asyncio - - print("=" * 60) - print("AI 客服系统 - 单进程模式") - print("=" * 60) - print(f"AI Agent: {'已启用' if enable_agent else '未启用'}") - print("=" * 60) - + from core.websocket_client import QingjianAPIClient + + logger.info("=" * 60) + logger.info("AI 客服系统 - WebSocket 模式") + logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}") + logger.info("=" * 60) + client = QingjianAPIClient(enable_agent=enable_agent) try: asyncio.run(client.run()) except KeyboardInterrupt: - print("\n已停止") + logger.info("已停止") + + +def run_tianwang(enable_agent: bool, port: int): + """完整版: HTTP API + WebSocket + AI Agent""" + import asyncio + from api.http_server import start_http_server + from core.websocket_client import QingjianAPIClient + + logger.info("=" * 60) + logger.info("AI 客服系统 - 天网协作版(完整)") + logger.info("=" * 60) + + start_http_server(host='0.0.0.0', port=port) + _print_api_info(port) + logger.info("=" * 60) + + logger.info("正在连接轻简 API...") + client = QingjianAPIClient(enable_agent=enable_agent) + logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}") + logger.info("=" * 60) + logger.info("系统已就绪,等待消息和任务...") + + def _signal_handler(signum, frame): + logger.info("收到退出信号,正在停止...") + if hasattr(client, 'task_scheduler'): + asyncio.run(client.task_scheduler.stop()) + sys.exit(0) + + signal.signal(signal.SIGINT, _signal_handler) + signal.signal(signal.SIGTERM, _signal_handler) + + try: + asyncio.run(client.connect()) + except KeyboardInterrupt: + logger.info("已停止") + except Exception as e: + logger.error(f"运行异常:{e}") + sys.exit(1) + + +def run_api_only(port: int): + """仅 HTTP API(不含 WebSocket / AI Agent)""" + import time + from api.http_server import start_http_server + + logger.info("=" * 60) + logger.info("AI 客服系统 - 天网协作版(仅 API)") + logger.info("=" * 60) + + start_http_server(host='0.0.0.0', port=port) + _print_api_info(port) + logger.info("=" * 60) + logger.info("系统已就绪,等待天网任务...") + + def _signal_handler(signum, frame): + logger.info("收到退出信号") + sys.exit(0) + + signal.signal(signal.SIGINT, _signal_handler) + signal.signal(signal.SIGTERM, _signal_handler) + + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + logger.info("已停止") def run_multi_process(num_workers: int, enable_agent: bool): """多进程模式""" from scripts.multi_process_launcher import Coordinator - - print("=" * 60) - print("AI 客服系统 - 多进程异步并行模式") - print("=" * 60) - print(f"工作进程数:{num_workers}") - print(f"AI Agent: {'已启用' if enable_agent else '未启用'}") - print("=" * 60) - - coordinator = Coordinator(num_workers=num_workers) - + + logger.info("=" * 60) + logger.info("AI 客服系统 - 多进程异步并行模式") + logger.info(f"工作进程数:{num_workers}") + logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}") + logger.info("=" * 60) + + coordinator = Coordinator(num_workers=num_workers, enable_agent=enable_agent) try: coordinator.start() except KeyboardInterrupt: - print("\n已停止") + logger.info("已停止") coordinator.stop() +def run_tianwang_multi(num_workers: int, enable_agent: bool, port: int): + """天网 + 多进程:HTTP API + 多进程 WebSocket 客户端""" + from api.http_server import start_http_server + from scripts.multi_process_launcher import Coordinator + + logger.info("=" * 60) + logger.info("AI 客服系统 - 天网协作版(HTTP API) + 多进程模式") + logger.info("=" * 60) + + start_http_server(host='0.0.0.0', port=port) + _print_api_info(port) + logger.info("=" * 60) + + logger.info(f"工作进程数:{num_workers}") + logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}") + logger.info("=" * 60) + + coordinator = Coordinator(num_workers=num_workers or 0, enable_agent=enable_agent) + + def _signal_handler(signum, frame): + logger.info("收到退出信号,正在停止多进程协调器...") + coordinator.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, _signal_handler) + signal.signal(signal.SIGTERM, _signal_handler) + + try: + coordinator.start() + except KeyboardInterrupt: + logger.info("已停止") + coordinator.stop() def main(): - parser = argparse.ArgumentParser(description='AI 客服系统启动器') - - parser.add_argument( - '--no-agent', - action='store_true', - help='不启用 AI Agent,仅基础回复' + parser = argparse.ArgumentParser( + description='AI 客服系统启动器', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +启动模式: + (默认) WebSocket 客服模式 + --tianwang HTTP API + WebSocket(天网完整版) + --tianwang-multi HTTP API + 多进程 WebSocket + --api-only 仅 HTTP API(不含 WebSocket / AI Agent) + --multi 多进程模式 + """ ) - - parser.add_argument( - '--multi', - action='store_true', - help='多进程模式' - ) - - parser.add_argument( - '-w', '--workers', - type=int, - default=None, - help='工作进程数(默认:CPU 核心数,仅多进程模式有效)' - ) - + + mode = parser.add_mutually_exclusive_group() + mode.add_argument('--tianwang', action='store_true', help='天网完整版(HTTP API + WebSocket)') + mode.add_argument('--tianwang-multi', action='store_true', help='天网 + 多进程(HTTP API + 多进程 WebSocket)') + mode.add_argument('--api-only', action='store_true', help='仅 HTTP API(不含 WebSocket / AI Agent)') + mode.add_argument('--multi', action='store_true', help='多进程模式') + + parser.add_argument('--no-agent', action='store_true', help='不启用 AI Agent') + parser.add_argument('--port', '-p', type=int, default=DEFAULT_HTTP_PORT, help=f'HTTP API 端口(默认 {DEFAULT_HTTP_PORT})') + parser.add_argument('--workers', '-w', type=int, default=None, help='工作进程数(仅多进程模式,默认 CPU 核心数)') + args = parser.parse_args() - enable_agent = not args.no_agent - - if args.multi: - # 多进程模式 - run_multi_process( - num_workers=args.workers, - enable_agent=enable_agent - ) + + if args.api_only: + run_api_only(port=args.port) + elif args.tianwang: + run_tianwang(enable_agent=enable_agent, port=args.port) + elif args.tianwang_multi: + run_tianwang_multi(num_workers=args.workers, enable_agent=enable_agent, port=args.port) + elif args.multi: + run_multi_process(num_workers=args.workers, enable_agent=enable_agent) else: - # 单进程模式(默认) - run_single_process(enable_agent=enable_agent) + run_websocket(enable_agent=enable_agent) if __name__ == "__main__": diff --git a/run_tianwang_simple.py b/run_tianwang_simple.py deleted file mode 100755 index d6d0d5d..0000000 --- a/run_tianwang_simple.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -天网协作版 - 简化版(仅 HTTP API + 任务调度,不含 AI Agent) -""" -import sys -import os -import logging -import signal -from pathlib import Path - -_root = Path(__file__).resolve().parent -if str(_root) not in sys.path: - sys.path.insert(0, str(_root)) - -logging.basicConfig( - level=logging.INFO, - format='[%(asctime)s] %(levelname)s: %(message)s' -) -logger = logging.getLogger(__name__) - -def main(): - logger.info("=" * 60) - logger.info("天网协作版 - HTTP API 服务器") - logger.info("=" * 60) - - # 1. 启动 HTTP API 服务器 - from api.http_server import start_http_server - - http_thread = start_http_server(host='0.0.0.0', port=6060) - logger.info("HTTP API 服务器已启动:http://0.0.0.0:5678") - logger.info("") - logger.info("天网任务接口:") - logger.info(" POST /api/task/receive - 接收任务") - logger.info(" POST /api/task/cancel - 取消任务") - logger.info(" GET /api/task/status/:id - 查询任务状态") - logger.info(" GET /api/task/list - 任务列表") - logger.info(" GET /api/health - 健康检查") - logger.info("=" * 60) - logger.info("系统已就绪,等待天网任务...") - logger.info("") - - # 注册信号处理 - def signal_handler(signum, frame): - logger.info("收到退出信号") - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - # 保持运行 - try: - while True: - import time - time.sleep(1) - except KeyboardInterrupt: - logger.info("已停止") - -if __name__ == "__main__": - main() diff --git a/run_with_tianwang.py b/run_with_tianwang.py deleted file mode 100644 index bdca811..0000000 --- a/run_with_tianwang.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -""" -AI 客服系统 - 天网协作版启动器 -支持 HTTP API 接收天网任务 -""" -import sys -import os -import signal -import logging -from pathlib import Path - -# 确保项目根目录在 sys.path 首位 -_root = Path(__file__).resolve().parent -if str(_root) not in sys.path: - sys.path.insert(0, str(_root)) - -# 配置日志 -logging.basicConfig( - level=logging.INFO, - format='[%(asctime)s] %(levelname)s: %(message)s' -) -logger = logging.getLogger(__name__) - -def main(): - logger.info("=" * 60) - logger.info("AI 客服系统 - 天网协作版") - logger.info("=" * 60) - - # 1. 启动 HTTP API 服务器 - logger.info("正在启动 HTTP API 服务器...") - from api.http_server import start_http_server - - http_thread = start_http_server(host='0.0.0.0', port=5678) - logger.info("HTTP API 服务器已启动:http://0.0.0.0:5678") - logger.info("") - logger.info("天网任务接口:") - logger.info(" POST /api/task/receive - 接收任务") - logger.info(" POST /api/task/cancel - 取消任务") - logger.info(" GET /api/task/status/:id - 查询任务状态") - logger.info(" GET /api/task/list - 任务列表") - logger.info(" GET /api/health - 健康检查") - logger.info("=" * 60) - - # 2. 启动 WebSocket 客服客户端 - logger.info("正在连接轻简 API...") - from core.websocket_client import QingjianAPIClient - - import argparse - parser = argparse.ArgumentParser(description='AI 客服系统 - 天网协作版') - parser.add_argument('--no-agent', action='store_true', help='不启用 AI Agent') - args = parser.parse_args() - - enable_agent = not args.no_agent - client = QingjianAPIClient(enable_agent=enable_agent) - - logger.info(f"AI Agent: {'已启用' if enable_agent else '未启用'}") - logger.info("=" * 60) - logger.info("系统已就绪,等待消息和任务...") - logger.info("") - - # 注册信号处理 - def signal_handler(signum, frame): - logger.info("收到退出信号,正在停止...") - if hasattr(client, 'task_scheduler'): - import asyncio - asyncio.run(client.task_scheduler.stop()) - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGTERM, signal_handler) - - # 启动客户端 - import asyncio - try: - asyncio.run(client.connect()) - except KeyboardInterrupt: - logger.info("已停止") - except Exception as e: - logger.error(f"运行异常:{e}") - sys.exit(1) - - -if __name__ == "__main__": - main() diff --git a/scripts/chat_log_viewer.py b/scripts/chat_log_viewer.py index 7864292..d626c41 100755 --- a/scripts/chat_log_viewer.py +++ b/scripts/chat_log_viewer.py @@ -177,17 +177,8 @@ def cmd_live(refresh: int = 3): try: while True: - import sqlite3 - conn = sqlite3.connect(db._DB_PATH) - conn.row_factory = sqlite3.Row - rows = conn.execute(""" - SELECT id, customer_id, customer_name, direction, message, timestamp - FROM chat_logs - ORDER BY id DESC LIMIT 20 - """).fetchall() - conn.close() - - new_rows = [dict(r) for r in rows if r["id"] not in seen_ids] + rows = db.get_latest_messages(20) + new_rows = [r for r in rows if r["id"] not in seen_ids] if new_rows: new_rows.reverse() for r in new_rows: diff --git a/scripts/multi_process_launcher.py b/scripts/multi_process_launcher.py index beee3ba..f944537 100644 --- a/scripts/multi_process_launcher.py +++ b/scripts/multi_process_launcher.py @@ -24,22 +24,23 @@ logger = logging.getLogger(__name__) class WorkerProcess: """工作进程""" - def __init__(self, worker_id: int, shard_keys: List[str]): + def __init__(self, worker_id: int, shard_keys: List[str], enable_agent: bool = True): self.worker_id = worker_id self.shard_keys = shard_keys + self.enable_agent = enable_agent self.process = None def start(self): """启动工作进程""" self.process = Process( target=self._run, - args=(self.worker_id, self.shard_keys), + args=(self.worker_id, self.shard_keys, self.enable_agent), name=f"ai-cs-worker-{self.worker_id}" ) self.process.start() logger.info(f"Worker {self.worker_id} 启动 (PID: {self.process.pid})") - def _run(self, worker_id: int, shard_keys: List[str]): + def _run(self, worker_id: int, shard_keys: List[str], enable_agent: bool): """工作进程入口""" try: # 设置进程环境变量 @@ -50,7 +51,7 @@ class WorkerProcess: from core.websocket_client import QingjianAPIClient logger.info(f"Worker {worker_id} 初始化 Agent...") - client = QingjianAPIClient(enable_agent=True) + client = QingjianAPIClient(enable_agent=enable_agent) # 只处理分配给这个 worker 的客户 client.shard_keys = set(shard_keys) @@ -77,10 +78,11 @@ class WorkerProcess: class Coordinator: """协调器 - 管理多个工作进程""" - def __init__(self, num_workers: int = None): + def __init__(self, num_workers: int = None, enable_agent: bool = True): self.num_workers = num_workers or max(2, cpu_count()) self.workers: List[WorkerProcess] = [] self.running = False + self.enable_agent = enable_agent def _get_shard_key(self, acc_id: str, from_id: str) -> int: """根据店铺 ID + 客户 ID 计算分片 key""" @@ -117,7 +119,8 @@ class Coordinator: for worker_id in range(self.num_workers): worker = WorkerProcess( worker_id=worker_id, - shard_keys=shards.get(worker_id, []) + shard_keys=shards.get(worker_id, []), + enable_agent=self.enable_agent ) worker.start() self.workers.append(worker) diff --git a/services/service_gemini.py b/services/service_gemini.py index 6596345..c4b9ead 100755 --- a/services/service_gemini.py +++ b/services/service_gemini.py @@ -16,15 +16,12 @@ from pathlib import Path import logging +from utils.service_base import BaseService + logger = logging.getLogger(__name__) -class ServiceBase: - """最小化基类,替代缺失的 utils.service_base""" - pass - - -class GeminiExtractV2Service(ServiceBase): +class GeminiExtractV2Service(BaseService): """Gemini印花提取V2服务类 - 使用服务,更经济""" SERVICE_NAME = "gemini_extract_v2" @@ -69,7 +66,7 @@ class GeminiExtractV2Service(ServiceBase): DEFAULT_PROMPT = "提取印花图案,把褶皱移除。补齐缺失的部分,要生成完整,细节丰富,严格按照原图的元素位置生成平面的印花图,不要相似的,相似度要100%,生成高质量的印刷图" # DEFAULT_PROMPT = "生成图片,把衣服的图案展开起来做成数码印花印刷平面图。去掉皱褶,生成图案增强细节。排除衣服图案以外内容" def __init__(self): - super().__init__() + super().__init__(name="gemini_extract_v2") self.session = None def image_to_base64(self, image_path: str) -> str: diff --git a/tests/test_ai_chat.py b/tests/test_ai_chat.py new file mode 100644 index 0000000..e7df80f --- /dev/null +++ b/tests/test_ai_chat.py @@ -0,0 +1,313 @@ +""" +AI Agent 对话测试脚本 +从数据库加载聊天记录,测试 AI 回复效果 +""" +import sqlite3 +import asyncio +from datetime import datetime + +# 颜色代码 +COLORS = { + 'header': '\033[95m\033[1m', + 'customer': '\033[94m', + 'agent': '\033[92m', + 'system': '\033[90m', + 'price': '\033[93m', + 'error': '\033[91m', + 'cyan': '\033[96m', + 'reset': '\033[0m', +} + +def cprint(text, color='reset'): + print(f"{COLORS.get(color, '')}{text}{COLORS['reset']}") + +def check_database(): + """检查数据库内容""" + db_path = 'db/chat_log_db/chats.db' + try: + conn = sqlite3.connect(db_path) + cursor = conn.execute("SELECT COUNT(*) FROM chat_logs") + count = cursor.fetchone()[0] + + if count == 0: + cprint(f"\n✗ 数据库为空,没有聊天记录", 'error') + cprint("提示:需要先有一些聊天记录才能测试", 'system') + conn.close() + return None + + cprint(f"\n✓ 数据库连接成功!共 {count} 条聊天记录", 'system') + + # 获取客户列表 + cursor = conn.execute(""" + SELECT customer_id, customer_name, COUNT(*) as cnt, MAX(timestamp) as last + FROM chat_logs + GROUP BY customer_id + ORDER BY cnt DESC + LIMIT 20 + """) + customers = cursor.fetchall() + + cprint(f"\n找到 {len(customers)} 个客户:", 'cyan') + for i, (cid, name, cnt, last) in enumerate(customers, 1): + cprint(f" {i:2d}. {name or cid:30s} | {cnt:4d}条 | 最后:{last}", 'customer') + + conn.close() + return customers + + except Exception as e: + cprint(f"\n✗ 数据库检查失败:{e}", 'error') + return None + +async def test_customer_conversation(customer_id, customer_name, limit=5): + """测试某个客户的对话""" + cprint(f"\n{'='*70}", 'cyan') + cprint(f"测试客户:{customer_name or customer_id}", 'header') + cprint(f"{'='*70}\n", 'cyan') + + # 获取对话记录 + conn = sqlite3.connect('db/chat_log_db/chats.db') + cursor = conn.execute(""" + SELECT direction, message, timestamp + FROM chat_logs + WHERE customer_id = ? + ORDER BY timestamp ASC + LIMIT ? + """, (customer_id, limit)) + conversations = cursor.fetchall() + conn.close() + + if not conversations: + cprint(" 该客户没有对话记录", 'system') + return + + # 初始化 AI Agent + try: + from core.pydantic_ai_agent import CustomerServiceAgent, CustomerMessage + agent = CustomerServiceAgent(skills_dir="skills") + cprint("✓ AI Agent 已加载", 'system') + except Exception as e: + cprint(f"✗ AI Agent 加载失败:{e}", 'error') + return + + # 模拟对话 + for i, (direction, message, timestamp) in enumerate(conversations, 1): + if direction == 'in': + # 客户消息 + cprint(f"\n【消息 {i}/{len(conversations)}】{timestamp}", 'system') + cprint(f"客户:{message}", 'customer') + + # 创建测试消息 + test_msg = CustomerMessage( + msg_id=f"test_{i}", + acc_id="test_shop", + msg=message, + from_id=customer_id, + from_name=customer_name or "测试", + cy_id=customer_id, + acc_type="AliWorkbench", + msg_type=0, + cy_name=customer_name or "测试", + goods_name="专业找图", + goods_order="" + ) + + # 获取 AI 回复 + start = datetime.now() + try: + response = await agent.process_message(test_msg) + elapsed = (datetime.now() - start).total_seconds() * 1000 + + if response.should_reply: + cprint(f"AI [{elapsed:.0f}ms]: {response.reply}", 'agent') + + # 检测特殊内容 + if any(kw in response.reply for kw in ['元', '块', '价格']): + cprint(" ↳ [价格信息]", 'price') + if response.need_transfer: + cprint(" ↳ [转人工]", 'error') + else: + cprint("[AI 静默]", 'system') + + except Exception as e: + cprint(f"✗ AI 回复失败:{e}", 'error') + + elif direction == 'out': + cprint(f"\n[历史回复] {timestamp}", 'system') + cprint(f"客服:{message}", 'system') + + cprint(f"\n{'='*70}", 'cyan') + +async def test_all_customers(customers, limit_per_customer=5): + """批量测试所有客户""" + cprint(f"\n{'='*70}", 'header') + cprint(f" 开始批量测试 {len(customers)} 个客户", 'header') + cprint(f" 每个客户测试前 {limit_per_customer} 条消息", 'header') + cprint(f"{'='*70}\n", 'header') + + total_msgs = 0 + total_replies = 0 + + for i, (cid, name, cnt, _) in enumerate(customers, 1): + cprint(f"\n\n{'='*70}", 'cyan') + cprint(f"进度:{i}/{len(customers)} - {name or cid} ({cnt}条消息)", 'cyan') + cprint(f"{'='*70}", 'cyan') + + if cnt == 0: + cprint(" 跳过(无消息记录)", 'system') + continue + + # 获取对话记录 + conn = sqlite3.connect('db/chat_log_db/chats.db') + cursor = conn.execute(""" + SELECT direction, message, timestamp + FROM chat_logs + WHERE customer_id = ? + ORDER BY timestamp ASC + LIMIT ? + """, (cid, limit_per_customer)) + conversations = cursor.fetchall() + conn.close() + + # 初始化 AI Agent(只初始化一次) + try: + from core.pydantic_ai_agent import CustomerServiceAgent, CustomerMessage + if i == 1: # 第一个客户时初始化 + agent = CustomerServiceAgent(skills_dir="skills") + cprint("✓ AI Agent 已加载", 'system') + except Exception as e: + cprint(f"✗ AI Agent 加载失败:{e}", 'error') + return + + # 模拟对话 + for j, (direction, message, timestamp) in enumerate(conversations, 1): + if direction == 'in': + total_msgs += 1 + + # 创建测试消息 + test_msg = CustomerMessage( + msg_id=f"test_{i}_{j}", + acc_id="test_shop", + msg=message, + from_id=cid, + from_name=name or "测试", + cy_id=cid, + acc_type="AliWorkbench", + msg_type=0, + cy_name=name or "测试", + goods_name="专业找图", + goods_order="" + ) + + # 获取 AI 回复 + start = datetime.now() + try: + response = await agent.process_message(test_msg) + elapsed = (datetime.now() - start).total_seconds() * 1000 + + if response.should_reply: + total_replies += 1 + cprint(f"\n[{i}/{len(customers)}] {name or cid} - 消息 {j}", 'system') + cprint(f"客户:{message}", 'customer') + cprint(f"AI [{elapsed:.0f}ms]: {response.reply}", 'agent') + + # 检测特殊内容 + if any(kw in response.reply for kw in ['元', '块', '价格']): + cprint(" ↳ [价格信息]", 'price') + if response.need_transfer: + cprint(" ↳ [转人工]", 'error') + else: + cprint(f"\n[{i}/{len(customers)}] [AI 静默]", 'system') + + except Exception as e: + cprint(f"✗ AI 回复失败:{e}", 'error') + + # 每个客户之间休息一下 + await asyncio.sleep(0.5) + + # 统计结果 + cprint(f"\n\n{'='*70}", 'header') + cprint(f" 批量测试完成!", 'header') + cprint(f"{'='*70}", 'header') + cprint(f"\n统计:", 'system') + cprint(f" 测试客户数:{len(customers)}", 'cyan') + cprint(f" 处理消息数:{total_msgs}", 'cyan') + cprint(f" AI 回复数:{total_replies}", 'cyan') + if total_msgs > 0: + reply_rate = (total_replies / total_msgs) * 100 + cprint(f" 回复率:{reply_rate:.1f}%", 'cyan') + +async def main(): + cprint("="*70, 'header') + cprint(" AI Agent 对话测试", 'header') + cprint(" 从数据库加载聊天记录,测试 AI 回复效果", 'header') + cprint("="*70, 'header') + + # 检查数据库 + customers = check_database() + if not customers: + return + + # 选择测试模式 + cprint(f"\n请选择测试模式:", 'cyan') + cprint(f" 1. 交互式测试 (手动选择客户)", 'customer') + cprint(f" 2. 批量测试所有客户 (自动)", 'agent') + cprint(f" 3. 快速测试前 5 个客户", 'price') + cprint(f" q. 退出", 'system') + + mode = input("\n选择:").strip().lower() + + if mode == 'q': + cprint("\n测试结束!", 'system') + return + + try: + if mode == '1': + # 交互式测试 + cprint(f"\n请输入客户编号 (1-{len(customers)}) 进行测试:", 'cyan') + + while True: + try: + choice = input("\n选择:").strip() + + if choice.lower() == 'q': + cprint("\n测试结束!", 'system') + return + + choice_num = int(choice) + if 1 <= choice_num <= len(customers): + cid, name, cnt, _ = customers[choice_num - 1] + await test_customer_conversation(cid, name or cid, limit=min(cnt, 10)) + else: + cprint(f"请输入 1-{len(customers)} 之间的数字", 'error') + + except ValueError: + cprint("请输入有效数字或 q 退出", 'error') + except KeyboardInterrupt: + cprint("\n\n测试中断", 'error') + return + except Exception as e: + cprint(f"错误:{e}", 'error') + + elif mode == '2': + # 批量测试所有客户 + await test_all_customers(customers, limit_per_customer=5) + + elif mode == '3': + # 快速测试前 5 个客户 + top_5 = customers[:5] + cprint(f"\n快速测试前 5 个客户...", 'cyan') + await test_all_customers(top_5, limit_per_customer=5) + + else: + cprint("无效的选择", 'error') + + except KeyboardInterrupt: + cprint("\n\n测试中断", 'error') + except Exception as e: + cprint(f"错误:{e}", 'error') + +if __name__ == "__main__": + try: + asyncio.run(main()) + except Exception as e: + cprint(f"\n程序异常:{e}", 'error') diff --git a/tests/test_config.py b/tests/test_config.py deleted file mode 100755 index a90eb03..0000000 --- a/tests/test_config.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -"""配置中心测试""" -import sys -from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -def test_config_paths(): - from config.config import ROOT, LOG_DIR, RESULTS_DIR, CONFIG_DIR - assert ROOT.exists() - assert ROOT.is_dir() - assert (ROOT / "config").samefile(CONFIG_DIR) - assert LOG_DIR == ROOT / "logs" - print("config paths OK") - - -def test_config_values(): - from config.config import ( - IMAGE_QUEUE_MAX_CONCURRENT, - IMAGE_QUEUE_MAX_SIZE, - LOG_MAX_BYTES, - LOG_BACKUP_COUNT, - ) - assert IMAGE_QUEUE_MAX_CONCURRENT >= 1 - assert IMAGE_QUEUE_MAX_SIZE >= 1 - assert LOG_MAX_BYTES > 0 - assert LOG_BACKUP_COUNT >= 1 - print("config values OK") - - -if __name__ == "__main__": - test_config_paths() - test_config_values() - print("All config tests passed") diff --git a/tests/test_designer_roster.py b/tests/test_designer_roster.py deleted file mode 100755 index 2555b68..0000000 --- a/tests/test_designer_roster.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -"""设计师派单数据库测试""" -import sys -from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - - -def test_list_designers(): - """测试列出设计师(不修改数据)""" - from db.designer_roster_db import list_designers - designers = list_designers() - assert isinstance(designers, list) - print(f"designer roster: {len(designers)} designers") - print("designer roster OK") - - -if __name__ == "__main__": - test_list_designers() - print("All designer roster tests passed") diff --git a/tests/test_health_check.py b/tests/test_health_check.py deleted file mode 100755 index 82b24e5..0000000 --- a/tests/test_health_check.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -"""健康检查测试""" -import sys -from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - - -def test_set_status(): - """测试状态设置""" - from utils.health_check import set_qingjian_connected, set_wechat_ok - set_qingjian_connected(True) - set_qingjian_connected(False) - set_wechat_ok(True) - print("health check status OK") - - -async def test_run_check(): - """测试执行健康检查(不实际发告警)""" - from utils.health_check import run_health_check - await run_health_check(lambda: True) - print("health check run OK") - - -if __name__ == "__main__": - import asyncio - test_set_status() - asyncio.run(test_run_check()) - print("All health check tests passed") diff --git a/tests/test_image_queue.py b/tests/test_image_queue.py deleted file mode 100755 index 5fa0622..0000000 --- a/tests/test_image_queue.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -"""图片队列测试""" -import sys -import asyncio -from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - - -async def test_queue_semaphore(): - """测试队列并发限制""" - from utils.image_queue import init, run_with_queue, release - init(max_concurrent=2, max_queue=5) - running = 0 - max_running = 0 - - async def fake_task(): - nonlocal running, max_running - running += 1 - max_running = max(max_running, running) - await asyncio.sleep(0.1) - running -= 1 - return "ok" - - results = await asyncio.gather( - run_with_queue(fake_task()), - run_with_queue(fake_task()), - run_with_queue(fake_task()), - ) - assert all(r == "ok" for r in results) - assert max_running <= 2 - print("image queue OK") - - -if __name__ == "__main__": - asyncio.run(test_queue_semaphore()) - print("All image queue tests passed") diff --git a/tests/test_process.py b/tests/test_process.py deleted file mode 100755 index 823e8a3..0000000 --- a/tests/test_process.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -端到端测试:模拟客户付款后的自动图片处理流程 -运行:python test_process.py -""" -import sys -import io -sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace") -sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace") - -import asyncio -import os -from dotenv import load_dotenv - -load_dotenv() - -# ── 测试参数 ──────────────────────────────────────────────── -TEST_CUSTOMER_ID = "test_user_001" -TEST_ACC_ID = "小威哥1216" -TEST_IMAGE_URL = ( - "https://img.alicdn.com/imgextra/i3/O1CN01tqQst21qIEOdcUOCQ" - "_!!4611686018427380880-0-amp.jpg" -) -# ──────────────────────────────────────────────────────────── - - -async def step1_analyze(): - """Step 1: 图片分析(模拟 analyze_image 工具调用)""" - print("\n" + "="*60) - print("Step 1: 图片分析") - print("="*60) - from image.image_analyzer import image_analyzer - result = await image_analyzer.analyze(TEST_IMAGE_URL) - print(f"分析结果: {result}") - return result - - -async def step2_create_task(analysis: dict): - """Step 2: 创建 Workflow 任务(模拟 image_analysis_result)""" - print("\n" + "="*60) - print("Step 2: 创建 Workflow 任务") - print("="*60) - from core.workflow import workflow - await workflow.image_analysis_result( - customer_id = TEST_CUSTOMER_ID, - image_url = TEST_IMAGE_URL, - complexity = analysis.get("complexity", "normal"), - acc_id = TEST_ACC_ID, - acc_type = "AliWorkbench", - gemini_prompt= analysis.get("gemini_prompt", ""), - aspect_ratio = analysis.get("aspect_ratio", "1:1"), - perspective = analysis.get("perspective", "no"), - proc_type = analysis.get("proc_type", ""), - subject = analysis.get("subject", ""), - quality = analysis.get("quality", ""), - ) - - task_id = workflow.customer_active_task.get(TEST_CUSTOMER_ID) - if task_id: - task = workflow.tasks[task_id] - print(f"任务已创建: {task_id[:8]}...") - print(f" requirements: {task.requirements}") - print(f" image: {task.original_image[:80]}...") - else: - print("⚠️ 任务创建失败!") - sys.exit(1) - return task_id - - -async def step3_trigger_payment(): - """Step 3: 模拟付款触发处理(等待 Gemini 完成)""" - print("\n" + "="*60) - print("Step 3: 模拟付款,触发 Gemini 处理(同步等待完成)") - print("="*60) - from core.workflow import workflow - - # 注入发送函数(避免真实发消息,只打印) - async def fake_send(**kw): - cid = kw.get("customer_id", kw.get("from_id", "?")) - content = kw.get("content", kw.get("msg", "")) - print(f"[FAKE SEND -> {cid}] {str(content)[:120]}") - - workflow._send_message = fake_send - - # 直接调用 _auto_process 同步等待,而非后台 task - task_id = workflow.customer_active_task.get(TEST_CUSTOMER_ID) - if not task_id: - print(" 找不到待处理任务!") - return - - print(f" 开始处理任务: {task_id[:8]}...") - await workflow._auto_process(task_id, acc_id=TEST_ACC_ID, acc_type="AliWorkbench") - - -async def step4_check_result(): - """Step 4: 检查结果""" - print("\n" + "="*60) - print("Step 4: 检查处理结果") - print("="*60) - result_dir = os.getenv("RESULT_IMAGE_DIR", "results") - if not os.path.exists(result_dir): - print(f"结果目录不存在: {result_dir}") - return - - files = sorted( - [f for f in os.listdir(result_dir) if f.startswith("result_")], - key=lambda f: os.path.getmtime(os.path.join(result_dir, f)), - reverse=True, - ) - if files: - latest = files[0] - path = os.path.join(result_dir, latest) - size = os.path.getsize(path) - print(f"最新结果文件: {latest}") - print(f" 大小: {size:,} bytes ({size/1024:.1f} KB)") - else: - print("结果目录为空,可能处理失败") - - -async def main(): - print("=" * 60) - print(" 图片处理流程端到端测试") - print("=" * 60) - print(f"测试图片: {TEST_IMAGE_URL[:80]}...") - - try: - analysis = await step1_analyze() - await step2_create_task(analysis) - await step3_trigger_payment() - await step4_check_result() - print("\n[OK] 测试完成") - except KeyboardInterrupt: - print("\n测试被中断") - except Exception as e: - import traceback - print(f"\n[FAIL] 测试失败: {e}") - traceback.print_exc() - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/tests/test_transfer_flow.py b/tests/test_transfer_flow.py deleted file mode 100755 index 904dda7..0000000 --- a/tests/test_transfer_flow.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -"""测试转人工流程:设计师在线查询 + 派单 + 无人在线时企微提醒""" -import asyncio -import os -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - -# 测试用 API 地址(文档中的) -os.environ.setdefault("DESIGNER_ROSTER_API", "http://huichang.online:8001/online") - - -async def main(): - from utils.designer_roster import poll_and_update_roster - from db.designer_roster_db import list_designers, get_transfer_group_for_shop - - print("1. 查询并同步设计师在线状态...") - await poll_and_update_roster() - print(" OK") - - print("\n2. 当前设计师状态:") - for d in list_designers(): - status = "在线" if d["is_online"] else "离线" - print(f" - {d['name']} ({d['wechat_user_id']}): {status}") - - print("\n3. 派单测试 (店铺: 小威哥1216):") - shop_id = "小威哥1216" - group_id = get_transfer_group_for_shop(shop_id) - if group_id: - print(f" 派单成功 -> group_id={group_id}") - else: - print(" 无人在线,将回退到静态配置") - print(" (转人工时会发企微「谁在线啊」)") - - print("\n4. 静态回退:") - from config.config import CONFIG_DIR - import json - cfg_path = CONFIG_DIR / "transfer_groups.json" - default = "20252916034" - if cfg_path.exists(): - with open(cfg_path, "r", encoding="utf-8") as f: - cfg = json.load(f) - fallback = cfg.get(shop_id, cfg.get("default", default)) - else: - fallback = default - print(f" 回退分组: {fallback}") - - print("\n测试完成") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/tests/test_transfer_groups.py b/tests/test_transfer_groups.py deleted file mode 100755 index 1784bf0..0000000 --- a/tests/test_transfer_groups.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -"""转接分组测试""" -import sys -import json -from pathlib import Path -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - - -def test_get_transfer_group(): - """测试转接分组查找逻辑""" - from config.config import CONFIG_DIR - config_path = CONFIG_DIR / "transfer_groups.json" - default = "20252916034" - if not config_path.exists(): - print("transfer_groups.json 不存在,跳过") - return - with open(config_path, "r", encoding="utf-8") as f: - cfg = json.load(f) - got = cfg.get("default", default) - assert got - print(f"default group: {got}") - print("transfer groups OK") - - -if __name__ == "__main__": - test_get_transfer_group() - print("All transfer tests passed") diff --git a/tests/test_wechat_alert.py b/tests/test_wechat_alert.py deleted file mode 100755 index 5ccca1b..0000000 --- a/tests/test_wechat_alert.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -"""测试企微「谁在线啊」消息发送""" -import asyncio -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) - - -async def main(): - from config.config import WECHAT_WEBHOOK - import httpx - - if not WECHAT_WEBHOOK: - print("未配置 WECHAT_WEBHOOK,无法测试") - return - - print(f"发送测试消息到企微...") - async with httpx.AsyncClient(timeout=10) as client: - resp = await client.post(WECHAT_WEBHOOK, json={ - "msgtype": "text", - "text": {"content": "谁在线啊"} - }) - print(f"状态码: {resp.status_code}") - print(f"响应: {resp.text}") - if resp.status_code == 200: - data = resp.json() - if data.get("errcode") == 0: - print("发送成功,请检查企微群是否收到") - else: - print(f"企微返回错误: {data}") - else: - print("发送失败") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/三种工作流功能说明.md b/三种工作流功能说明.md deleted file mode 100644 index 7b7148d..0000000 --- a/三种工作流功能说明.md +++ /dev/null @@ -1,229 +0,0 @@ -# 三种工作流功能说明 - -## 📋 功能说明 - -根据客户说的话,自动判断执行不同的工作流程: - -1. **"找一下这个图"** → 查找图片工作流 -2. **"做一下"** → 处理图片工作流 -3. **"做不了"** → 转人工派单工作流 - ---- - -## 🔄 工作流 1:查找图片 - -### 触发条件 -客户说: -- "找一下这个图" -- "找图" -- "找原图" -- "帮我找" -- "能找到吗" -- "有吗" -- "有没有" - -### 执行流程 -``` -客户:找一下这个图 [发送图片] - ↓ -AI 检测到"找一下"关键词 - ↓ -执行查找图片工作流 - ↓ -1. 创建任务(operation=find) -2. 上传图片到图绘平台 -3. 更新任务状态为 completed - ↓ -回复客户:"找到了!图片在这里:http://tuhui.cloud/works/123" -``` - -### 示例对话 -``` -客户:找一下这个图 [图片] -AI: 找到了!图片在这里:http://tuhui.cloud/works/123 -``` - ---- - -## 🔄 工作流 2:处理图片 - -### 触发条件 -客户说: -- "做一下" -- "处理一下" -- "安排" -- "开始做" -- "弄一下" -- "修一下" -- "P 一下" -- "P 图" - -### 执行流程 -``` -客户:做一下 [发送图片] - ↓ -AI 检测到"做一下"关键词 - ↓ -执行处理图片工作流 - ↓ -1. 创建任务(operation=enhance) -2. 回复客户"稍等,我看看...好的,可以做,马上处理" -3. 启动图片处理流程 - ↓ -处理完成后发送结果给客户 -``` - -### 示例对话 -``` -客户:做一下 [图片] -AI: 稍等,我看看...好的,可以做,马上处理 - -[处理中...] - -AI: 做好了,请查看 [结果图] -``` - ---- - -## 🔄 工作流 3:转人工派单 - -### 触发条件 -客户说: -- "做不了" -- "处理不了" -- "弄不了" -- "无法处理" -- "做不到" -- "搞不定" - -或者 AI 判断无法处理时 - -### 执行流程 -``` -AI:做不了 - ↓ -检测到"做不了"关键词 - ↓ -执行转人工派单工作流 - ↓ -1. 创建任务(operation=manual) -2. 查询企业微信在线设计师 - - 调用 API: GET http://huichang.online:8001/online -3. 有人在线 → 派单给设计师 - 无人在线 → 通知客户稍后联系 - ↓ -回复客户:"好的,已帮您安排设计师处理,请稍候" -``` - -### 示例对话 -``` -客户:这个能做吗 [图片] -AI: 抱歉,这个我做不了,帮您转接设计师 - -[查询在线设计师...] - -AI: 好的,已帮您安排设计师处理,请稍候 -``` - ---- - -## 🔧 技术实现 - -### 1. 工作流程路由器 - -文件:`/root/ai_customer_service/ai_cs/core/workflow_router.py` - -**关键词检测**: -```python -find_keywords = ["找一下", "找图", "找原图", ...] -process_keywords = ["做一下", "处理一下", "安排", ...] -unable_keywords = ["做不了", "处理不了", "弄不了", ...] -``` - -### 2. 工作流执行器 - -文件:`/root/ai_customer_service/ai_cs/core/workflow.py` - -**三种方法**: -- `find_image_workflow()` - 查找图片 -- `process_image_workflow()` - 处理图片 -- `transfer_to_designer_workflow()` - 转人工派单 - -### 3. 消息处理器 - -文件:`/root/ai_customer_service/ai_cs/core/pydantic_ai_agent.py` - -**处理方法**: -```python -async def _handle_image_workflow(self, message, data, image_urls): - # 检测工作流类型 - workflow_type, confidence = self.workflow_router.detect_workflow(message) - - # 执行对应工作流 - if workflow_type == "find_image": - await workflow.find_image_workflow(...) - elif workflow_type == "process_image": - await workflow.process_image_workflow(...) - elif workflow_type == "transfer_human": - await workflow.transfer_to_designer_workflow(...) -``` - ---- - -## 📊 设计师在线状态 API - -### 查询在线设计师 - -**接口**:`GET http://huichang.online:8001/online` - -**返回**: -```json -{ - "online_count": 2, - "online_users": ["lz", "ZuoWei"], - "update_time": "2026-02-27 18:40:00" -} -``` - -### 更新设计师状态 - -**接口**:`POST http://huichang.online:8001/update-status` - -**请求**: -```json -{ - "username": "lz", - "status": "online" -} -``` - ---- - -## ⚠️ 注意事项 - -1. **关键词匹配**:支持多种说法,自动识别 -2. **置信度**:>0.9 才执行对应工作流 -3. **无人在线处理**:通知客户稍后联系,企业微信预警 -4. **任务记录**:所有工作流都保存到数据库 -5. **状态追踪**:任务状态自动流转 - ---- - -## 🔍 日志查看 - -```bash -# 查看工作流执行日志 -grep "工作流" /tmp/ai-cs.log - -# 查看派单记录 -grep "派单" /tmp/ai-cs.log - -# 查看在线设计师查询 -grep "在线设计师" /tmp/ai-cs.log -``` - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 - diff --git a/作图失败转接人工说明.md b/作图失败转接人工说明.md deleted file mode 100644 index fb12e67..0000000 --- a/作图失败转接人工说明.md +++ /dev/null @@ -1,207 +0,0 @@ -# 作图失败转接人工功能 - -## 📋 功能说明 - -当 AI 作图失败或效果不佳时,系统会自动转接人工客服处理。 - ---- - -## 🔄 触发场景 - -### 1. AI 作图失败 - -**触发条件**: -- API 调用失败 -- 图片处理超时 -- 图片质量不达标 -- Gemini/Qwen API 报错 - -**自动转接**: -``` -作图失败:[错误信息],请稍后重试,我帮您转接人工处理 -``` - ---- - -### 2. 客户不满意 - -**触发条件**: -- 客户说"效果不好" -- 客户说"不满意" -- 客户要求重做多次 - -**自动转接**: -``` -好的,我帮您转接人工客服处理 -``` - ---- - -### 3. 特殊要求 - -**触发条件**: -- 客户有特殊需求 -- AI 无法处理的复杂需求 -- 需要人工判断的情况 - -**自动转接**: -``` -这个需求比较特殊,我帮您转接人工客服 -``` - ---- - -## ⚠️ 转接流程 - -``` -客户发送图片 - ↓ -AI 尝试作图 - ↓ -┌───────────────────────┐ -│ 作图成功? │ -└───────────┬───────────┘ - NO │ YES - │ - ↓ - ┌───────────────┐ - │ 作图失败 │ - │ 自动转人工 │ - └───────┬───────┘ - ↓ - ┌───────────────┐ - │ 通知客户 │ - │ "帮您转人工" │ - └───────┬───────┘ - ↓ - ┌───────────────┐ - │ 转接人工客服 │ - │ 说明失败原因 │ - └───────────────┘ -``` - ---- - -## 📝 转接话术 - -### 作图失败 - -``` -- 作图失败:[错误信息],请稍后重试,我帮您转接人工处理 -- 作图失败:[错误信息],我帮您转接人工客服处理 -- 处理遇到点问题,我帮您转接人工客服 -``` - -### 客户不满意 - -``` -- 好的,我帮您转接人工客服处理 -- 明白,让同事来帮您看看 -- 这个我帮您转接人工客服 -``` - -### 特殊要求 - -``` -- 这个需求比较特殊,我帮您转接人工客服 -- 这个需要人工判断,我帮您转接 -- 稍等,我让同事来帮您处理 -``` - ---- - -## 🔧 技术实现 - -### 失败检测 - -文件:`/root/ai_customer_service/ai_cs/core/pydantic_ai_agent.py` - -**作图函数**: -```python -async def process_image_gemini(ctx: RunContext[AgentDeps], customer_id: str = "") -> str: - try: - # 作图逻辑 - ... - except Exception as e: - # 作图失败,自动转接 - return f"作图失败:{e},请稍后重试,我帮您转接人工处理" -``` - ---- - -### 转接工具 - -**transfer_to_human 工具**: -```python -async def transfer_to_human(ctx: RunContext[AgentDeps]) -> str: - """转接人工客服""" - # 标记需要转接 - st.need_transfer = True - st.transfer_reason = "作图失败" - return "好的,帮您转接人工客服" -``` - ---- - -## 📊 转接统计 - -### 转接原因分类 - -| 原因 | 比例 | 说明 | -|------|------|------| -| 作图失败 | 40% | API 报错/超时/失败 | -| 客户不满意 | 30% | 效果不好/要求重做 | -| 特殊要求 | 20% | AI 无法处理的复杂需求 | -| 其他 | 10% | 投诉/退款等 | - ---- - -## ⚠️ 注意事项 - -1. **失败必转**:作图失败必须转人工,不自动重试超过 2 次 -2. **告知客户**:转接前告知客户原因 -3. **记录原因**:记录转接原因便于后续优化 -4. **快速响应**:转接后人工客服需快速响应 - ---- - -## 🔍 日志查看 - -### 查看转接记录 - -```bash -# 查看转接日志 -grep "转接人工" /tmp/ai-cs.log - -# 查看作图失败 -grep "作图失败" /tmp/ai-cs.log - -# 查看转接统计 -grep "TRANSFER" /tmp/ai-cs.log | wc -l -``` - ---- - -## 📈 优化建议 - -### 降低转接率 - -1. **提升作图成功率** - - 优化 API 调用逻辑 - - 增加重试机制(最多 2 次) - - 降级兜底方案 - -2. **提升客户满意度** - - 提前告知预期效果 - - 提供效果样例 - - 设置合理期望 - -3. **识别特殊需求** - - 提前识别复杂需求 - - 直接转人工,避免无效尝试 - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 - diff --git a/图片任务数据库功能说明.md b/图片任务数据库功能说明.md deleted file mode 100644 index aba14b2..0000000 --- a/图片任务数据库功能说明.md +++ /dev/null @@ -1,247 +0,0 @@ -# 图片任务数据库功能说明 - -## 📋 功能说明 - -图片任务现在会保存到 SQLite 数据库,支持: -1. ✅ 任务持久化(重启不丢失) -2. ✅ 客户后续增加需求细节 -3. ✅ 需求变更历史记录 -4. ✅ 任务状态追踪 - ---- - -## 🗄️ 数据库表结构 - -### 1. image_tasks(图片任务表) - -| 字段 | 类型 | 说明 | -|------|------|------| -| task_id | TEXT | 任务 ID(主键) | -| customer_id | TEXT | 客户 ID | -| customer_name | TEXT | 客户名称 | -| original_image | TEXT | 原图 URL | -| operation | TEXT | 操作类型(enhance/remove_bg/vectorize) | -| requirements | TEXT | 需求 JSON(复杂度、比例、透视等) | -| customer_notes | TEXT | 客户备注/需求细节 | -| status | TEXT | 状态(pending/paid/processing/completed/failed) | -| created_at | TEXT | 创建时间 | -| paid_at | TEXT | 付款时间 | -| started_at | TEXT | 开始处理时间 | -| completed_at | TEXT | 完成时间 | -| result_image | TEXT | 结果图 URL | -| error_message | TEXT | 错误信息 | -| retry_count | INTEGER | 重试次数 | -| acc_id | TEXT | 店铺 ID | -| acc_type | TEXT | 平台类型 | - ---- - -### 2. task_requirement_changes(需求变更表) - -| 字段 | 类型 | 说明 | -|------|------|------| -| id | INTEGER | 自增 ID(主键) | -| task_id | TEXT | 任务 ID(外键) | -| change_type | TEXT | 变更类型(add_note/modify_operation/add_requirement) | -| old_value | TEXT | 旧值 | -| new_value | TEXT | 新值 | -| changed_at | TEXT | 变更时间 | -| changed_by | TEXT | 变更者(customer/staff) | - ---- - -## 🎯 使用场景 - -### 场景 1:客户发送图片,创建任务 - -```python -# AI 识别图片后自动创建 -workflow.image_analysis_result( - customer_id="customer_123", - image_url="https://xxx.com/image.jpg", - complexity="normal", - aspect_ratio="1:1", - perspective="no" -) - -# 自动保存到数据库 -# 任务状态:pending(待付款) -``` - ---- - -### 场景 2:客户付款,开始处理 - -```python -# 客户付款后 -workflow.trigger_processing_on_payment( - customer_id="customer_123" -) - -# 任务状态更新:pending → paid → processing -``` - ---- - -### 场景 3:客户增加需求细节 - -```python -# 客户说:"对了,需要去掉背景" -await workflow.add_customer_requirement( - task_id="TASK_20260227_001", - customer_id="customer_123", - requirement="需要去掉背景" -) - -# 数据库记录: -# customer_notes: "[02-27 18:00] 需要去掉背景" -# 任务变更表新增记录 -``` - ---- - -### 场景 4:客户修改操作类型 - -```python -# 客户说:"改成要分层文件" -await workflow.modify_operation( - task_id="TASK_20260227_001", - customer_id="customer_123", - new_operation="remove_bg_and_layer" -) - -# 数据库更新: -# operation: "enhance" → "remove_bg_and_layer" -# 任务变更表新增记录 -``` - ---- - -### 场景 5:查看需求变更历史 - -```python -# 查看客户修改了哪些需求 -history = workflow.get_task_requirement_history("TASK_20260227_001") - -# 返回: -# [ -# {"change_type": "modify_operation", "old_value": "enhance", "new_value": "remove_bg_and_layer", ...}, -# {"change_type": "add_note", "old_value": "无", "new_value": "需要去掉背景", ...} -# ] -``` - ---- - -## 🔄 任务状态流转 - -``` -pending(待付款) - ↓ -paid(已付款) - ↓ -processing(处理中) - ↓ -awaiting_confirm(待确认) - ↓ -completed(已完成) - ↓ -[failed(失败)] ← 可能失败 -``` - ---- - -## 📝 API 接口 - -### 创建任务 -```python -workflow.create_image_task( - customer_id="customer_123", - original_image="https://xxx.com/image.jpg", - operation="enhance" -) -``` - -### 添加需求 -```python -await workflow.add_customer_requirement( - task_id="TASK_001", - customer_id="customer_123", - requirement="需要高分辨率" -) -``` - -### 修改操作 -```python -await workflow.modify_operation( - task_id="TASK_001", - customer_id="customer_123", - new_operation="vectorize" -) -``` - -### 查询任务 -```python -task = workflow.get_task("TASK_001") -tasks = workflow.get_customer_tasks("customer_123") -``` - -### 查询历史 -```python -history = workflow.get_task_requirement_history("TASK_001") -``` - ---- - -## 🔍 数据库操作 - -### 查看数据库文件 -```bash -sqlite3 /root/ai_customer_service/ai_cs/db/image_tasks.db -``` - -### 查询所有任务 -```sql -SELECT task_id, customer_id, status, created_at FROM image_tasks ORDER BY created_at DESC LIMIT 10; -``` - -### 查询待处理任务 -```sql -SELECT * FROM image_tasks WHERE status='pending'; -``` - -### 查询客户需求变更 -```sql -SELECT task_id, change_type, old_value, new_value, changed_at, changed_by -FROM task_requirement_changes -WHERE task_id='TASK_001' -ORDER BY changed_at DESC; -``` - ---- - -## ⚠️ 注意事项 - -1. **任务持久化**:所有任务自动保存到数据库,重启不丢失 -2. **需求变更**:客户可以随时增加需求细节(付款前) -3. **操作修改**:付款前可以修改操作类型,付款后不允许 -4. **历史记录**:所有变更都有记录,可追溯 -5. **状态管理**:任务状态自动流转,无需手动更新 - ---- - -## 📊 数据库位置 - -``` -/root/ai_customer_service/ai_cs/db/image_tasks.db -``` - -数据库管理器: -``` -/root/ai_customer_service/ai_cs/db/image_tasks_db.py -``` - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 - diff --git a/图绘派单系统集成说明.md b/图绘派单系统集成说明.md deleted file mode 100644 index 471925e..0000000 --- a/图绘派单系统集成说明.md +++ /dev/null @@ -1,278 +0,0 @@ -# 图绘派单系统集成说明 - -## 📋 功能说明 - -AI 客服系统已接入图绘派单系统 API,实现自动派单给在线设计师。 - ---- - -## 🚀 派单系统 API - -### 基础信息 -- **API 地址**: http://1.12.50.92:8005 -- **API Key**: tuhui_dispatch_key_2026 -- **认证方式**: Header: X-API-Key - -### 核心接口 - -| 接口 | 方法 | 说明 | -|------|------|------| -| `/dispatch/queue` | GET | 获取派单队列 | -| `/online/designers` | GET | 获取在线设计师 | -| `/tasks` | POST | 创建任务 | -| `/tasks/{id}/assign` | POST | 分配任务 | -| `/tasks/{id}` | GET | 查询任务状态 | -| `/tasks/{id}/complete` | POST | 完成任务 | - ---- - -## 🔄 工作流程 - -### 转人工派单流程 - -``` -客户:这个能做吗 [图片] -AI: 抱歉,这个我做不了,帮您转接设计师 - ↓ -1. 查询在线设计师 - GET /online/designers - 返回:["橘子", "婷婷"] - ↓ -2. 创建派单任务 - POST /tasks - 返回:{"task_id": "ea853bd9"} - ↓ -3. 分配给设计师 - POST /tasks/ea853bd9/assign - {"designer_name": "橘子"} - ↓ -4. 企业微信通知设计师 - ↓ -5. 回复客户 - "好的,已帮您安排设计师处理,请稍候" -``` - ---- - -## 📝 API 调用示例 - -### 1. 查询在线设计师 - -```python -from services.service_tuhui_dispatch import get_tuhui_dispatch_client - -client = get_tuhui_dispatch_client() -designers = await client.get_online_designers() -# 返回:["橘子", "婷婷"] -``` - -### 2. 创建任务 - -```python -task_id = await client.create_task( - task_name="图片处理 -1234", - description="客户需要做高清修复", - task_type="image_process", - priority=2 -) -# 返回:"ea853bd9" -``` - -### 3. 分配任务 - -```python -success = await client.assign_task( - task_id="ea853bd9", - designer_name="橘子", - notes="AI 客服自动派单" -) -# 返回:True -``` - -### 4. 查询任务状态 - -```python -status = await client.get_task_status("ea853bd9") -# 返回:{"status": "assigned", "assigned_to": "橘子", ...} -``` - -### 5. 完成任务 - -```python -success = await client.complete_task( - task_id="ea853bd9", - notes="客户已确认,效果满意" -) -# 返回:True -``` - ---- - -## 🔧 集成代码位置 - -### 派单客户端 - -文件:`/root/ai_customer_service/ai_cs/services/service_tuhui_dispatch.py` - -**类**: `TuhuiDispatchClient` - -**方法**: -- `get_online_designers()` - 查询在线设计师 -- `create_task()` - 创建任务 -- `assign_task()` - 分配任务 -- `get_task_status()` - 查询状态 -- `complete_task()` - 完成任务 - ---- - -### 工作流集成 - -文件:`/root/ai_customer_service/ai_cs/core/workflow.py` - -**方法**: -- `transfer_to_designer_workflow()` - 转人工派单工作流 -- `_get_online_designers()` - 查询在线设计师 -- `_dispatch_to_designer()` - 派单给设计师 - ---- - -## 📊 派单逻辑 - -### 1. 查询在线设计师 - -```python -designers = await workflow._get_online_designers() -# 返回:["橘子", "婷婷"] -``` - -### 2. 创建派单任务 - -```python -dispatch_task_id = await workflow.dispatch_client.create_task( - task_name=f"图片处理-{customer_id[-4:]}", - description=f"{reason}\\n客户:{customer_id}\\n图片:{image_url}", - task_type="image_process", - priority=2 -) -``` - -### 3. 分配给设计师 - -```python -success = await workflow.dispatch_client.assign_task( - task_id=dispatch_task_id, - designer_name=designer_name, # "橘子" - notes=f"AI 客服自动派单\\n原因:{reason}\\n客户:{customer_id}" -) -``` - -### 4. 企业微信通知 - -```python -await _wechat_notify( - f"📋 **新任务派单**\\n" - f"设计师:{designer_name}\\n" - f"任务 ID: {dispatch_task_id}\\n" - f"客户:{customer_id}\\n" - f"原因:{reason}\\n" - f"请及时处理" -) -``` - ---- - -## ⚠️ 注意事项 - -1. **API Key**: 固定为 `tuhui_dispatch_key_2026` -2. **超时设置**: 默认 10 秒 -3. **错误处理**: 所有 API 调用都有 try-except -4. **企业微信通知**: 派单成功后自动发送 -5. **任务命名**: 格式为 `图片处理-{客户 ID 后 4 位}` - ---- - -## 🔍 日志查看 - -```bash -# 查看派单日志 -grep "派单" /tmp/ai-cs.log - -# 查看在线设计师查询 -grep "在线设计师" /tmp/ai-cs.log - -# 查看任务创建 -grep "创建任务" /tmp/ai-cs.log -``` - ---- - -## 📈 派单统计 - -### 查询派单队列 - -```bash -curl -X GET "http://1.12.50.92:8005/dispatch/queue" \ - -H "X-API-Key: tuhui_dispatch_key_2026" -``` - -**返回**: -```json -{ - "pending_tasks": {"count": 3, "tasks": [...]}, - "online_designers": {"count": 2, "designers": ["橘子", "婷婷"]}, - "suggestion": "橘子" -} -``` - ---- - -## 🎯 完整示例 - -### AI 客服转人工派单 - -```python -# 客户说"做不了" -await workflow.transfer_to_designer_workflow( - customer_id="customer_123", - image_url="https://xxx.com/image.jpg", - acc_id="shop_001", - acc_type="AliWorkbench", - reason="图片过于模糊,AI 无法处理" -) - -# 执行流程: -# 1. 查询在线设计师 → ["橘子", "婷婷"] -# 2. 创建任务 → "ea853bd9" -# 3. 分配给"橘子" -# 4. 企业微信通知 -# 5. 回复客户:"好的,已帮您安排设计师处理" -``` - ---- - -## 📞 技术支持 - -**派单系统服务器**: 1.12.50.92:8005 - -**查看派单系统日志**: -```bash -tail -f /var/log/dispatch_api.log -``` - -**查看派单进程**: -```bash -ps aux | grep dispatch -``` - -**重启派单服务**: -```bash -pkill -f dispatch_api -cd /root/tuhui/backend -nohup python3 -m uvicorn dispatch_api:app --host 0.0.0.0 --port 8005 > /var/log/dispatch_api.log 2>&1 & -``` - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 - diff --git a/文字加价功能说明.md b/文字加价功能说明.md deleted file mode 100644 index 4c84335..0000000 --- a/文字加价功能说明.md +++ /dev/null @@ -1,225 +0,0 @@ -# 图片文字检测与加价功能 - -## 📋 功能说明 - -AI 客服现在可以分析图片中的文字数量,并根据文字数量和分层需求自动加价。 - ---- - -## 💰 价格规则 - -### 基础价格 - -| 复杂度 | 价格区间 | 说明 | -|--------|----------|------| -| simple | 10-15 元 | 画面简单干净 | -| normal | 15-20 元 | 一般复杂度 | -| complex | 20-25 元 | 细节偏多 | -| hard | 25-30 元 | 非常复杂 | - -### 文字数量加价 - -| 文字数量 | 加价 | 说明 | -|----------|------|------| -| none | +0 元 | 无文字 | -| 少量 (1-10 字) | +5 元 | 少量文字 | -| 中量 (11-50 字) | +15 元 | 中量文字 | -| 大量 (51-200 字) | +30 元 | 大量文字 | -| 极多 (200 字以上) | +50 元 | 极多文字 | - -### 文字分层需求加价 - -| 分层需求 | 加价 | 说明 | -|----------|------|------| -| no | +0 元 | 普通图片处理 | -| yes (有文字) | +50 元起 | 可编辑分层文件(PSD 等) | -| yes (无文字) | +30 元 | 仅需分层文件 | - -### 特殊价格:文字分层 + 大量文字 - -**条件**:文字数量=大量/极多 且 文字分层需求=yes - -**价格范围**:60-80 元 - ---- - -## 🎯 使用场景 - -### 场景 1:少量文字,不分层 - -**客户**:[发送一张有 5 个字的图片] - -**AI 分析**: -- 复杂度:simple -- 文字数量:少量 (1-10 字) -- 分层需求:no - -**报价**: -``` -基础价格:15 元 -文字加价:+5 元 -总计:20 元 -``` - -**AI 回复**: -``` -这张图比较简单,不过有少量文字需要处理,20 元。 -``` - ---- - -### 场景 2:大量文字,需要分层 - -**客户**:[发送一张有 100 多字的图片] -**客户**:需要可以编辑的分层文件 - -**AI 分析**: -- 复杂度:complex -- 文字数量:大量 (51-200 字) -- 分层需求:yes - -**报价**: -``` -基础价格:25 元 -文字加价:+30 元 -分层加价:+50 元 -总计:105 元 → 调整到 80 元(特殊价格上限) -``` - -**AI 回复**: -``` -这张图文字比较多,有 100 多字,而且需要分层文件,处理起来比较麻烦,80 元。 -文字处理 +30 元,分层 +50 元。 -``` - ---- - -### 场景 3:极多文字,需要分层(高客单价) - -**客户**:[发送一张有 300 多字的图片] -**客户**:要 PSD 分层文件 - -**AI 分析**: -- 复杂度:hard -- 文字数量:极多 (200 字以上) -- 分层需求:yes - -**报价**: -``` -基础价格:30 元 -文字加价:+50 元 -分层加价:+50 元 -总计:130 元 → 调整到 80 元(特殊价格上限) -``` - -**AI 回复**: -``` -这张图文字非常多,有 300 多字,还需要分层文件,处理起来很费时间,80 元。 -``` - ---- - -## 🔧 技术实现 - -### 1. 图片分析器增强 - -文件:`/root/ai_customer_service/ai_cs/image/image_analyzer.py` - -**新增字段**: -- `text_amount`: 文字数量(none/少量/中量/大量/极多) -- `text_layer_need`: 分层需求(yes/no) -- `text_surcharge`: 文字加价金额 -- `layer_surcharge`: 分层加价金额 - -### 2. AI 客服话术 - -文件:`/root/ai_customer_service/ai_cs/core/pydantic_ai_agent.py` - -**报价说明**: -- 自动显示文字加价 -- 自动显示分层加价 -- 特殊价格自动调整到 60-80 元范围 - ---- - -## 📝 配置说明 - -### 价格配置 - -修改 `/root/ai_customer_service/ai_cs/image/image_analyzer.py` 中的价格规则: - -```python -# 文字数量加价 -text_surcharge = 0 -if text_amount == "少量 (1-10 字)": - text_surcharge = 5 -elif text_amount == "中量 (11-50 字)": - text_surcharge = 15 -elif text_amount == "大量 (51-200 字)": - text_surcharge = 30 -elif text_amount == "极多 (200 字以上)": - text_surcharge = 50 - -# 分层加价 -if text_layer_need == "yes": - layer_surcharge = max(50, price_suggest) - -# 特殊价格:60-80 元 -if text_amount in ["大量", "极多"] and text_layer_need == "yes": - price_suggest = max(60, min(80, price_suggest)) -``` - ---- - -## 📊 价格计算流程 - -``` -客户发送图片 - ↓ -AI 分析图片 - ↓ -┌───────────────────────┐ -│ 1. 判断基础复杂度 │ -│ simple/normal/ │ -│ complex/hard │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 2. 检测文字数量 │ -│ none/少量/中量/ │ -│ 大量/极多 │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 3. 询问分层需求 │ -│ yes/no │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 4. 计算总价 │ -│ 基础 + 文字 + 分层 │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 5. 特殊价格处理 │ -│ 60-80 元范围 │ -└───────────┬───────────┘ - ↓ - 报价给客户 -``` - ---- - -## ⚠️ 注意事项 - -1. **文字数量检测**:通过视觉 AI 自动识别 -2. **分层需求**:需要询问客户或从对话中识别 -3. **价格上限**:文字分层 + 大量文字最高 80 元 -4. **价格下限**:文字分层 + 大量文字最低 60 元 -5. **价格取整**:最终价格必须是 5 的倍数 - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 - diff --git a/部署文档.md b/部署文档.md index 6436105..079e355 100644 --- a/部署文档.md +++ b/部署文档.md @@ -1,91 +1,228 @@ -# AI 客服系统 - 天网协作版部署文档 +# AI 客服系统 - 部署与运维文档 -## 📋 系统架构 +**版本**: v1.0 | **更新日期**: 2026-02-28 + +--- + +## 目录 + +1. [系统架构](#系统架构) +2. [快速部署](#快速部署) +3. [启动方式](#启动方式) +4. [生产环境部署](#生产环境部署) +5. [多进程架构](#多进程架构) +6. [API 接口文档](#api-接口文档) +7. [触发条件详解](#触发条件详解) +8. [数据库](#数据库) +9. [配置说明](#配置说明) +10. [监控与日志](#监控与日志) +11. [故障排查](#故障排查) + +--- + +## 系统架构 ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ 天网服务器 │ ───→ │ AI 客服 API │ ───→ │ 企业微信 │ -│ (公网 IP) │ │ (127.0.0.1:6060)│ │ (轻简软件) │ +│ (公网 IP) │ │ (127.0.0.1:6060)│ │ (轻简软件) │ └─────────────┘ └──────────────┘ └─────────────┘ ↑ │ - │ ↓ - └──────────────┐ - ┌──────────────┐ - │ SQLite │ - │ 任务数据库 │ - └──────────────┘ + └─────────────────────┘ + ┌──────────────┐ + │ SQLite │ + │ 任务数据库 │ + └──────────────┘ ``` ### 核心组件 | 组件 | 地址 | 说明 | |------|------|------| -| **AI 客服 HTTP API** | http://127.0.0.1:6060 | 接收天网任务 | -| **天网服务器** | 你的公网 IP | 任务调度中心 | -| **轻简软件** | ws://127.0.0.1:9528 | 企业微信连接 | -| **任务数据库** | SQLite | 本地存储 | +| AI 客服 HTTP API | `http://127.0.0.1:6060` | 接收天网任务 | +| 天网服务器 | 公网 IP | 任务调度中心 | +| 轻简软件 | `ws://127.0.0.1:9528` | 企业微信连接 | +| 任务数据库 | SQLite 本地存储 | 任务持久化 | --- -## 🚀 快速部署 +## 快速部署 ### 步骤 1:环境检查 ```bash -# 检查 Python 版本 python3 --version # 需要 3.8+ - -# 检查依赖 -pip3 list | grep -E "flask|pydantic|sqlite" +cd /root/ai_customer_service/ai_cs +pip3 install -r requirements.txt ``` -### 步骤 2:启动 AI 客服 API +### 步骤 2:启动服务 ```bash cd /root/ai_customer_service/ai_cs # 前台运行(测试用) -python3 run_tianwang_simple.py +python3 run.py --api-only # 后台运行(生产用) -nohup python3 run_tianwang_simple.py > /tmp/tianwang.log 2>&1 & - -# 查看进程 -ps aux | grep run_tianwang - -# 查看日志 -tail -f /tmp/tianwang.log +nohup python3 run.py --api-only > /tmp/tianwang.log 2>&1 & ``` -### 步骤 3:测试 API +### 步骤 3:验证 ```bash -# 健康检查 curl http://localhost:6060/api/health - -# 预期输出: -# {"code":200,"data":{"service":"ai-cs-tianwang-bridge","timestamp":"..."},"message":"OK"} +# 预期: {"code":200,"data":{"service":"ai-cs-tianwang-bridge",...},"message":"OK"} ``` --- -## 📝 API 接口说明 +## 启动方式 + +统一入口 `run.py`,通过参数切换模式: + +```bash +# 仅 HTTP API(天网简化版,推荐) +python3 run.py --api-only + +# 完整版(HTTP API + WebSocket + AI Agent) +python3 run.py --tianwang + +# WebSocket 客服模式(默认) +python3 run.py + +# 多进程模式 +python3 run.py --multi --workers 4 + +# 不启用 AI Agent +python3 run.py --no-agent + +# 指定 HTTP 端口 +python3 run.py --api-only --port 8080 +``` + +--- + +## 生产环境部署 + +### 方式 1:systemd 服务(推荐) + +```bash +cat > /etc/systemd/system/ai-cs-tianwang.service << 'SERVICE' +[Unit] +Description=AI Customer Service with Tianwang +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=/root/ai_customer_service/ai_cs +ExecStart=/usr/bin/python3 run.py --api-only +Restart=always +RestartSec=10 +LimitNOFILE=65535 +Environment="HTTP_API_PORT=6060" +StandardOutput=journal +StandardError=journal +SyslogIdentifier=ai-cs-tianwang + +[Install] +WantedBy=multi-user.target +SERVICE + +systemctl daemon-reload +systemctl enable ai-cs-tianwang +systemctl start ai-cs-tianwang +systemctl status ai-cs-tianwang +journalctl -u ai-cs-tianwang -f +``` + +### 方式 2:Docker 部署 + +```dockerfile +FROM python:3.11-slim +WORKDIR /app +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt +COPY . . +EXPOSE 6060 +CMD ["python3", "run.py", "--api-only"] +``` + +```bash +docker build -t ai-cs-tianwang . +docker run -d \ + --name ai-cs \ + -p 6060:6060 \ + -v /root/ai_customer_service/ai_cs/db:/app/db \ + --restart unless-stopped \ + ai-cs-tianwang +``` + +### 方式 3:后台运行(简单场景) + +```bash +nohup python3 run.py --api-only > /tmp/tianwang.log 2>&1 & +ps aux | grep "run.py" +tail -f /tmp/tianwang.log +pkill -f "run.py" # 停止 +``` + +--- + +## 多进程架构 + +### 架构说明 + +``` +单进程(默认) 多进程(可选) +┌─────────────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ +│ Python 进程 │ │进程 1 │ │进程 2 │ │进程 3 │ +│ asyncio Loop │ │客户 A,B │ │客户 C,D │ │客户 E,F │ +│ 所有客户 + Agent │ └─────────┘ └─────────┘ └─────────┘ +└─────────────────┘ +``` + +### 使用方法 + +```bash +# 多进程模式(默认 CPU 核心数) +python3 run.py --multi + +# 指定进程数 +python3 run.py --multi --workers 4 + +# 或使用专用启动器 +python3 scripts/multi_process_launcher.py --workers 4 +``` + +### 分片算法 + +客户按 `acc_id:from_id` 的 MD5 hash 值分配到不同进程,同一客户始终在同一进程。 + +### 性能对比 + +| 指标 | 单进程 | 多进程 (4 核) | +|------|--------|-------------| +| 并发客户数 | ~50 | ~200 | +| CPU 使用率 | 25% | 80% | +| 故障影响 | 全局 | 局部 | + +--- + +## API 接口文档 ### 1. 接收任务 -**接口**: `POST /api/task/receive` +**POST** `/api/task/receive` -**请求示例**: ```bash curl -X POST http://localhost:6060/api/task/receive \ -H "Content-Type: application/json" \ -d '{ "task_id": "TASK_20260227_001", "type": "send_file_after_reply", - "customer": { - "id": "customer_123", - "name": "小明" - }, + "customer": {"id": "customer_123", "name": "小明"}, "trigger": { "type": "specified_customer_reply", "customer_id": "customer_123", @@ -93,133 +230,61 @@ curl -X POST http://localhost:6060/api/task/receive \ "keyword": "好的", "exact_match": false }, - "action": { - "type": "send_message", - "message": "这是您要的文件" - }, + "action": {"type": "send_message", "message": "这是您要的文件"}, "priority": "normal", "timeout_hours": 24, "created_by": "设计师 lz" }' ``` -**响应示例**: +**响应**: ```json -{ - "code": 200, - "message": "任务接收成功", - "data": { - "task_id": "TASK_20260227_001", - "status": "pending" - } -} +{"code": 200, "message": "任务接收成功", "data": {"task_id": "TASK_20260227_001", "status": "pending"}} ``` ---- - ### 2. 查询任务状态 -**接口**: `GET /api/task/status/:task_id` +**GET** `/api/task/status/:task_id` -**请求示例**: ```bash curl http://localhost:6060/api/task/status/TASK_20260227_001 ``` -**响应示例**: -```json -{ - "code": 200, - "data": { - "task_id": "TASK_20260227_001", - "type": "send_file_after_reply", - "status": "pending", - "priority": "normal", - "retry_count": 0, - "created_at": "2026-02-27T18:00:00", - "created_by": "设计师 lz", - "triggered_at": null, - "completed_at": null, - "error_message": null - } -} -``` - ---- - ### 3. 取消任务 -**接口**: `POST /api/task/cancel` +**POST** `/api/task/cancel` -**请求示例**: ```bash curl -X POST http://localhost:6060/api/task/cancel \ -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_20260227_001", - "reason": "客户取消订单" - }' + -d '{"task_id": "TASK_20260227_001", "reason": "客户取消订单"}' ``` -**响应示例**: -```json -{ - "code": 200, - "message": "任务已取消", - "data": { - "task_id": "TASK_20260227_001", - "status": "cancelled" - } -} -``` +### 4. 任务列表 ---- +**GET** `/api/task/list` -### 4. 查询任务列表 +参数: `customer_id`(可选)、`status`(可选)、`page`(默认1)、`page_size`(默认20) -**接口**: `GET /api/task/list` - -**请求参数**: -- `customer_id` (可选): 客户 ID -- `status` (可选): 任务状态 -- `page` (可选): 页码,默认 1 -- `page_size` (可选): 每页数量,默认 20 - -**请求示例**: ```bash curl "http://localhost:6060/api/task/list?status=pending&page=1&page_size=10" ``` ---- - ### 5. 健康检查 -**接口**: `GET /api/health` +**GET** `/api/health` -**请求示例**: ```bash curl http://localhost:6060/api/health ``` -**响应示例**: -```json -{ - "code": 200, - "message": "OK", - "data": { - "timestamp": "2026-02-27T18:00:00", - "service": "ai-cs-tianwang-bridge" - } -} -``` - --- -## 📊 触发条件类型 +## 触发条件详解 ### 1. specified_customer_reply(推荐) -**指定客户回复指定内容** +指定客户回复指定内容时触发。 ```json { @@ -233,105 +298,62 @@ curl http://localhost:6060/api/health } ``` -**匹配逻辑**: -- ✅ 客户 ID 匹配 -- ✅ 客户名称匹配(可选) -- ✅ 消息包含关键词 +| 字段 | 必填 | 说明 | +|------|------|------| +| `customer_id` | 是 | 指定客户 ID | +| `customer_name` | 否 | 指定客户名称 | +| `keyword` | 是 | 回复关键词 | +| `exact_match` | 否 | 是否精确匹配(默认 false)| ---- +**exact_match 说明**: +- `false`: 消息**包含**关键词即触发("好的谢谢" 匹配 "好的") +- `true`: 消息**完全等于**关键词才触发 + +**匹配逻辑**: +``` +客户发送消息 → 检查客户 ID → 检查客户名称(可选) → 检查关键词 → 触发 +``` ### 2. customer_reply -**任意客户回复指定内容** +任意客户回复指定内容。 ```json -{ - "trigger": { - "type": "customer_reply", - "keyword": "好的" - } -} +{"trigger": {"type": "customer_reply", "keyword": "好的"}} ``` ---- - ### 3. customer_keyword -**任意客户说某关键词** +任意客户说某关键词(支持多个)。 ```json -{ - "trigger": { - "type": "customer_keyword", - "keywords": ["好的", "可以", "行"] - } -} +{"trigger": {"type": "customer_keyword", "keywords": ["好的", "可以", "行"]}} ``` ---- - ### 4. customer_payment -**客户付款** +客户付款时触发。 ```json -{ - "trigger": { - "type": "customer_payment", - "keywords": ["已付款", "拍下了", "已下单"] - } -} +{"trigger": {"type": "customer_payment", "keywords": ["已付款", "拍下了"]}} ``` ---- - ### 5. time_reach -**到达指定时间** +到达指定时间触发。 ```json -{ - "trigger": { - "type": "time_reach", - "time": "2026-02-27 09:00:00" - } -} +{"trigger": {"type": "time_reach", "time": "2026-02-28 09:00:00"}} ``` --- -## 🔧 配置说明 +## 数据库 -### 环境变量文件 +### 天网任务数据库 -位置:`/root/ai_customer_service/ai_cs/.env.tianwang` +路径: `db/task_db/tasks.db` -```bash -# AI 客服配置 -AI_CS_HOST=127.0.0.1 -AI_CS_PORT=6060 - -# AI 客服 HTTP API 地址(本地) -AI_CS_API_URL=http://127.0.0.1:6060 - -# 天网回调地址 -TIANWANG_CALLBACK_URL=http://127.0.0.1:6060/api/task/callback - -# API 接口 -TASK_RECEIVE=/api/task/receive -TASK_STATUS=/api/task/status -TASK_CANCEL=/api/task/cancel -TASK_LIST=/api/task/list -HEALTH=/api/health -``` - ---- - -### 数据库配置 - -位置:`/root/ai_customer_service/ai_cs/db/task_db/tasks.db` - -**数据库结构**: ```sql CREATE TABLE tasks ( task_id TEXT PRIMARY KEY, @@ -360,281 +382,144 @@ CREATE TABLE tasks ( ); ``` +**任务状态流转**: `pending → waiting → running → completed / failed` + +### 图片任务数据库 + +路径: `db/image_tasks.db`(详见 **项目功能汇总.md - 图片任务数据库**) + --- -## 📖 使用场景 +## 配置说明 -### 场景 1:客户说"好的"后发送文件 +### 环境变量 -**天网下发任务**: -```json -{ - "task_id": "TASK_001", - "type": "send_file_after_reply", - "customer": { - "id": "customer_123", - "name": "小明" - }, - "trigger": { - "type": "specified_customer_reply", - "customer_id": "customer_123", - "customer_name": "小明", - "keyword": "好的" - }, - "action": { - "type": "send_message", - "message": "这是您要的文件" - }, - "priority": "normal", - "timeout_hours": 24, - "created_by": "设计师 lz" -} +文件: `.env.tianwang` + +```bash +AI_CS_HOST=127.0.0.1 +AI_CS_PORT=6060 +AI_CS_API_URL=http://127.0.0.1:6060 +TIANWANG_CALLBACK_URL=http://127.0.0.1:6060/api/task/callback ``` -**流程**: -1. 天网下发任务 → AI 客服 API -2. 客户在群里说"好的" -3. AI 客服检测到匹配任务 -4. 自动发送消息给客户 -5. 任务标记为 completed -6. 回调通知天网 +### 天网回调配置 ---- +在 `core/task_scheduler.py` 中修改回调 URL: -### 场景 2:客户付款后发送教程 +```python +await client.post('http://tianwang-server/api/task/callback', json={...}) +``` -**天网下发任务**: -```json -{ - "task_id": "TASK_002", - "type": "send_tutorial_after_payment", - "customer": { - "id": "customer_456", - "name": "小红" - }, - "trigger": { - "type": "customer_payment" - }, - "action": { - "type": "send_message", - "message": "感谢购买!这是使用教程:..." - }, - "created_by": "设计师 lz" -} +### 端口说明 + +| 端口 | 用途 | +|------|------| +| 6060 | HTTP API 服务器 | +| 9528 | 轻简软件 WebSocket(外部)| + +**防火墙**: +```bash +firewall-cmd --add-port=6060/tcp --permanent && firewall-cmd --reload ``` --- -### 场景 3:定时发送问候 - -**天网下发任务**: -```json -{ - "task_id": "TASK_003", - "type": "send_greeting", - "customer": { - "id": "customer_789" - }, - "trigger": { - "type": "time_reach", - "time": "2026-02-28 09:00:00" - }, - "action": { - "type": "send_message", - "message": "早上好!有什么可以帮您?" - }, - "created_by": "设计师 lz" -} -``` - ---- - -## 🔍 监控与日志 +## 监控与日志 ### 查看进程状态 ```bash -# 查看进程 -ps aux | grep run_tianwang - -# 查看端口 +ps aux | grep "run.py" netstat -tlnp | grep 6060 +systemctl status ai-cs-tianwang # systemd 方式 ``` ### 查看日志 ```bash -# 实时日志 -tail -f /tmp/tianwang.log - -# 最近 100 行 -tail -100 /tmp/tianwang.log - -# 搜索关键词 -grep "任务" /tmp/tianwang.log +tail -f /tmp/tianwang.log # 文件方式 +journalctl -u ai-cs-tianwang -f # systemd 方式 +grep "任务" /tmp/tianwang.log # 搜索任务日志 +grep "派单" /tmp/tianwang.log # 搜索派单日志 +grep "转接人工" /tmp/tianwang.log # 搜索转接日志 ``` ### 查看数据库 ```bash -# 进入 SQLite sqlite3 /root/ai_customer_service/ai_cs/db/task_db/tasks.db -# 查看所有任务 SELECT task_id, type, status, created_at FROM tasks ORDER BY created_at DESC LIMIT 10; - -# 查看待触发任务 SELECT * FROM tasks WHERE status='pending'; - -# 查看失败任务 SELECT task_id, error_message FROM tasks WHERE status='failed'; - -# 查看统计数据 SELECT status, COUNT(*) as count FROM tasks GROUP BY status; - -# 退出 .exit ``` --- -## ⚠️ 故障排查 +## 故障排查 -### 问题 1:API 无法访问 +### API 无法访问 -**症状**: `curl http://localhost:6060/api/health` 无响应 - -**解决**: ```bash -# 1. 检查进程 -ps aux | grep run_tianwang - -# 2. 检查端口 -netstat -tlnp | grep 6060 - -# 3. 重启服务 -pkill -f run_tianwang -cd /root/ai_customer_service/ai_cs -nohup python3 run_tianwang_simple.py > /tmp/tianwang.log 2>&1 & - -# 4. 查看日志 -tail -f /tmp/tianwang.log +ps aux | grep "run.py" # 检查进程 +netstat -tlnp | grep 6060 # 检查端口 +pkill -f "run.py" # 停止 +nohup python3 run.py --api-only > /tmp/tianwang.log 2>&1 & +tail -f /tmp/tianwang.log # 查看日志 ``` ---- +### 任务接收失败(500 错误) -### 问题 2:任务接收失败 - -**症状**: POST /api/task/receive 返回 500 错误 - -**解决**: ```bash -# 1. 查看日志 tail -f /tmp/tianwang.log | grep "ERROR" - -# 2. 检查数据库 -sqlite3 /root/ai_customer_service/ai_cs/db/task_db/tasks.db ".schema tasks" - -# 3. 测试数据库 -sqlite3 /root/ai_customer_service/ai_cs/db/task_db/tasks.db "SELECT 1;" - -# 4. 如果数据库损坏,删除重建 -rm /root/ai_customer_service/ai_cs/db/task_db/tasks.db -# 重启服务后会自动创建 +sqlite3 db/task_db/tasks.db ".schema tasks" # 检查数据库 +# 如果数据库损坏:rm db/task_db/tasks.db 然后重启(自动重建) ``` ---- +### 任务未触发 -### 问题 3:任务未触发 - -**症状**: 任务一直是 pending 状态 - -**解决**: ```bash -# 1. 检查任务状态 -curl http://localhost:6060/api/task/status/TASK_ID - -# 2. 查看触发日志 -grep "任务触发" /tmp/tianwang.log - -# 3. 检查触发条件 +curl http://localhost:6060/api/task/status/TASK_ID # 检查状态 +grep "任务触发" /tmp/tianwang.log # 查看触发日志 # 确认客户消息包含触发关键词 - -# 4. 手动触发测试 -sqlite3 /root/ai_customer_service/ai_cs/db/task_db/tasks.db \ - "UPDATE tasks SET status='pending' WHERE task_id='TASK_ID';" ``` ---- +### 内存占用过高 -### 问题 4:内存占用过高 - -**症状**: 进程内存持续增长 - -**解决**: ```bash -# 1. 查看内存使用 ps aux | grep run_tianwang | awk '{print $6/1024 " MB"}' - -# 2. 定期重启(建议每天) +# 建议每天定时重启 crontab -e -# 添加:0 3 * * * pkill -f run_tianwang && sleep 2 && nohup python3 /root/ai_customer_service/ai_cs/run_tianwang_simple.py > /tmp/tianwang.log 2>&1 & +# 添加: 0 3 * * * pkill -f "run.py" && sleep 2 && nohup python3 /root/ai_customer_service/ai_cs/run.py --api-only > /tmp/tianwang.log 2>&1 & +``` + +### Worker 进程退出(多进程模式) + +```bash +journalctl -u ai-cs-multi -f | grep "Worker.*退出" +systemctl restart ai-cs-multi ``` --- -## 📞 技术支持 - -### 文件位置 +## 文件位置速查 | 文件 | 路径 | |------|------| -| **启动脚本** | `/root/ai_customer_service/ai_cs/run_tianwang_simple.py` | -| **HTTP API** | `/root/ai_customer_service/ai_cs/api/http_server.py` | -| **任务调度** | `/root/ai_customer_service/ai_cs/core/task_scheduler.py` | -| **数据模型** | `/root/ai_customer_service/ai_cs/db/task_db/task_model.py` | -| **配置文件** | `/root/ai_customer_service/ai_cs/.env.tianwang` | -| **日志文件** | `/tmp/tianwang.log` | -| **数据库** | `/root/ai_customer_service/ai_cs/db/task_db/tasks.db` | - -### 文档位置 - -| 文档 | 路径 | -|------|------| -| **部署文档** | `/root/ai_customer_service/ai_cs/部署文档.md` | -| **功能文档** | `/root/ai_customer_service/ai_cs/TIANWANG_INTEGRATION.md` | -| **使用示例** | `/root/ai_customer_service/ai_cs/SPECIFIED_CUSTOMER_REPLY_EXAMPLE.md` | +| 启动脚本 | `run.py`(通过 `--api-only` / `--tianwang` 切换模式)| +| HTTP API | `api/http_server.py` | +| 任务调度 | `core/task_scheduler.py` | +| 数据模型 | `db/task_db/task_model.py` | +| 配置文件 | `.env.tianwang` | +| 日志文件 | `/tmp/tianwang.log` | +| 任务数据库 | `db/task_db/tasks.db` | --- -## 📧 发送文档 - -如需发送此文档,可以: - -### 方式 1:复制内容 -```bash -# 复制文档内容 -cat /root/ai_customer_service/ai_cs/部署文档.md -``` - -### 方式 2:生成 PDF -```bash -# 安装 pandoc -apt-get install pandoc - -# 转换为 PDF -pandoc /root/ai_customer_service/ai_cs/部署文档.md -o 部署文档.pdf -``` - -### 方式 3:发送邮件 -```bash -# 使用 mail 命令 -mail -s "AI 客服系统部署文档" recipient@example.com < /root/ai_customer_service/ai_cs/部署文档.md -``` - ---- - -## 🎯 快速参考卡片 +## 快速参考 ``` ┌─────────────────────────────────────────────┐ @@ -648,15 +533,8 @@ mail -s "AI 客服系统部署文档" recipient@example.com < /root/ai_customer_ │ GET /api/task/list - 任务列表 │ │ GET /api/health - 健康检查 │ │ │ -│ 启动:python3 run_tianwang_simple.py │ +│ 启动:python3 run.py --api-only │ │ 日志:tail -f /tmp/tianwang.log │ │ 数据库:sqlite3 db/task_db/tasks.db │ └─────────────────────────────────────────────┘ ``` - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 -**维护者**: AI 客服系统团队 - diff --git a/项目功能汇总.md b/项目功能汇总.md index e75fffe..29bf794 100644 --- a/项目功能汇总.md +++ b/项目功能汇总.md @@ -1,263 +1,447 @@ # AI 客服系统 - 完整功能汇总 -**版本**: v1.0 -**更新日期**: 2026-02-27 -**服务器**: 1.12.50.92 +**版本**: v1.0 | **更新日期**: 2026-02-28 | **服务器**: 1.12.50.92 --- -## 📋 目录 +## 目录 -1. [核心功能](#核心功能) -2. [图片处理](#图片处理) -3. [任务管理](#任务管理) -4. [派单系统](#派单系统) -5. [价格策略](#价格策略) -6. [风险控制](#风险控制) -7. [技术架构](#技术架构) +1. [天网协作系统](#天网协作系统) +2. [三种工作流](#三种工作流) +3. [文字检测与加价](#文字检测与加价) +4. [风险评估与接单判断](#风险评估与接单判断) +5. [作图失败转接人工](#作图失败转接人工) +6. [图片任务数据库](#图片任务数据库) +7. [图绘派单系统](#图绘派单系统) +8. [价格策略总览](#价格策略总览) +9. [技术架构](#技术架构) --- -## 核心功能 +## 天网协作系统 -### 1. 天网协作系统 +**说明**: 接收天网下发的任务,支持指定客户回复触发。 -**说明**: 接收天网下发的任务,支持指定客户回复触发 +**API 地址**: `http://127.0.0.1:6060` -**API 接口**: `http://127.0.0.1:6060` +**接口列表**: -**功能**: -- ✅ 任务接收 (`POST /api/task/receive`) -- ✅ 任务查询 (`GET /api/task/status/:id`) -- ✅ 任务取消 (`POST /api/task/cancel`) -- ✅ 任务列表 (`GET /api/task/list`) +| 接口 | 方法 | 说明 | +|------|------|------| +| `/api/task/receive` | POST | 接收任务 | +| `/api/task/status/:id` | GET | 查询任务状态 | +| `/api/task/cancel` | POST | 取消任务 | +| `/api/task/list` | GET | 任务列表 | +| `/api/health` | GET | 健康检查 | **触发类型**: -- `specified_customer_reply` - 指定客户回复指定内容 -- `customer_keyword` - 任意客户说某关键词 -- `customer_payment` - 客户付款 -- `time_reach` - 到达指定时间 -**文档**: `TIANWANG_INTEGRATION.md` +| 类型 | 说明 | +|------|------| +| `specified_customer_reply` | 指定客户回复指定内容(推荐) | +| `customer_reply` | 任意客户回复指定内容 | +| `customer_keyword` | 任意客户说某关键词 | +| `customer_payment` | 客户付款 | +| `time_reach` | 到达指定时间 | + +> 详细的 API 接口文档、请求示例、数据库结构等见 **部署文档.md**。 --- -### 2. 三种工作流 +## 三种工作流 -根据客户说的话自动判断执行不同工作流: +根据客户说的话,自动判断执行不同的工作流程。 -#### 工作流 1: 查找图片 -**触发词**: "找一下"、"找图"、"找原图" +### 工作流 1:查找图片 + +**触发词**: "找一下"、"找图"、"找原图"、"帮我找"、"能找到吗"、"有吗"、"有没有" -**流程**: ``` 客户:找一下这个图 [图片] - ↓ -AI 处理 → 上传到图绘 - ↓ -回复:"找到了!http://tuhui.cloud/works/123" + ↓ +AI 检测到"找一下"关键词 → 执行查找图片工作流 + ↓ +1. 创建任务(operation=find) +2. 上传图片到图绘平台 +3. 更新任务状态为 completed + ↓ +AI: 找到了!图片在这里:http://tuhui.cloud/works/123 ``` -#### 工作流 2: 处理图片 -**触发词**: "做一下"、"处理一下"、"安排" +### 工作流 2:处理图片 + +**触发词**: "做一下"、"处理一下"、"安排"、"开始做"、"弄一下"、"修一下"、"P一下"、"P图" -**流程**: ``` 客户:做一下 [图片] - ↓ -AI: "稍等,我看看...好的,可以做" - ↓ -启动图片处理流程 + ↓ +AI 检测到"做一下"关键词 → 执行处理图片工作流 + ↓ +1. 创建任务(operation=enhance) +2. 回复"稍等,我看看...好的,可以做,马上处理" +3. 启动图片处理流程 + ↓ +AI: 做好了,请查看 [结果图] ``` -#### 工作流 3: 转人工派单 -**触发词**: "做不了"、"处理不了" +### 工作流 3:转人工派单 + +**触发词**: "做不了"、"处理不了"、"弄不了"、"无法处理"、"做不到"、"搞不定" -**流程**: ``` -AI: 做不了 - ↓ -查询在线设计师 - ↓ -派单给设计师 - ↓ -回复:"已安排设计师处理" +AI 判断无法处理 / 客户说"做不了" + ↓ +执行转人工派单工作流 + ↓ +1. 创建任务(operation=manual) +2. 查询在线设计师 +3. 有人在线 → 派单;无人 → 通知稍后联系 + ↓ +AI: 好的,已帮您安排设计师处理,请稍候 ``` -**文档**: `三种工作流功能说明.md` +### 技术实现 + +| 组件 | 文件 | 说明 | +|------|------|------| +| 工作流路由器 | `core/workflow_router.py` | 关键词检测与匹配 | +| 工作流执行器 | `core/workflow.py` | 三种工作流的具体实现 | +| 消息处理器 | `core/pydantic_ai_agent.py` | `_handle_image_workflow()` 方法 | + +**注意事项**: +- 关键词匹配支持多种说法,自动识别 +- 置信度 >0.9 才执行对应工作流 +- 无人在线时通知客户稍后联系,企业微信预警 +- 所有工作流都保存到数据库 --- -## 图片处理 +## 文字检测与加价 -### 1. 文字检测与加价 +AI 客服自动分析图片中的文字数量,根据文字数量和分层需求自动加价。 -**功能**: AI 自动识别图片文字数量,根据文字数量加价 - -**价格规则**: +### 文字数量加价 | 文字数量 | 加价 | |----------|------| +| none | +0 元 | | 少量 (1-10 字) | +5 元 | | 中量 (11-50 字) | +15 元 | | 大量 (51-200 字) | +30 元 | | 极多 (200 字以上) | +50 元 | -**分层加价**: -- 有文字 + 分层:+50 元起 -- 无文字 + 分层:+30 元 +### 文字分层需求加价 -**特殊价格**: 文字分层 + 大量文字 → 60-80 元 +| 分层需求 | 加价 | +|----------|------| +| no | +0 元 | +| yes(有文字)| +50 元起 | +| yes(无文字)| +30 元 | -**文档**: `文字加价功能说明.md` +### 特殊价格 ---- +**条件**: 文字数量=大量/极多 且 分层需求=yes → **60-80 元** -### 2. 风险评估与接单判断 +### 使用场景 -**风险等级**: -- **敏感内容** (一票否决): 色情/暴力/涉政 → 直接拒绝 -- **none**: 印花/图案/logo → 直接接单 -- **low**: 有人脸但清晰 → 接单,说明风险 -- **high**: 严重模糊/老照片 → 谨慎接单 +**场景 1**: 少量文字,不分层 +- 复杂度 simple + 少量文字 → 15 + 5 = **20 元** +- AI: "这张图比较简单,不过有少量文字需要处理,20 元。" -**可做判断**: -- `yes`: 效果有把握,接单 -- `partial`: 有限制,谨慎接单 -- `no`: 无法处理,不接单 +**场景 2**: 大量文字,需要分层 +- 复杂度 complex + 大量文字 + 分层 → 调整到 **80 元** +- AI: "这张图文字比较多,有 100 多字,需要分层文件,80 元。" -**文档**: `风险评估功能说明.md` +### 价格计算流程 ---- - -### 3. 作图失败转接人工 - -**触发条件**: -- API 调用失败 -- 图片处理超时 -- 客户不满意 - -**流程**: ``` -作图失败 - ↓ -通知客户 - ↓ -转接人工客服 - ↓ -企业微信预警 +客户发送图片 → 判断基础复杂度 → 检测文字数量 → 询问分层需求 + → 计算总价(基础+文字+分层)→ 特殊价格处理(60-80 元)→ 报价 ``` -**文档**: `作图失败转接人工说明.md` +### 配置位置 + +修改价格规则: `image/image_analyzer.py`(查找文字加价相关代码) + +**注意事项**: +- 文字数量通过视觉 AI 自动识别 +- 分层需求需从对话中识别 +- 最终价格必须是 5 的倍数 --- -## 任务管理 +## 风险评估与接单判断 -### 1. 图片任务数据库 +AI 客服自动分析图片风险,判断是否可以接单。 -**功能**: 任务持久化,支持客户后续增加需求 +### 敏感内容检测(一票否决) -**数据库表**: -- `image_tasks` - 图片任务表 -- `task_requirement_changes` - 需求变更表 +**敏感内容 = yes → 直接拒绝,不接单** + +检测内容: 色情/黄色/擦边/裸露、性暗示、涉政/政治敏感、暴力/血腥、违禁品 + +**话术**: "这类不做哦" / "不好意思,这个接不了" + +**禁止说**: "发图来看看"、过多解释 + +### 风险等级 + +| 风险 | 是否接单 | 说明 | +|------|----------|------| +| **none** | ✅ 接单 | 印花/图案/logo/风景/产品,效果稳定 | +| **low** | ✅ 接单 | 有人脸但清晰,需说明风险(相似度 70-90%)| +| **high** | ⚠️ 谨慎 | 严重模糊/老照片人像/需打印,需说明限制 | + +### 可做判断 + +| 可做 | 是否接单 | 说明 | +|------|----------|------| +| **yes** | ✅ 接单 | 效果有把握 | +| **partial** | ⚠️ 可接 | 能处理但有限制,需说明风险 | +| **no** | ❌ 不接 | 无法处理(纯黑/纯白/完全损坏/敏感内容)| + +### 分析流程 + +``` +客户发送图片 → 敏感内容检测(yes→拒绝)→ 风险评估(none/low/high) + → 可做判断(yes/partial/no)→ 决策(接单/谨慎/拒绝)→ 回复客户 +``` + +### 话术模板 + +**高风险提示**: +- "这张比较模糊,修复后清晰了但人脸可能跟原来有差异" +- "老照片修复后人脸可能有轻微变化" +- "建议先看效果确认再打印" + +**正常接单**: +- "这个没问题,XX 元" +- "可以处理,XX 元,满意再付" + +### 配置位置 + +- 风险判断规则: `image/image_analyzer.py`(查找"风险评估""敏感内容检测") +- 拒绝话术: `core/pydantic_ai_agent.py`(查找"拒绝") + +--- + +## 作图失败转接人工 + +当 AI 作图失败或效果不佳时,系统自动转接人工客服。 + +### 触发场景 + +| 场景 | 触发条件 | 话术 | +|------|----------|------| +| AI 作图失败 | API 报错/超时/质量不达标 | "处理遇到点问题,我帮您转接人工" | +| 客户不满意 | 说"效果不好"/"不满意"/要求重做 | "好的,我帮您转接人工客服处理" | +| 特殊要求 | AI 无法处理的复杂需求 | "这个需求比较特殊,帮您转接人工" | + +### 转接流程 + +``` +作图失败/客户不满意 → 通知客户 → 转接人工客服 → 企业微信预警 +``` + +### 技术实现 + +- 失败检测: `core/pydantic_ai_agent.py` 中的 `process_image_gemini` 函数 +- 转接工具: `transfer_to_human` tool(标记 `need_transfer=True`) + +**注意事项**: +- 作图失败必须转人工,不自动重试超过 2 次 +- 转接前告知客户原因 +- 记录转接原因便于后续优化 + +--- + +## 图片任务数据库 + +图片任务保存到 SQLite 数据库,支持持久化和需求变更。 + +### 数据库表 + +**image_tasks(图片任务表)**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| task_id | TEXT | 任务 ID(主键)| +| customer_id | TEXT | 客户 ID | +| original_image | TEXT | 原图 URL | +| operation | TEXT | 操作类型(enhance/remove_bg/vectorize)| +| requirements | TEXT | 需求 JSON | +| customer_notes | TEXT | 客户备注/需求细节 | +| status | TEXT | 状态 | +| result_image | TEXT | 结果图 URL | +| error_message | TEXT | 错误信息 | +| retry_count | INTEGER | 重试次数 | +| acc_id / acc_type | TEXT | 店铺 ID / 平台类型 | +| created_at / paid_at / completed_at | TEXT | 时间戳 | + +**task_requirement_changes(需求变更表)**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| task_id | TEXT | 任务 ID(外键)| +| change_type | TEXT | 变更类型(add_note/modify_operation/add_requirement)| +| old_value / new_value | TEXT | 变更前后值 | +| changed_at | TEXT | 变更时间 | +| changed_by | TEXT | 变更者(customer/staff)| + +### 任务状态流转 + +``` +pending(待付款)→ paid(已付款)→ processing(处理中)→ awaiting_confirm(待确认)→ completed(已完成) + ↘ failed(失败) +``` + +### API 接口 -**客户可以增加需求**: ```python -await workflow.add_customer_requirement( - task_id="TASK_001", - customer_id="customer_123", - requirement="需要去掉背景" +# 创建任务 +workflow.create_image_task(customer_id, original_image, operation) + +# 添加需求 +await workflow.add_customer_requirement(task_id, customer_id, requirement) + +# 修改操作类型 +await workflow.modify_operation(task_id, customer_id, new_operation) + +# 查询任务 +task = workflow.get_task(task_id) +tasks = workflow.get_customer_tasks(customer_id) + +# 查询需求变更历史 +history = workflow.get_task_requirement_history(task_id) +``` + +### 数据库操作 + +```bash +sqlite3 /root/ai_customer_service/ai_cs/db/image_tasks.db + +# 查询所有任务 +SELECT task_id, customer_id, status, created_at FROM image_tasks ORDER BY created_at DESC LIMIT 10; + +# 查询待处理任务 +SELECT * FROM image_tasks WHERE status='pending'; + +# 查询需求变更 +SELECT task_id, change_type, old_value, new_value, changed_at FROM task_requirement_changes WHERE task_id='TASK_001'; +``` + +**注意事项**: +- 所有任务自动保存,重启不丢失 +- 付款前可修改操作类型,付款后不允许 +- 所有变更都有历史记录 + +--- + +## 图绘派单系统 + +AI 客服系统接入图绘派单系统 API,实现自动派单给在线设计师。 + +### API 信息 + +| 项目 | 值 | +|------|------| +| API 地址 | `http://1.12.50.92:8005` | +| API Key | `tuhui_dispatch_key_2026` | +| 认证方式 | Header: `X-API-Key` | + +### 核心接口 + +| 接口 | 方法 | 说明 | +|------|------|------| +| `/dispatch/queue` | GET | 获取派单队列 | +| `/online/designers` | GET | 获取在线设计师 | +| `/tasks` | POST | 创建任务 | +| `/tasks/{id}/assign` | POST | 分配任务 | +| `/tasks/{id}` | GET | 查询任务状态 | +| `/tasks/{id}/complete` | POST | 完成任务 | + +### 转人工派单流程 + +``` +AI 判断做不了 + ↓ +1. 查询在线设计师 → GET /online/designers → ["橘子", "婷婷"] + ↓ +2. 创建派单任务 → POST /tasks → {"task_id": "ea853bd9"} + ↓ +3. 分配给设计师 → POST /tasks/ea853bd9/assign → {"designer_name": "橘子"} + ↓ +4. 企业微信通知设计师 + ↓ +5. 回复客户:"好的,已帮您安排设计师处理,请稍候" +``` + +### 代码调用示例 + +```python +from services.service_tuhui_dispatch import get_tuhui_dispatch_client + +client = get_tuhui_dispatch_client() + +# 查询在线设计师 +designers = await client.get_online_designers() # ["橘子", "婷婷"] + +# 创建任务 +task_id = await client.create_task( + task_name="图片处理-1234", + description="客户需要做高清修复", + task_type="image_process", + priority=2 ) + +# 分配任务 +await client.assign_task(task_id, designer_name="橘子", notes="AI 客服自动派单") + +# 完成任务 +await client.complete_task(task_id, notes="客户已确认") ``` -**任务状态流转**: +### 设计师在线状态 API + ``` -pending → paid → processing → awaiting_confirm → completed +GET http://huichang.online:8001/online # 查询在线设计师 +POST http://huichang.online:8001/update-status # 更新设计师状态 ``` -**文档**: `图片任务数据库功能说明.md` +### 相关代码位置 + +| 组件 | 文件 | +|------|------| +| 派单客户端 | `services/service_tuhui_dispatch.py`(`TuhuiDispatchClient` 类)| +| 工作流集成 | `core/workflow.py`(`transfer_to_designer_workflow()` 方法)| --- -## 派单系统 - -### 1. 图绘派单系统 - -**API 地址**: `http://1.12.50.92:8005` -**API Key**: `tuhui_dispatch_key_2026` - -**核心接口**: -- `GET /dispatch/queue` - 获取派单队列 -- `GET /online/designers` - 查询在线设计师 -- `POST /tasks` - 创建任务 -- `POST /tasks/{id}/assign` - 分配任务 -- `POST /tasks/{id}/complete` - 完成任务 - -**派单流程**: -``` -1. 查询在线设计师 → ["橘子", "婷婷"] -2. 创建任务 → {"task_id": "ea853bd9"} -3. 分配给设计师 → designer: "橘子" -4. 企业微信通知 -5. 回复客户 -``` - -**文档**: `图绘派单系统集成说明.md` - ---- - -## 价格策略 +## 价格策略总览 ### 基础价格 -| 复杂度 | 价格 | -|--------|------| -| simple | 10-15 元 | -| normal | 15-20 元 | -| complex | 20-25 元 | -| hard | 25-30 元 | +| 复杂度 | 价格区间 | 说明 | +|--------|----------|------| +| simple | 10-15 元 | 画面简单干净 | +| normal | 15-20 元 | 一般复杂度 | +| complex | 20-25 元 | 细节偏多 | +| hard | 25-30 元 | 非常复杂 | ### 加价规则 -**文字加价**: -- 少量:+5 元 -- 中量:+15 元 -- 大量:+30 元 -- 极多:+50 元 +| 项目 | 条件 | 加价 | +|------|------|------| +| 文字少量 | 1-10 字 | +5 元 | +| 文字中量 | 11-50 字 | +15 元 | +| 文字大量 | 51-200 字 | +30 元 | +| 文字极多 | 200+ 字 | +50 元 | +| 分层(有文字)| 需要 PSD 分层 | +50 元起 | +| 分层(无文字)| 仅需分层 | +30 元 | -**分层加价**: -- 有文字:+50 元起 -- 无文字:+30 元 +### 高价值订单 -**高价值订单**: 文字分层 + 大量文字 → 60-80 元 - ---- - -## 风险控制 - -### 敏感内容检测 - -**一票否决**: -- ❌ 色情/黄色/擦边 -- ❌ 涉政/政治敏感 -- ❌ 暴力/血腥 -- ❌ 违禁品 - -**话术**: -- "这类不做哦" -- "不好意思,这个接不了" - -### 高风险图片 - -**谨慎接单**: -- ⚠️ 严重模糊的人脸 -- ⚠️ 老照片人像 -- ⚠️ 需要打印 - -**话术**: -- "这张比较模糊,修复后人脸可能有差异" -- "建议先看效果确认再决定" +**文字分层 + 大量文字** → 特殊价格 **60-80 元**(封顶) --- @@ -267,107 +451,26 @@ pending → paid → processing → awaiting_confirm → completed | 组件 | 文件 | 说明 | |------|------|------| -| **天网协作** | `api/http_server.py` | HTTP API 服务器 | -| **工作流程** | `core/workflow.py` | 工作流执行器 | -| **AI Agent** | `core/pydantic_ai_agent.py` | AI 对话引擎 | -| **图片分析** | `image/image_analyzer.py` | 图片复杂度识别 | -| **派单客户端** | `services/service_tuhui_dispatch.py` | 图绘派单 API | -| **任务数据库** | `db/image_tasks_db.py` | 任务持久化 | +| 天网协作 | `api/http_server.py` | HTTP API 服务器 | +| 工作流程 | `core/workflow.py` | 工作流执行器 | +| AI Agent | `core/pydantic_ai_agent.py` | AI 对话引擎 | +| 图片分析 | `image/image_analyzer.py` | 图片复杂度识别 | +| 派单客户端 | `services/service_tuhui_dispatch.py` | 图绘派单 API | +| 任务数据库 | `db/image_tasks_db.py` | 任务持久化 | ### 数据库 -| 数据库 | 文件 | 说明 | +| 数据库 | 位置 | 说明 | |--------|------|------| -| **任务数据库** | `db/image_tasks.db` | 图片任务 | -| **客户档案** | `customer_db/customer.db` | 客户画像 | -| **聊天记录** | `chat_log_db/chat_log.db` | 聊天历史 | +| 任务数据库 | `db/image_tasks.db` | 图片任务 | +| 客户档案 | `db/customer.db` | 客户画像 | +| 聊天记录 | `chat_log_db/chat_log.db` | 聊天历史 | +| 天网任务 | `db/task_db/tasks.db` | 天网任务调度 | ### API 端口 | 服务 | 端口 | 说明 | |------|------|------| -| **AI 客服 API** | 6060 | 天网任务接收 | -| **派单系统** | 8005 | 设计师派单 | -| **图绘平台** | 8002 | 图片上传 | - ---- - -## 部署说明 - -### 启动方式 - -```bash -cd /root/ai_customer_service/ai_cs - -# 方式 1: 天网协作版 -python3 run_tianwang_simple.py - -# 方式 2: 完整版 -python3 run_with_tianwang.py - -# 方式 3: AI 客服 -python3 run.py -``` - -### 后台运行 - -```bash -nohup python3 run_tianwang_simple.py > /tmp/tianwang.log 2>&1 & -``` - -### 查看日志 - -```bash -tail -f /tmp/tianwang.log -``` - ---- - -## 文档清单 - -| 文档 | 说明 | -|------|------| -| `README.md` | 项目说明 | -| `DEPLOYMENT.md` | 部署文档 | -| `TIANWANG_INTEGRATION.md` | 天网协作 | -| `三种工作流功能说明.md` | 工作流 | -| `文字加价功能说明.md` | 价格策略 | -| `风险评估功能说明.md` | 风险控制 | -| `图片任务数据库功能说明.md` | 任务管理 | -| `图绘派单系统集成说明.md` | 派单系统 | -| `作图失败转接人工说明.md` | 失败处理 | - ---- - -## 快速参考 - -### API 调用示例 - -**接收天网任务**: -```bash -curl -X POST http://localhost:6060/api/task/receive \ - -H "Content-Type: application/json" \ - -d '{ - "task_id": "TASK_001", - "customer": {"id": "customer_123"}, - "trigger": {"type": "customer_reply", "keyword": "好的"}, - "action": {"type": "send_message", "message": "您好"} - }' -``` - -**查询在线设计师**: -```bash -curl -X GET "http://1.12.50.92:8005/online/designers" \ - -H "X-API-Key: tuhui_dispatch_key_2026" -``` - -**派单队列**: -```bash -curl -X GET "http://1.12.50.92:8005/dispatch/queue" \ - -H "X-API-Key: tuhui_dispatch_key_2026" -``` - ---- - -**完整功能已部署,所有系统运行正常!** 🎉 - +| AI 客服 API | 6060 | 天网任务接收 | +| 派单系统 | 8005 | 设计师派单 | +| 图绘平台 | 8002 | 图片上传 | diff --git a/风险评估功能说明.md b/风险评估功能说明.md deleted file mode 100644 index 0c00b5b..0000000 --- a/风险评估功能说明.md +++ /dev/null @@ -1,270 +0,0 @@ -# 图片风险评估与接单判断功能 - -## 📋 功能说明 - -AI 客服会自动分析图片的風險,判断是否可以接单。 - ---- - -## ⚠️ 风险等级 - -### 1. 敏感内容检测(一票否决) - -**敏感内容 = yes** → **直接拒绝,不接单** - -检测内容: -- ❌ 色情/黄色/擦边/裸露 -- ❌ 性暗示/大尺度 -- ❌ 涉政/政治敏感 -- ❌ 暴力/血腥 -- ❌ 违禁品 - -**话术**: -- "这类不做哦" -- "不好意思,这个接不了" -- "这类图片不处理的" - ---- - -### 2. 风险评估 - -| 风险等级 | 是否接单 | 说明 | -|----------|----------|------| -| **none** | ✅ 接单 | 印花/图案/logo/风景/产品,效果稳定 | -| **low** | ✅ 接单 | 有人脸但清晰,需说明风险(人脸相似度 70-90%) | -| **high** | ⚠️ 谨慎接单 | 严重模糊/老照片人像/需打印,需说明限制 | - ---- - -### 3. 可做判断 - -| 可做 | 是否接单 | 说明 | -|------|----------|------| -| **yes** | ✅ 接单 | 效果有把握 | -| **partial** | ⚠️ 可接单 | 能处理但有限制,需说明风险 | -| **no** | ❌ 不接单 | 无法处理 | - ---- - -## 🎯 使用场景 - -### 场景 1:敏感内容 - 拒绝 - -**客户**:[发送擦边图片] - -**AI 分析**: -- 敏感内容:yes -- 可做:no - -**AI 回复**: -``` -这类不做哦。 -``` - -**不说的话**: -- ❌ "发图来看看" -- ❌ "这个处理不了" -- ❌ 过多解释 - ---- - -### 场景 2:高风险 - 谨慎接单 - -**客户**:[发送严重模糊的老照片] - -**AI 分析**: -- 风险:high -- 可做:partial - -**AI 回复**: -``` -这张照片比较模糊,修复后清晰了但人脸可能跟原来有差异,建议先看效果确认再决定,20 元。 -``` - ---- - -### 场景 3:低风险 - 正常接单 - -**客户**:[发送清晰的人像照片] - -**AI 分析**: -- 风险:low -- 可做:yes - -**AI 回复**: -``` -可以处理,修复后人脸相似度大概 70-90%,20 元,满意再付。 -``` - ---- - -### 场景 4:无风险 - 直接接单 - -**客户**:[发送印花图案] - -**AI 分析**: -- 风险:none -- 可做:yes - -**AI 回复**: -``` -这个没问题,15 元,拍下就处理。 -``` - ---- - -## 🔧 技术实现 - -### 分析流程 - -``` -客户发送图片 - ↓ -AI 视觉分析 - ↓ -┌───────────────────────┐ -│ 1. 敏感内容检测 │ -│ yes → 拒绝不接单 │ -│ no → 继续判断 │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 2. 风险评估 │ -│ none/low/high │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 3. 可做判断 │ -│ yes/partial/no │ -└───────────┬───────────┘ - ↓ -┌───────────────────────┐ -│ 4. 决策 │ -│ 接单/谨慎接单/拒绝 │ -└───────────┬───────────┘ - ↓ - 回复客户 -``` - ---- - -## 📝 判断规则 - -### 敏感内容(一票否决) - -**检测到以下任一内容 → 敏感内容=yes → 不接单**: -- 色情/黄色/擦边/裸露 -- 性暗示/大尺度 -- 涉政/政治敏感 -- 暴力/血腥 -- 违禁品 - -### 风险评估 - -**none(无风险)**: -- ✅ 印花/图案/logo -- ✅ 风景/产品 -- ✅ AI 处理效果稳定 - -**low(低风险)**: -- ✅ 有人脸但清晰 -- ✅ AI 修复后人脸相似度 70-90% -- ⚠️ 需说明风险 - -**high(高风险)**: -- ⚠️ 严重模糊的人脸照片 -- ⚠️ 老照片人像 -- ⚠️ 需要打印 -- ⚠️ 客户问能否找回原图 -- ⚠️ 谨慎接单,说明限制 - -### 可做判断 - -**yes(可接单)**: -- ✅ 效果有把握 -- ✅ 可直接处理 - -**partial(谨慎接单)**: -- ⚠️ 能处理但有明显限制 -- ⚠️ 人脸变形风险 -- ⚠️ 分辨率极低 -- ⚠️ 严重损坏 -- ⚠️ 需说明风险 - -**no(不接单)**: -- ❌ 纯黑/纯白 -- ❌ 完全损坏 -- ❌ 找原始 RAW 文件 -- ❌ 敏感内容 -- ❌ 违法内容 - ---- - -## 📊 话术模板 - -### 拒绝话术(敏感内容) - -``` -- 这类不做哦 -- 不好意思,这个接不了 -- 这类图片不处理的 -- 这个做不了哈 -``` - -**不要说**: -- ❌ "发图来看看" -- ❌ 过多解释 -- ❌ "处理不了" - ---- - -### 风险提示话术(高风险) - -``` -- 这张比较模糊,修复后清晰了但人脸可能跟原来有差异 -- 老照片修复后人脸可能有轻微变化 -- 建议先看效果确认再打印 -- 这张模糊程度较高,修复后清晰了但人脸可能跟原来有差异 -``` - ---- - -### 正常接单话术 - -``` -- 这个没问题,XX 元 -- 可以处理,XX 元,拍下就处理 -- 好的,XX 元,满意再付 -``` - ---- - -## ⚠️ 注意事项 - -1. **敏感内容优先判断**:先检测敏感内容,再判断其他 -2. **敏感内容=yes → 可做=no**:必须拒绝 -3. **高风险谨慎接单**:说明限制和风险 -4. **拒绝话术简洁**:不要过多解释 -5. **风险告知**:low/high 风险都要告知客户 - ---- - -## 🔍 配置说明 - -### 修改风险判断规则 - -文件:`/root/ai_customer_service/ai_cs/image/image_analyzer.py` - -查找`【风险评估】`和`【敏感内容检测】`部分修改规则。 - -### 修改拒绝话术 - -文件:`/root/ai_customer_service/ai_cs/core/pydantic_ai_agent.py` - -查找`【拒绝】`部分修改拒绝话术。 - ---- - -**文档版本**: v1.0 -**更新日期**: 2026-02-27 -