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