# 交互组件详细规范

本文档定义了 1688-item-title-optimizer Skill 中所有交互组件的具体数据结构与映射规则。大模型在调用 `show_interaction` 前需查阅本文档，确保数据结构正确。

---

## 1. confirm_apply_title (Card 组件)

### 组件类型

`type: card` — 用户选定标题后，确认是否将新标题应用到商品。

### 数据槽位定义

- **`questions`**:
  - 类型: `Array<Object>`
  - 说明: 问题列表，每项包含 `question`（问题文本）和 `options`（选项数组）

### 构造规则

- `question` 中应展示原标题和选定的新标题，让用户做最终确认
- 明确说明"应用"操作的含义（会替换当前商品标题）

### 完整数据示例

```json
{
  "questions": [
    {
      "question": "确认将以下标题应用到商品？\n\n原标题：304不锈钢水杯\n新标题：2026新款304不锈钢保温杯 大容量便携户外水杯 男女通用\n\n应用后将替换当前的商品标题。",
      "options": [
        "✅ 确认，应用到商品标题",
        "✏️ 我想手动微调后再应用",
        "💾 仅记录，暂不应用到商品"
      ],
      "required": true
    },
    {
      "question": "**如需微调标题**，请在下方输入修改后的完整标题：\n\n> 当前推荐标题：2026新款304不锈钢保温杯 大容量便携户外水杯 男女通用",
      "options": [],
      "required": false
    }
  ]
}
```

> **说明**：第二个 question 的 `options` 为空数组，端侧会渲染为**大输入框**（而非小的"输入其他"框），方便用户输入完整的商品标题（通常 30-60 字）。`required: false` 表示仅当用户选择"手动微调"时才需要填写。

### 用户选择后的处理

| 用户选择 | Agent 行为 |
|---------|-----------|
| 确认应用 | 调用 `1688-item-one-click` 将选定标题更新到商品，完成后输出成功提示 |
| 手动微调 | 使用第二个 question 中用户输入的标题，调用 `1688-item-one-click` 应用微调版本 |
| 仅记录 | 结束流程，提示用户可后续手动应用 |

---

## 2. select_items_to_optimize (Card 组件)

### 组件类型

`type: card` — 当用户传入 3 个及以上商品 ID 时，展示商品列表让用户筛选需要优化的商品，缩小分析范围。

### 触发条件

- 用户在一次请求中传入 **≥ 3 个商品 ID**
- Agent 必须在执行优化前触发此交互，不得自动全部执行

### 数据槽位定义

- **`questions`**:
  - 类型: `Array<Object>`
  - 说明: 问题列表，每项包含 `question`（问题文本）和 `options`（选项数组）
  - 端侧会自动追加"输入其他"选项

### 构造规则

- `question` 中应列出所有传入的商品 ID 及其当前标题，方便用户辨识
- 选项中应包含每个商品的 ID + 标题摘要（截取前 20 字），以及"全部优化"和"取消"选项
- 如果无法预先获取标题，可仅展示商品 ID

### 完整数据示例

```json
{
  "questions": [
    {
      "question": "您提供了 4 个商品，一次优化过多可能影响效率和结果质量。请选择需要优化的商品（建议不超过 3 个）：\n\n1. 商品 831034165952：304不锈钢水杯大容量...\n2. 商品 742091827364：儿童书包男生小学生...\n3. 商品 653928174051：夏季短袖T恤男纯棉...\n4. 商品 590817263940：办公椅电脑椅家用舒适...",
      "options": [
        "优化商品1：831034165952",
        "优化商品2：742091827364",
        "优化商品3：653928174051",
        "优化商品4：590817263940",
        "全部优化（可能耗时较长）",
        "❌ 取消，暂不优化"
      ]
    }
  ]
}
```

### 用户选择后的处理

| 用户选择 | Agent 行为 |
|---------|-----------|
| 选择特定商品（可多选） | 仅对选中的商品执行并发优化流程 |
| 全部优化 | 对所有商品逐个执行优化流程，按顺序展示结果 |
| 取消 | 结束流程，输出友好的结束语 |
| 输入其他（端侧追加） | 用户可手动输入要优化的商品 ID 列表 |

---

## 3. title_comparison_card (Table 组件 — 相邻同值合并 + 组级单选)

> ### ⚠️ 端侧版本依赖（v2 协议）
>
> 本交互依赖 `show_interaction.table` 的 **v2 协议扩展字段**：`mergedColumns` / `selectionGranularity` / `selectionMode` / `groupBy`。这 4 个字段**仅在已升级到 v2 的客户端**（如 newton-desktop v2 及以上）才会生效。
>
> **当前 1688 工作台 / 找工厂客户端尚未升级到 v2**，该客户端会**忽略**这 4 个字段，把 payload 退化为默认的 `row + multiple`（行级多选）模式渲染：
>
> | 现象 | v2 客户端（期望） | 未升级客户端（当前实际）|
> |------|------------------|----------------------|
> | 勾选框数量 | 2 个（每组组首行 1 个，rowSpan=4）| 8 个（每行 1 个）|
> | 方案列单元格 | 「方案A」「方案B」各合并为 1 个大 cell | 8 行各显示一次「方案A」/「方案B」|
> | 勾选语义 | 整组互斥单选（A↔B 二选一）| 行级多选，可任意勾 N 行 |
> | 已选计数 | 0/8 或 4/8 | 0/8 ~ 8/8 任意 |
>
> **重要原则**：
> 1. **payload 不要为兼容降级而修改** —— 协议字段写法是正确的，问题在端侧未实现，等客户端升级即可自动生效
> 2. **Agent 不要因为看到「每行一个 checkbox」而判断 payload 错了** —— 这是端侧降级渲染的预期表现
> 3. 在未升级客户端上，Agent 收到的 `selectedRows` 可能是用户跨方案勾选的若干行（不一定是同一方案的 4 行）；处理算法已在下方"Agent 处理逻辑"小节做了**统一兜底**（按 `plan` 分组、跨方案时重新弹窗、未勾新标题时重新弹窗），**禁止**自行启发式猜测用户意图

### 组件类型

`type: table` — 两种优化方案生成后，在左侧弹出表格。3 列（方案 + 属性标签 + 内容），每个**成功**方案的 4 个维度各占一行（两个都成功 = 8 行，一个成功一个失败 = 仅展示成功方案的 4 行，两个都失败 = 不弹表格）。利用端侧 `show_interaction.table` v2 协议的**相邻同值合并**能力（`mergedColumns` + `groupBy`），方案列连续同值的行在视觉上合并为一个 rowSpan 大单元格；勾选粒度为**组**（`selectionGranularity: "group"`），数量为**单选**（`selectionMode: "single"`），方案 A / 方案 B 互斥，整组共用一个勾选框（渲染在组首行）。新标题行的内容列可直接编辑。

### 端侧能力依赖（v2 协议 4 个字段）

| 字段 | 维度 | 本场景取值 | 语义 |
|------|------|-----------|------|
| `mergedColumns` | 视觉合并 | `["plan"]` | 该列**相邻同值**的连续行自动合并为 rowSpan 大单元格；不做全局聚合，不跨 `groupBy` 边界 |
| `groupBy` | 分组依据 | `"plan"` | 按 `plan` 字段切分相邻分组（`selectionGranularity:"group"` 时**必填**）|
| `selectionGranularity` | 勾选**单位** | `"group"` | 每组一个 checkbox（渲染在组首行），点击即整组勾选 |
| `selectionMode` | 勾选**数量** | `"single"` | 互斥单选；选中新组自动取消旧组；点击已选项 → 清空（视为取消，等价于跳过）|

> **正交设计**：`selectionGranularity × selectionMode` 是两个正交维度，本场景使用 `group + single`（方案互斥）。其余 3 种组合（`row+multiple` 默认 / `row+single` / `group+multiple`）由端侧统一支持。

### 协议硬约束（违反会被主进程 validator 拒绝 / 端侧渲染异常）

1. **`mergedColumns` 中的列不能是 `editable: true` 的列** —— 合并后编辑态语义歧义。本场景虽然 `value` 列已不开启列级 editable（改为行级 `rows[i].editable: true` 仅作用于"新标题"行，详见下方"行级 editable 协议"小节），但本场景仍**只合并 `plan` 列**，原因变成了"`value` 列每行内容都不同，没有相邻同值可合并"，而非 editable 冲突
2. **`field` 列必须显式声明 `editable: false`**（不可省略）—— 用户实测发现：省略 `editable` 字段时端侧可能把该列误渲染为可编辑；显式写 `false` 才能保证该列稳定只读
3. **`selectionGranularity: "group"` 时必须同时传 `groupBy`**
4. **`mergedColumns` / `groupBy` 的 key 必须存在于 `columns`**
5. **行数 ≤ 10**：超过端侧分页阈值后会**自动关闭合并**（防止一组被拆到两页导致 rowSpan 跨页错乱）。本场景最多 8 行（仅成功方案入表），安全
6. 这 4 个字段（`mergedColumns` / `groupBy` / `selectionGranularity` / `selectionMode`）是 `table` 专有，不允许出现在 `card` / `input` / `open_tab` 类型下

### 触发条件

- 两种优化方式（方式A `optimize_title` + 方式B `optimize_title_llm`）**均执行结束**后触发
- **仅展示成功方案**：失败方案（CLI 返回 `success: false` 或异常）**不进入 rows**，直接跳过。rows 中仅填入成功方案的 4 行
- **一个成功一个失败** → 触发本交互，`rows.length = 4`（仅成功方案）；在对话中简要告知用户另一方案未生成成功
- **两个都成功** → 触发本交互，`rows.length = 8`（两个方案各 4 行）
- **两个都失败** → **不触发**本交互；直接在对话中告知用户"两种优化方案均未生成成功，建议稍后重试"，并提示可重新触发优化

### 失败方案处理（严禁展示）

> ⚠️ **核心规则**：失败方案**不进入表格 rows**，不要为失败方案填入任何占位行、兜底行、提示行。用户在表格中只能看到成功生成的方案。如果某方案 CLI 返回 `success: false` 或异常，在对话文本中简要说明该方案未成功即可，**禁止**在 `title_comparison_card` 的 rows 里塞入失败信息。

### 数据槽位定义

- **`title`**:
  - 类型: `String`
  - 说明: 表单标题，格式为"请选择新标题 — 商品名称（商品ID）"

- **`columns`**:
  - 类型: `Array<Object>`
  - 说明: 固定 3 列

  | key | label | width | editable | 说明 |
  |-----|-------|-------|----------|------|
  | `plan` | 方案 | 80 | — | 方案标识列，端侧根据 `mergedColumns` 自动合并相同值 |
  | `field` | 属性 | 140 | **`false`**（必须显式声明）| 属性标签列：方案名称 / 新标题 / 生成逻辑及优化说明 / 预估曝光变化。⚠️ **必须显式写 `editable: false`**，省略字段会让端侧把整列误渲染为可编辑（用户实测确认，2026-05）|
  | `value` | 内容 | 620 | — | 对应属性的具体内容；**列级不传 editable**，由 `rows[i].editable: true` 行级控制（仅"新标题"行开启）|

- **`mergedColumns`** ⭐ 关键字段:
  - 类型: `Array<String>`
  - 值: `["plan"]`
  - 说明: 指定 `plan` 列做**相邻同值合并** —— 连续同值的行在该列自动合并为一个 rowSpan 大单元格。**该列不能是列级 `editable: true`**（行级 `rows[i].editable` 不受此约束限制，但若把 `value` 加入 `mergedColumns` 同样无意义，因为 `value` 每行内容都不同）

- **`groupBy`** ⭐ 关键字段:
  - 类型: `String`
  - 值: `"plan"`
  - 说明: 按 `plan` 字段切分相邻分组，相同 `plan` 值的**连续行**属于同一组（`selectionGranularity:"group"` 时必填）

- **`selectionGranularity`** ⭐ 关键字段:
  - 类型: `String`
  - 枚举: `"row"` / `"group"`
  - 值: `"group"`
  - 说明: 勾选**单位**为"组"，每组一个 checkbox（渲染在组首行），整组共用，勾选时整组同时选中

- **`selectionMode`** ⭐ 关键字段:
  - 类型: `String`
  - 枚举: `"single"` / `"multiple"`（缺省 = `"multiple"`，向后兼容）
  - 值: `"single"`
  - 说明: 勾选**数量**为单选，方案 A / 方案 B 互斥；选中新组自动取消旧组；再次点击已选项 → 清空（落到空选，等价于跳过）。**与 `selectionGranularity` 正交**，4 种组合都合法

- **`actions`**:
  - 类型: `Array<Object>`
  - 数组长度: **固定 1 项**（仅 `adopt`）；端侧会自动在 actions 之外额外渲染"跳过/关闭"按钮，**禁止**在 actions 里再追加 `skip` / `cancel` 等元素
  - 说明: 自定义主按钮，覆盖端侧默认的"确认选择"标签，明确语义为"采用此方案"

  | key | label | description | variant |
  |-----|-------|-------------|---------|
  | `adopt` | 采用此方案 | 用户采用该方案下的新标题（以 value 列的最终编辑值为准），后续流程基于选中方案继续 | `primary` |

- **`rows`**:
  - 类型: `Array<Object>`
  - 说明: 每个**成功**方案占 4 行（方案名称、新标题、生成逻辑及优化说明、预估曝光变化）。两个都成功 = 8 行，一个成功一个失败 = 4 行（失败方案不进入 rows）

  | 字段 | 类型 | 说明 |
  |------|------|------|
  | `plan` | String | 方案标识，**同组所有行都填相同值**（如"方案A"或"方案B"），端侧依靠相邻同值进行合并和分组。**方案 A 的 4 行必须连续在前，方案 B 的 4 行必须连续在后**，不能交错（端侧只合并相邻同值，不做全局聚合）|
  | `field` | String | 属性标签名，固定枚举：`方案名称` / `新标题` / `生成逻辑及优化说明` / `预估曝光变化` |
  | `value` | String | 属性值；`field === "新标题"` 那行的 value 在 UI 上可被用户直接编辑（依赖**行级** `rows[i].editable: true`，**仅在新标题行设置**）|
  | `editable` | Boolean? | **可选，行级 editable 开关**。仅 `field === "新标题"` 的行设置 `editable: true`，其他 3 行不设置（端侧默认只读）。**取代列级 `columns[].editable`**，让方案名称 / 生成逻辑 / 预估曝光变化 这 3 行物理只读，从根上避免用户误编辑（详见下方"行级 editable 协议"小节）|

  **总行数硬上限**：`rows.length ≤ 10`。本场景最多 8 行（2 方案均成功 × 4 维度）、一方失败时仅 4 行，永不触达上限；超过 10 行端侧会触发分页并**自动关闭合并**，破坏视觉效果。如需扩展第 3 个方案，先评估是否需要改用其他展示形态。

- **`totalCount`**:
  - 类型: `Integer`
  - 值: 等于 `rows.length`（本场景为 `4` 或 `8`，取决于成功方案数）
  - 说明: 与 `rows.length` 一致；用于端侧分页判定

### 为什么"只能合并 plan 列、不能合并 value 列"

- `mergedColumns` **只能包含 `["plan"]`**
- 不需要加入 `value`：`value` 列每行内容都不同，没有相邻同值可合并，加了也无效果
- 不需要加入 `field`：每行 `field` 不同，没有可合并的相邻同值

### 行级 editable 协议（2026-05 用户实测确认有效）

> **背景**：1688 端侧官方曾答复（2026-05 钉钉）"列级 editable 是当前能力，不支持行级控制"。但用户后续**实测确认**端侧已支持 `rows[i].editable: true` 行级控制，且本场景配合下述 columns 写法可达到"仅新标题行可编辑、其他行只读"的预期效果。
>
> **关键事实（用户实测，与官方初步答复不一致 → 以实测为准）**：
> - 端侧识别 `rows[i].editable: true`（行级开启编辑）
> - 端侧对 `columns[i].editable` **省略字段** vs **显式写 `false`** 的处理不同：省略时部分列会被误渲染为可编辑，**必须显式声明 `editable: false`** 才能确保该列只读

#### 协议写法（实测有效形态）

```json
"columns": [
  { "key": "plan",  "label": "方案", "width":  80 },                          // 不传 editable（端侧合并列默认只读）
  { "key": "field", "label": "属性", "width": 140, "editable": false },       // ⚠️ 必须显式 false
  { "key": "value", "label": "内容", "width": 620 }                           // 不传 editable，由行级控制
],
"rows": [
  { "plan": "方案A", "field": "方案名称",     "value": "..." },                   // 默认只读
  { "plan": "方案A", "field": "新标题",       "value": "...", "editable": true }, // 行级开启
  { "plan": "方案A", "field": "生成逻辑...",  "value": "..." },                   // 默认只读
  { "plan": "方案A", "field": "预估曝光变化", "value": "..." }                    // 默认只读
]
```

#### 端侧识别行为（用户实测）

| 字段 | 写法 | 端侧行为 |
|------|------|---------|
| `columns[].editable` | **省略** | 该列可能被误渲染为可编辑（已观察到现象，原因未深查）|
| `columns[].editable` | **显式 `false`** | 该列稳定渲染为只读 ✅ |
| `columns[].editable` | **显式 `true`** | 该列所有 cell 都渲染为可编辑输入框（列级开关）|
| `rows[i].editable` | **省略** | 该行 cell 受所在列的列级 editable 控制 |
| `rows[i].editable` | **显式 `true`** | 该行 cell 强制渲染为可编辑输入框（行级覆盖列级） ✅ |

> **本场景的具体配置**：`field` 列显式 `editable: false`、`value` 列省略（不开启列级），仅"新标题"行的 `rows[i].editable: true` 让该 cell 可编辑 —— 综合起来达到"仅新标题行可编辑、其他 7 个 value cell + 全部 8 个 field cell 都只读"的效果。

#### 业务侧软兜底（防御性，不依赖端侧识别）

无论端侧是否识别行级 editable，Agent 在读取回传 `selectedRows` 时**必须始终遵守**：

| 行 (`field`) | 用户编辑的处理 |
|--------------|---------------|
| `新标题` | **采用编辑后值**（作为最终标题写入 `confirm_apply_title`）|
| `方案名称` | **显式忽略**用户编辑，回传值仅用于"识别选中方案"，不参与最终标题构造 |
| `生成逻辑及优化说明` | **完全忽略**用户编辑，仅作为 UI 展示用途 |
| `预估曝光变化` | **完全忽略**用户编辑，仅作为 UI 展示用途 |

> **双层保护设计**：
> - 第一层：协议层 `rows[i].editable` 让端侧物理只读其他 3 行（最理想）
> - 第二层：Agent 软兜底确保即使第一层失效（端侧不识别），其他 3 行的用户编辑也不会污染最终标题
>
> 这样无论端侧版本如何演进，业务正确性都有保障。

### 布局示意（合并单元格 + 组级单选效果）

```
┌──────┬───────┬────────────────────┬────────────────────────────────────┐
│  ☐   │ 方案  │ 属性                │ 内容                                │
├──────┼───────┼────────────────────┼────────────────────────────────────┤
│      │       │ 方案名称            │ 添加热词优化（规则版）              │
│      │       ├────────────────────┼────────────────────────────────────┤
│      │       │ 新标题              │ 304不锈钢保温杯便携大容量 ✎         │
│  ☐   │ 方案A ├────────────────────┼────────────────────────────────────┤
│      │       │ 生成逻辑及优化说明   │ 👀 304、不锈钢 / 🔥 保温杯(8500)…   │
│      │       ├────────────────────┼────────────────────────────────────┤
│      │       │ 预估曝光变化        │ +15% ~ +25%                        │
├──────┼───────┼────────────────────┼────────────────────────────────────┤
│      │       │ 方案名称            │ AI深度重写                          │
│      │       ├────────────────────┼────────────────────────────────────┤
│      │       │ 新标题              │ 2026新款304不锈钢保温杯… ✎          │
│  ☐   │ 方案B ├────────────────────┼────────────────────────────────────┤
│      │       │ 生成逻辑及优化说明   │ 📈 2026、新款 / 🔥 保温杯(8500)…    │
│      │       ├────────────────────┼────────────────────────────────────┤
│      │       │ 预估曝光变化        │ +20% ~ +35%（含年份更新加成）       │
└──────┴───────┴────────────────────┴────────────────────────────────────┘

图例：
  ☐  = 整组共用的 checkbox，仅在每组「组首行」渲染（rowSpan = 4）；
       组内非首行不渲染勾选 td。
  ✎  = 仅"新标题"行可编辑（依靠行级 rows[i].editable: true，详见
       「行级 editable 协议」小节）；其他 3 行端侧渲染为纯文本只读。
       即使端侧暂不识别行级 editable，Agent 也会软兜底只采用新标题行的
       编辑值，其他 3 行编辑被显式忽略。
  方案A / 方案B 单元格 = plan 列 mergedColumns 自动合并，rowSpan = 4。
```

> **设计目的**：依靠端侧 `mergedColumns: ["plan"]` 实现 plan 列相邻同值的视觉合并；依靠 `selectionGranularity: "group"` 让每组共用一个组首 checkbox；依靠 `selectionMode: "single"` 让方案 A / 方案 B **互斥**（选 A 后再选 B，A 自动取消）。三者正交叠加，达到"行 × 列双向合并 + 方案二选一"的最终效果。

### 构造规则

**"生成逻辑及优化说明"行的 `value`** 按四大维度分类词并标注热度，维度之间用 ` / ` 分隔，每个维度内多个词用顿号 `、` 分隔，最后附优化说明：

| 维度 | 对应 tag | 格式示例 |
|------|---------|---------|
| 🔥 热词 | `热词` | `🔥 保温杯(热度:8500)、便携(热度:6200)` |
| 📈 流量获取 | `时间词`、`修饰词` | `📈 2026、新款` |
| ✨ 吸引力 | `场景词`、`风格词` | `✨ 户外、男女通用` |
| 👀 买家关注 | `属性词`、`材质词`、`品类词`、`功能词` | `👀 304、不锈钢` |

构造规则：

- **维度间分隔符**：` / `（前后各一个空格）
- **维度内词间分隔符**：`、`（中文顿号）
- 如某维度无对应词则**整段省略**（不要保留空的 emoji + 分隔符）
- `weight` 字段不存在时省略 `(热度:xxxx)` 标注，仅保留词名
- 最后追加 ` / 优化说明：<optimize_reason 或亮点描述>`，作为最末一段

### 曝光量变化预测规则

1. 新标题每新增 1 个热搜词，预估曝光提升 5%-15%
2. 含当前年份词（如"2026新款"），额外提升 3%-8%
3. 给出保守区间（如 "+10% ~ +25%"）

### 完整数据示例

```json
{
  "type": "table",
  "selectionType": "title_plan",
  "title": "请选择新标题 — 304不锈钢水杯（831034165952）",
  "columns": [
    { "key": "plan", "label": "方案", "width": 80 },
    { "key": "field", "label": "属性", "width": 140, "editable": false },
    { "key": "value", "label": "内容", "width": 620 }
  ],
  "mergedColumns": ["plan"],
  "selectionGranularity": "group",
  "selectionMode": "single",
  "groupBy": "plan",
  "actions": [
    {
      "key": "adopt",
      "label": "采用此方案",
      "description": "用户采用该方案下的新标题（以「新标题」行的最终编辑值为准），后续流程基于选中方案继续落库或上线。",
      "variant": "primary"
    }
  ],
  "rows": [
    { "plan": "方案A", "field": "方案名称", "value": "添加热词优化（规则版）" },
    { "plan": "方案A", "field": "新标题", "value": "304不锈钢保温杯便携大容量", "editable": true },
    { "plan": "方案A", "field": "生成逻辑及优化说明", "value": "👀 304、不锈钢 / 🔥 保温杯(热度:8500)、便携(热度:6200)、大容量(热度:5100) / 优化说明：添加热词保温杯、便携、大容量" },
    { "plan": "方案A", "field": "预估曝光变化", "value": "+15% ~ +25%；实际效果受商品权重、类目竞争、市场环境等多因素影响，仅供参考" },
    { "plan": "方案B", "field": "方案名称", "value": "AI深度重写" },
    { "plan": "方案B", "field": "新标题", "value": "2026新款304不锈钢保温杯 大容量便携户外水杯 男女通用", "editable": true },
    { "plan": "方案B", "field": "生成逻辑及优化说明", "value": "📈 2026、新款 / 👀 304、不锈钢、水杯 / 🔥 保温杯(热度:8500)、便携(热度:6200)、大容量(热度:5100) / ✨ 户外、男女通用 / 优化说明：AI深度重写，融合热词与场景描述" },
    { "plan": "方案B", "field": "预估曝光变化", "value": "+20% ~ +35%（含年份更新加成）；实际效果受商品权重、类目竞争、市场环境等多因素影响，仅供参考" }
  ],
  "totalCount": 8  // 两个方案都成功时为 8；若一方失败则为 4（仅成功方案入表）
}
```

> ⚠️ 曝光预估基于关键词热度数据，实际效果受类目竞争、商品权重等多因素影响，仅供参考。

### 回传契约（关键：respond 始终回传展开 rows）

> **核心保证**：无论是 `group` 还是 `single` 模式，端侧 respond 给 Agent 的 `selectedRows` 始终是**展开后的原始行数组**（与 `multiple+row` 模式同构），后端 / 大模型对"组"和"单选"完全无感。

| 用户操作 | 回传 `selectedRows` | 说明 |
|---------|----------------------|------|
| 勾选方案 A 整组并点"采用此方案" | 方案 A 对应的 **4 行展开数据**（按原顺序，含 cell 内编辑后的最新 value） | 协议契约：组级勾选 → 展开 4 行回传 |
| 勾选方案 A 后改勾方案 B 再点"采用此方案" | 方案 B 对应的 **4 行展开数据**（A 已被自动取消，不出现在回传中） | `selectionMode: "single"` 互斥归一化的结果 |
| 勾选方案 A 后再次点击方案 A 取消，再点"采用此方案" | `[]`（空选 = 跳过，合理语义） | 点击已选项 = 取消 |
| 不勾选任何方案直接点"采用此方案" | `[]`（空选 = 跳过） | 与"全部取消"等价 |

> 跳过 / 关闭 弹窗的回传形态由端侧通用机制决定（不属于本交互的协议层定义），Agent 侧只需按下方"处理逻辑"判断 `selectedRows` 是否为空即可，无需关心具体跳过形态。

### Agent 处理逻辑（统一兼容 v2 客户端 与 未升级客户端）

收到 `selectedRows` 后，**无需事先判断端侧版本**，按以下统一算法处理。该算法在 v2 客户端上等价于"读 `selectedRows[0].plan`"的简单路径，在未升级客户端上自动启用兜底分支。

#### 前置：触发本交互前必须保留的上下文

在触发 `title_comparison_card` 之前，Agent 必须确保以下两份数据**仍可在当前对话上下文中访问**（无需独立缓存模块，依靠 prompt context 中保留的工具调用结果即可）：

- `optimize_title`（方案A）的 CLI 完整返回 JSON
- `optimize_title_llm`（方案B）的 CLI 完整返回 JSON

> **为什么需要这两份数据**：未升级客户端可能让用户勾选不完整（缺新标题行）或跨方案混选，此时 Agent 需要"重新触发 `title_comparison_card`" 来让用户重选 —— 重新触发时 Agent 必须用成功方案的原始返回**重新构造 payload**（仅填入成功方案的行；不能重新调用 CLI，会浪费配额且 LLM 结果不可重现）。在 v2 客户端上这两份数据虽然不会被用到，但保留无开销。

#### Step 1 — 空选判定

- `selectedRows.length === 0` → 视为用户放弃选择
- 立即回退提示用户："是否需要重新生成方案 / 结束本轮优化"，等待用户回复
- **禁止**进入 `confirm_apply_title`，**禁止**自行编造标题继续流程

#### Step 2 — 按 `plan` 分组聚合

```
groups = group_by(selectedRows, row => row.plan)
// v2 客户端典型形态：{ "方案A": [4 行] }
// 未升级客户端可能形态：
//   { "方案A": [1 行] }                       — 用户只勾了 1 行
//   { "方案A": [3 行], "方案B": [2 行] }       — 跨方案混勾
//   { "方案B": [4 行] }                       — 与 v2 同形态
```

#### Step 3 — 根据分组数分支处理

| `groups` 的 key 数 | 含义 | 处理 |
|-------------------|------|------|
| `1` | v2 客户端正常路径 / 未升级客户端用户只勾了一个方案的若干行 | 进入 Step 4 |
| `≥ 2` | **仅在未升级客户端可能出现**（v2 互斥单选会拦截）：用户跨方案勾了行 | 走 **Step 3a 重新弹窗** |

##### Step 3a — 跨方案混勾的重新弹窗流程（仅 ≥2 分支）

**严禁**用"取行数最多的方案"等启发式猜测算法（用户在未升级客户端的勾选行为可能是任意组合，Agent 没有依据猜测意图，强行猜测会导致采用了用户实际不想要的方案 → 数据污染风险）。

具体执行：

1. 给用户一句对话提示（明文消息，不要塞进 table）：
   > "检测到您勾选了多个方案的行（方案 A：N₁ 行 / 方案 B：N₂ 行）。由于本次只能采用一个方案，请在重新弹出的表格中**仅勾选您想采用的那一个方案**，再点「采用此方案」。"
2. **重新触发** `title_comparison_card`，**用前置小节提到的成功方案的原始 CLI 返回值重新构造 payload**（仅填入成功方案的行，payload 字段与首次触发相同，包括 4 个 v2 协议字段；**禁止**重新调用 `optimize_title` / `optimize_title_llm`）
3. 重新等待用户回传，回到 Step 1 重新走一遍

> 在 v2 客户端上 Step 3a 永远不会被执行（互斥单选拦截在端侧），所以这段逻辑只在降级场景下活。

#### Step 4 — 选中行集合完整性校验

设 `selectedFields = selectedRows.map(r => r.field)` 是用户实际勾选了哪些 field（仅唯一方案下的那些行）。

- **必须包含 `"方案名称"` 与 `"新标题"` 两个 field 中的全部** —— 否则后续的"读取最终新标题"会因数据缺失而失效
- **缺失任一时**：执行 **Step 4a 缺字段重新弹窗**

##### Step 4a — 缺字段时的重新弹窗流程

1. 给用户对话提示：
   > "您勾选的行不完整（缺少 `<缺失的 field 列表>`）。为了正确采用方案，请在重新弹出的表格中**勾选包含「方案名称」和「新标题」的完整行集合**。"
2. **重新触发** `title_comparison_card`（同 Step 3a 第 2 点：用原始 CLI 返回值重构 payload，禁止重调 CLI）
3. 重新等待用户回传，回到 Step 1

> 在 v2 客户端上，组级勾选保证整组 4 行齐全，Step 4a 永远不会被执行。

#### Step 5 — （已移除：失败方案不再进入表格）

> 由于失败方案不再进入 rows，表格中所有方案均为成功方案，无需在回传后识别失败方案。直接进入 Step 6。

#### Step 6 — 读取最终新标题（仅采用「新标题」行的编辑值）

在唯一选中方案的所有行里，找 `field === "新标题"` 的行（Step 4 已保证此行必然存在）：

- 取该行的 `value` 作为最终新标题
- **该值可能已被用户在 cell 内编辑过**（依靠行级 `editable: true`），必须以此回传值为准
- **禁止**回退读 CLI 原始返回里的 `new_title`（用户编辑会被覆盖丢失）
- **禁止**用其他 field 的 value 当标题

> ⚠️ **关于其他 3 行（方案名称 / 生成逻辑及优化说明 / 预估曝光变化）的用户编辑**：
> 协议层已通过**不在这 3 行设置** `editable: true` 让端侧渲染为只读（详见「行级 editable 协议」小节）。但万一端侧暂不识别 `rows[i].editable` 退化为"全部 cell 可编辑"或"全部 cell 不可编辑"，Agent 仍须按以下软兜底处理：
> - `方案名称` 行的 value 仅用于"识别选中方案"，即便被用户改过也不影响判断逻辑，但**不参与最终标题构造**
> - `生成逻辑及优化说明` 行 / `预估曝光变化` 行的 value **完全不读**，仅作 UI 展示
> - **严禁**因用户改了其他行就把它当成新标题写入 `confirm_apply_title`

#### Step 7 — 进入应用确认

携带以下参数触发 `confirm_apply_title`：

- 选中方案标识（来自 `groups` 的唯一 key）
- 最终新标题（Step 6 取到的 `value`，可能含用户编辑）
- 商品 ID
- 商品原标题

> **设计原则总结**：
> - 算法只依赖 `selectedRows` 的扁平结构 + `plan`/`field` 字段语义，不依赖端侧"组"/"单选"等实现细节
> - 在 v2 客户端上：Step 3 的 `≥2` 分支与 Step 4 的"缺字段"分支永不触发（端侧已保证完整性），算法等价于"取 `selectedRows[0].plan` + 找新标题行"的极简路径
> - 在未升级客户端上：Step 3a / Step 4a 兜底按需启用，依靠 Agent 用 context 中已有的 CLI 原始返回值重构 payload 重新弹窗，**不再调用任何 CLI**，确保不浪费配额、结果可重现
> - 待 1688 客户端升级到 v2 后：**无需任何文档 / 逻辑回滚**，兜底分支自动失效

### 未升级客户端兜底处理（v2 协议未生效时）

在未升级到 v2 协议的客户端（如当前 1688 工作台 / 找工厂客户端）上，端侧不会做 group 展开和 single 互斥归一化，Agent 收到的 `selectedRows` 形态可能与 v2 不同：

| 场景 | v2 客户端回传 | 未升级客户端回传 |
|------|--------------|-----------------|
| 用户勾"方案A 的 4 行" | 方案A 的 4 行（自动整组展开）| 方案A 的 4 行（同结果）|
| 用户只勾"方案A 的新标题"行 | 方案A 的 4 行（整组展开）| **只有 1 行**（无展开）|
| 用户同时勾"方案A 的 1 行 + 方案B 的 2 行" | 不可能（互斥单选拦截）| **3 行混合，含两个 plan**（无互斥拦截）|
| 用户全勾 8 行 | 不可能（互斥单选拦截）| **8 行，含两个 plan** |

**Agent 兜底处理算法**（同时兼容 v2 / 未升级两种端侧）：

1. 按 `plan` 字段对 `selectedRows` 分组，得到 `Map<plan, rows[]>`
2. 若分组数 = 0 → 视为跳过，与 v2 空选处理一致
3. 若分组数 = 1 → 直接进入"识别选中方案 + 读取最终新标题"流程（与 v2 路径一致）
4. 若分组数 ≥ 2（仅在未升级客户端可能出现）→ **取行数最多的方案**作为用户意图；行数相同时优先取方案 A；处理后**显式提示用户**："检测到您勾选了多个方案的行，已按 `<选中方案>` 处理；如需选择另一方案，请重新勾选并仅保留该方案对应的行"
5. 在选定方案的行集合中，找 `field === "新标题"` 的行 → 取 `value`；若该 `field` 未被用户勾选 → **回退到原始优化结果中该方案的 `new_title`**（不是编造，而是 CLI 已返回的真实值），并提示用户"未勾选新标题行，已使用方案默认标题"

> 本兜底逻辑确保：哪怕端侧暂未升级，Skill 仍然可用；待端侧升级到 v2 后，分组数恒为 0 或 1，兜底分支自动失效，行为与原 v2 设计一致，**无需任何代码 / 文档回滚**。

---

## 4. open_tab_select_product (Open Tab 组件)

### 组件类型

`type: open_tab` — 当用户未提供商品 ID 时，直接输出该 JSON 唤起商品选择页面，**流程到此结束，不允许反问用户，不允许输出其他内容**。

### 触发条件

- 用户触发标题优化意图，但**上下文中没有商品 ID**
- **禁止反问用户是否要提供商品 ID，禁止询问用户任何问题**
- 直接输出以下 JSON，流程结束

### 完整数据示例

```json
{
  "type": "open_tab",
  "selectionType": "shop_backend",
  "url": "https://air.1688.com/app/CSBC-modules/csbc-ai-component-loader/picture-optimize.html?mode=newton-select-offer&skillCode=1688-item-title-optimizer",
  "pageTitle": "选择商品",
  "pageDescription": "选择商品优化标题",
  "icon": "https://img.alicdn.com/imgextra/i3/O1CN01gQPY341cm5b1gzS1k_!!6000000003642-2-tps-80-80.png"
}
```

### 行为说明

- 该交互为 **fire-and-forget** 模式，输出 JSON 后流程即结束
- 聊天区会同步出现一张"已为你打开商品选择"的只读气泡卡片
- **不再执行后续的优化步骤**

---

