文件预览

interaction-specs.md

查看 1688 Item Title Optimizer 技能包中的文件内容。

文件内容

references/interaction-specs.md

# 交互组件详细规范

本文档定义了 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 后流程即结束
- 聊天区会同步出现一张"已为你打开商品选择"的只读气泡卡片
- **不再执行后续的优化步骤**

---