文旅素材搜索(API)
搜索流程说明(每次搜索都适用)
用户每次发起搜索时(包括首次、后续、重新搜索、换关键词等),智能体都须按以下流程执行,不得省略或合并为一次:
Step 1. 计算两次调用的 pageSize(成片优先配比)
设用户想要总条数为 N(默认 3,上限 5;用户可指定 1-5):
pageSize_finished = ceil(N / 2) — 成片
pageSize_material = floor(N / 2) — 素材
用户想要 N | 成片条数 | 素材条数 |
|---|
| 1 | 1 | 0 |
| 2 | 1 | 1 |
| 3 | 2 | 1 |
| 4 | 2 | 2 |
| 5 | 3 | 2 |
Step 2. 两次 POST 调用(顺序执行)
- 第一次(成片):请求体
commodityCode = finished_commodity_code,URL pageSize = pageSize_finished
- 第二次(素材):请求体
commodityCode = null,URL pageSize = pageSize_material(若为 0 则跳过第二次)
Step 3. 兜底补齐(配比失败时)
若成片实际返回 < pageSize_finished,用素材补齐到 N 条;反之亦然。目标是总条数尽量等于 N。
Step 4. 合并与判定类别
- 合并两次返回的
datas,按 businessCode/id 去重
- 逐条以"返回数据本身"的
commodityCode 判定(不是按哪次调用):
item.commodityCode === finished_commodity_code → category = "成片"
- 否则 →
category = "素材"
Step 5. 展示与汇总(统计必须基于 category 字段,不是请求参数!)
成片条数 = sum(1 for item in results if item.category == "成片")
素材条数 = sum(1 for item in results if item.category == "素材")
- 汇总行(表格之前):
📽 共找到 N 条资源:成片 **X** 条 | 素材 **Y** 条
X、Y 必须与表格里实际带 【成片】/【素材】 徽章的行数严格一致
- 即使
X=0 或 Y=0 也要写出(说明搜过了但无结果)
- 表格每行:标题旁强制带
【成片】 / 【素材】 徽章(以 item.category 为准)
绝对禁止:
- ❌ 省略成片调用,只调一次接口就返回
- ❌ 汇总行的数字与表格里实际徽章不一致(最常见错误:按"请求参数 commodityCode 是什么"算成片条数,而不是按"返回记录本身的 commodityCode"算)
- ❌ 输出表格时省略类别徽章
此规则等同于主路径「步骤 1」,优先级最高。
购买流程说明(当前调试阶段)
本 Skill 处于调试阶段,真实交易 API 尚未上线。用户发出「购买」「下单」「买 N」等指令时,当前约定的完整响应流程为:
- 构造 mock 交易响应:
video_url = 搜索结果中该素材的 fragmentUrl(预览地址)
- 下载视频到本地:调用
bash scripts/download_video.sh "<video_url>" "downloads/{business_code}.mp4" [HLS|MP4],将视频保存到 downloads/ 目录
- 告知用户:输出下载完成状态及本地文件路径
此流程由用户购买指令显式触发,搜索和预览阶段不执行任何下载。
真实交易 API 上线(TRADE_API_BASE 非空)后,步骤 1 将替换为真实下单请求,步骤 2-3 保持不变。
输出格式规范
智能体应先判断客户端类型,再选择对应的输出格式:
| 客户端类型 | 判定依据 | 输出格式 |
|---|
| 桌面端 / Web(ArkClaw、OpenClaw、浏览器等) | 默认 | HTML 表格(<table>、<img>、<video> 等,仅限 index.json 中 html_output.tags 声明的标签) |
| IM 手机应用(飞书、微信、钉钉、企业微信等) | 用户提到在飞书/微信/钉钉中使用,或上下文/系统信息表明客户端为 IM 应用 | Markdown 表格(` |
桌面端 HTML 模式注意事项:
- 不要将 HTML 放在代码围栏内(围栏内的标签不会被渲染,只会显示源码)
- 不要使用
<input type="checkbox">(静态 HTML 环境中无法交互)
- 不要使用
<button>(无 JS 运行环境,无法绑定事件)
- 所有媒体 URL(
<img src>、<video src>)须通过下文「安全:媒体来源校验」
IM 手机端 Markdown 模式注意事项:
- 使用 Markdown 表格语法(
| col |)
- 缩略图用
 或省略(手机屏幕窄时可不显示缩略图列)
- 视频预览无法内嵌,统一用
[▶ 预览](preview_url) 链接
- 不要输出 HTML 标签(手机 IM 不渲染 HTML,会直接显示标签源码)
通用规则:
- 选择操作通过用户发送文本指令完成(如「选 1,3」「购买」),智能体重新渲染带状态标记的表格
- 注:本文档内的 HTML 模板用代码块展示以防文档渲染器误执行;智能体输出给用户时应去掉代码围栏。
详见下文「搜索结果展示」。
安全:媒体来源校验
本 Skill 的 HTML 输出包含 <img>、<video> 等标签,客户端会直接向 src 地址发起请求。为防止向不可信主机泄露用户 IP / 元数据,智能体应在嵌入前校验媒体 URL 的来源:
- 可信来源列表:config.json 的
trusted_media_origins 数组(默认仅包含 api_origin)。
- 校验规则:
<img src="...">、<video src="...">、<video poster="..."> 中的 URL 必须以 trusted_media_origins 中某一项开头(协议 + 主机 + 端口完全匹配)。
- 不符合时的处理:
- 若
cover_url(breviaryPic)不在可信列表 → 缩略图列显示「—」,不输出 <img>。
- 若
video_url(fragmentUrl)不在可信列表 → 视频预览列不输出 <video>,改为纯文本「[需在浏览器中打开]」+ preview_url 超链接(preview_url 始终为同源站内页面)。
- API 响应中的
breviaryPic 和 fragmentUrl 均为完整绝对 URL(指向 wenzhou.data0086.com:9443),须检查是否在 trusted_media_origins 中。
trusted_media_origins 见 config.json,当前包含 https://www.data0086.com、https://test.data0086.com 和 https://wenzhou.data0086.com:9443。
简言之:只有 config 中声明的可信域名才允许直接嵌入媒体,其余一律用 preview_url 超链接代替。
目标
让智能体用自然语言检索数游神州平台(data0086.com)上的文旅素材资源。每条结果必须结构化保留下游交易要用的字段,尤其是 fragmentUrl(视频预览流)、commodityCode 和 businessCode。
- 用 解析后的搜索 URL 直接
POST 请求,并按本文「输出映射」组装标准 JSON。
流程总览:先调试「预览 → 选择/购买」
当前阶段(交易 API 未定时):智能体优先走通 搜索 → 列表 → 预览 → 选择/购买 → Mock 交易返回 → 视频下载。不在此阶段调用真实交易接口;购买后 mock 交易返回数据(video_url 复用搜索 API 的 fragmentUrl),然后 将视频下载到本地 并输出文件路径。
真实交易 API:TRADE_API_BASE / trade_api_base 配置为非空后将替换 mock 流程(见「交易 API」)。当前为空 → 使用 mock + 下载。
智能体按顺序引导;上一步未就绪时不要进入下一步。用户可随时要求「重新搜索」。
主路径(调试优先)
| 步骤 | 行为要点 |
|---|
| 1. 搜索(两次调用) | 针对同一关键词,智能体依次发起两次 POST {api_origin}/ms-base/home/getList?pageNum=1&pageSize=3 请求,合并结果:<br>① 第 1 次:请求体 commodityCode 填 config.json 的 finished_commodity_code(当前 CommodityType-7bf0aa3057bc)。<br>② 第 2 次:请求体 commodityCode 填 null。<br>合并时按每条记录自身的 commodityCode 字段判定类别:若 item.commodityCode === finished_commodity_code → category = "成片",否则 → category = "素材"。展示时可在标题后加 <small> 徽章区分。pageSize 对每次调用独立生效,默认 3(最多 5);用户要更少时按用户数量传。保留 P0/P1 字段。 |
| 2. 列出结果 | 用表格展示(HTML 或 Markdown,取决于客户端类型),不要把工具/API 返回的整段 JSON 当作对用户的主回复(见下「搜索结果展示」与「展示与 JSON」):列 状态|标题|缩略图|视频预览|详情;首列用颜色徽章标记选中状态(非 checkbox)。表格下方附操作说明(见「选择与购买」)。 |
| 3. 预览 | 引导用户打开 preview_url(数游神州商品详情页 /#/multimodal?businessCode=…,统一处理视频播放);可同时说明 stream_type 与原始 video_url 供技术核对。预览仅用于选片,不等于已购原片。 |
| 4. 选择 / 购买 | 支持两种操作模式,无需额外确认步骤:<br>模式 A — 先选后买:用户先发送选择指令(「选 1、3」「全选」),智能体重新渲染表格标记选中状态;之后用户发送「购买」或「下单」,直接进入步骤 5,不再要求二次确认。<br>模式 B — 直接购买:用户直接发送「购买 1,2」「下单 3」「买第1条」等包含序号的购买指令,智能体一步完成选中并立即进入步骤 5,跳过选择环节。<br>⚠️ 禁止在用户发出购买指令后回复"接口未开放""暂不支持下单"等拒绝性文字——当前阶段购买指令的执行方式就是步骤 5 的 mock + 下载。<br>用户在选择阶段可追加/移除(「加上第2条」「去掉第3条」),智能体每次都重新渲染完整表格。 |
| 5. Mock 交易返回 + 视频下载(必须执行) | 用户发出购买/下单指令后,智能体必须立即执行以下操作,不得跳过或以"接口未就绪"为由拒绝:<br>A. 构造 mock 交易响应:为每条选中素材生成 {"code":0, "resData":{"video_url":"..."}} ,其中 video_url = 该素材搜索结果的 fragmentUrl(预览地址)。<br>B. 下载视频到本地:调用 bash scripts/download_video.sh "<video_url>" "<output_path>" [HLS|MP4] 将视频保存到 downloads/ 目录,文件名为 {business_code}.mp4。<br>C. 输出本地文件路径:下载完成后向用户展示每条素材的 local_path。<br>详见「购买后输出」。 |
搜索结果展示 — 根据客户端类型选择输出格式
输出前应先判断客户端类型,选择对应格式。
桌面端 / Web 客户端(HTML 模式)
客户端(ArkClaw / OpenClaw)支持 HTML 渲染。展示搜索结果时,直接输出 HTML 标签(仅限 index.json 中 html_output.tags 声明的标签)。
注意事项(HTML 模式):
- 不要使用
<input type="checkbox"> 或 <button>(聊天气泡无 JS,无法交互)
- 不要把 HTML 放在代码块里(代码块只会显示源码,不会渲染)
- 所有
<img src> 和 <video src> 的 URL 须属于 trusted_media_origins(见「安全:媒体来源校验」)
正确做法:在回复中直接书写 HTML 标签,不加代码围栏,让客户端渲染。选择状态用颜色徽章表示,用户通过发送文本指令选择/购买。
(本文档内的模板为防止文档自身渲染 <video> 导致页面刷新,使用了代码块包裹——这仅限于文档展示,智能体输出时应去掉代码围栏。)
IM 手机应用(Markdown 模式)
飞书、微信、钉钉等 IM 手机应用不渲染 HTML,会将标签作为纯文本显示。此时应使用 Markdown 表格。
注意事项(Markdown 模式):
- 不要输出 HTML 标签(手机 IM 会直接显示标签源码)
- 不要把表格放在代码围栏内
正确做法:使用 Markdown 表格语法,缩略图用  或省略,预览链接用 [▶ 预览](preview_url)。
表格列定义(固定五列)
表格之前必须输出一行类别汇总,格式示例:
🎬 共找到 **5** 条资源:成片 **2** 条 | 素材 **3** 条
即便成片为 0 也要明示 成片 0 条,以表明已执行两次搜索。
HTML 模式(桌面端 / Web):
| 列名 | HTML 元素 | 内容 |
|---|
| 状态 | <span> 带背景色 | 未选中:灰底序号 <span style="background:#e0e0e0;padding:2px 8px;border-radius:4px;">1</span>;选中:绿底 ✅ <span style="background:#67c23a;color:#fff;padding:2px 8px;border-radius:4px;">✅ 1</span> |
| 标题 | 纯文本 + <small> + 类别徽章 | title + 必标 类别徽章:<br>成片 → <small style="background:#f56c6c;color:#fff;padding:1px 6px;border-radius:3px;margin-left:4px;">成片</small><br>素材 → <small style="background:#909399;color:#fff;padding:1px 6px;border-radius:3px;margin-left:4px;">素材</small><br>可附 <br><small style="color:#999;">id</small> |
| 缩略图 | <img src="..." width="120"> | cover_url;无封面写「—」 |
| 视频预览 | <video controls width="240" poster="..." src="..."> | src 用 video_url(fragmentUrl),poster 用 cover_url |
| 详情 | 纯文本 + <a href="..."> | 分辨率/时长/大小,附 preview_url 超链接兜底 |
Markdown 模式(IM 手机端):
| 列名 | Markdown 写法 | 内容 |
|---|
| 状态 | 纯文本序号或 ✅ | 未选中:1;选中:✅ 1 |
| 标题 | 纯文本 + 类别前缀 | 必标 类别:【成片】title 或 【素材】title(即使列表只有一类也要标) |
| 缩略图 |  或省略 | 手机屏幕窄时可不显示此列 |
| 预览 | [▶ 预览](preview_url) | 链接到站内播放页 |
| 详情 | 纯文本 | 分辨率/时长/大小 |
选择与购买(表格下方操作区)
表格下方直接输出操作提示 HTML。
注意:以下模板在本文档中用代码块展示,防止文档渲染器误执行。智能体输出给用户时应去掉代码围栏,直接输出 HTML。
核心规则:无需二次确认,购买 = mock 交易 + 下载视频
- 「购买 1,2」「下单 3」等带序号的购买指令→ 直接选中 → 立即执行 mock 交易返回 + 视频下载到本地 + 输出文件路径,一步到位
- 「选 1,3」→ 仅标记选中,渲染表格;之后用户说「购买」/「下单」→ 立即执行 mock + 下载
- 不要在用户已发出购买指令后再要求「请确认」或「确认购买」
- 不要回复"接口未开放""暂不支持""请到官网购买"——当前阶段购买就是 mock + 下载
初始状态(无选中)— HTML 模式:
<div style="background:#f6f8fa; border-left:4px solid #409eff; padding:10px 14px; margin:10px 0; border-radius:4px;">
<b>💡 操作说明</b><br>
· 回复「<b>购买 1、3</b>」或「<b>下单 2</b>」直接购买并下载视频到本地<br>
· 回复「选 1、3」先标记,之后回复「<b>购买</b>」一键下载<br>
· 回复「取消 3」取消已选
</div>
初始状态(无选中)— Markdown 模式(IM 手机端):
💡 **操作说明**
· 回复「**购买 1、3**」或「**下单 2**」直接购买并下载视频到本地
· 回复「选 1、3」先标记,之后回复「**购买**」一键下载
· 回复「取消 3」取消已选
有选中时 — HTML 模式:
<div style="background:#f0f9eb; border-left:4px solid #67c23a; padding:10px 14px; margin:10px 0; border-radius:4px;">
✅ 已选 <b>N</b> 条素材 | 总时长 XXs | 总大小 <b>XXXM</b><br><br>
👉 回复「<b>购买</b>」或「<b>下单</b>」立即购买并下载到本地<br>
👉 回复序号可继续追加或取消选择
</div>
有选中时 — Markdown 模式(IM 手机端):
✅ 已选 **N** 条素材 | 总时长 XXs | 总大小 **XXXM**
👉 回复「**购买**」或「**下单**」立即购买并下载到本地
👉 回复序号可继续追加或取消选择
当用户发送选择指令后,智能体重新渲染完整表格:
- 选中行首列显示绿色 ✅ 徽章,行背景加
style="background:#f0f9eb;"
- 未选行首列显示灰色序号徽章
- 表格下方显示选中汇总 + 购买提示
展示与 JSON(必读)
- 对用户:搜索列表的默认形态是表格(桌面端用 HTML 表格,IM 手机端用 Markdown 表格)。禁止用 JSON 代码块替代。
- 对程序/联调:若用户明确说「给我原始 JSON」「导出结构」或已进入购买阶段,再按「标准结果 JSON 形状」或「购买后输出」给出 JSON。
- 条数:URL 参数
pageSize 默认传 5(上限),用户要求更少时按用户数量传;若接口返回超出请求数量,只取前 N 条映射为表格。
完整输出模板(直接复制替换真实值)
智能体输出搜索结果时,按照以下模板逐行输出 HTML,将 {变量} 替换为真实值。
注意:以下模板在本文档中用代码块展示,防止文档渲染器误加载 <video>/<img> 导致页面刷新。智能体输出给用户时应去掉代码围栏,直接输出 HTML 标签。
初始状态(全部未选中)— HTML 模式:
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse; width:100%;">
<thead style="background:#f5f7fa;">
<tr><th>状态</th><th>标题</th><th>缩略图</th><th>视频预览</th><th>详情</th></tr>
</thead>
<tbody>
<tr>
<td><span style="background:#e0e0e0;padding:2px 8px;border-radius:4px;">1</span></td>
<td>{title}<br><small style="color:#999;">{id}</small></td>
<td><img src="{cover_url}" width="120"></td>
<td><video controls width="240" poster="{cover_url}" src="{video_url}"></video></td>
<td>{resolution}<br>{duration_seconds}s / {file_size}<br><a href="{preview_url}">浏览器播放</a></td>
</tr>
<tr>
<td><span style="background:#e0e0e0;padding:2px 8px;border-radius:4px;">2</span></td>
<td>{title}<br><small style="color:#999;">{id}</small></td>
<td><img src="{cover_url}" width="120"></td>
<td><video controls width="240" poster="{cover_url}" src="{video_url}"></video></td>
<td>{resolution}<br>{duration_seconds}s / {file_size}<br><a href="{preview_url}">浏览器播放</a></td>
</tr>
</tbody>
</table>
<div style="background:#f6f8fa; border-left:4px solid #409eff; padding:10px 14px; margin:10px 0; border-radius:4px;">
<b>💡 操作说明</b><br>
· 回复「<b>购买 1、3</b>」或「<b>下单 2</b>」直接购买指定素材<br>
· 回复「选 1、3」先标记,之后回复「<b>购买</b>」一键下单
</div>
初始状态(全部未选中)— Markdown 模式(IM 手机端):
| 状态 | 标题 | 预览 | 详情 |
|------|------|------|------|
| 1 | {title} | [▶ 预览]({preview_url}) | {resolution} / {duration_seconds}s / {file_size} |
| 2 | {title} | [▶ 预览]({preview_url}) | {resolution} / {duration_seconds}s / {file_size} |
💡 **操作说明**
· 回复「**购买 1、3**」或「**下单 2**」直接购买指定素材
· 回复「选 1、3」先标记,之后回复「**购买**」一键下单
选中第 1、2 条后(用户发送「选 1,2」后智能体重新渲染)— HTML 模式:
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse; width:100%;">
<thead style="background:#f5f7fa;">
<tr><th>状态</th><th>标题</th><th>缩略图</th><th>视频预览</th><th>详情</th></tr>
</thead>
<tbody>
<tr style="background:#f0f9eb;">
<td><span style="background:#67c23a;color:#fff;padding:2px 8px;border-radius:4px;">✅ 1</span></td>
<td>{title}</td>
<td><img src="{cover_url}" width="120"></td>
<td><video controls width="240" src="{video_url}"></video></td>
<td>{resolution}<br>{duration_seconds}s / {file_size}</td>
</tr>
<tr style="background:#f0f9eb;">
<td><span style="background:#67c23a;color:#fff;padding:2px 8px;border-radius:4px;">✅ 2</span></td>
<td>{title}</td>
<td><img src="{cover_url}" width="120"></td>
<td><video controls width="240" src="{video_url}"></video></td>
<td>{resolution}<br>{duration_seconds}s / {file_size}</td>
</tr>
</tbody>
</table>
<div style="background:#f0f9eb; border-left:4px solid #67c23a; padding:10px 14px; margin:10px 0; border-radius:4px;">
✅ 已选 <b>2</b> 条素材 | 总时长 236.6s | 总大小 <b>273.9M</b><br><br>
👉 回复「<b>购买</b>」或「<b>下单</b>」立即下单<br>
👉 回复序号可追加或取消选择
</div>
选中第 1、2 条后 — Markdown 模式(IM 手机端):
| 状态 | 标题 | 预览 | 详情 |
|------|------|------|------|
| ✅ 1 | {title} | [▶ 预览]({preview_url}) | {resolution} / {duration_seconds}s / {file_size} |
| ✅ 2 | {title} | [▶ 预览]({preview_url}) | {resolution} / {duration_seconds}s / {file_size} |
✅ 已选 **2** 条素材 | 总时长 236.6s | 总大小 **273.9M**
👉 回复「**购买**」或「**下单**」立即下单
👉 回复序号可追加或取消选择
其中:
{preview_url} = {api_origin}/#/multimodal?businessCode={businessCode}(数游神州商品详情页)。
{cover_url} = breviaryPic(已是完整 URL),{video_url} = fragmentUrl。嵌入前须校验来源属于 trusted_media_origins(见「安全:媒体来源校验」),不符合则不输出 <img> / <video>。
❌ 错误输出 vs ✅ 正确输出
错误(HTML 模式下使用 checkbox / 代码围栏):
| □ | # | 标题 | 缩略图 | 预览 |
|---|---|------|--------|------|
| □ | 1 | 雁荡山素材 | [封面](url) | [预览 · HLS](url) |
或使用 <input type="checkbox">(渲染出来但无法勾选)、<button>(无法点击触发)。
错误(IM 手机端输出 HTML 标签):
在飞书/微信中输出 <table><tr><td>... — 用户看到的是一堆 HTML 源码文本。
✅ 正确 — HTML 模式(桌面端 / Web,无代码围栏):
智能体回复中应直接包含如下 HTML(不被 ``` 包裹):
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse; width:100%;">
<tr>
<td><span style="background:#e0e0e0;padding:2px 8px;border-radius:4px;">1</span></td>
<td>雁荡山素材</td>
<td><img src="https://…/cover.jpg" width="120"></td>
<td><video controls width="240" src="https://…/index.m3u8"></video></td>
<td>2560×1440<br>118.5s / 98.3M</td>
</tr>
</table>
✅ 正确 — Markdown 模式(IM 手机端):
| 状态 | 标题 | 预览 | 详情 |
|---|
| 1 | 雁荡山素材 | ▶ 预览 | 240.9s / ¥45 |
<video> 标签用法
src 必须用 video_url(即 fragmentUrl,实际媒体流),不是 preview_url(商品详情页)。
- 来源校验:嵌入前须确认
video_url 的来源在 trusted_media_origins 中(见「安全:媒体来源校验」)。不在列表中 → 不输出 <video>,改用 preview_url 超链接。
poster 用 cover_url(封面图)。无封面时省略 poster。同样须通过来源校验。
- MP4:浏览器原生支持。
- HLS(m3u8):Safari 原生支持;Chrome 依赖客户端 hls.js。若
<video> 无法播放,详情列的 preview_url 链接兜底。
预览与本地播放(能力边界)
SKILL 无法在对话气泡内嵌入可执行的 HLS 播放器(多数客户端会拦截脚本、iframe,且 OpenClaw / 飞书等需产品侧组件才能内嵌播放)。本 Skill 通过下列方式支持用户在本机观看预览:
-
浏览器打开 preview_url(推荐)
- 数游神州商品详情页已统一处理视频播放,用户直接点击表格中的「预览」链接即可。
- 若需在系统播放器中打开原始流,再使用
video_url + stream_type(见下文)。
-
仅当无可用 HTML 播放页、且必须直接拉流时:再考虑 video_url
stream_type 为 MP4:多数浏览器可直接打开直链。
stream_type 为 HLS:裸 m3u8 在 Chrome 常下载或无法播放;Safari 相对友好。
- 智能体应优先推
preview_url,避免向非技术用户丢裸 m3u8 链。
-
本仓库脚本(本地用浏览器打开某 URL)
- 路径:scripts/open_preview.sh(相对本 Skill 根目录)。
- 用法:优先
bash scripts/open_preview.sh "<preview_url>" MP4(播放页为 HTML 时按 HTTP 打开即可);若需直接拉流,再传入 video_url 与 HLS/MP4。
- 适用于 Cursor / 终端;飞书/OpenClaw 仍以点击
preview_url 为主。
-
可选:本机播放器(原始流)
- VLC / ffplay 可粘贴
video_url;日常选片以 preview_url 为主。
飞书 / OpenClaw:卡片按钮 URL 与表格一致,指向 preview_url(即 {api_origin}/#/multimodal?businessCode={businessCode});若产品另有独立 H5 域名,以产品为准,但须能播同一条素材。
购买后输出(调试 / 联调)⚠️ 必须执行
强制规则:用户发出「购买」「下单」等购买指令后,智能体必须立即执行下述步骤 A + B。禁止回复"交易接口未开放""暂不支持""请到官网购买"等拒绝性、引导性文字。当前阶段的"购买"就是 mock + 下载。
触发条件:仅当用户显式发送「购买」「下单」「买第 N 条」等购买指令时触发,搜索、浏览、预览阶段不写入任何本地文件。
在未接真实交易 API 时,用户发出购买指令后智能体执行以下两步:
步骤 A:构造 Mock 交易响应
为每条选中素材构造 mock 交易返回数据。核心字段 video_url 直接复用搜索 API 返回的 fragmentUrl(预览地址),模拟交易成功后服务端返回的视频下载链接。
单条 mock 交易响应示例:
{
"code": 0,
"msg": "mock: 交易成功(模拟)",
"resData": {
"video_url": "https://wenzhou.data0086.com:9443/res/hls/preview/product/77758f136a4648888d1acd615ec24dbe"
}
}
说明:video_url 的值 = 搜索结果中该素材的 fragmentUrl。当交易 API 真正上线后,此字段将由服务端返回真实的原片/授权下载地址。
批量购买时,智能体为每条素材分别构造一个 mock 响应,汇总后输出完整的购买结果 JSON:
{
"stage": "purchase_mock_completed",
"items": [
{
"id": 459,
"title": "雁荡山-飞拉达雾天-一镜到底-无人机航拍",
"commodity_code": "CommodityType-e744a1044794",
"business_code": "Commodity-20260406211854879",
"video_url": "https://wenzhou.data0086.com:9443/res/hls/preview/product/77758f136a4648888d1acd615ec24dbe",
"stream_type": "HLS",
"duration_seconds": "240.9",
"price": 45.0,
"local_path": ""
},
{
"id": 461,
"title": "雁荡山-飞拉达-云雾多视频2-无人机航拍",
"commodity_code": "CommodityType-948a39e64144",
"business_code": "Commodity-20260406212609534",
"video_url": "https://wenzhou.data0086.com:9443/res/hls/preview/product/b68efe02182c436dbcf35f045b94ed30",
"stream_type": "HLS",
"duration_seconds": "30.5",
"price": 45.0,
"local_path": ""
}
],
"summary": {
"total_items": 2,
"total_duration_seconds": "271.4",
"total_price": 90.0
}
}
local_path 在步骤 B 下载完成后填入。
步骤 B:视频下载到本地
获得 mock 交易响应中的 video_url 后,智能体 调用下载脚本将视频保存到本地。
- 下载目录:
downloads/(位于 Skill 根目录下;不存在时自动创建)。也可通过 config.json 的 download_dir 配置。
- 文件命名:
{business_code}.{ext}(如 Commodity-20260406211854879.mp4)。扩展名由 stream_type 决定:MP4 → .mp4;HLS → .mp4(ffmpeg 转封装后输出)。
- 下载命令:调用
bash scripts/download_video.sh "<video_url>" "<output_path>" [HLS|MP4]。
- MP4:直接用
curl 下载。
- HLS:用
ffmpeg 下载 m3u8 并转封装为 MP4(需本机安装 ffmpeg)。
- 输出:下载完成后,将
local_path 填入结果 JSON 并向用户输出文件路径。
下载完成后的最终输出示例:
{
"stage": "download_completed",
"items": [
{
"id": 459,
"title": "雁荡山-飞拉达雾天-一镜到底-无人机航拍",
"commodity_code": "CommodityType-e744a1044794",
"business_code": "Commodity-20260406211854879",
"video_url": "https://wenzhou.data0086.com:9443/res/hls/preview/product/77758f136a4648888d1acd615ec24dbe",
"stream_type": "HLS",
"local_path": "downloads/Commodity-20260406211854879.mp4",
"duration_seconds": "240.9",
"price": 45.0
},
{
"id": 461,
"title": "雁荡山-飞拉达-云雾多视频2-无人机航拍",
"commodity_code": "CommodityType-948a39e64144",
"business_code": "Commodity-20260406212609534",
"video_url": "https://wenzhou.data0086.com:9443/res/hls/preview/product/b68efe02182c436dbcf35f045b94ed30",
"stream_type": "HLS",
"local_path": "downloads/Commodity-20260406212609534.mp4",
"duration_seconds": "30.5",
"price": 45.0
}
],
"summary": {
"total_items": 2,
"total_duration_seconds": "271.4",
"total_price": 90.0,
"downloaded": 2,
"failed": 0
}
}
向用户展示时,在 JSON 之前/之后用自然语言说明:
已完成 2 条素材的模拟购买与下载:
- 雁荡山-飞拉达雾天-一镜到底-无人机航拍 →
downloads/Commodity-20260406211854879.mp4
- 雁荡山-飞拉达-云雾多视频2-无人机航拍 →
downloads/Commodity-20260406212609534.mp4
错误处理:若某条素材下载失败(网络错误、ffmpeg 未安装等),local_path 设为空字符串 "",summary.failed 计数 +1,并向用户说明失败原因与重试建议。
单条选择同样使用此结构(items 数组长度为 1),保持输出格式统一。
交易 API(预留,未来替换 mock)
以下仅在 TRADE_API_BASE 或 trade_api_base 已配置为非空值 且产品已约定路径/鉴权后执行。
- 基址:环境变量
TRADE_API_BASE 优先;否则 config.json 的 trade_api_base 非空字符串。
- 调用:用已锁定资源的
id、commodityCode、businessCode 按产品约定发起请求;鉴权勿泄露到对话。
- 接入后替换 mock:交易 API 上线后,步骤 A 的 mock 响应将被真实接口响应替换;
video_url 以服务端返回为准。步骤 B 的下载逻辑保持不变。
当前状态:trade_api_base 为空 → 不调用真实交易 API,但必须执行 mock + 下载流程(见上文「购买后输出」)。绝不能因为交易 API 未就绪就拒绝用户的购买指令。
API 基址配置(可更换)
单一默认值:与本 Skill 同目录的 config.json(api_origin + search_path)。
解析规则:
- 若存在环境变量
WENLV_API_ORIGIN(仅站点根,如 https://test.data0086.com),则以其为 api_origin。
- 否则使用
config.json 中的 api_origin。
search_url = api_origin(去掉末尾 /)+ search_path(默认 /ms-base/home/getList)+ ?pageNum={pageNum}&pageSize={pageSize}。
| 项目 | 说明 |
|---|
| 基址 | {api_origin},当前值见 config.json(https://test.data0086.com) |
| 搜索 | POST {api_origin}/ms-base/home/getList?pageNum=1&pageSize=5 |
| 鉴权 | 无(公开接口,token 头可为空) |
| Content-Type | application/json |
换环境时:改 config.json 或部署侧设置 WENLV_API_ORIGIN 即可,无需改 Skill 正文中的长 URL。交易相关另设 TRADE_API_BASE 或 config.json 的 trade_api_base(非空时生效)。
详情页地址(用户点击「预览」)⚠️ 拼接规则
统一使用数游神州前端的商品详情页(与原始 fragmentUrl 区分)。
拼接公式(唯一正确形式):
preview_url = {api_origin} + {detail_path} + "?businessCode=" + {businessCode}
api_origin:WENLV_API_ORIGIN 或 config.json 的 api_origin(当前 https://test.data0086.com,无末尾 /)。
detail_path:默认 /#/multimodal(见 config.json 的 detail_path)。
businessCode:搜索结果中每条素材的 businessCode 字段。
✅ 正确格式:
https://test.data0086.com/#/multimodal?businessCode=Commodity-20260406211854879
❌ 错误格式 —— 不要用 fragmentUrl 当预览链接:
https://wenzhou.data0086.com:9443/res/hls/preview/product/77758f136a4648888d1acd615ec24dbe
表格「预览」列、飞书卡片按钮、OpenClaw 内链均应使用 preview_url(商品详情页)。video_url(fragmentUrl)仍须保留在结构化结果中,供交易、调试与核对原始流。
播放页与裸流(必读:避免一点就下载 m3u8)
video_url(fragmentUrl) 指向的是 媒体流(常见为 HLS 预览地址),不是给用户点的「网页预览」。在 Markdown 里若写成 [预览](video_url),浏览器对 HLS 往往会 下载 m3u8 或无法内联播放。禁止将 video_url 作为表格「预览」列或卡片按钮的主链接。
preview_url 对应数游神州的商品详情页(SPA 页面,内含视频播放器),用户点击后在浏览器页面里播放,而不是下载清单文件。
- 映射:
preview_url 一律只由 {api_origin}{detail_path}?businessCode={businessCode} 按上文公式拼接(见 config.json),不得把 fragmentUrl 填进 preview_url,也不得用 video_url 顶替。
请求 URL 参数
分页通过 URL query 传递:
| 参数 | 必填 | 说明 |
|---|
pageNum | 否 | 页码,默认 1 |
pageSize | 否 | 每页条数,接口默认 18;智能体侧默认传 5(上限),用户要求更少时按用户数量传(如 1、2、3) |
请求体
{
"commodityCode": null,
"sceneType": "",
"tradeType": "",
"search": "雁荡山飞拉达",
"city": "330300"
}
| 字段 | 必填 | 说明 |
|---|
search | 是 | 关键词,支持中文自然语言 |
city | 否 | 城市行政区划代码,默认 "330300"(温州) |
commodityCode | 否 | 按商品编码精确筛选。本 Skill 通过此字段区分「成片 / 素材」两类资源(见下) |
sceneType | 否 | 场景类型筛选,如 "慢直播",默认 "" |
tradeType | 否 | 交易类型筛选,如 "cash",默认 "" |
两次搜索:成片 + 素材
同一关键词下,本 Skill 依次发起两次搜索,并按"成片优先 50:50"配比分配条数:
pageSize 分配(用户想要总数 N,默认 3,上限 5):
| 顺序 | 请求 commodityCode | URL pageSize |
|---|
| 第 1 次(成片) | config.json 的 finished_commodity_code(当前 CommodityType-7bf0aa3057bc) | ceil(N / 2) |
| 第 2 次(素材) | null | floor(N / 2)(为 0 则跳过) |
配比参考表(N=1~5):
兜底补齐:某一类返回不足时(如成片只返回 1 条而期望 2 条),用另一类补齐,总条数尽量达到 N。
类别判定规则(核心):
以返回的每条记录自身的 commodityCode 字段为准,而不是按哪一次调用:
if item.commodityCode === finished_commodity_code:
item.category = "成片"
else:
item.category = "素材"
这样即便两次调用返回数据有重复或交叉,最终每条记录的类别依然由其自身 commodityCode 决定,去重与标记逻辑一致。
合并策略:
- 两次调用按配比表分配
pageSize(不是各自独立的 N)。
- 合并两次返回的
resData.datas,按 businessCode 或 id 去重(避免两次调用返回同一条记录时重复展示)。
- 按上述判定规则为每条记录打
category 标签。
- 排序:成片在前、素材在后;同类内部保留接口返回顺序。
- 若某一类无数据,用另一类兜底补齐到
N 条;两类都无数据时,向用户说明"未找到相关资源"。
展示要求(强制):
- 汇总行(必须,统计口径以
category 字段为准):
- 格式示例:
📽 共找到 5 条资源:**成片 3 条** | **素材 2 条**
- 即使某类为 0 也要写出(如
📽 共找到 3 条资源:**成片 0 条** | **素材 3 条**),让用户知道两次搜索确实都执行过。
- ⚠️ 统计算法:
成片条数 = 表格里 category == "成片" 的行数、素材条数 = 表格里 category == "素材" 的行数;不要按"请求参数里 commodityCode 是什么"来算,以免出现"请求成片 3 条但返回的有 1 条实际是素材"时汇总错位。
- 自检:汇总行的 X 和 Y 必须等于表格里实际打
【成片】/【素材】 徽章的行数,加起来 = 总条数。
- 类别徽章(必须,每行都标):
- HTML 模式:标题旁追加
<small style="background:#f56c6c;color:#fff;padding:1px 6px;border-radius:3px;margin-left:4px;">成片</small> 或 <small style="background:#909399;color:#fff;padding:1px 6px;border-radius:3px;margin-left:4px;">素材</small>
- Markdown 模式:标题前加
【成片】 或 【素材】 文本前缀
- 不允许只在混合类型时才标注;即使全部是同一类别也要每行标出。
finished_commodity_code 可配置:
- 修改 config.json 的
finished_commodity_code 即可调整成片归类的商品编码,无需改 Skill 正文。
- 值为空字符串或未配置时,跳过第 1 次搜索,仅按「素材」单次搜索——此时所有返回记录都标记为
category: "素材"。
响应约定
- 业务成功:
code === 0,列表在 resData.datas,总数 resData.total。
- HTTP 非 2xx 或
code !== 0:向用户说明错误,勿伪造结果。
更完整的原始字段说明见 references/api_reference.md。标准接口规范(含交易下单)见 api.md。
字段优先级
P0 — 核心(交易直连,缺一不可)
| 来源字段 | 输出键名 | 说明 |
|---|
id | id | 素材唯一 ID(数字) |
commodityName | title | 标题 |
fragmentUrl | video_url | 原始预览流(HLS),交易/核对用,表格「预览」列不用作主链 |
commodityCode | commodity_code | 商品编码,交易下单必需 |
businessCode | business_code | 业务编码,交易下单必需,也用于详情页 URL |
派生(必选,用于展示与点击)
| 派生规则 | 输出键名 | 说明 |
|---|
{api_origin}{detail_path}?businessCode={businessCode} | preview_url | 表格「预览」列、卡片按钮统一用此链接(商品详情页);detail_path 默认 /#/multimodal,见 config.json |
同时必须输出 stream_type(由 video_url / fragmentUrl 判定):
- URL 含路径片段
/hls/ → "HLS"(需 HLS 播放器,如 hls.js)
- URL 以
.mp4 结尾 → "MP4"(浏览器可直接播)
- 若
fragmentUrl 为空:不要编造 video_url;可省略 stream_type 或标为无法判定(勿写假链接)
P1 — 重要(展示与交易辅助)
| 来源 | 输出键名 |
|---|
explain | description(HTML 格式,包含清晰度/格式等信息) |
fragmentTime | duration_seconds(建议保留一位小数,字符串或数字均可) |
price | price(元) |
breviaryPic | cover_url:已是完整 URL,嵌入前须属于 trusted_media_origins |
sceneType | scene_type(逗号分隔的场景类型) |
tag(JSON.parse) | tags:逗号分隔或数组 |
createUser | merchant(商家名称) |
P2 — 辅助(可选)
videoProductCode、contactName、contactPhone、merchantBusinessCode、priceJson、location 等,在用户需要排查或对接数据源时再输出。
标准结果 JSON 形状(解析/转发用)
智能体整理输出时,每条结果建议符合:
{
"total": 185,
"pageNum": 1,
"pageSize": 5,
"results": [
{
"#": "1",
"id": 459,
"title": "雁荡山-飞拉达雾天-一镜到底-无人机航拍",
"commodity_code": "CommodityType-e744a1044794",
"business_code": "Commodity-20260406211854879",
"preview_url": "https://test.data0086.com/#/multimodal?businessCode=Commodity-20260406211854879",
"video_url": "https://wenzhou.data0086.com:9443/res/hls/preview/product/77758f136a4648888d1acd615ec24dbe",
"stream_type": "HLS",
"duration_seconds": "240.9",
"price": 45.0,
"cover_url": "https://wenzhou.data0086.com:9443/res/covers/77758f136a4648888d1acd615ec24dbe.jpg",
"scene_type": "慢直播,创作素材,无人机航拍",
"tags": "视频创作, 无人机航拍",
"merchant": "温州鼎诚体育发展有限公司"
}
]
}
curl 示例
默认站点根须与 config.json 中 api_origin 保持一致(换域名时改 config 并同步下面默认值,或只用 export)。
# 环境变量优先;未设置时读取 config.json 的 api_origin(当前为 https://test.data0086.com)
ORIGIN="${WENLV_API_ORIGIN:-https://test.data0086.com}"
FINISHED_CODE="CommodityType-7bf0aa3057bc" # config.json finished_commodity_code
# 第 1 次:成片
curl -sS "${ORIGIN}/ms-base/home/getList?pageNum=1&pageSize=5" \
-H 'Content-Type: application/json' \
-H "Origin: ${ORIGIN}" \
-H "Referer: ${ORIGIN}/" \
-H 'token;' \
--data-raw "{\"commodityCode\":\"${FINISHED_CODE}\",\"sceneType\":\"\",\"tradeType\":\"\",\"search\":\"雁荡山\",\"city\":\"330300\"}"
# 第 2 次:素材
curl -sS "${ORIGIN}/ms-base/home/getList?pageNum=1&pageSize=5" \
-H 'Content-Type: application/json' \
-H "Origin: ${ORIGIN}" \
-H "Referer: ${ORIGIN}/" \
-H 'token;' \
--data-raw '{"commodityCode":null,"sceneType":"","tradeType":"","search":"雁荡山","city":"330300"}'
合并两次返回的 resData.datas(按 businessCode/id 去重),然后对每条记录按其自身的 commodityCode 判定:commodityCode === finished_commodity_code → category = "成片",否则 → category = "素材"。最终输出 results 数组(成片在前、素材在后)。
智能体行为要点
- 须同时保留
preview_url(详情列超链接兜底)与 video_url(<video> 播放源);列表展示须用五列表格:状态(颜色徽章)|标题|缩略图|视频预览|详情(见「搜索结果展示」)。桌面端用 HTML 表格,IM 手机端用 Markdown 表格。不要使用 <input>、<button> 等需要 JS 交互的元素。
- 面向用户的列表:桌面端以 HTML 表格 为主(含颜色徽章状态、
<img>、<video> 元素),IM 手机端以 Markdown 表格 为主;结构化字段在表格列中体现。不要用整段 JSON 代替表格。仅在用户索要导出、或购买后的约定 JSON 块中再输出 JSON。
- 分页:默认只拉第 1 页且
pageSize 默认 5(上限),用户要求更少时按用户数量传;用户要「更多」时递增 pageNum,注意 total 边界。跨页选择:翻页时保留已选中条目;若用户在第 1 页选了第 1、3 条,翻到第 2 页后再选第 2 条,最终选中列表包含累计选中的所有条目。
- 两次搜索 + 成片优先配比(每次搜索都必须执行,无例外):不管是用户第 1 次搜索还是第 N 次搜索、同一关键词还是换关键词,智能体对每一次搜索都顺序发起两次 POST,按 50:50 成片优先 配比分配
pageSize:
- 成片:
commodityCode = config.finished_commodity_code,pageSize = ceil(N / 2)
- 素材:
commodityCode = null,pageSize = floor(N / 2)
- 示例:N=3 → 成片 2 条 + 素材 1 条;N=5 → 成片 3 条 + 素材 2 条
- 禁止因为"已经搜过""连续搜索""结果相似"等理由跳过成片那次调用
- 合并两次返回的
datas(按 businessCode/id 去重),按每条记录自身的 commodityCode 判定 category;某类不足时用另一类兜底补齐到 N 条
- 展示时强制输出:(a) 汇总行
📽 共找到 N 条资源:成片 X 条 | 素材 Y 条,X/Y 必须按 category 字段统计而非请求参数,即便 X=0 也要写;(b) 每行标题带 【成片】/【素材】 徽章
finished_commodity_code 为空时才可跳过第一次调用,汇总行写明 未配置 finished_commodity_code,仅搜素材
- 详见「两次搜索:成片 + 素材」
- 预览与成单分离:用户点击的预览必须是
preview_url(商品详情页),不要把 video_url(HLS 流) 做成主链接以免下载或无法播放。预览仅用于选片。当前调试阶段:用户发出购买指令后,智能体必须执行 mock 交易 + 视频下载(步骤 5),不得以"接口未就绪"为由拒绝或引导用户去官网手动购买。
- 交易安全(启用交易 API 后):不在对话中输出完整密钥;失败时返回接口错误摘要,不猜测成功。
- 多选与购买(无需二次确认):支持两种下单方式:① 直接购买——用户发送「购买 1,3」「下单 2」等带序号的购买指令,智能体一步完成选中 + mock 交易 + 视频下载;② 先选后买——用户先「选 1,3」标记,再发送「购买」/「下单」直接执行 mock 交易 + 下载,不再要求二次确认。选择过程中用户可随时追加(「加上第2条」)或移除(「取消 3」),智能体每次都重新渲染完整表格,用绿色/灰色徽章标记选中状态。
- Mock 交易与视频下载:购买确认后,智能体构造 mock 交易响应(
video_url = 搜索结果的 fragmentUrl),然后调用 scripts/download_video.sh 将视频下载到 downloads/ 目录(HLS 用 ffmpeg 转 MP4,MP4 用 curl 直接下载),最终向用户输出每条素材的本地文件路径。详见「购买后输出」。
- 交互约束:聊天气泡是静态 HTML(无 JavaScript),禁止使用
<input>、<button>、<form> 等需要 JS 交互的元素。所有用户操作通过发送文本消息完成,智能体解析后重新渲染表格。
- 客户端适配:智能体应根据客户端类型选择输出格式(HTML 或 Markdown)。判定方式见文首「输出格式规范」。若无法确定客户端类型,默认使用 HTML 模式;当用户反馈看到 HTML 源码时,切换为 Markdown 模式。
具体包名与发布流程以实际 npm 包为准;本 Skill 约束 行为、配置解析顺序、API 路径与输出字段。