# 1688 智能采购询价机器人 — 项目说明文档

## 1. 项目概述

本项目是一个基于 **Playwright + Supabase + LLM** 的 1688 采购自动化平台，实现从搜索商品到多轮询价的完整工作流。系统通过 RPA 技术自动操作旺旺 IM 聊天窗口，结合大语言模型进行意图识别与信息提取，对采购清单中的商品逐一向多家商家发起 4 轮漏斗式询价，最终输出结构化的有效报价池。

### 技术栈

| 组件 | 技术 | 说明 |
|------|------|------|
| 浏览器自动化 | Playwright (Chromium) | 模拟人类操作旺旺聊天 |
| 后端/运行时 | Node.js (ES Module) | 所有脚本均为 ESM |
| 数据库 | Supabase (PostgreSQL) | 存储采购清单、对话状态、消息日志 |
| AI 意图解析 | OpenAI 兼容 API | 支持 DeepSeek / 智谱 / 通义 / GPT 等 |
| 反检测 | 自研 human.js | 随机延迟、拟人化鼠标/键盘操作 |

---

## 2. 核心业务流程

### 2.1 多轮漏斗式询价

系统采用 4 轮渐进式询价策略，每一轮都会过滤不合格的商家：

```
采购清单 (BOM) + 商家池
    │
    ▼
┌─────────────────────────────────────┐
│     多轮次自动询价引擎 (RPA + LLM)    │
└─────────────────────────────────────┘
    │
    ├── 轮次 1：货源基础摸底
    │   "老板，请问【商品标题】目前有现货吗？"
    │   → LLM 判断: 有货 → R1_PASSED / 无货 → REJECTED
    │
    ├── 轮次 2：规格与起订量确认
    │   "我们需要【规格】，起订量【MOQ】件能做吗？"
    │   → LLM 判断: 支持 → R2_PASSED / 不支持 → REJECTED
    │
    ├── 轮次 3：核心商务谈判（价格与交期）
    │   "按照这个数量，最低什么价格？交期多久？"
    │   → LLM 提取: 单价、交期天数 → R3_PASSED / 含糊 → HUMAN_TAKEOVER
    │
    └── 轮次 4：合规与资质核验
        "产品符合国标吗？能开增值税专用发票吗？"
        → LLM 判断: 合规+可开票 → COMPLETED / 不合规 → REJECTED
            │
            ▼
        最终有效报价池
```

### 2.2 状态机

每个"商品-商家"对话任务的状态流转如下：

```
PENDING_SEND → (发送消息) → WAITING_REPLY
                                │
                    ┌───────────┼───────────┐
                    ▼           ▼           ▼
              Rx_PASSED    REJECTED   TIMEOUT_REJECTED
                    │
                    ▼
            PENDING_SEND (下一轮)
                    │
                    ▼ (第4轮通过)
               COMPLETED
```

特殊状态：
- `HUMAN_TAKEOVER` — 需要人工介入（如商家回复含糊）
- `TIMEOUT_REJECTED` — 超过最大重试次数（10 次）后自动剔除

### 2.3 指数退避巡检机制

当消息发出后等待商家回复时，系统采用指数退避策略进行巡检：

| 巡检次数 | 等待间隔 | 说明 |
|----------|----------|------|
| 第 1 次 | 3 分钟 | 防止秒回遗漏 |
| 第 2 次 | 6 分钟 | |
| 第 3 次 | 12 分钟 | |
| 第 4 次 | 24 分钟 | |
| 第 5 次 | 48 分钟 | |
| 第 6 次+ | 60 分钟（封顶） | 最大间隔 1 小时 |
| 超过 10 次 | — | 标记为 `TIMEOUT_REJECTED` |

---

## 3. 项目结构

```
playwright_1688_yueyue_chatbot/
├── package.json                # 项目配置和 pnpm scripts
├── .env                        # 环境变量（需自行创建）
├── row-002.json                # 示例搜索结果数据
│
├── docs/
│   ├── chat.md                 # 旺旺交互架构设计文档
│   ├── README.md               # 本文档（项目说明）
│   └── OPERATIONS.md           # 运维文档
│
├── output/
│   ├── results.json            # 搜索结果输出
│   ├── contact-log.json        # 商家联系日志
│   └── images/                 # 商品图片下载目录
│
├── sql/
│   ├── 006_customer_service_chats.sql  # 对话状态表 DDL
│   └── 007_chat_messages.sql           # 消息日志表 DDL
│
├── src/
│   ├── chat-init.js            # 首轮批量发送入口
│   ├── chat-worker.js          # 巡检 Worker（轮询+发送）
│   ├── chat-scheduler.js       # 对话调度器（状态管理）
│   ├── chat-templates.js       # 4 轮话术模板
│   ├── llm-intent.js           # LLM 意图解析网关
│   ├── wangwang-rpa.js         # 旺旺 IM RPA 操作库
│   ├── human.js                # 反检测/拟人化工具函数
│   ├── offer-normalizer.js     # 搜索结果规范化
│   ├── login-1688.js           # 交互式登录工具
│   ├── run-1688.js             # 基础搜索流程（单次）
│   └── run-supabase.js         # 集成 Supabase 的搜索流程
│
├── test/
│   ├── chat-scheduler.test.js  # 调度器单元测试
│   ├── chat-templates.test.js  # 话术模板单元测试
│   ├── offer-normalizer.test.js # 规范化工具单元测试
│   └── wangwang-rpa.test.js    # 旺旺 RPA 单元测试
│
└── .browser/
    └── 1688-profile/           # Playwright 浏览器持久化配置（登录态）
```

---

## 4. 模块详解

### 4.1 chat-init.js — 首轮批量发送入口

**pnpm script**: `pnpm --dir chatbot run chat:init`

**职责**：
1. 从 Supabase `procurement_materials` 表读取采购清单
2. 使用 Playwright 在 1688 搜索每个材料关键词
3. 提取搜索结果（含商品信息、商家、旺旺链接）
4. 在 `customer_service_chats` 表批量创建对话任务
5. 逐个打开旺旺聊天窗口，发送 Round 1 话术
6. 记录消息日志并更新任务状态

### 4.2 chat-worker.js — 巡检 Worker

**pnpm script**: `pnpm --dir chatbot run chat:worker`

**职责**：持续循环执行以下两类任务——
1. **发送任务** (`PENDING_SEND`)：拉取待发送消息 → 打开旺旺 → 发送话术 → 标记为 `WAITING_REPLY`
2. **巡检任务** (`WAITING_REPLY`)：拉取到期的巡检任务 → 抓取旺旺消息 → 判断是否有新回复 → LLM 分析 → 推进/剔除/延迟

**核心参数**：
- `WORKER_BATCH_SIZE`：每轮拉取任务数（默认 5）
- `WORKER_IDLE_SEC`：队列空时休眠秒数（默认 30）

### 4.3 chat-scheduler.js — 对话调度器

纯逻辑模块，不涉及浏览器操作。管理对话任务的全生命周期：

| 函数 | 说明 |
|------|------|
| `initChatTasks()` | 批量创建对话任务（插入 DB） |
| `markSent()` | 标记消息已发送，状态 → `WAITING_REPLY` |
| `advanceRound()` | 根据 LLM 结果推进轮次或剔除 |
| `deferCheck()` | 指数退避，推迟下次巡检时间 |
| `fetchPendingChecks()` | 拉取到期的 `WAITING_REPLY` 任务 |
| `fetchPendingSends()` | 拉取 `PENDING_SEND` 任务 |
| `logMessage()` | 记录每条消息到 `chat_messages` 表 |

### 4.4 chat-templates.js — 话术模板

提供 4 轮询价话术模板，支持字段插值。模板使用 `askContext` 对象中的字段：

```javascript
askContext = {
  title:                  "商品标题",
  material_name:          "材料名称",
  specification:          "规格描述",
  quantity:               "需求数量",
  unit:                   "单位",
  brand:                  "品牌",
  technical_standard:     "技术标准",
  material_requirement:   "材料要求"
}
```

### 4.5 llm-intent.js — LLM 意图解析网关

接收客服回复文本，根据当前轮次调用对应的 System Prompt 进行分析：

| 轮次 | 分析类型 | LLM 返回 |
|------|----------|----------|
| Round 1 | 二分类（有货/无货） | `{ passed, reason }` |
| Round 2 | 二分类（支持/不支持） | `{ passed, reason }` |
| Round 3 | 实体提取（价格/交期） | `{ passed, status, price, delivery_days, reason }` |
| Round 4 | 多分类（合规/开票） | `{ passed, is_guobiao, can_invoice, reason }` |

支持任何 OpenAI 兼容 API（DeepSeek、智谱、通义千问等），通过 `LLM_BASE_URL` 配置。

### 4.6 wangwang-rpa.js — 旺旺 IM RPA 操作库

封装了旺旺聊天窗口的所有底层自动化操作：

| 功能分类 | 关键函数 |
|----------|----------|
| 登录检测 | `isLoginUrl()`, `promptForManualLogin()` |
| iframe 操作 | `getChatFrame()`, `dismissChatGuide()` |
| 消息发送 | `findChatInput()`, `sendChatMessage()` |
| 会话管理 | `createAndSelectConversation()`, `buildConversationNick()` |
| 消息抓取 | `scrapeLatestMessages()`, `getMessageHash()`, `hasNewReply()` |
| 完整流程 | `contactMerchant()` (首次联系), `sendMessageInExistingChat()` (后续轮次) |

### 4.7 human.js — 反检测工具函数库

提供一系列拟人化操作函数，降低被 1688 风控系统检测的风险：

| 函数 | 说明 | 典型延迟 |
|------|------|----------|
| `randomPause(min, max)` | 随机休眠 | 自定义 |
| `humanMoveTo()` | 鼠标平滑移动 | — |
| `humanClick()` | 鼠标带抖动点击 | — |
| `humanType()` | 逐字符输入（带随机间隔） | — |
| `humanScrollPage()` | 分步滚动页面 | — |
| `antiDetectPause()` | 反检测长停顿 | 3-5 秒 |
| `chatPause()` | 对话间隔停顿 | 10-45 秒 |
| `jitterDelay()` | 基础延迟 ±20% 抖动 | — |

### 4.8 offer-normalizer.js — 搜索结果规范化

将 1688 搜索页面提取的原始数据规范化为统一结构：

```javascript
{
  index:        1,
  title:        "304不锈钢立柱...",
  offerUrl:     "http://detail.m.1688.com/...",
  imageUrl:     "https://cbu01.alicdn.com/...",
  merchantName: "佛山市鑫腾创金属制品有限公司",
  merchantUrl:  "http://shop583i783790460.1688.com/",
  contact: {
    type:   "wangwang",
    url:    "https://air.1688.com/...",
    handle: "鑫腾创金属制品有限公司",
    site:   "cnalichn"
  }
}
```

### 4.9 login-1688.js — 交互式登录工具

**pnpm script**: `pnpm --dir chatbot run login`

交互式工具，打开浏览器让用户手动完成 1688 登录（含验证码），登录态保存在 `.browser/1688-profile/` 目录中，后续脚本复用此浏览器配置。

### 4.10 run-1688.js — 基础搜索流程

**pnpm script**: `pnpm --dir chatbot run start`

单次搜索流程，不依赖 Supabase，适合快速测试：
1. 搜索关键词 → 提取搜索结果
2. 下载商品图片保存到 `output/images/`
3. 联系商家（可选）
4. 保存结果到 `output/results.json`

### 4.11 run-supabase.js — 集成 Supabase 的搜索流程

**pnpm script**: `pnpm --dir chatbot run supabase`

完整的 Supabase 集成搜索流程：
1. 从 Supabase 读取采购清单
2. 搜索 + 提取结果
3. 搜索结果写入 Supabase（带重试）
4. 联系商家并记录状态

---

## 5. 数据库设计

### 5.1 customer_service_chats 表

多轮询价对话状态表，每条记录代表一个"商品-商家"的询价对话：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 主键 |
| `intent_id` | UUID | 关联 `procurement_materials` 表 |
| `search_result_id` | UUID | 关联搜索结果 |
| `batch_id` | TEXT | 批次 ID |
| `merchant_id` | TEXT | 商家 ID |
| `merchant_name` | TEXT | 商家名称 |
| `contact_url` | TEXT | 旺旺聊天链接 |
| `contact_handle` | TEXT | 旺旺 ID |
| `offer_url` | TEXT | 商品链接 |
| `title` | TEXT | 商品标题 |
| `current_round` | INTEGER | 当前轮次 (1-4) |
| `status` | TEXT | 状态枚举 |
| `next_check_time` | TIMESTAMPTZ | 下次巡检时间 |
| `retry_count` | INTEGER | 当前轮次巡检次数 |
| `last_message_hash` | TEXT | 最后消息哈希（判断新回复） |
| `extracted_data` | JSONB | LLM 提取的结构化数据 |
| `ask_context` | JSONB | 话术上下文（材料规格等） |

**索引**：
- `idx_csc_worker_poll` — 巡检 Worker 核心查询（status + next_check_time）
- `idx_csc_batch` — 按批次查询
- `idx_csc_intent` — 按采购意向查询

### 5.2 chat_messages 表

聊天消息日志表，记录每一条发出和收到的消息：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | UUID | 主键 |
| `chat_id` | UUID | 关联 `customer_service_chats.id` |
| `round` | INTEGER | 所属轮次 |
| `direction` | TEXT | `SENT` / `RECEIVED` |
| `content` | TEXT | 消息文本 |
| `message_hash` | TEXT | MD5 哈希 |
| `llm_intent` | JSONB | LLM 解析结果 |
| `created_at` | TIMESTAMPTZ | 创建时间 |

---

## 6. 环境变量

创建 `.env` 文件，配置以下环境变量：

```bash
# ========== Supabase ==========
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_service_key=eyJhbGci...

# ========== LLM API ==========
LLM_API_KEY=sk-xxx
LLM_BASE_URL=https://api.deepseek.com/v1    # 或其他 OpenAI 兼容 API
LLM_MODEL=deepseek-chat                     # 模型名

# ========== 1688 登录（可选预填） ==========
LOGIN_1688_USER=your_username
LOGIN_1688_PASS=your_password

# ========== 搜索/运行参数 ==========
SEARCH_QUERY=苹果新机 17                     # run-1688.js 用
ASK_MESSAGE=是否有 iPhone 17 Pro Max 256G   # run-1688.js 用
RESULT_LIMIT=10                              # 搜索结果数量上限
HEADLESS=0                                   # 无头模式 (0=有头, 1=无头)
CONTACT_MERCHANTS=1                          # 是否联系商家
KEEP_BROWSER_OPEN=0                          # 完成后保持浏览器打开

# ========== Worker 参数 ==========
WORKER_BATCH_SIZE=5                          # 每轮拉取任务数
WORKER_IDLE_SEC=30                           # 队列空时休眠秒数
```

---

## 7. 快速开始

### 7.1 安装依赖

```bash
pnpm install
pnpm exec playwright install chromium
```

### 7.2 配置环境

1. 创建 `.env` 文件（参考第 6 节）
2. 在 Supabase 执行 SQL 建表：
   - `sql/006_customer_service_chats.sql`
   - `sql/007_chat_messages.sql`

### 7.3 登录 1688

```bash
pnpm --dir chatbot run login
```

浏览器会自动打开 1688 登录页，手动完成登录和验证码后按 Enter 关闭。登录态保存在 `.browser/1688-profile/`。

### 7.4 运行方式

#### 方式一：快速测试（无 Supabase）

```bash
# 单次搜索 + 联系商家
pnpm --dir chatbot run start
```

#### 方式二：Supabase 集成搜索

```bash
# 从 Supabase 读取采购清单 → 搜索 → 保存结果
pnpm --dir chatbot run supabase
```

#### 方式三：多轮自动询价（推荐）

```bash
# 步骤 1: 初始化对话任务 + 发送第一轮话术
pnpm --dir chatbot run chat:init

# 步骤 2: 启动巡检 Worker（持续运行）
pnpm --dir chatbot run chat:worker
```

### 7.5 运行测试

```bash
pnpm --dir chatbot test
```

---

## 8. pnpm scripts 速查

| 命令 | 说明 |
|------|------|
| `pnpm --dir chatbot run login` | 交互式登录 1688，保存登录态 |
| `pnpm --dir chatbot run start` | 单次搜索流程（不依赖 Supabase） |
| `pnpm --dir chatbot run supabase` | Supabase 集成搜索流程 |
| `pnpm --dir chatbot run chat:init` | 初始化多轮询价 + 发送 Round 1 |
| `pnpm --dir chatbot run chat:worker` | 启动巡检 Worker（持续运行） |
| `pnpm --dir chatbot test` | 运行单元测试 |

---

## 9. 测试覆盖

| 模块 | 测试文件 | 覆盖内容 |
|------|----------|----------|
| chat-scheduler | `test/chat-scheduler.test.js` | 指数退避计算、状态常量、最大重试次数 |
| chat-templates | `test/chat-templates.test.js` | 4 轮话术插值、字段缺失容错、无效轮次处理 |
| offer-normalizer | `test/offer-normalizer.test.js` | 结果规范化、缺失 URL 容错 |
| wangwang-rpa | `test/wangwang-rpa.test.js` | 消息哈希 MD5、新回复判断逻辑 |
