Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 90c009f1d8 |
@@ -32,7 +32,7 @@
|
||||
|
||||
Demo 只做科普对比,不做现场代码改造。
|
||||
|
||||
建议任务设定为:在一个电商页面里完成“搜索商品、加入购物车、进入结算”这一类常见流程。
|
||||
建议任务设定为:在一个后台系统里完成“创建订单管理模块”,包括创建词条、创建菜单、绑定角色权限。
|
||||
|
||||
对比讲法:
|
||||
|
||||
@@ -44,7 +44,7 @@ Demo 只做科普对比,不做现场代码改造。
|
||||
|
||||
### 4. 核心概念:WebMCP 是什么
|
||||
|
||||
WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口描述”。
|
||||
WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具说明书”。
|
||||
|
||||
一个工具通常包含:
|
||||
|
||||
@@ -54,11 +54,29 @@ WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口
|
||||
- 执行逻辑:调用后网页做什么
|
||||
- 返回结果:告诉 Agent 成功还是失败
|
||||
|
||||
可以用 `navigator.modelContext.registerTool` 作为概念示例。需要强调:WebMCP 仍是实验性方向,适合作为渐进增强,不应该假设所有浏览器都已经稳定支持。
|
||||
可以用 `document.modelContext.registerTool` 作为概念示例。需要强调:WebMCP 仍是实验性方向,适合作为渐进增强,不应该假设所有浏览器都已经稳定支持。
|
||||
|
||||
### 5. Chrome 新特性演示
|
||||
### 5. 核心概念:任务流程说明
|
||||
|
||||
这一部分分成正式演示和快闪:
|
||||
WebMCP 解决“能调用什么工具”。复杂任务还需要一层任务流程说明,解决“应该按什么步骤走”。
|
||||
|
||||
这里不要把这层流程能力包装成官方浏览器标准或 WebMCP 同级概念。更稳妥的表述是:在应用实践中,可以把复杂任务沉淀成 skill、recipe、runbook 或任务说明,让 Agent 先理解流程,再按步骤调用页面暴露的工具。
|
||||
|
||||
例如用户说“帮我申请价保”,这不是一个单工具任务。它可能需要:
|
||||
|
||||
1. 去订单页查订单
|
||||
2. 拿到订单号
|
||||
3. 跳到价保页
|
||||
4. 填申请单
|
||||
5. 最后让用户确认提交
|
||||
|
||||
任务流程说明的角色就是告诉 Agent 这些流程规则。可以用一句话总结:
|
||||
|
||||
> WebMCP 是工具箱,任务流程说明是操作手册。
|
||||
|
||||
### 6. Chrome 新特性快闪
|
||||
|
||||
这一部分只做快速展示,不展开细节:
|
||||
|
||||
- **Modern Web Guidance**:给 AI 编程工具补现代 Web 知识,避免写过时代码。
|
||||
- **Chrome DevTools for agents**:让 AI 能读取 console、network、performance、Lighthouse 等真实浏览器信息。
|
||||
@@ -69,15 +87,7 @@ WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口
|
||||
|
||||
这一章服务于主线:Chrome 正在让 Web 同时面向人、开发者工具和 AI Agent 进化。
|
||||
|
||||
正式 demo 入口:
|
||||
|
||||
- `public/demo/webmcp/`:WebMCP 工具契约演示。
|
||||
- `scripts/demo-modern-web-guidance.sh` 和 `public/demo/modern-web-guidance/`:Modern Web Guidance 官方 CLI 演示。
|
||||
- `public/demo/devtools-agents/`:DevTools for agents 检查目标页。
|
||||
- `public/html-in-canvas/`:HTML-in-Canvas origin trial / fallback 演示。
|
||||
- `public/demo/view-transitions/`:View Transitions 渐进增强演示。
|
||||
|
||||
### 6. 收束:开发者现在该记住什么
|
||||
### 7. 收束:开发者现在该记住什么
|
||||
|
||||
- Web 页面未来不只是给人看的,也会被 Agent 使用。
|
||||
- 语义化、可访问性、清晰的业务函数边界会变得更重要。
|
||||
@@ -96,9 +106,6 @@ WebMCP 可以理解为“网页给 AI Agent 暴露的一组工具清单和接口
|
||||
- [slides.md](./slides.md):默认首页、章节入口和全局顺序
|
||||
- [pages/outline.md](./pages/outline.md):分享大纲
|
||||
- [pages/demo/](./pages/demo/):模拟展示详情页
|
||||
- [public/demo/](./public/demo/):可运行浏览器 demo 入口
|
||||
- [public/html-in-canvas/](./public/html-in-canvas/):HTML-in-Canvas 专门演示
|
||||
- [scripts/demo-modern-web-guidance.sh](./scripts/demo-modern-web-guidance.sh):Modern Web Guidance 官方 CLI 演示脚本
|
||||
- [style.css](./style.css):主题微调
|
||||
|
||||
## 常用脚本
|
||||
|
||||
+1
-5
@@ -5,8 +5,7 @@
|
||||
"scripts": {
|
||||
"build": "slidev build",
|
||||
"dev": "slidev --open",
|
||||
"export": "slidev export",
|
||||
"export:pptx": "slidev export --format pptx --output slides-export.pptx"
|
||||
"export": "slidev export"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/logos": "^1.2.11",
|
||||
@@ -16,8 +15,5 @@
|
||||
"slidev-theme-penguin": "^2.3.1",
|
||||
"vite-svg-loader": "^5.1.1",
|
||||
"vue": "^3.5.33"
|
||||
},
|
||||
"devDependencies": {
|
||||
"playwright-chromium": "^1.60.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# 现在怎么落地:先做 Agent 友好,不赌单一 API
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">现在就值得做</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 补语义化 HTML 与可访问性
|
||||
- 梳理核心业务动作边界
|
||||
- 把复杂流程沉淀成任务说明
|
||||
- 在内部系统试验 WebMCP / SDK
|
||||
- 给高风险动作加确认和审计
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-red">
|
||||
<div class="font-semibold mb-3">暂时不要这样做</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 把实验 API 当生产强依赖
|
||||
- 假设 Safari / Firefox 已支持
|
||||
- 让 Agent 自动执行不可逆操作
|
||||
- 用工具调用绕过权限系统
|
||||
- 忽略降级和人工接管路径
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页讲落地策略。
|
||||
|
||||
因为 WebMCP 和部分内置 AI 能力仍然在实验阶段,所以我们不能把这场分享讲成“明天所有项目都要接这个 API”。这样讲既不准确,也不负责任。更稳妥的落地策略是:先做 Agent 友好,不赌单一 API。
|
||||
|
||||
现在就值得做的事情有几类。第一,补语义化 HTML 和可访问性,因为这本来就是现代 Web 的基本功,也能让 Agent 更容易理解页面。第二,梳理核心业务动作边界,比如创建、查询、提交、审批、导出,这些动作未来都有可能成为可调用能力。第三,把复杂流程写成任务说明或操作手册,让 Agent 知道什么顺序是安全的。第四,在内部后台、Electron 或受控环境里试验 WebMCP 或相关 SDK。第五,对高风险动作加确认、权限和审计。
|
||||
|
||||
同时,暂时不要做的事情也要说清楚。不要把实验 API 当成生产强依赖,不要假设 Safari、Firefox 或各种 WebView 都已经支持,不要让 Agent 自动执行不可逆操作,不要用工具调用绕过原有权限系统,也不要忽略降级和人工接管路径。
|
||||
|
||||
如果我们把这件事放到工程管理里,可以这样落地:先选一个低风险、内部可控的场景,比如后台里的查询、草稿生成、菜单创建、报表筛选;先暴露只读或低风险工具;然后加日志、加确认、加灰度;最后再考虑是否扩展到更高风险流程。
|
||||
|
||||
这页的核心是帮团队把兴奋感转成工程判断:方向要跟,边界要稳。越是自动化,越不能牺牲安全和可回退性。
|
||||
-->
|
||||
@@ -0,0 +1,56 @@
|
||||
# Chrome 新特性快闪:三条线一起推进
|
||||
|
||||
<div class="grid grid-cols-3 gap-5 mt-8">
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">Agent 交互</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- WebMCP
|
||||
- 可复用任务能力
|
||||
- Prompt API / Built-in AI
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">开发者工具</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- Modern Web Guidance
|
||||
- DevTools for agents
|
||||
- Baseline
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-amber">
|
||||
<div class="font-semibold mb-3">体验升级</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- HTML in Canvas
|
||||
- View Transitions
|
||||
- Navigation API
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-2xl font-bold">
|
||||
主线不是“Chrome 又加了几个 API”,而是 Web 的使用者和开发方式都在变。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页把 Chrome 新特性做一次快闪归类,帮助大家从一堆名字里抓住结构。
|
||||
|
||||
第一条线是 Agent 交互。WebMCP、可复用任务能力、Prompt API 或 Built-in AI 这些方向,都在回答同一个问题:当 AI 进入浏览器和页面之后,网页怎么参与这种新交互。这里要注意,不要把“可复用任务能力”包装成 WebMCP 标准的一部分,它更像产品层或工具层对任务流程的组织方式。
|
||||
|
||||
第二条线是开发者工具。Modern Web Guidance 和 DevTools for agents 不是给终端用户看的,而是给开发者和编码 Agent 用的。它们要解决的问题是:模型知识会滞后,页面运行时很复杂,Agent 不能只靠静态代码猜测现代 Web 应该怎么写、怎么调试。
|
||||
|
||||
第三条线是体验升级。HTML in Canvas、View Transitions、Navigation API 这些能力,仍然是传统 Web 平台演进的一部分。它们让 Web 页面可以做出更自然、更连续、更沉浸的体验,也让 Web 应用更接近用户对原生应用的期待。
|
||||
|
||||
所以这不是几个孤立 API,而是 Chrome 在同时推动三件事:Agent 能用 Web,开发者能更好地构建 Web,用户能获得更自然的 Web 体验。
|
||||
|
||||
下面我们先看开发者工具这条线的第一个例子:Modern Web Guidance。
|
||||
-->
|
||||
+29
-7
@@ -1,17 +1,39 @@
|
||||
# 收尾:建立团队的 Chrome 更新雷达
|
||||
# 开发者现在该记住什么?
|
||||
|
||||
<div class="mt-8 space-y-4 text-sm">
|
||||
|
||||
<div class="p-4 rounded-lg bg-white/5 border border-white/10">
|
||||
Chrome 更新不是新闻流,而是 Web 产品能力池。
|
||||
<div class="p-4 rounded-lg bg-emerald-500/8 border border-emerald-500/25">
|
||||
Web 页面未来不只是给人看的,也会被 Agent 理解、调用和操作。
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-white/5 border border-white/10">
|
||||
真正的价值是筛选、验证、降级和沉淀,而不是追逐每一个新 API。
|
||||
<div class="p-4 rounded-lg bg-blue-500/8 border border-blue-500/25">
|
||||
语义化、可访问性、清晰的业务函数边界,会变成新的工程基础设施。
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-white/5 border border-white/10">
|
||||
建议形成固定节奏:每月扫描一次,季度做一次 demo day,成熟能力进入技术方案模板。
|
||||
<div class="p-4 rounded-lg bg-amber-500/8 border border-amber-500/25">
|
||||
WebMCP 的方向是从“AI 猜 UI”走向“网页主动提供能力”。
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-red-500/8 border border-red-500/25">
|
||||
越自动化,越需要权限、确认、审计和渐进增强。
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
最后这一页做收束。
|
||||
|
||||
今天我们讲了很多名字:WebMCP、任务流程说明、Modern Web Guidance、DevTools for agents、HTML in Canvas、View Transitions、Navigation API、Baseline。但我希望大家真正带走的不是这些名词,而是一条主线。
|
||||
|
||||
第一,Web 页面未来不只是给人看的,也会被 Agent 理解、调用和操作。所以我们写页面时,不只要考虑用户能不能看懂,也要考虑页面能力能不能被结构化描述,能不能被安全调用。
|
||||
|
||||
第二,语义化、可访问性和清晰的业务函数边界,会变成新的工程基础设施。过去这些能力更多被看作体验优化或规范要求,接下来它们会直接影响 Agent 能不能可靠理解和操作我们的应用。
|
||||
|
||||
第三,WebMCP 代表的方向,是从“AI 猜 UI”走向“网页主动提供能力”。这会让复杂业务系统里的 AI 操作更快、更准、更可控。但它不是魔法,也不是马上覆盖所有浏览器的稳定能力,所以我们要用渐进增强的方式看待它。
|
||||
|
||||
第四,越自动化,越需要边界。权限、确认、审计、降级和人工接管,不是上线前补一补的细节,而是设计阶段就要考虑的安全基础。尤其是财务、审批、删除、提交这类动作,必须让用户保留最终控制权。
|
||||
|
||||
所以最后给开发者一句话:不要只问“Chrome 新增了什么 API”,而要问“我的网站准备好被 Agent 使用了吗?”如果这个问题能进入团队的技术设计和代码评审,这场分享就有价值了。
|
||||
|
||||
谢谢大家。
|
||||
-->
|
||||
|
||||
+35
-28
@@ -1,35 +1,42 @@
|
||||
# Demo 7:本地兜底,不抢 WebMCP 主线
|
||||
# 任务流程说明:工具箱之外,还要操作手册
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
<div class="mt-8">
|
||||
|
||||
```mermaid {scale: 0.72}
|
||||
flowchart LR
|
||||
A[用户目标] --> B[识别任务类型]
|
||||
B --> C[读取流程说明]
|
||||
C --> D[打开必要页面]
|
||||
D --> E[调用工具]
|
||||
E --> F[关键动作交给用户确认]
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-6 mt-7">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-2">WebMCP</div>
|
||||
<div class="text-sm op-70">解决“有哪些工具可以调用”。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">本地页面做什么</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 官方资料导航
|
||||
- 三路径对比仪表盘
|
||||
- WebMCP 可运行工程入口
|
||||
- MWG / DevTools / Canvas / View Transitions 演示入口
|
||||
- 现场讲述卡片
|
||||
- 网络不可用时继续演示
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-red">
|
||||
<div class="font-semibold mb-3">本地页面不做什么</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 不复刻所有 Chrome API
|
||||
- 不依赖 flags 才能讲完
|
||||
- 不做厂商 SDK 接入教程
|
||||
- 不把实验能力讲成稳定能力
|
||||
|
||||
</div>
|
||||
<div class="font-semibold mb-2">任务流程说明</div>
|
||||
<div class="text-sm op-70">解决“复杂任务应该按什么流程走”。</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-blue text-sm">
|
||||
打开总入口:<span class="font-mono">/demo/index.html</span>。正式 demo 入口都从这里进入。
|
||||
</div>
|
||||
<!--
|
||||
这一页讲任务流程说明。这里我会先特别说明:这不是 WebMCP 同级的官方浏览器概念,也不是一个标准 API 名称。它更像我们在应用实践里需要补上的流程层能力。
|
||||
|
||||
WebMCP 解决的是“有哪些工具可以调用”。但真实任务往往不是一个工具就能完成。比如用户说:“帮我申请价保。”这句话背后可能包含很多步骤:先去订单页查订单,确认订单是否符合规则,拿到订单号,再跳到价保页,填写申请信息,最后还要停下来让用户确认提交。
|
||||
|
||||
如果只给 Agent 一堆工具,它仍然可能不知道流程顺序。它可能先调了不该先调的工具,也可能过早提交,或者漏掉关键确认步骤。所以在应用实践里,我们通常还需要一层流程说明,可以叫 recipe、runbook、task flow,或者就叫操作手册。它的作用是告诉 Agent:遇到这类用户目标时,应该按什么顺序推进,每一步调用什么能力,哪一步必须停下来交给用户确认。
|
||||
|
||||
所以可以用一句话总结:WebMCP 是工具箱,任务流程说明是操作手册。工具箱让 Agent 有能力做事,操作手册让 Agent 知道怎样安全、稳定、按业务规则完成一件事。
|
||||
|
||||
这也是为什么未来前端不只要写 UI 组件,还要沉淀任务语义、页面能力和流程规则。我们写的不是随便贴给模型看的提示词,而是应用自身的可操作结构。这个结构越清晰,Agent 越不需要猜,风险也越容易被控制。
|
||||
|
||||
到这里,Agent 交互这条线就讲完了。接下来我们把视角拉宽,看 Chrome 这次还有哪些和开发工具、用户体验相关的新方向。
|
||||
-->
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# Demo 6:DevTools for agents 官方流程
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">官方 MCP 配置</div>
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"chrome-devtools": {
|
||||
"command": "npx",
|
||||
"args": ["chrome-devtools-mcp@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-red">
|
||||
<div class="font-semibold mb-3">本地检查目标</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- <span class="font-mono">/demo/devtools-agents/index.html</span>
|
||||
- 故意制造 console warning / error
|
||||
- 故意制造 404 图片
|
||||
- 点击按钮触发长任务
|
||||
- 让 Agent 读取 console、network、trace
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card text-sm">
|
||||
核心结论:DevTools for agents 不是页面 API,而是把真实浏览器运行时交给 coding agent,让它不再只根据源码猜问题。
|
||||
</div>
|
||||
@@ -1,39 +1,52 @@
|
||||
# Demo 5:AI 操作 Web 的三种路径
|
||||
# WebMCP 的两种接入方式
|
||||
|
||||
<div class="mt-8">
|
||||
<div class="grid grid-cols-2 gap-6 mt-7">
|
||||
|
||||
```mermaid {scale: 0.82}
|
||||
flowchart LR
|
||||
A[用户一句话任务] --> B{Agent 如何操作网页?}
|
||||
B --> C[看截图<br/>Vision]
|
||||
B --> D[读结构<br/>DOM / A11y]
|
||||
B --> E[调工具<br/>WebMCP]
|
||||
C --> F[慢 / 贵 / UI 敏感]
|
||||
D --> G[更结构化 / 仍易误点]
|
||||
E --> H[语义工具 / 参数明确 / 可控]
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">声明式:增强表单语义</div>
|
||||
|
||||
```html
|
||||
<form tool-description="提交酒店筛选条件">
|
||||
<input
|
||||
name="city"
|
||||
tool-parameter-description="目的地城市" />
|
||||
</form>
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-5 mt-8">
|
||||
|
||||
<div class="card-red">
|
||||
<div class="font-semibold mb-2">视觉识别</div>
|
||||
<div class="text-sm text-gray-500">通用,不改页面;但每一步都要观察、推理、点击,token 与耗时容易升高。</div>
|
||||
<div class="text-xs op-65 mt-3">适合已有 HTML 表单和渐进增强。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-2">DOM / A11y</div>
|
||||
<div class="text-sm text-gray-500">比截图更结构化;但复杂组件、隐藏状态、错误点击仍然会影响可靠性。</div>
|
||||
</div>
|
||||
<div class="font-semibold mb-3">命令式:注册业务工具</div>
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-2">WebMCP</div>
|
||||
<div class="text-sm text-gray-500">页面主动暴露工具,Agent 调业务能力,不再猜按钮和表单。</div>
|
||||
```ts
|
||||
document.modelContext.registerTool({
|
||||
name: 'create_order_menu',
|
||||
description: '创建订单管理菜单',
|
||||
inputSchema,
|
||||
execute: createOrderMenu,
|
||||
})
|
||||
```
|
||||
|
||||
<div class="text-xs op-65 mt-3">适合复杂业务动作和后台系统。</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-amber text-sm">
|
||||
示例任务:创建“订单管理”模块,包括词条、菜单、角色权限绑定。案例数据只说明趋势,不承诺所有项目都有相同收益。
|
||||
<div class="mt-7 card-amber text-sm">
|
||||
WebMCP 仍属于实验性方向,实际接入要做能力检测、渐进增强和人工确认。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页讲 WebMCP 怎么接入。
|
||||
|
||||
从公开资料和演示里可以看到,WebMCP 的接入大体可以分成两个方向。第一种是声明式接入,也就是在已有 HTML 表单上补充工具描述和参数描述。这样 Agent 不只是看到一个 input,而是知道这个 input 对应什么业务含义。例如这个字段不是普通文本框,而是“目的地城市”;这个表单不是普通表单,而是“提交酒店筛选条件”。
|
||||
|
||||
第二种是命令式接入,也就是通过 JavaScript 注册工具。工具里会包含 name、description、input schema 和 execute 函数。Agent 根据用户意图选择工具,传入结构化参数,然后页面执行我们已经定义好的业务逻辑。对复杂应用来说,这个方式更像是把现有业务函数安全地暴露给 Agent。
|
||||
|
||||
对高级前端来说,这个区别很关键。声明式方式适合增强已有表单,成本低,和语义化 HTML 的方向一致;命令式方式适合后台系统、复杂流程、跨页面业务,因为它可以复用已有的 service、store、接口封装和权限判断。也就是说,我们不一定要重写一套 AI 专用逻辑,而是把原有能力以更清晰的 schema 暴露出来。
|
||||
|
||||
但这里一定要提醒大家:WebMCP 仍然是实验性和快速演进中的方向,不能把它当成所有浏览器都稳定支持的能力。真正落地时,第一要做 feature detection,也就是先判断环境是否支持;第二要做渐进增强,不支持时仍然回到正常 UI 流程;第三,高风险动作必须有人类确认,不能让 Agent 自动越过权限、审计和确认边界。
|
||||
|
||||
讲到这里,我们已经有工具了。但真实任务往往不是一个工具就能完成,所以下一页要补一个实践层问题:复杂任务需要流程说明。
|
||||
-->
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# Demo 7:HTML-in-Canvas
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">官方能力</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 把真实 DOM 绘制进 Canvas / WebGL / WebGPU 场景
|
||||
- 保留可访问性、搜索、翻译等浏览器能力
|
||||
- 当前是 origin trial,环境要求高
|
||||
- 现场必须准备 fallback
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">本地演示</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 打开 <span class="font-mono">/html-in-canvas/index.html</span>
|
||||
- 支持时调用 <span class="font-mono">drawElementImage()</span>
|
||||
- 不支持时显示同一 DOM 表单的 fallback 预览
|
||||
- 讲清楚“DOM 进入画布”而不是“canvas 手绘控件”
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-amber text-sm">
|
||||
运行条件:Chrome Canary 149+,开启 <span class="font-mono">chrome://flags/#canvas-draw-element</span>;Chrome 官方说明该 API 在 Chrome 148-150 处于 origin trial。
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
# Demo 5:Modern Web Guidance 官方 CLI
|
||||
|
||||
<div class="grid grid-cols-[1.1fr_0.9fr] gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">现场命令</div>
|
||||
|
||||
```bash
|
||||
npx modern-web-guidance@latest search "animate a dialog modal backdrop"
|
||||
npx modern-web-guidance@latest retrieve "animate-to-from-top-layer"
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">演示重点</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- AI coding agent 容易写旧方案
|
||||
- MWG 把现代 Web 指南变成可检索技能
|
||||
- search 找 use case,retrieve 拿具体指南
|
||||
- 指南会带 Baseline / fallback 意识
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-amber text-sm">
|
||||
本地辅助页:<span class="font-mono">/demo/modern-web-guidance/index.html</span>;脚本:<span class="font-mono">scripts/demo-modern-web-guidance.sh</span>。
|
||||
</div>
|
||||
+30
-16
@@ -1,34 +1,48 @@
|
||||
# Demo 9:最后快闪与串联
|
||||
# WebMCP:网页给 Agent 的工具说明书
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">Agentic Web 相关</div>
|
||||
<div class="font-semibold mb-3">一个工具通常描述什么?</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- WebMCP:页面暴露工具给 Agent
|
||||
- WebMCP:页面暴露工具给 Agent
|
||||
- Modern Web Guidance:已演示官方 CLI
|
||||
- DevTools for agents:已演示官方 MCP 流程
|
||||
- Prompt API:官方 demo 快闪
|
||||
- 名字:工具叫什么
|
||||
- 描述:什么时候应该调用
|
||||
- 参数:需要哪些输入
|
||||
- 执行:调用后做什么业务逻辑
|
||||
- 返回:成功、失败和结果信息
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="font-semibold mb-3">体验与平台能力</div>
|
||||
<div class="space-y-2 text-sm text-gray-300">
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">它改变了什么?</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- HTML-in-Canvas:已演示本地页和官方限制
|
||||
- Baseline:判断哪些 Web 能力可以放心评估
|
||||
- View Transitions:已演示本地页
|
||||
- Navigation API:更现代的导航状态管理
|
||||
- UI 改版不等于能力失效
|
||||
- Agent 不再猜按钮位置
|
||||
- 业务边界由开发者定义
|
||||
- 权限、确认、审计可以前置设计
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-amber text-sm">
|
||||
收束判断:Chrome 新能力不是孤立 API,而是同时升级 Agent 交互、开发工作流和用户体验。
|
||||
<div class="mt-8 text-2xl font-bold">
|
||||
从“AI 操作页面”升级为“页面向 AI 暴露能力”。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页解释 WebMCP 的核心概念。
|
||||
|
||||
最简单的理解方式是:WebMCP 是网页给 AI Agent 的工具说明书。过去网页把最终渲染出来的 UI 给用户看,Agent 如果要操作,只能从这个 UI 反推背后的意图。WebMCP 的方向则是反过来:网页主动告诉 Agent,我有哪些可调用能力,每个能力叫什么,适合什么时候用,需要什么参数,执行后会返回什么结果。
|
||||
|
||||
举几个例子会更好理解。一个电商页面可以暴露 add_to_cart 工具,参数是商品 ID、尺码和数量;一个后台系统可以暴露 create_menu 工具,参数是菜单名称、父级菜单、路由和权限标识;一个文档应用可以暴露 summarize_selection 工具,参数是当前选区和摘要长度。Agent 不需要猜按钮在哪里,它只需要判断用户意图和哪个工具匹配。
|
||||
|
||||
这对前端开发者很重要。因为以前我们经常担心 AI 乱点页面:点错按钮、误删数据、跳过校验。但如果能力是我们主动暴露出去的,业务边界就能回到开发者手里。哪些动作可以给 Agent 调,哪些动作必须用户确认,哪些动作需要审计,哪些工具只在特定页面或特定权限下出现,这些都可以在设计阶段明确。
|
||||
|
||||
所以 WebMCP 不是“让 AI 更会点按钮”。它真正代表的是一个更大的变化:网页从 UI 自动化,走向能力自动化。Agent 不再只是模仿人点页面,而是通过结构化工具参与业务流程。
|
||||
|
||||
下一页我们看它大概怎么接入:一种偏声明式,一种偏命令式。
|
||||
-->
|
||||
|
||||
+38
-36
@@ -1,47 +1,49 @@
|
||||
# Demo 1:演示交付物总览
|
||||
# 同一个任务,三种完成方式
|
||||
|
||||
<div class="grid grid-cols-3 gap-5 mt-8">
|
||||
<div class="grid grid-cols-3 gap-5 mt-7">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">幻灯片内容</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 分别介绍新增能力
|
||||
- 说明解决的问题
|
||||
- 标注成熟度和限制
|
||||
- 给出官方资料来源
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">工程演示什么</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- WebMCP 可运行页面
|
||||
- 声明式表单工具
|
||||
- 指令式 registerTool
|
||||
- 工具 schema 和调用日志
|
||||
- mock runtime 兜底
|
||||
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="text-sm font-semibold text-blue-500 mb-2">方式 1</div>
|
||||
<div class="text-xl font-bold mb-2">看截图</div>
|
||||
<div class="text-sm op-70">逐屏识别、逐步点击、逐项填表。</div>
|
||||
<div class="mt-5 text-3xl font-bold">分钟级</div>
|
||||
<div class="text-xs op-60 mt-1">案例中 token 可到几十万级</div>
|
||||
</div>
|
||||
|
||||
<div class="card-amber">
|
||||
<div class="font-semibold mb-3">新增正式 demo</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- Modern Web Guidance
|
||||
- DevTools for agents
|
||||
- HTML-in-Canvas
|
||||
- View Transitions
|
||||
- Prompt API / Baseline 保留快闪
|
||||
|
||||
<div class="text-sm font-semibold text-amber-500 mb-2">方式 2</div>
|
||||
<div class="text-xl font-bold mb-2">读结构</div>
|
||||
<div class="text-sm op-70">读取 DOM / 可访问性树,再定位控件。</div>
|
||||
<div class="mt-5 text-3xl font-bold">更快但脆</div>
|
||||
<div class="text-xs op-60 mt-1">复杂交互下仍可能点错或漏选</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="text-sm font-semibold text-emerald-500 mb-2">方式 3</div>
|
||||
<div class="text-xl font-bold mb-2">调工具</div>
|
||||
<div class="text-sm op-70">调用页面暴露的业务工具。</div>
|
||||
<div class="mt-5 text-3xl font-bold">秒级</div>
|
||||
<div class="text-xs op-60 mt-1">案例中约 30-40 秒、约 1.7 万 token</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card text-sm">
|
||||
原则:WebMCP 是主 demo;Modern Web Guidance 和 DevTools for agents 按官方流程演示;HTML-in-Canvas 和 View Transitions 用本地页面辅助展示。
|
||||
<div class="mt-7 card-blue text-sm">
|
||||
案例任务:后台系统内完成“创建订单管理模块”,包括词条、菜单、角色权限绑定。数据来自仓库原始实战材料,作为特定案例结果,不代表所有场景通用值。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页用一个具体任务,把刚才三类方案的差异讲得更直观一些。
|
||||
|
||||
我们假设有一个后台管理系统,任务是创建一个“订单管理模块”。听起来很简单,但拆开看其实有好几步:先创建词条,再创建菜单,再把菜单绑定到角色权限里。对熟悉系统的人来说,这可能就是几分钟的常规操作;但对 AI 来说,这里面包含多个页面、多个表单、树形结构、权限勾选、搜索和确认动作。
|
||||
|
||||
如果用视觉识别,AI 需要一屏一屏截图。它要先看到菜单在哪里,再判断哪个是新增按钮,再识别表单字段,再填值,再点提交。这个过程有点像让一个人隔着截图远程操作电脑,能做,但非常绕。仓库里的实战材料里,这类方案可以完成任务,但过程是分钟级,token 消耗可以到几十万级。
|
||||
|
||||
如果用 DOM 或可访问性树识别,它会更快一点,因为它不只是看图,而是读结构。但这并不等于稳定。复杂组件里有很多状态,页面结构也会随着权限和交互变化。实战材料里就提到过,结构识别方案在复杂菜单和权限操作里,仍然可能点错、漏选,甚至把不该删的东西删掉。
|
||||
|
||||
如果用 WebMCP 思路,页面直接暴露“创建词条”“创建菜单”“绑定角色权限”这些工具。AI 不需要知道按钮在左边还是右边,不需要知道菜单有没有展开,它只需要根据用户目标选择正确的工具,传入参数,然后拿到执行结果。实战材料里,同类任务可以压到几十秒,token 也会明显下降。
|
||||
|
||||
这里我要特别说明一下:页面上的数字不是行业通用基准,也不是说所有场景都会有完全相同的提升。它只是一个具体案例。但它说明了一个趋势:越复杂、越业务化、越高风险的系统,越不应该让 AI 在界面里猜;我们越应该把能力封装好,让 Agent 调用明确的能力。
|
||||
|
||||
接下来,我们就进入核心概念:WebMCP 到底是什么。
|
||||
-->
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# Demo 8:View Transitions
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">为什么演示</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 效果直观,风险低
|
||||
- 能说明 Chrome 在升级 Web 体验能力
|
||||
- 适合和 Agentic Web 主线形成对照:工具层和体验层都在进化
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">运行入口</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 打开 <span class="font-mono">/demo/view-transitions/index.html</span>
|
||||
- 点击订单 / 分析 / 设置
|
||||
- 支持时走 <span class="font-mono">document.startViewTransition()</span>
|
||||
- 不支持时普通切换
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card text-sm">
|
||||
核心结论:View Transitions 把“状态变化”从手写动画提升为浏览器级能力,适合渐进增强。
|
||||
</div>
|
||||
@@ -1,34 +0,0 @@
|
||||
# Demo 2:WebMCP 到底演示什么?
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">一句话</div>
|
||||
<div class="space-y-3 text-sm">
|
||||
|
||||
WebMCP 让网页把业务能力暴露成结构化工具,Agent 看到的是工具名、描述、参数 schema 和执行结果,而不是只靠截图或 DOM 猜按钮。
|
||||
|
||||
<div class="font-mono text-xs opacity-80">
|
||||
navigator.modelContext.registerTool(...)
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">两种 API</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 声明式:给标准 HTML 表单添加工具属性
|
||||
- 指令式:用 JavaScript 注册复杂工具
|
||||
- 共同目标:把页面能力变成 Agent 可理解的契约
|
||||
- 演示口径:提高可靠性,不承诺零错误率
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-amber text-sm">
|
||||
实验边界:Chrome 官方文档标注 WebMCP 仍处于 proposed / origin trial / flag 阶段;现场 demo 必须有 mock 兜底。
|
||||
</div>
|
||||
@@ -1,33 +0,0 @@
|
||||
# Demo 3:WebMCP 原始工程能跑起来
|
||||
|
||||
<div class="grid grid-cols-[1.05fr_0.95fr] gap-8 mt-8">
|
||||
|
||||
<div class="card">
|
||||
<div class="font-semibold mb-3">工程文件</div>
|
||||
|
||||
```txt
|
||||
public/demo/webmcp/
|
||||
├── index.html # 业务 UI + 声明式表单工具
|
||||
├── styles.css # 演示界面样式
|
||||
└── app.js # registerTool + mock runtime + 执行日志
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">运行入口</div>
|
||||
<div class="space-y-3 text-sm">
|
||||
|
||||
- 启动:`pnpm dev`
|
||||
- 打开:`/demo/webmcp/index.html`
|
||||
- 无 WebMCP 环境:走本地 mock runtime
|
||||
- 有 WebMCP 环境:优先调用 `navigator.modelContext`
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-green text-sm">
|
||||
现场打开工程,不只展示链接;页面 UI 和 Agent 工具视角需要同时呈现。
|
||||
</div>
|
||||
@@ -1,37 +0,0 @@
|
||||
# Demo 4:现场操作脚本
|
||||
|
||||
<div class="grid grid-cols-4 gap-4 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">1. 人类 UI</div>
|
||||
<div class="text-sm">
|
||||
提交售后工单表单,说明原有页面流程没有被替换。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">2. 工具发现</div>
|
||||
<div class="text-sm">
|
||||
右侧展示工具名、描述、schema:这是 Agent 看到的契约。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-amber">
|
||||
<div class="font-semibold mb-3">3. 单工具调用</div>
|
||||
<div class="text-sm">
|
||||
点击查询订单、生成回复、退款审核,观察状态和日志变化。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-red">
|
||||
<div class="font-semibold mb-3">4. 复合任务</div>
|
||||
<div class="text-sm">
|
||||
执行复合任务:查询订单 → 进入审核 → 生成回复草稿。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card text-sm">
|
||||
核心结论:WebMCP 不是让 Agent 更会点击,而是让网页直接声明“我有哪些可调用能力”。敏感动作仍然要保留用户确认、权限和审计。
|
||||
</div>
|
||||
@@ -0,0 +1,49 @@
|
||||
# DevTools for agents:让 Agent 看见真实浏览器
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">人类调试时看什么?</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- Console 错误
|
||||
- Network 请求
|
||||
- Performance 录制
|
||||
- Lighthouse 结果
|
||||
- Memory / Heap snapshot
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">Agent 也应该看什么?</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 页面真实渲染状态
|
||||
- 运行时错误和警告
|
||||
- 资源瀑布与接口失败
|
||||
- 性能瓶颈和内存线索
|
||||
- 扩展和多窗口调试上下文
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-2xl font-bold">
|
||||
只把代码发给 Agent 不够,它还需要理解代码在浏览器里怎么运行。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页讲 DevTools for agents。
|
||||
|
||||
人类开发者调试 Web,不会只看源码。我们会打开 Console 看报错,打开 Network 看请求和瀑布图,打开 Performance 看主线程和长任务,打开 Lighthouse 看性能、可访问性和最佳实践,必要时还会看 Memory 和 Heap snapshot。
|
||||
|
||||
但很多编码 Agent 只拿到代码上下文,它并不知道页面真实运行时发生了什么。代码看起来没问题,不代表页面真的渲染正确;接口写对了,不代表请求真的成功;组件逻辑合理,不代表交互不卡;构建通过,也不代表用户在浏览器里看到的是正确页面。
|
||||
|
||||
DevTools for agents 的方向,就是把这些浏览器调试能力开放给 Agent,让它不只是“读代码”,还可以“看运行现场”。换句话说,Agent 不应该只基于文件做推理,它也应该能结合 Console、Network、Performance 和页面状态来判断问题。
|
||||
|
||||
这会改变我们和 Agent 的协作方式。过去我们可能只是把错误栈贴给 AI,让它猜哪里错了;未来更合理的方式是让它连接真实浏览器环境,看到当前页面、当前请求、当前报错和当前性能瓶颈,再提出修改建议。
|
||||
|
||||
举个例子,如果页面白屏,Agent 不应该只搜索代码里的 mounted 或 useEffect;它应该先看 Console 有没有运行时异常,再看 Network 有没有资源 404 或接口 500,再看页面是否真的挂载了根节点。这样调试会更接近一个高级前端真实排查问题的路径。
|
||||
-->
|
||||
@@ -0,0 +1,47 @@
|
||||
# Modern Web Guidance:给编码 Agent 补现代 Web 知识
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-red">
|
||||
<div class="font-semibold mb-3">问题</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 浏览器能力每几周更新
|
||||
- 模型训练知识经常滞后
|
||||
- AI 容易写旧方案或复杂 polyfill
|
||||
- 兼容性和降级策略容易漏掉
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">解法</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 把现代 Web 指南装进 AI 工具
|
||||
- 查询 Baseline 和最佳实践
|
||||
- 给出 gotchas 与 fallbacks
|
||||
- 让 Agent 写“今天的 Web”
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-blue text-sm">
|
||||
可以把它类比成:以前 Browserslist / Autoprefixer 帮构建工具查兼容性;现在 Modern Web Guidance 帮编码 Agent 查现代 Web 知识。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页讲 Modern Web Guidance。
|
||||
|
||||
我们现在用 AI 写前端代码,经常遇到一个矛盾:Chrome 和 Web 平台更新很快,但大模型的训练数据可能停留在过去。也就是说,我们在 2026 年问一个编码 Agent 写现代 Web,它可能仍然拿几年前的经验回答。结果就是,它会写旧语法、引入不必要的库,或者不知道某个现代 CSS 和 Web API 已经可以用了。
|
||||
|
||||
Modern Web Guidance 的目标,就是把 Chrome 团队和社区维护的现代 Web 指南,作为技能包或知识包提供给编码 Agent。它不是替你写业务逻辑,也不是一个 UI 库;它更像一个本地的现代 Web 知识补丁。当 Agent 准备写代码时,它可以把现代 Web 的能力、兼容性、避坑点和降级方案注入上下文。
|
||||
|
||||
可以用一个前端熟悉的类比来理解。以前 Browserslist 和 Autoprefixer 帮打包工具查目标浏览器,决定要不要加前缀、要不要转译。那时写代码的是人,构建工具负责查兼容性。现在写代码的人里多了 AI Agent,所以也需要一个机制帮 Agent 查现代 Web 知识,避免它用过时方案解决今天已经有原生能力的问题。
|
||||
|
||||
举一个具体场景:你让 Agent 写一个弹出菜单,它可能会直接引入一个复杂的定位库,或者写一堆监听 scroll 和 resize 的坐标计算。如果它能检索到现代平台指南,就可能优先考虑 Popover API、Anchor Positioning,以及必要的 fallback。这样产出的不是“能跑的旧方案”,而是更接近今天平台能力的方案。
|
||||
|
||||
所以这条线的价值不是炫技,而是工程治理:让团队里的 AI 代码产出更接近现代平台,而不是把历史包袱继续复制到新项目里。
|
||||
-->
|
||||
+30
-20
@@ -1,29 +1,39 @@
|
||||
# 分享大纲
|
||||
# 为什么 AI 操作网页会不可靠?
|
||||
|
||||
<div class="grid grid-cols-2 gap-6 mt-8">
|
||||
<div class="grid grid-cols-3 gap-5 mt-8">
|
||||
|
||||
<div class="card p-5">
|
||||
<div class="text-blue-400 text-sm font-semibold mb-2">01</div>
|
||||
<div class="text-xl font-bold mb-2">浏览器的新使用者</div>
|
||||
<div class="text-sm text-gray-400">Human UI · Agent layer · Structured actions</div>
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">视觉识别</div>
|
||||
<div class="text-sm op-75 mb-4">AI 看截图,猜按钮和输入框在哪里。</div>
|
||||
<div class="text-xs op-65">通用性强,但慢、贵、受 UI 变化影响。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-green p-5">
|
||||
<div class="text-emerald-400 text-sm font-semibold mb-2">02</div>
|
||||
<div class="text-xl font-bold mb-2">AI 操作网页为什么难</div>
|
||||
<div class="text-sm text-gray-400">Vision · DOM / A11y · Tool calling</div>
|
||||
<div class="card-amber">
|
||||
<div class="font-semibold mb-3">DOM / 可访问性树</div>
|
||||
<div class="text-sm op-75 mb-4">AI 读取结构,寻找元素再点击。</div>
|
||||
<div class="text-xs op-65">比视觉结构化,但复杂组件和隐藏状态仍会干扰。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-amber p-5">
|
||||
<div class="text-amber-400 text-sm font-semibold mb-2">03</div>
|
||||
<div class="text-xl font-bold mb-2">MCP / WebMCP</div>
|
||||
<div class="text-sm text-gray-400">Tool schema · Structured actions · Safety boundary</div>
|
||||
</div>
|
||||
|
||||
<div class="card-red p-5">
|
||||
<div class="text-red-400 text-sm font-semibold mb-2">04</div>
|
||||
<div class="text-xl font-bold mb-2">官方 demo 快闪</div>
|
||||
<div class="text-sm text-gray-400">MWG · DevTools agents · Prompt API · HTML-in-Canvas</div>
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">WebMCP 工具调用</div>
|
||||
<div class="text-sm op-75 mb-4">网页主动暴露工具,AI 调用明确能力。</div>
|
||||
<div class="text-xs op-65">把“猜 UI”变成“调业务能力”。</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 text-2xl font-bold">
|
||||
AI 擅长结构化信息,不擅长在复杂 UI 里猜下一步。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页开始讲问题:为什么 AI 操作网页经常不可靠?
|
||||
|
||||
现在让 AI 操作网页,大概有三类方案。第一类是视觉识别。它的方式很直观:AI 不断看截图,判断按钮在哪里、输入框在哪里、下一步应该点哪里。这种方式最大的好处是通用,不需要你改页面,任何网站理论上都能试一试。但它的代价也很明显:慢,贵,token 消耗大,而且 UI 一变,模型就可能误判。
|
||||
|
||||
第二类是 DOM 或可访问性树识别。它比纯视觉更进一步,不只是看图,而是读取页面结构,找按钮、表单、菜单和文本。这当然更结构化,也更接近前端工程。但真实业务系统里有大量复杂组件:弹窗、抽屉、虚拟滚动、级联选择、树形权限、隐藏状态、动态权限。结构一复杂,Agent 仍然可能找错元素、点错按钮,甚至误删数据。
|
||||
|
||||
第三类就是我们今天重点看的 WebMCP 思路。它不是继续让 AI 猜页面,而是让网页主动告诉 AI:我这里有哪些工具,每个工具适合什么场景,需要什么参数,调用之后会执行什么业务逻辑。这样,AI 做的事情就从“在 UI 上摸索”变成“调用明确的业务能力”。
|
||||
|
||||
所以这页的结论很简单:AI 擅长结构化信息,不擅长在复杂 UI 里猜下一步。只要我们让它继续猜,就会慢、贵、脆弱;如果我们把网页能力结构化地交给它,它才有机会变得稳定、可控。
|
||||
-->
|
||||
|
||||
+29
-13
@@ -1,29 +1,45 @@
|
||||
# 这次分享的目标
|
||||
# 一个变化:网页不只给人用了
|
||||
|
||||
<div class="grid grid-cols-2 gap-8 mt-8">
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">核心收获</div>
|
||||
<div class="font-semibold mb-3">过去的 Web</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 理解 AI Agent 操作网页为什么困难
|
||||
- 看懂 Chrome 新增能力各自解决什么问题
|
||||
- 知道幻灯片与项目代码如何配合演示
|
||||
- 建立实验能力的风险边界
|
||||
- 用户看页面
|
||||
- 用户理解信息
|
||||
- 用户点击控件
|
||||
- 用户完成流程
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="font-semibold mb-3">不展开做什么</div>
|
||||
<div class="space-y-2 text-sm text-gray-300">
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">Agentic Web</div>
|
||||
<div class="space-y-2 text-sm">
|
||||
|
||||
- 不逐条朗读全部 release notes
|
||||
- 不做厂商 SDK 接入教程
|
||||
- 不复刻每一个 Chrome 实验 API
|
||||
- 不承诺实验能力可直接进入生产核心链路
|
||||
- Agent 理解页面意图
|
||||
- Agent 拆解多步任务
|
||||
- Agent 调用网页能力
|
||||
- 用户确认关键动作
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-amber text-sm">
|
||||
核心判断:今天的网页“能被 AI 操作”,但还没有真正“为 Agent 优化”。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页我们先建立背景。
|
||||
|
||||
过去我们做 Web,默认使用者是人。所以我们会关注视觉层面的体验:页面好不好看,信息层级清不清楚,按钮是不是容易点,表单是不是容易填,错误提示是不是能让用户理解。也就是说,用户通过“看”来理解页面,再通过“点”和“输入”来完成操作。
|
||||
|
||||
但在 Agentic Web 里,使用者不只有人。用户可能不会亲自打开每个页面、阅读每个字段、点击每个按钮,而是直接说一句:“帮我找一个会议附近的酒店,把预算和评分筛选好,再给我几个候选方案。”这时候,真正要进入网页、理解信息、执行步骤的,就变成了 Agent。
|
||||
|
||||
这并不是说网页以后不需要 UI 了,也不是说 Web 要从“给人用”变成“不给人用”。更准确的说法是,Web 会多出一层使用方式。上层仍然是人类看到的界面,下层则需要有 Agent 能理解、能调用、能安全执行的能力结构。
|
||||
|
||||
所以这页的核心判断是:今天很多网页已经“能被 AI 操作”,因为 AI 可以看截图、读 DOM、模拟点击;但它们还没有真正“为 Agent 优化”。接下来几页,我们就看为什么这件事会成为问题。
|
||||
-->
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 体验侧的新能力:让 Web 更像应用,也更可组合
|
||||
|
||||
<div class="grid grid-cols-3 gap-5 mt-8">
|
||||
|
||||
<div class="card-green">
|
||||
<div class="font-semibold mb-3">HTML in Canvas</div>
|
||||
<div class="text-sm op-70">把真实 DOM 内容放进 Canvas / 3D 场景,保留选择、翻译和可访问性等 Web 能力。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-blue">
|
||||
<div class="font-semibold mb-3">View Transitions</div>
|
||||
<div class="text-sm op-70">用更少的 JavaScript 做页面状态切换,让 Web 应用的过渡更自然。</div>
|
||||
</div>
|
||||
|
||||
<div class="card-amber">
|
||||
<div class="font-semibold mb-3">Navigation API</div>
|
||||
<div class="text-sm op-70">用统一事件管理前进、后退和应用导航,为 SPA 和跨页转场打基础。</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-8 card-blue text-sm">
|
||||
这些能力服务同一个目标:把 Web 的开放性、可访问性和应用体验放到同一条轨道上。
|
||||
</div>
|
||||
|
||||
<!--
|
||||
这一页讲体验侧的新能力。
|
||||
|
||||
Chrome 原文里有一个很有画面感的 demo:在一个 3D 房间里嵌入浏览器内容,同时还能选择文本、翻译内容、查找 WebGPU。这个 demo 背后对应的是 HTML in Canvas 这个方向。它想解决的问题是:当我们进入 Canvas、游戏、3D 或沉浸式场景时,不应该牺牲 DOM 的文本能力、可访问性和 Web 语义。
|
||||
|
||||
View Transitions 解决的是另一个常见问题:Web 应用切换状态时,经常感觉像“突然换了一页”。以前我们要写很多动画逻辑,或者引入动画库,才能让列表到详情、页面到页面的切换更自然。现在平台开始提供更原生的转场能力,帮助改善感知性能和连续性。
|
||||
|
||||
Navigation API 则解决导航管理问题。传统 History API 在复杂应用里会显得比较分散,前进、后退、页面切换和应用状态管理经常需要我们自己拼很多逻辑。Navigation API 的方向,是把这些导航事件统一起来。它和 View Transitions 配合之后,Web 应用会更像一个完整应用,而不是一堆页面拼接。
|
||||
|
||||
这一页不需要深入 API 细节。我们只要让听众理解:Chrome 不只在做 AI,也在继续补 Web 的应用体验能力。Agentic Web 是新方向,但 Web 作为应用平台本身,也还在继续变强。
|
||||
|
||||
讲完能力,我们最后要回到团队落地:这些东西今天应该怎么跟,哪些可以做,哪些不能急。
|
||||
-->
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
# Part 2 Demo Plan: 官方优先的 Agentic Web 演示
|
||||
|
||||
## 目标和产物
|
||||
|
||||
第二部分用于支撑 40 分钟分享中的 demo 段,目标不是泛泛介绍概念,而是生成两类明确产物:
|
||||
|
||||
1. **PPT 主线**:分别介绍 Chrome 最近新增或正在推进的关键能力,说明每个功能解决什么问题、处在什么成熟度、适合用什么官方材料展示。
|
||||
2. **项目代码演示**:创建并维护一个配套前端项目。WebMCP 必须有可运行原始工程;Modern Web Guidance、DevTools for agents、HTML-in-Canvas、View Transitions 都要有明确演示入口。
|
||||
|
||||
它不是落地教程,也不是厂商 SDK 接入课;目标是让对 Chrome 新特性不熟悉的 Web 开发者看懂:
|
||||
|
||||
- AI 操作 Web 为什么容易慢、贵、不稳定。
|
||||
- WebMCP 为什么从“猜 UI”转向“调用页面暴露的工具”。
|
||||
- Chrome 相关新能力如何作为这个方向的证据链,而不是每个都现场复刻。
|
||||
- 哪些是官方能力或标准探索,哪些只是厂商实现概念。
|
||||
|
||||
## 总原则
|
||||
|
||||
1. **官方优先**:优先使用 Chrome 官方文档、官方 demo、官方 demo collection、官方命令示例和官方截图/录屏作为展示材料。
|
||||
2. **WebMCP 必须可运行**:WebMCP 是第二部分的主 demo,当前仓库必须提供可打开、可点击、可看源码的本地工程。
|
||||
3. **官方优先但不空讲**:官方材料用于确认标准口径和 API 边界;本地工程用于承接现场演示。
|
||||
4. **不复刻重实验能力**:不在本地项目里复刻 Prompt API 等环境依赖重、版本敏感的能力;HTML-in-Canvas 只做官方 API 方向和 fallback 展示。
|
||||
5. **保持科普口径**:所有 token、耗时、成功率数据都作为具体案例,不当作普适结论;WebMCP 讲“提高可靠性”,不讲“零错误率”。
|
||||
6. **实验能力明确标注**:WebMCP、Prompt API、HTML-in-Canvas 等能力需要标注实验性、origin trial、渐进增强或浏览器支持边界。
|
||||
7. **剔除厂商私货**:OpenTiny、VAgent、WebAgent、Remote、WebSkills、遥控器组件、SDK 版本等只可作为原始材料背景,不作为 PPT 主概念。若需要表达类似思想,使用中立说法:“Agent 操作说明”“流程说明”“工具调用编排”。
|
||||
|
||||
## Demo 结构
|
||||
|
||||
### 1. Demo 导航页
|
||||
|
||||
先给观众一张总览:第二部分会按“新增功能介绍”和“项目代码演示”展开。
|
||||
|
||||
- 新增功能介绍:WebMCP、Modern Web Guidance、DevTools for agents、Prompt API、HTML-in-Canvas、Baseline / View Transitions。
|
||||
- 项目代码演示:WebMCP、DevTools for agents、HTML-in-Canvas、View Transitions 提供本地页面;Modern Web Guidance 提供官方 CLI 脚本和辅助页。
|
||||
- 兜底原则:正式 demo 都要有入口;高风险实验能力不承诺原生 API 一定可用。
|
||||
|
||||
### 2. 主线演示:AI 操作 Web 的三种方式
|
||||
|
||||
任务设定:在后台系统中完成“创建订单管理模块”,包括创建词条、创建菜单、绑定角色权限。
|
||||
|
||||
对比三种路径:
|
||||
|
||||
- 视觉识别:靠截图理解界面。优点是通用;问题是慢、token 高、UI 变化敏感。
|
||||
- DOM / 可访问性树识别:靠页面结构找元素。比纯视觉结构化,但仍可能被复杂组件、隐藏状态和错误点击影响。
|
||||
- WebMCP / 工具调用:页面主动暴露工具和参数,Agent 调用业务能力,不再猜 UI。
|
||||
|
||||
展示方式:
|
||||
|
||||
1. PPT 用表格和流程图描述三种路径。
|
||||
2. WebMCP 打开 `/demo/webmcp/index.html` 现场跑工程。
|
||||
3. 其他正式 demo 从 `/demo/index.html` 进入,或运行 `scripts/demo-modern-web-guidance.sh`。
|
||||
|
||||
### 3. 新增功能介绍:官方 demo 快闪
|
||||
|
||||
每个能力只讲“它是什么、解决什么问题、现场怎么展示”,不展开完整 API 课。
|
||||
|
||||
- **WebMCP**:使用官方文档确认 API 口径,同时打开 `/demo/webmcp/index.html` 演示声明式表单工具、指令式 `registerTool`、工具 schema 和调用日志。
|
||||
- **Modern Web Guidance**:运行官方 `search/retrieve` CLI,并打开 `/demo/modern-web-guidance/index.html` 辅助讲解。
|
||||
- **Chrome DevTools for agents**:使用官方 DevTools MCP 配置,让 Agent 检查 `/demo/devtools-agents/index.html` 的 console、network 和性能问题。
|
||||
- **Prompt API**:使用官方 Prompt API demos / playground 说明“浏览器内置 AI 能力,适合渐进增强”。
|
||||
- **HTML-in-Canvas**:打开 `/html-in-canvas/index.html`,支持时跑原生 API,不支持时展示 fallback 和官方运行条件。
|
||||
- **Baseline / View Transitions**:Baseline 做方法快闪;View Transitions 打开 `/demo/view-transitions/index.html`。
|
||||
|
||||
## 项目代码演示职责
|
||||
|
||||
当前仓库的本地 demo 承担六件事:
|
||||
|
||||
1. 提供一个稳定的“官方 demo 导航页”,列出每个新增功能对应的官方资料、推荐讲法和现场备用链接。
|
||||
2. 提供一个可运行的 WebMCP 原始工程:`public/demo/webmcp/index.html`、`styles.css`、`app.js`。
|
||||
3. 提供 Modern Web Guidance 官方 CLI 脚本和辅助页面。
|
||||
4. 提供 DevTools for agents 可检查目标页,故意包含 console、network、performance 问题。
|
||||
5. 提供 HTML-in-Canvas 和 View Transitions 演示页面。
|
||||
6. 提供一个“本地兜底说明”:如果官方 demo 现场不可用,如何用项目页面和 PPT 中的截图/流程继续讲,不中断分享。
|
||||
|
||||
不做以下事情:
|
||||
|
||||
- 不把本地项目做成厂商 WebMCP SDK 接入教程。
|
||||
- 不实现所有 Chrome 新特性。
|
||||
- 不把实验 API 当作生产可用能力演示。
|
||||
- 不要求现场浏览器开启 flags 才能完成主流程。
|
||||
- 不把 WebSkills / WebAgent / VAgent / Remote 等厂商概念写成标准能力。
|
||||
|
||||
## 时间分配
|
||||
|
||||
第二部分建议 15 分钟左右:
|
||||
|
||||
- 2 分钟:demo 交付物总览。
|
||||
- 6 分钟:WebMCP 可运行工程演示。
|
||||
- 4 分钟:Modern Web Guidance 官方 CLI。
|
||||
- 4 分钟:DevTools for agents 检查本地问题页。
|
||||
- 4 分钟:HTML-in-Canvas / View Transitions。
|
||||
- 2 分钟:Prompt API / Baseline 快闪和关键风险提示。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- WebMCP imperative API: https://developer.chrome.com/docs/ai/webmcp/imperative-api
|
||||
- Prompt API: https://developer.chrome.com/docs/ai/prompt-api
|
||||
- Modern Web Guidance: https://developer.chrome.com/docs/modern-web-guidance
|
||||
- Chrome DevTools for agents: https://developer.chrome.com/docs/devtools/agents/get-started
|
||||
- HTML-in-Canvas origin trial: https://developer.chrome.com/blog/html-in-canvas-origin-trial
|
||||
- Baseline: https://web.dev/baseline
|
||||
- View Transitions demos: https://view-transitions.chrome.dev/
|
||||
- Chrome demos index: https://chrome.dev/
|
||||
Generated
+2
-24
@@ -13,7 +13,7 @@ importers:
|
||||
version: 1.2.11
|
||||
'@slidev/cli':
|
||||
specifier: ^52.16.0
|
||||
version: 52.16.0(@nuxt/kit@3.21.8)(@types/markdown-it@14.1.2)(@types/node@22.19.21)(@vue/compiler-sfc@3.5.38)(esbuild@0.23.1)(lightningcss@1.32.0)(markdown-it@14.2.0)(playwright-chromium@1.60.0)(postcss@8.5.15)(rolldown@1.0.3)(sass@1.101.0)(tsx@4.22.4)
|
||||
version: 52.16.0(@nuxt/kit@3.21.8)(@types/markdown-it@14.1.2)(@types/node@22.19.21)(@vue/compiler-sfc@3.5.38)(esbuild@0.23.1)(lightningcss@1.32.0)(markdown-it@14.2.0)(postcss@8.5.15)(rolldown@1.0.3)(sass@1.101.0)(tsx@4.22.4)
|
||||
'@slidev/theme-default':
|
||||
specifier: latest
|
||||
version: 0.25.0
|
||||
@@ -29,10 +29,6 @@ importers:
|
||||
vue:
|
||||
specifier: ^3.5.33
|
||||
version: 3.5.38(typescript@6.0.3)
|
||||
devDependencies:
|
||||
playwright-chromium:
|
||||
specifier: ^1.60.0
|
||||
version: 1.60.0
|
||||
|
||||
packages:
|
||||
|
||||
@@ -3331,16 +3327,6 @@ packages:
|
||||
plantuml-encoder@1.4.0:
|
||||
resolution: {integrity: sha512-sxMwpDw/ySY1WB2CE3+IdMuEcWibJ72DDOsXLkSmEaSzwEUaYBT6DWgOfBiHGCux4q433X6+OEFWjlVqp7gL6g==}
|
||||
|
||||
playwright-chromium@1.60.0:
|
||||
resolution: {integrity: sha512-xxz9pc2HIxQW/Qg9ijG2fZOHRT//KhLo0KfvJRa45YYRrcA7ZONoilgJR40SW5pmecb6HkuROaeViXoCaXTZyQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright-core@1.60.0:
|
||||
resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
points-on-curve@0.2.0:
|
||||
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
|
||||
|
||||
@@ -5109,7 +5095,7 @@ snapshots:
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@slidev/cli@52.16.0(@nuxt/kit@3.21.8)(@types/markdown-it@14.1.2)(@types/node@22.19.21)(@vue/compiler-sfc@3.5.38)(esbuild@0.23.1)(lightningcss@1.32.0)(markdown-it@14.2.0)(playwright-chromium@1.60.0)(postcss@8.5.15)(rolldown@1.0.3)(sass@1.101.0)(tsx@4.22.4)':
|
||||
'@slidev/cli@52.16.0(@nuxt/kit@3.21.8)(@types/markdown-it@14.1.2)(@types/node@22.19.21)(@vue/compiler-sfc@3.5.38)(esbuild@0.23.1)(lightningcss@1.32.0)(markdown-it@14.2.0)(postcss@8.5.15)(rolldown@1.0.3)(sass@1.101.0)(tsx@4.22.4)':
|
||||
dependencies:
|
||||
'@antfu/ni': 30.1.0
|
||||
'@antfu/utils': 9.3.0
|
||||
@@ -5182,8 +5168,6 @@ snapshots:
|
||||
vue: 3.5.38(typescript@6.0.3)
|
||||
yaml: 2.9.0
|
||||
yargs: 18.0.0
|
||||
optionalDependencies:
|
||||
playwright-chromium: 1.60.0
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- '@nuxt/kit'
|
||||
@@ -7841,12 +7825,6 @@ snapshots:
|
||||
|
||||
plantuml-encoder@1.4.0: {}
|
||||
|
||||
playwright-chromium@1.60.0:
|
||||
dependencies:
|
||||
playwright-core: 1.60.0
|
||||
|
||||
playwright-core@1.60.0: {}
|
||||
|
||||
points-on-curve@0.2.0: {}
|
||||
|
||||
points-on-path@0.2.1:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
allowBuilds:
|
||||
'@parcel/watcher': true
|
||||
esbuild: true
|
||||
playwright-chromium: true
|
||||
vue-demi: true
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { escapeHtml, logTo, setText } from "../shared.js";
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: "View Transitions API",
|
||||
available: () => "startViewTransition" in document,
|
||||
advice: "可作为体验增强;不支持时直接更新 DOM。",
|
||||
},
|
||||
{
|
||||
name: "Popover API",
|
||||
available: () => "popover" in HTMLElement.prototype,
|
||||
advice: "适合替代一部分自研浮层;保留 dialog/menu fallback。",
|
||||
},
|
||||
{
|
||||
name: "CSS Container Queries",
|
||||
available: () => CSS.supports("container-type", "inline-size"),
|
||||
advice: "适合组件级响应式;老环境使用传统 media query。",
|
||||
},
|
||||
{
|
||||
name: "Prompt API",
|
||||
available: () => "LanguageModel" in window,
|
||||
advice: "实验/受环境限制;必须做能力检测和 mock 或云端 fallback。",
|
||||
},
|
||||
{
|
||||
name: "WebMCP",
|
||||
available: () => "modelContext" in navigator,
|
||||
advice: "实验方向;适合演示 Agentic Web 的工具契约。",
|
||||
},
|
||||
];
|
||||
|
||||
const table = document.querySelector("#feature-table");
|
||||
const counts = { ready: 0, enhanced: 0, blocked: 0 };
|
||||
|
||||
table.innerHTML = features
|
||||
.map((feature) => {
|
||||
const ok = feature.available();
|
||||
const type = ok ? "ready" : feature.name.includes("API") ? "enhanced" : "blocked";
|
||||
counts[type] += 1;
|
||||
logTo("#log", "baseline.feature", {
|
||||
feature: feature.name,
|
||||
available: ok,
|
||||
advice: feature.advice,
|
||||
});
|
||||
return `<tr>
|
||||
<td><strong>${escapeHtml(feature.name)}</strong></td>
|
||||
<td><span class="pill ${ok ? "green" : "amber"}">${ok ? "available" : "not detected"}</span></td>
|
||||
<td>${escapeHtml(feature.advice)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
setText("#ready-count", String(counts.ready));
|
||||
setText("#enhanced-count", String(counts.enhanced));
|
||||
setText("#blocked-count", String(counts.blocked));
|
||||
@@ -1,70 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Baseline Demo</title>
|
||||
<link rel="stylesheet" href="../shared.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<aside class="rail">
|
||||
<p class="eyebrow">Web Platform</p>
|
||||
<h1>Baseline</h1>
|
||||
<p class="lead">演示如何把“能不能用某个 Web API”变成特性状态、运行检测和 fallback 策略。</p>
|
||||
<nav class="nav" aria-label="Demo navigation">
|
||||
<a href="../index.html"><span class="num">0</span><span>返回总览</span></a>
|
||||
<a href="../webmcp/index.html"><span class="num">1</span><span>WebMCP</span></a>
|
||||
<a href="../prompt-api/index.html"><span class="num">2</span><span>Prompt API</span></a>
|
||||
<a href="../devtools-agents/index.html"><span class="num">3</span><span>DevTools Agents</span></a>
|
||||
<a href="../modern-web-guidance/index.html"><span class="num">4</span><span>Modern Web Guidance</span></a>
|
||||
<a class="active" href="./index.html"><span class="num">5</span><span>Baseline</span></a>
|
||||
<a href="../view-transitions/index.html"><span class="num">6</span><span>View Transitions</span></a>
|
||||
</nav>
|
||||
<div class="note">本地矩阵用于现场讲述;生产判断仍应回到官方 Baseline 数据和项目目标浏览器。</div>
|
||||
</aside>
|
||||
|
||||
<section class="stage">
|
||||
<div class="hero">
|
||||
<section class="statement">
|
||||
<p class="eyebrow">Can I ship it?</p>
|
||||
<h2>从“Chrome 支持了”升级到“目标用户能稳定用”。</h2>
|
||||
<p>
|
||||
Baseline 的价值不是替代 feature detection,而是把团队讨论从版本号转成特性成熟度和采用策略。
|
||||
</p>
|
||||
</section>
|
||||
<aside class="metrics">
|
||||
<div class="metric"><strong id="ready-count">0</strong><span>当前环境可直接运行的能力</span></div>
|
||||
<div class="metric"><strong id="enhanced-count">0</strong><span>适合渐进增强的能力</span></div>
|
||||
<div class="metric"><strong id="blocked-count">0</strong><span>需要 fallback 或实验开关</span></div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section class="two-col">
|
||||
<div class="panel">
|
||||
<p class="eyebrow">Feature matrix</p>
|
||||
<h2>本地特性检测</h2>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>能力</th>
|
||||
<th>运行检测</th>
|
||||
<th>采用建议</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="feature-table"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<aside class="console">
|
||||
<p class="eyebrow">Decision log</p>
|
||||
<h2>判断依据</h2>
|
||||
<pre id="log">正在检测...</pre>
|
||||
</aside>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="module" src="./baseline.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,14 +0,0 @@
|
||||
console.warn("Demo warning: this page intentionally includes diagnosable issues.");
|
||||
|
||||
const longTaskButton = document.querySelector("#long-task");
|
||||
const taskResult = document.querySelector("#task-result");
|
||||
|
||||
longTaskButton.addEventListener("click", () => {
|
||||
const start = performance.now();
|
||||
while (performance.now() - start < 650) {
|
||||
Math.sqrt(Math.random() * Number.MAX_SAFE_INTEGER);
|
||||
}
|
||||
const duration = Math.round(performance.now() - start);
|
||||
taskResult.textContent = `已触发约 ${duration}ms 的主线程长任务。`;
|
||||
console.error("Demo error: long task was triggered for DevTools MCP inspection.");
|
||||
});
|
||||
@@ -1,68 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>DevTools for Agents Demo Target</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<section class="hero">
|
||||
<p class="eyebrow">Chrome DevTools for agents</p>
|
||||
<h1>给 Agent 一个真实浏览器现场</h1>
|
||||
<p>
|
||||
这个页面故意包含 console warning、404 图片、可触发长任务和布局溢出。
|
||||
演示时让 Chrome DevTools MCP 连接到这个页面,要求 Agent 读取 console、network、DOM 和 performance。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="prompt-board">
|
||||
<article>
|
||||
<p class="eyebrow">Official setup</p>
|
||||
<h2>MCP 配置</h2>
|
||||
<pre><code>{
|
||||
"mcpServers": {
|
||||
"chrome-devtools": {
|
||||
"command": "npx",
|
||||
"args": ["chrome-devtools-mcp@latest"]
|
||||
}
|
||||
}
|
||||
}</code></pre>
|
||||
</article>
|
||||
|
||||
<article>
|
||||
<p class="eyebrow">Prompt to run</p>
|
||||
<h2>现场提问</h2>
|
||||
<pre><code>Open http://localhost:3030/demo/devtools-agents/index.html.
|
||||
Inspect console and network errors.
|
||||
Then click "触发长任务" and record a performance trace.
|
||||
Summarize what you found.</code></pre>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="target">
|
||||
<div class="broken-card">
|
||||
<span>Network</span>
|
||||
<h2>这张图片故意 404</h2>
|
||||
<img src="./missing-dashboard.png" alt="Missing dashboard screenshot" />
|
||||
</div>
|
||||
|
||||
<div class="broken-card overflow">
|
||||
<span>Layout</span>
|
||||
<h2>这段文本故意撑破容器</h2>
|
||||
<p>SuperLongUnbrokenEnterpriseDashboardIdentifier_ChromeAgentRuntimeDebugging_2026_Overflow</p>
|
||||
</div>
|
||||
|
||||
<div class="broken-card">
|
||||
<span>Performance</span>
|
||||
<h2>触发可检测的长任务</h2>
|
||||
<button id="long-task">触发长任务</button>
|
||||
<p id="task-result">等待触发。</p>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="./app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,139 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #111827;
|
||||
--muted: #667085;
|
||||
--paper: #f7f8fa;
|
||||
--panel: #ffffff;
|
||||
--line: #d7dce2;
|
||||
--red: #b42318;
|
||||
--blue: #1849a9;
|
||||
--code: #111827;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(90deg, rgb(17 24 39 / 0.05) 1px, transparent 1px),
|
||||
linear-gradient(rgb(17 24 39 / 0.05) 1px, transparent 1px),
|
||||
var(--paper);
|
||||
background-size: 28px 28px;
|
||||
font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(1180px, calc(100vw - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 42px 0 60px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 760px;
|
||||
margin-bottom: 18px;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(42px, 7vw, 86px);
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.hero p,
|
||||
.broken-card p {
|
||||
max-width: 760px;
|
||||
color: var(--muted);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.prompt-board,
|
||||
.target {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 18px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.target {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
article,
|
||||
.broken-card {
|
||||
border: 1px solid var(--line);
|
||||
background: rgb(255 255 255 / 0.92);
|
||||
box-shadow: 0 18px 58px rgb(17 24 39 / 0.12);
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
border: 1px solid #2f3947;
|
||||
background: var(--code);
|
||||
color: #e7edf4;
|
||||
padding: 16px;
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.broken-card span {
|
||||
display: inline-flex;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid rgb(180 35 24 / 0.35);
|
||||
padding: 4px 9px;
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.broken-card img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
border: 1px dashed var(--red);
|
||||
background: #fff5f4;
|
||||
}
|
||||
|
||||
.overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.overflow p {
|
||||
width: max-content;
|
||||
color: var(--blue);
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 42px;
|
||||
border: 1px solid var(--ink);
|
||||
background: var(--ink);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 10px 14px;
|
||||
font: inherit;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.prompt-board,
|
||||
.target {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Chrome API Demo Desk</title>
|
||||
<link rel="stylesheet" href="./shared.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<aside class="rail">
|
||||
<p class="eyebrow">Public Demo Desk</p>
|
||||
<h1>Chrome API Lab</h1>
|
||||
<p class="lead">
|
||||
这里专门放可运行 demo。启动方式统一使用 <strong>npm run dev</strong>,再访问
|
||||
<strong>/demo/</strong> 下的不同能力页面。
|
||||
</p>
|
||||
<nav class="nav" aria-label="Chrome API demos">
|
||||
<a class="active" href="./index.html"><span class="num">0</span><span>Demo 总览</span></a>
|
||||
<a href="./webmcp/index.html"><span class="num">1</span><span>WebMCP</span></a>
|
||||
<a href="./prompt-api/index.html"><span class="num">2</span><span>Prompt API</span></a>
|
||||
<a href="./devtools-agents/index.html"><span class="num">3</span><span>DevTools Agents</span></a>
|
||||
<a href="./modern-web-guidance/index.html"><span class="num">4</span><span>Modern Web Guidance</span></a>
|
||||
<a href="../html-in-canvas/index.html"><span class="num">5</span><span>HTML-in-Canvas</span></a>
|
||||
<a href="./view-transitions/index.html"><span class="num">6</span><span>View Transitions</span></a>
|
||||
<a href="./baseline/index.html"><span class="num">7</span><span>Baseline</span></a>
|
||||
</nav>
|
||||
<div class="note">
|
||||
这部分只修改 public 静态代码,不进入 Slidev PPT 页面。实验 API 都按渐进增强处理,有能力检测和本地兜底。
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="stage">
|
||||
<div class="hero">
|
||||
<section class="statement">
|
||||
<p class="eyebrow">Run with npm run dev</p>
|
||||
<h2>一个入口,分别展示不同 Chrome API 能力。</h2>
|
||||
<p>
|
||||
总览页负责现场导航;每个能力页都可以独立打开、独立演示,不依赖 PPT 路由。
|
||||
适合分享时从幻灯片跳出,展示真实浏览器中的 API 边界和 fallback。
|
||||
</p>
|
||||
</section>
|
||||
<aside class="metrics">
|
||||
<div class="metric">
|
||||
<strong>7</strong>
|
||||
<span>WebMCP、Prompt API、DevTools Agents、Modern Web Guidance、HTML-in-Canvas、View Transitions、Baseline</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong>静态</strong>
|
||||
<span>全部放在 public 静态目录,由 Slidev dev server 直接托管</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong>兜底</strong>
|
||||
<span>实验能力不可用时仍能讲清楚输入、输出和边界</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section class="section">
|
||||
<h2>能力页面</h2>
|
||||
<div class="grid">
|
||||
<a class="card" href="./webmcp/index.html">
|
||||
<span class="tag blue">Agentic Web</span>
|
||||
<h3>WebMCP 工具契约</h3>
|
||||
<p>声明式表单工具、JavaScript 注册工具、工具 schema 和复合任务调用日志。</p>
|
||||
<ul>
|
||||
<li>能力检测:navigator.modelContext</li>
|
||||
<li>兜底:本地 mock runtime</li>
|
||||
</ul>
|
||||
</a>
|
||||
<a class="card" href="./prompt-api/index.html">
|
||||
<span class="tag green">Built-in AI</span>
|
||||
<h3>Prompt API</h3>
|
||||
<p>检测 LanguageModel,可用时创建本地模型会话,不可用时展示 mock 摘要流程。</p>
|
||||
<ul>
|
||||
<li>能力检测:window.LanguageModel</li>
|
||||
<li>重点:本地模型、下载状态、渐进增强</li>
|
||||
</ul>
|
||||
</a>
|
||||
<a class="card" href="./devtools-agents/index.html">
|
||||
<span class="tag violet">Workflow</span>
|
||||
<h3>DevTools for Agents</h3>
|
||||
<p>提供一个故意带问题的页面,让 Chrome DevTools MCP 检查 console、network 和 performance。</p>
|
||||
<ul>
|
||||
<li>官方工具:chrome-devtools-mcp</li>
|
||||
<li>重点:让 Agent 看到真实浏览器运行时</li>
|
||||
</ul>
|
||||
</a>
|
||||
<a class="card" href="./modern-web-guidance/index.html">
|
||||
<span class="tag green">Guidance</span>
|
||||
<h3>Modern Web Guidance</h3>
|
||||
<p>展示官方 CLI 如何给 AI coding agent 检索现代 Web 用法和 Baseline 边界。</p>
|
||||
<ul>
|
||||
<li>重点:把现代 Web 知识注入 agent</li>
|
||||
<li>兜底:可复制命令和预期输出</li>
|
||||
</ul>
|
||||
</a>
|
||||
<a class="card" href="../html-in-canvas/index.html">
|
||||
<span class="tag amber">Origin Trial</span>
|
||||
<h3>HTML-in-Canvas</h3>
|
||||
<p>展示真实 DOM 表单绘制进 canvas 的 API 方向;不支持时显示 fallback 预览。</p>
|
||||
<ul>
|
||||
<li>能力检测:drawElementImage / requestPaint</li>
|
||||
<li>重点:不是 canvas 手绘控件</li>
|
||||
</ul>
|
||||
</a>
|
||||
<a class="card" href="./baseline/index.html">
|
||||
<span class="tag amber">Compatibility</span>
|
||||
<h3>Baseline</h3>
|
||||
<p>把“能不能放心用”从浏览器版本猜测,改成特性状态和 fallback 策略。</p>
|
||||
<ul>
|
||||
<li>重点:跨浏览器采用判断</li>
|
||||
<li>兜底:本地特性矩阵</li>
|
||||
</ul>
|
||||
</a>
|
||||
<a class="card" href="./view-transitions/index.html">
|
||||
<span class="tag red">UX API</span>
|
||||
<h3>View Transitions</h3>
|
||||
<p>展示 same-document 页面切换动画,并在不支持时退化为普通 DOM 更新。</p>
|
||||
<ul>
|
||||
<li>能力检测:document.startViewTransition</li>
|
||||
<li>重点:体验增强而非业务依赖</li>
|
||||
</ul>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<h2>本地运行</h2>
|
||||
<pre class="code"><code>npm run dev
|
||||
|
||||
# Slidev dev server 启动后访问:
|
||||
http://localhost:3030/demo/index.html
|
||||
http://localhost:3030/demo/webmcp/index.html
|
||||
http://localhost:3030/demo/prompt-api/index.html
|
||||
http://localhost:3030/demo/devtools-agents/index.html
|
||||
http://localhost:3030/demo/modern-web-guidance/index.html
|
||||
http://localhost:3030/html-in-canvas/index.html
|
||||
http://localhost:3030/demo/baseline/index.html
|
||||
http://localhost:3030/demo/view-transitions/index.html
|
||||
|
||||
# Modern Web Guidance 官方 CLI
|
||||
scripts/demo-modern-web-guidance.sh</code></pre>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,16 +0,0 @@
|
||||
const dialog = document.querySelector("#guided-dialog");
|
||||
const openDialog = document.querySelector("#open-dialog");
|
||||
const copyButton = document.querySelector("#copy");
|
||||
const commands = document.querySelector("#commands");
|
||||
|
||||
openDialog.addEventListener("click", () => {
|
||||
dialog.showModal();
|
||||
});
|
||||
|
||||
copyButton.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(commands.textContent.trim());
|
||||
copyButton.textContent = "已复制";
|
||||
setTimeout(() => {
|
||||
copyButton.textContent = "复制命令";
|
||||
}, 1200);
|
||||
});
|
||||
@@ -1,87 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Modern Web Guidance Demo</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<section class="hero">
|
||||
<p class="eyebrow">Official CLI demo</p>
|
||||
<h1>Modern Web Guidance</h1>
|
||||
<p>
|
||||
这段演示直接使用 Chrome 官方 CLI:先搜索某个 Web 开发意图,再取回对应指南。
|
||||
重点不是背 API,而是让开发者看到 AI coding agent 如何被现代 Web 知识和 Baseline 边界纠偏。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<article class="terminal">
|
||||
<div class="terminal-head">
|
||||
<span></span><span></span><span></span>
|
||||
<strong>terminal</strong>
|
||||
</div>
|
||||
<pre><code id="commands">npx modern-web-guidance@latest search "animate a dialog modal backdrop"
|
||||
npx modern-web-guidance@latest retrieve "animate-to-from-top-layer"</code></pre>
|
||||
<button id="copy">复制命令</button>
|
||||
</article>
|
||||
|
||||
<article class="result">
|
||||
<p class="eyebrow">Expected official output</p>
|
||||
<h2>搜索会返回 use case</h2>
|
||||
<ul>
|
||||
<li><strong>light-dismiss-a-dialog</strong>:用原生 dialog 处理 light dismiss。</li>
|
||||
<li><strong>animate-to-from-top-layer</strong>:用现代 CSS 动画进入/离开 top layer。</li>
|
||||
<li><strong>declarative-dialog-popover-control</strong>:用声明式控制 dialog / popover。</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="demo">
|
||||
<div>
|
||||
<p class="eyebrow">Guided implementation</p>
|
||||
<h2>指南会把 Agent 拉回现代 Web 写法</h2>
|
||||
<p>
|
||||
这个页面用一个可运行的 dialog 动画做对应说明:优先使用原生 <code><dialog></code>、
|
||||
<code>::backdrop</code>、<code>@starting-style</code> 和 <code>transition-behavior</code>,
|
||||
而不是用大量 JavaScript 重新造弹层。
|
||||
</p>
|
||||
<button id="open-dialog">打开 dialog demo</button>
|
||||
</div>
|
||||
|
||||
<pre class="code"><code>dialog {
|
||||
opacity: 0;
|
||||
transform: translateY(16px) scale(0.96);
|
||||
transition-property: opacity, transform, display, overlay;
|
||||
transition-duration: 220ms;
|
||||
transition-behavior: allow-discrete;
|
||||
}
|
||||
|
||||
dialog[open] {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
dialog[open] {
|
||||
opacity: 0;
|
||||
transform: translateY(16px) scale(0.96);
|
||||
}
|
||||
}</code></pre>
|
||||
</section>
|
||||
|
||||
<dialog id="guided-dialog">
|
||||
<form method="dialog">
|
||||
<p class="eyebrow">Native top layer</p>
|
||||
<h2>这是原生 dialog</h2>
|
||||
<p>演示重点:Modern Web Guidance 让 Agent 找到现代 HTML/CSS 能力,并提醒兼容和降级边界。</p>
|
||||
<button>关闭</button>
|
||||
</form>
|
||||
</dialog>
|
||||
</main>
|
||||
|
||||
<script src="./app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,222 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #17202a;
|
||||
--muted: #5f6f78;
|
||||
--paper: #fffef8;
|
||||
--panel: #eff4ef;
|
||||
--line: #cfd9cd;
|
||||
--green: #0b6b4f;
|
||||
--blue: #174ea6;
|
||||
--amber: #a46400;
|
||||
--code: #14202a;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(90deg, rgb(23 32 42 / 0.05) 1px, transparent 1px),
|
||||
linear-gradient(rgb(23 32 42 / 0.05) 1px, transparent 1px),
|
||||
#edf4ef;
|
||||
background-size: 30px 30px;
|
||||
font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 42px;
|
||||
border: 1px solid var(--ink);
|
||||
background: var(--ink);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
padding: 10px 14px;
|
||||
font: inherit;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(1180px, calc(100vw - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 40px 0 64px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
color: var(--green);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 780px;
|
||||
margin-bottom: 14px;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(42px, 7vw, 86px);
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.hero p,
|
||||
.demo p,
|
||||
.result li {
|
||||
color: var(--muted);
|
||||
line-height: 1.72;
|
||||
}
|
||||
|
||||
.grid,
|
||||
.demo {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(320px, 0.82fr);
|
||||
gap: 20px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.terminal,
|
||||
.result,
|
||||
.demo {
|
||||
border: 1px solid var(--line);
|
||||
background: rgb(255 254 248 / 0.92);
|
||||
box-shadow: 0 18px 58px rgb(23 32 42 / 0.12);
|
||||
}
|
||||
|
||||
.terminal,
|
||||
.result {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
background: var(--code);
|
||||
color: #dfe8ef;
|
||||
}
|
||||
|
||||
.terminal-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.terminal-head span {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 999px;
|
||||
background: #e15c4f;
|
||||
}
|
||||
|
||||
.terminal-head span:nth-child(2) {
|
||||
background: #e5ad35;
|
||||
}
|
||||
|
||||
.terminal-head span:nth-child(3) {
|
||||
background: #42aa6d;
|
||||
}
|
||||
|
||||
.terminal-head strong {
|
||||
margin-left: 8px;
|
||||
color: #9fb0be;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
margin: 0 0 16px;
|
||||
white-space: pre-wrap;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.result ul {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.demo {
|
||||
align-items: start;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
code {
|
||||
border: 1px solid rgb(23 32 42 / 0.13);
|
||||
background: #fff;
|
||||
padding: 1px 5px;
|
||||
}
|
||||
|
||||
.code {
|
||||
border: 1px solid #2d3944;
|
||||
background: var(--code);
|
||||
color: #dfe8ef;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
dialog {
|
||||
width: min(520px, calc(100vw - 36px));
|
||||
border: 1px solid var(--line);
|
||||
padding: 0;
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
opacity: 0;
|
||||
transform: translateY(16px) scale(0.96);
|
||||
transition-property: opacity, transform, display, overlay;
|
||||
transition-duration: 220ms;
|
||||
transition-timing-function: ease-out;
|
||||
transition-behavior: allow-discrete;
|
||||
}
|
||||
|
||||
dialog[open] {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
@starting-style {
|
||||
dialog[open] {
|
||||
opacity: 0;
|
||||
transform: translateY(16px) scale(0.96);
|
||||
}
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: rgb(12 18 24 / 0);
|
||||
transition:
|
||||
display 220ms allow-discrete,
|
||||
overlay 220ms allow-discrete,
|
||||
background-color 220ms ease-out;
|
||||
}
|
||||
|
||||
dialog[open]::backdrop {
|
||||
background: rgb(12 18 24 / 0.42);
|
||||
}
|
||||
|
||||
dialog form {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
dialog {
|
||||
transform: none;
|
||||
transition-duration: 80ms;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 880px) {
|
||||
.grid,
|
||||
.demo {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Prompt API Demo</title>
|
||||
<link rel="stylesheet" href="../shared.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<aside class="rail">
|
||||
<p class="eyebrow">Built-in AI</p>
|
||||
<h1>Prompt API</h1>
|
||||
<p class="lead">
|
||||
演示网页如何检测 Chrome 内置语言模型能力,并在不可用时退回到可解释的本地 mock。
|
||||
</p>
|
||||
<nav class="nav" aria-label="Demo navigation">
|
||||
<a href="../index.html"><span class="num">0</span><span>返回总览</span></a>
|
||||
<a href="../webmcp/index.html"><span class="num">1</span><span>WebMCP</span></a>
|
||||
<a class="active" href="./index.html"><span class="num">2</span><span>Prompt API</span></a>
|
||||
<a href="../devtools-agents/index.html"><span class="num">3</span><span>DevTools Agents</span></a>
|
||||
<a href="../modern-web-guidance/index.html"><span class="num">4</span><span>Modern Web Guidance</span></a>
|
||||
<a href="../baseline/index.html"><span class="num">5</span><span>Baseline</span></a>
|
||||
<a href="../view-transitions/index.html"><span class="num">6</span><span>View Transitions</span></a>
|
||||
</nav>
|
||||
<div class="note">
|
||||
讲述重点:Prompt API 是渐进增强。先检测 availability,再由用户动作触发模型创建和下载进度。
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="stage">
|
||||
<div class="hero">
|
||||
<section class="statement">
|
||||
<p class="eyebrow">window.LanguageModel</p>
|
||||
<h2>把浏览器内置模型当作页面能力,而不是远程服务。</h2>
|
||||
<p>
|
||||
可用时,页面直接创建本地模型会话;不可用时,页面仍能展示同一套输入、输出和错误边界,
|
||||
让观众理解这个 API 的产品形态。
|
||||
</p>
|
||||
</section>
|
||||
<aside class="metrics">
|
||||
<div class="metric">
|
||||
<strong id="support-status">检测中</strong>
|
||||
<span>当前浏览器 Prompt API 状态</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong id="availability">未知</strong>
|
||||
<span>LanguageModel.availability() 结果</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<strong id="mode">未运行</strong>
|
||||
<span>native 或 mock 执行路径</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<section class="two-col">
|
||||
<div class="panel">
|
||||
<p class="eyebrow">Task</p>
|
||||
<h2>把 Chrome 更新摘要改写成给业务同事看的说明</h2>
|
||||
<label class="field">
|
||||
输入材料
|
||||
<textarea id="prompt-input">Chrome 正在把浏览器能力分成三层:面向用户的体验 API、面向开发者的 DevTools agent workflow,以及面向 AI Agent 的 WebMCP 和 Built-in AI。</textarea>
|
||||
</label>
|
||||
<label class="field">
|
||||
输出风格
|
||||
<select id="tone">
|
||||
<option value="业务说明">业务说明</option>
|
||||
<option value="技术摘要">技术摘要</option>
|
||||
<option value="现场讲稿">现场讲稿</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<button id="run">运行 Prompt API</button>
|
||||
<button id="check" class="secondary">重新检测</button>
|
||||
</div>
|
||||
<pre class="code"><code>const availability = await LanguageModel.availability();
|
||||
const session = await LanguageModel.create({
|
||||
monitor(m) {
|
||||
m.addEventListener("downloadprogress", updateProgress);
|
||||
},
|
||||
});
|
||||
const result = await session.prompt(prompt);</code></pre>
|
||||
</div>
|
||||
|
||||
<aside class="console">
|
||||
<p class="eyebrow">Execution</p>
|
||||
<h2>输出与日志</h2>
|
||||
<pre id="log">等待运行...</pre>
|
||||
</aside>
|
||||
</section>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script type="module" src="./prompt-api.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,74 +0,0 @@
|
||||
import { logTo, setText } from "../shared.js";
|
||||
|
||||
const hasPromptApi = "LanguageModel" in window;
|
||||
|
||||
document.querySelector("#check").addEventListener("click", checkAvailability);
|
||||
document.querySelector("#run").addEventListener("click", runPrompt);
|
||||
|
||||
await checkAvailability();
|
||||
|
||||
async function checkAvailability() {
|
||||
setText("#support-status", hasPromptApi ? "支持" : "未暴露");
|
||||
setText("#mode", hasPromptApi ? "Native" : "Mock");
|
||||
|
||||
if (!hasPromptApi) {
|
||||
setText("#availability", "unavailable");
|
||||
logTo("#log", "prompt-api.detect", {
|
||||
supported: false,
|
||||
fallback: "Use local mock response for the presentation.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const availability = await LanguageModel.availability();
|
||||
setText("#availability", availability);
|
||||
logTo("#log", "prompt-api.availability", { availability });
|
||||
} catch (error) {
|
||||
setText("#availability", "error");
|
||||
logTo("#log", "prompt-api.error", { message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function runPrompt() {
|
||||
const source = document.querySelector("#prompt-input").value.trim();
|
||||
const tone = document.querySelector("#tone").value;
|
||||
const prompt = `请用${tone}风格,用三句话解释下面内容:\n\n${source}`;
|
||||
|
||||
if (!hasPromptApi) {
|
||||
const result = createMockSummary(source, tone);
|
||||
logTo("#log", "prompt-api.mock-result", { prompt, result });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setText("#mode", "Native running");
|
||||
const session = await LanguageModel.create({
|
||||
monitor(monitor) {
|
||||
monitor.addEventListener("downloadprogress", (event) => {
|
||||
const percent = Math.round(event.loaded * 100);
|
||||
setText("#availability", `downloading ${percent}%`);
|
||||
});
|
||||
},
|
||||
});
|
||||
const result = await session.prompt(prompt);
|
||||
setText("#mode", "Native complete");
|
||||
logTo("#log", "prompt-api.native-result", { prompt, result });
|
||||
} catch (error) {
|
||||
setText("#mode", "Native error");
|
||||
logTo("#log", "prompt-api.native-error", {
|
||||
prompt,
|
||||
message: error.message,
|
||||
fallback: createMockSummary(source, tone),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createMockSummary(source, tone) {
|
||||
const first = source.split(/[。.!?]/).find(Boolean)?.trim() || source;
|
||||
return [
|
||||
`[${tone}] Chrome 的新能力可以理解为三条线同时推进。`,
|
||||
`第一条是用户体验升级,第二条是开发者工具升级,第三条是让网页能被 AI Agent 结构化调用。`,
|
||||
`这是一段 mock 输出,用来在当前浏览器没有 Prompt API 时保持演示可运行;输入摘要:${first}。`,
|
||||
].join("");
|
||||
}
|
||||
@@ -1,463 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #121417;
|
||||
--muted: #65727d;
|
||||
--paper: #fffdf8;
|
||||
--panel: #f4f1e8;
|
||||
--line: #d7d0bf;
|
||||
--blue: #164db3;
|
||||
--green: #0c7356;
|
||||
--amber: #a26000;
|
||||
--red: #b03831;
|
||||
--violet: #6747b8;
|
||||
--code: #18222d;
|
||||
--shadow: 0 20px 60px rgb(35 37 42 / 0.13);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(90deg, rgb(18 20 23 / 0.055) 1px, transparent 1px),
|
||||
linear-gradient(rgb(18 20 23 / 0.055) 1px, transparent 1px),
|
||||
#ece6d8;
|
||||
background-size: 30px 30px;
|
||||
font-family: Avenir Next, Trebuchet MS, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.shell {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(260px, 350px) minmax(0, 1fr);
|
||||
gap: 22px;
|
||||
min-height: 100vh;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.rail,
|
||||
.stage,
|
||||
.panel,
|
||||
.card,
|
||||
.console {
|
||||
border: 1px solid var(--line);
|
||||
background: rgb(255 253 248 / 0.92);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.rail {
|
||||
position: sticky;
|
||||
top: 28px;
|
||||
align-self: start;
|
||||
min-height: calc(100vh - 56px);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.stage {
|
||||
padding: 26px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--blue);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 11ch;
|
||||
margin-bottom: 16px;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(40px, 5vw, 72px);
|
||||
line-height: 0.94;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 12px;
|
||||
font-size: clamp(24px, 3vw, 38px);
|
||||
line-height: 1.05;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 8px;
|
||||
font-size: 19px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.lead,
|
||||
.muted,
|
||||
.card p,
|
||||
.panel p,
|
||||
li {
|
||||
color: var(--muted);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 26px 0;
|
||||
}
|
||||
|
||||
.nav a,
|
||||
.nav button,
|
||||
.link-button,
|
||||
.primary,
|
||||
.ghost {
|
||||
display: inline-grid;
|
||||
grid-template-columns: 28px minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 44px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--paper);
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
padding: 10px 12px;
|
||||
text-align: left;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.nav a:hover,
|
||||
.nav a.active,
|
||||
.nav button:hover,
|
||||
.link-button:hover,
|
||||
.ghost:hover {
|
||||
border-color: var(--blue);
|
||||
background: #eef4ff;
|
||||
}
|
||||
|
||||
.primary {
|
||||
border-color: var(--ink);
|
||||
background: var(--ink);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.num,
|
||||
.icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: var(--ink);
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.note {
|
||||
border-left: 4px solid var(--amber);
|
||||
background: #fff3d6;
|
||||
color: #6f4707;
|
||||
padding: 12px 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.25fr) minmax(250px, 0.75fr);
|
||||
gap: 18px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.statement {
|
||||
min-height: 250px;
|
||||
padding: 28px;
|
||||
color: white;
|
||||
background:
|
||||
linear-gradient(135deg, rgb(18 20 23 / 0.96), rgb(22 77 179 / 0.83)),
|
||||
radial-gradient(circle at 20% 18%, rgb(255 255 255 / 0.22), transparent 26%);
|
||||
}
|
||||
|
||||
.statement .eyebrow,
|
||||
.statement p {
|
||||
color: rgb(255 255 255 / 0.78);
|
||||
}
|
||||
|
||||
.statement h2 {
|
||||
max-width: 760px;
|
||||
margin-bottom: 16px;
|
||||
font-size: clamp(30px, 5vw, 58px);
|
||||
}
|
||||
|
||||
.metrics,
|
||||
.grid,
|
||||
.two-col {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.metric,
|
||||
.panel,
|
||||
.card,
|
||||
.console {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.metric strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.metric span {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.two-col {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(340px, 0.82fr);
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tag,
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid transparent;
|
||||
padding: 3px 9px;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.tag.blue,
|
||||
.pill.blue {
|
||||
background: var(--blue);
|
||||
}
|
||||
|
||||
.tag.green,
|
||||
.pill.green {
|
||||
background: var(--green);
|
||||
}
|
||||
|
||||
.tag.amber,
|
||||
.pill.amber {
|
||||
background: var(--amber);
|
||||
}
|
||||
|
||||
.tag.red,
|
||||
.pill.red {
|
||||
background: var(--red);
|
||||
}
|
||||
|
||||
.tag.violet,
|
||||
.pill.violet {
|
||||
background: var(--violet);
|
||||
}
|
||||
|
||||
.pill.neutral {
|
||||
border-color: var(--line);
|
||||
background: var(--paper);
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 190px;
|
||||
transition:
|
||||
transform 160ms ease,
|
||||
box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
box-shadow: 0 14px 34px rgb(22 77 179 / 0.16);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.card ul {
|
||||
margin: 14px 0 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.controls button {
|
||||
min-height: 42px;
|
||||
border: 1px solid var(--ink);
|
||||
background: var(--ink);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 9px 13px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.controls button.secondary {
|
||||
border-color: var(--line);
|
||||
background: white;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.field {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin-bottom: 12px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
background: white;
|
||||
color: var(--ink);
|
||||
padding: 11px 12px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 110px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.code,
|
||||
.console pre {
|
||||
overflow: auto;
|
||||
border: 1px solid #303b48;
|
||||
background: var(--code);
|
||||
color: #d8e4ef;
|
||||
padding: 14px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.code {
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
.console {
|
||||
align-self: start;
|
||||
position: sticky;
|
||||
top: 28px;
|
||||
}
|
||||
|
||||
.console pre {
|
||||
max-height: 460px;
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.status-row,
|
||||
.list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.status {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.status span {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.status strong {
|
||||
display: block;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--paper);
|
||||
}
|
||||
|
||||
.table th,
|
||||
.table td {
|
||||
border: 1px solid var(--line);
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.table th {
|
||||
background: var(--ink);
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.table td {
|
||||
color: var(--muted);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
@media (max-width: 1020px) {
|
||||
.shell,
|
||||
.hero,
|
||||
.two-col {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.rail,
|
||||
.console {
|
||||
position: static;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.grid,
|
||||
.status-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
export function logTo(target, title, payload) {
|
||||
const el = typeof target === "string" ? document.querySelector(target) : target;
|
||||
if (!el) return;
|
||||
const stamp = new Date().toLocaleTimeString("zh-CN", { hour12: false });
|
||||
const entry = {
|
||||
time: stamp,
|
||||
event: title,
|
||||
...payload,
|
||||
};
|
||||
el.textContent = `${JSON.stringify(entry, null, 2)}\n\n${el.textContent}`.trim();
|
||||
}
|
||||
|
||||
export function setText(selector, value) {
|
||||
const el = document.querySelector(selector);
|
||||
if (el) el.textContent = value;
|
||||
}
|
||||
|
||||
export function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
const panel = document.querySelector("#panel");
|
||||
const buttons = Array.from(document.querySelectorAll("[data-view]"));
|
||||
|
||||
const views = {
|
||||
orders: {
|
||||
title: "订单工作台",
|
||||
body: "状态从列表切换到业务面板,浏览器负责把 DOM 更新过渡成连续动画。",
|
||||
metrics: ["128 个待处理", "96.4% 准时率"],
|
||||
},
|
||||
analytics: {
|
||||
title: "实时分析",
|
||||
body: "适合展示页面状态、卡片位置、详情视图之间的平滑切换。",
|
||||
metrics: ["42ms INP", "3 条异常趋势"],
|
||||
},
|
||||
settings: {
|
||||
title: "权限设置",
|
||||
body: "在支持的浏览器里使用 View Transitions;不支持时保留普通状态切换。",
|
||||
metrics: ["7 个角色", "24 项权限"],
|
||||
},
|
||||
};
|
||||
|
||||
function render(view) {
|
||||
const data = views[view];
|
||||
panel.innerHTML = `
|
||||
<article class="card">
|
||||
<p class="eyebrow">Current view</p>
|
||||
<h2>${data.title}</h2>
|
||||
<p>${data.body}</p>
|
||||
</article>
|
||||
<section class="summary">
|
||||
${data.metrics
|
||||
.map((metric) => `<article class="card"><strong>${metric}</strong><span>View transition keeps the context visible.</span></article>`)
|
||||
.join("")}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
|
||||
function setActive(view) {
|
||||
buttons.forEach((button) => {
|
||||
button.classList.toggle("active", button.dataset.view === view);
|
||||
});
|
||||
render(view);
|
||||
}
|
||||
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const next = button.dataset.view;
|
||||
if (document.startViewTransition) {
|
||||
document.startViewTransition(() => setActive(next));
|
||||
return;
|
||||
}
|
||||
setActive(next);
|
||||
});
|
||||
});
|
||||
|
||||
setActive("orders");
|
||||
@@ -1,29 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>View Transitions Demo</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="shell">
|
||||
<section class="hero">
|
||||
<p class="eyebrow">Chrome UI capability</p>
|
||||
<h1>View Transitions</h1>
|
||||
<p>点击不同状态,观察浏览器把 DOM 更新包装成一次连续过渡。没有支持时,页面仍然即时切换。</p>
|
||||
</section>
|
||||
|
||||
<section class="app" id="app">
|
||||
<nav class="tabs" aria-label="Demo states">
|
||||
<button class="active" data-view="orders">订单</button>
|
||||
<button data-view="analytics">分析</button>
|
||||
<button data-view="settings">设置</button>
|
||||
</nav>
|
||||
<div class="panel" id="panel"></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="./app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,126 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #17202a;
|
||||
--muted: #66717c;
|
||||
--paper: #f3f5f7;
|
||||
--panel: #ffffff;
|
||||
--line: #d8dee6;
|
||||
--blue: #1a5fb4;
|
||||
--green: #16784e;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
color: var(--ink);
|
||||
background: var(--paper);
|
||||
font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(1040px, calc(100vw - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 42px 0 60px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 10px;
|
||||
color: var(--blue);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 14px;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(44px, 8vw, 96px);
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
max-width: 680px;
|
||||
color: var(--muted);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.app {
|
||||
margin-top: 26px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
box-shadow: 0 22px 70px rgb(23 32 42 / 0.14);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
button {
|
||||
min-height: 40px;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
cursor: pointer;
|
||||
padding: 9px 14px;
|
||||
font: inherit;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
button.active {
|
||||
border-color: var(--blue);
|
||||
background: #e8f0ff;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.panel {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
min-height: 380px;
|
||||
padding: 22px;
|
||||
view-transition-name: demo-panel;
|
||||
}
|
||||
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: 1.2fr 0.8fr;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.card strong {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
::view-transition-old(demo-panel),
|
||||
::view-transition-new(demo-panel) {
|
||||
animation-duration: 320ms;
|
||||
animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.tabs,
|
||||
.summary {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
const logEl = document.querySelector("#log");
|
||||
const toolListEl = document.querySelector("#tool-list");
|
||||
const nativeStatusEl = document.querySelector("#native-status");
|
||||
const orderStateEl = document.querySelector("#order-state");
|
||||
|
||||
const nativeModelContext = window.navigator.modelContext;
|
||||
const registry = new Map();
|
||||
|
||||
const demoOrders = {
|
||||
"CHR-2026-0614": {
|
||||
orderId: "CHR-2026-0614",
|
||||
status: "delivered",
|
||||
risk: "missing_accessory",
|
||||
customerTier: "enterprise",
|
||||
nextAction: "open_refund_review",
|
||||
},
|
||||
};
|
||||
|
||||
const modelContext = nativeModelContext ?? createMockModelContext();
|
||||
|
||||
nativeStatusEl.textContent = nativeModelContext
|
||||
? "Native WebMCP detected"
|
||||
: "Mock WebMCP runtime";
|
||||
nativeStatusEl.className = `pill ${nativeModelContext ? "native" : "mock"}`;
|
||||
|
||||
registerDeclarativeForms();
|
||||
registerImperativeTools();
|
||||
renderTools();
|
||||
log("runtime.ready", {
|
||||
nativeWebMcp: Boolean(nativeModelContext),
|
||||
registeredTools: Array.from(registry.keys()),
|
||||
});
|
||||
|
||||
document.querySelector("#ticket-form").addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const form = event.currentTarget;
|
||||
const toolName = form.getAttribute("toolname");
|
||||
const payload = Object.fromEntries(new FormData(form).entries());
|
||||
const result = await runTool(toolName, payload);
|
||||
log("form.submit", result);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-run-tool]").forEach((button) => {
|
||||
button.addEventListener("click", async () => {
|
||||
const name = button.getAttribute("data-run-tool");
|
||||
const input = sampleInputFor(name);
|
||||
const result = await runTool(name, input);
|
||||
log(`tool.${name}`, result);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelector("#run-workflow").addEventListener("click", async () => {
|
||||
log("agent.plan", {
|
||||
task: "客户反馈缺少配件,请查询订单、判断是否进入退款审核,并生成回复草稿。",
|
||||
});
|
||||
const order = await runTool("lookup_order_status", { orderId: "CHR-2026-0614" });
|
||||
const refund = await runTool("approve_refund_review", {
|
||||
orderId: order.orderId,
|
||||
reason: "missing_accessory",
|
||||
});
|
||||
const reply = await runTool("draft_customer_reply", {
|
||||
email: "dev@example.com",
|
||||
category: "refund",
|
||||
orderId: order.orderId,
|
||||
});
|
||||
log("agent.workflow.complete", { order, refund, reply });
|
||||
});
|
||||
|
||||
function createMockModelContext() {
|
||||
return {
|
||||
registerTool(tool) {
|
||||
registry.set(tool.name, tool);
|
||||
return Promise.resolve({ name: tool.name, mock: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function registerDeclarativeForms() {
|
||||
document.querySelectorAll("form[toolname]").forEach((form) => {
|
||||
const name = form.getAttribute("toolname");
|
||||
const description = form.getAttribute("tooldescription") || `Submit ${name}`;
|
||||
const inputSchema = schemaFromForm(form);
|
||||
|
||||
registerTool({
|
||||
source: "declarative",
|
||||
name,
|
||||
description,
|
||||
inputSchema,
|
||||
execute: async (input) => {
|
||||
const ticketId = `SUP-${Date.now().toString().slice(-6)}`;
|
||||
return {
|
||||
ticketId,
|
||||
status: "created",
|
||||
summary: `${input.category} issue for ${input.email}`,
|
||||
visibleUpdate: "工单已创建,页面保留原有人类操作流程。",
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function registerImperativeTools() {
|
||||
registerTool({
|
||||
source: "imperative",
|
||||
name: "lookup_order_status",
|
||||
description: "Look up order status, risk and recommended next action by order id.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
orderId: { type: "string", description: "Order id shown in the support workspace." },
|
||||
},
|
||||
required: ["orderId"],
|
||||
},
|
||||
execute: async ({ orderId }) => {
|
||||
const order = demoOrders[orderId] ?? {
|
||||
orderId,
|
||||
status: "unknown",
|
||||
risk: "not_found",
|
||||
customerTier: "unknown",
|
||||
nextAction: "ask_user_for_more_information",
|
||||
};
|
||||
orderStateEl.textContent = order.nextAction;
|
||||
return order;
|
||||
},
|
||||
});
|
||||
|
||||
registerTool({
|
||||
source: "imperative",
|
||||
name: "approve_refund_review",
|
||||
description: "Move an eligible order into refund review after checking business policy.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
orderId: { type: "string" },
|
||||
reason: {
|
||||
type: "string",
|
||||
enum: ["missing_accessory", "damaged_item", "delivery_delay"],
|
||||
},
|
||||
},
|
||||
required: ["orderId", "reason"],
|
||||
},
|
||||
execute: async ({ orderId, reason }) => {
|
||||
orderStateEl.textContent = "已进入退款审核";
|
||||
return {
|
||||
orderId,
|
||||
reason,
|
||||
approvedForReview: true,
|
||||
needsHumanConfirmation: true,
|
||||
message: "敏感动作只进入审核,不直接退款。",
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
registerTool({
|
||||
source: "imperative",
|
||||
name: "draft_customer_reply",
|
||||
description: "Draft a support reply based on customer issue category and order id.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
email: { type: "string", format: "email" },
|
||||
category: { type: "string", enum: ["refund", "delivery", "account"] },
|
||||
orderId: { type: "string" },
|
||||
},
|
||||
required: ["email", "category", "orderId"],
|
||||
},
|
||||
execute: async ({ email, category, orderId }) => ({
|
||||
to: email,
|
||||
category,
|
||||
orderId,
|
||||
draft:
|
||||
"我们已经定位到该订单的缺件问题,并将它提交到退款审核。审核完成前不会自动扣改账户,请等待人工确认。",
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
function registerTool(tool) {
|
||||
registry.set(tool.name, tool);
|
||||
const nativePayload = {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
execute: tool.execute,
|
||||
};
|
||||
|
||||
try {
|
||||
modelContext.registerTool(nativePayload);
|
||||
} catch (error) {
|
||||
log("register.error", { tool: tool.name, message: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function runTool(name, input) {
|
||||
const tool = registry.get(name);
|
||||
if (!tool) {
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
const result = await tool.execute(input);
|
||||
return { tool: name, input, output: result };
|
||||
}
|
||||
|
||||
function schemaFromForm(form) {
|
||||
const properties = {};
|
||||
const required = [];
|
||||
form.querySelectorAll("input[name], select[name], textarea[name]").forEach((field) => {
|
||||
const name = field.getAttribute("name");
|
||||
properties[name] = {
|
||||
type: inputTypeToJsonSchema(field),
|
||||
description: field.closest("label")?.firstChild?.textContent?.trim() || name,
|
||||
};
|
||||
if (field.hasAttribute("required")) {
|
||||
required.push(name);
|
||||
}
|
||||
});
|
||||
return { type: "object", properties, required };
|
||||
}
|
||||
|
||||
function inputTypeToJsonSchema(field) {
|
||||
if (field.tagName === "SELECT" || field.tagName === "TEXTAREA") {
|
||||
return "string";
|
||||
}
|
||||
if (field.type === "number" || field.type === "range") {
|
||||
return "number";
|
||||
}
|
||||
if (field.type === "checkbox") {
|
||||
return "boolean";
|
||||
}
|
||||
return "string";
|
||||
}
|
||||
|
||||
function renderTools() {
|
||||
toolListEl.innerHTML = "";
|
||||
registry.forEach((tool) => {
|
||||
const card = document.createElement("article");
|
||||
card.className = "tool-card";
|
||||
card.innerHTML = `
|
||||
<h3>${tool.name}</h3>
|
||||
<p>${tool.description}</p>
|
||||
<span class="pill">${tool.source}</span>
|
||||
<pre class="schema">${escapeHtml(JSON.stringify(tool.inputSchema, null, 2))}</pre>
|
||||
`;
|
||||
toolListEl.append(card);
|
||||
});
|
||||
}
|
||||
|
||||
function sampleInputFor(name) {
|
||||
const samples = {
|
||||
create_support_ticket: {
|
||||
email: "dev@example.com",
|
||||
category: "refund",
|
||||
description: "订单 CHR-2026-0614 到货后发现配件缺失。",
|
||||
},
|
||||
lookup_order_status: { orderId: "CHR-2026-0614" },
|
||||
approve_refund_review: { orderId: "CHR-2026-0614", reason: "missing_accessory" },
|
||||
draft_customer_reply: {
|
||||
email: "dev@example.com",
|
||||
category: "refund",
|
||||
orderId: "CHR-2026-0614",
|
||||
},
|
||||
};
|
||||
return samples[name] ?? {};
|
||||
}
|
||||
|
||||
function log(title, payload) {
|
||||
const entry = document.createElement("div");
|
||||
entry.className = "entry";
|
||||
entry.innerHTML = `<strong>${title}</strong><pre>${escapeHtml(JSON.stringify(payload, null, 2))}</pre>`;
|
||||
logEl.prepend(entry);
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>WebMCP Demo - Tool Contracts</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Runnable demo</p>
|
||||
<h1>WebMCP 工具契约演示</h1>
|
||||
</div>
|
||||
<div class="runtime">
|
||||
<span id="native-status" class="pill">检测中</span>
|
||||
<a class="demo-home" href="../index.html">Demo 总览</a>
|
||||
<a href="https://developer.chrome.com/docs/ai/webmcp" target="_blank" rel="noreferrer">Chrome 官方文档</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="layout">
|
||||
<section class="workspace">
|
||||
<div class="intro">
|
||||
<p>
|
||||
这个页面模拟一个售后后台:左侧是人能操作的业务 UI,右侧是页面暴露给 Agent 的工具契约。
|
||||
如果当前 Chrome 没有开启 WebMCP,本页面会使用本地 mock,让现场仍然能看清楚“工具注册”和“工具调用”的流程。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">Declarative API</p>
|
||||
<h2>声明式:把 HTML 表单变成工具</h2>
|
||||
</div>
|
||||
<span class="mode">form attributes</span>
|
||||
</div>
|
||||
|
||||
<form
|
||||
id="ticket-form"
|
||||
class="business-form"
|
||||
toolname="create_support_ticket"
|
||||
tooldescription="Create a support ticket from user supplied issue details."
|
||||
>
|
||||
<label>
|
||||
客户邮箱
|
||||
<input name="email" type="email" required value="dev@example.com" />
|
||||
</label>
|
||||
<label>
|
||||
问题类型
|
||||
<select name="category" required>
|
||||
<option value="refund">退款</option>
|
||||
<option value="delivery">物流</option>
|
||||
<option value="account">账号</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
问题描述
|
||||
<textarea name="description" required>订单 CHR-2026-0614 到货后发现配件缺失。</textarea>
|
||||
</label>
|
||||
<button type="submit">提交工单</button>
|
||||
</form>
|
||||
|
||||
<pre class="code"><code><form
|
||||
toolname="create_support_ticket"
|
||||
tooldescription="Create a support ticket from user supplied issue details.">
|
||||
<input name="email" type="email" required />
|
||||
<select name="category" required>...</select>
|
||||
<textarea name="description" required></textarea>
|
||||
</form></code></pre>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<p class="eyebrow">Imperative API</p>
|
||||
<h2>指令式:用 JavaScript 注册业务工具</h2>
|
||||
</div>
|
||||
<span class="mode">registerTool()</span>
|
||||
</div>
|
||||
|
||||
<div class="order-grid">
|
||||
<article>
|
||||
<span>订单号</span>
|
||||
<strong>CHR-2026-0614</strong>
|
||||
<small>已签收,缺件风险</small>
|
||||
</article>
|
||||
<article>
|
||||
<span>客户等级</span>
|
||||
<strong>Enterprise</strong>
|
||||
<small>允许优先退款审核</small>
|
||||
</article>
|
||||
<article>
|
||||
<span>当前状态</span>
|
||||
<strong id="order-state">等待处理</strong>
|
||||
<small>由工具调用更新</small>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="action-row">
|
||||
<button data-run-tool="lookup_order_status">运行 lookup_order_status</button>
|
||||
<button data-run-tool="draft_customer_reply">运行 draft_customer_reply</button>
|
||||
<button data-run-tool="approve_refund_review">运行 approve_refund_review</button>
|
||||
</div>
|
||||
|
||||
<pre class="code"><code>navigator.modelContext.registerTool({
|
||||
name: "lookup_order_status",
|
||||
description: "Look up order status by order id.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: { orderId: { type: "string" } },
|
||||
required: ["orderId"]
|
||||
},
|
||||
execute: async ({ orderId }) => lookupOrder(orderId)
|
||||
});</code></pre>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<aside class="agent">
|
||||
<section class="agent-panel">
|
||||
<p class="eyebrow">Agent view</p>
|
||||
<h2>已暴露工具</h2>
|
||||
<div id="tool-list" class="tool-list"></div>
|
||||
</section>
|
||||
|
||||
<section class="agent-panel">
|
||||
<p class="eyebrow">Run script</p>
|
||||
<h2>现场演示步骤</h2>
|
||||
<ol class="steps">
|
||||
<li>先提交左侧表单,说明“人类 UI 仍然可用”。</li>
|
||||
<li>看右侧工具列表,说明 Agent 看到的是名称、描述和 JSON Schema。</li>
|
||||
<li>点击工具运行按钮,说明 Agent 不需要猜按钮位置,而是调用明确工具。</li>
|
||||
<li>点击“执行复合任务”,串起查询、决策和回复草稿。</li>
|
||||
</ol>
|
||||
<button id="run-workflow" class="primary">执行复合任务</button>
|
||||
</section>
|
||||
|
||||
<section class="agent-panel log-panel">
|
||||
<p class="eyebrow">Execution log</p>
|
||||
<h2>调用日志</h2>
|
||||
<div id="log" class="log"></div>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<script type="module" src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,359 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #161a1d;
|
||||
--muted: #60717c;
|
||||
--paper: #fffdfa;
|
||||
--panel: #f3f0e8;
|
||||
--line: #d9d2c2;
|
||||
--blue: #174ea6;
|
||||
--green: #116b4f;
|
||||
--amber: #9a5c00;
|
||||
--red: #a7352a;
|
||||
--code: #1b2430;
|
||||
--shadow: 0 20px 70px rgb(50 45 34 / 0.16);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(90deg, rgb(22 26 29 / 0.06) 1px, transparent 1px),
|
||||
linear-gradient(rgb(22 26 29 / 0.06) 1px, transparent 1px),
|
||||
#e7e1d6;
|
||||
background-size: 28px 28px;
|
||||
font-family: ui-sans-serif, "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue);
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 28px 32px 18px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: var(--blue);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(34px, 5vw, 68px);
|
||||
line-height: 0.95;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
font-size: 22px;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.runtime {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.demo-home {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--paper);
|
||||
padding: 6px 10px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.demo-home:hover {
|
||||
border-color: var(--blue);
|
||||
background: #eef4ff;
|
||||
}
|
||||
|
||||
.pill,
|
||||
.mode {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
border: 1px solid var(--line);
|
||||
padding: 4px 10px;
|
||||
background: var(--paper);
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.pill.native {
|
||||
border-color: rgb(17 107 79 / 0.35);
|
||||
background: #e8f4ee;
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.pill.mock {
|
||||
border-color: rgb(154 92 0 / 0.35);
|
||||
background: #fff3d8;
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
|
||||
gap: 22px;
|
||||
padding: 0 32px 32px;
|
||||
}
|
||||
|
||||
.workspace,
|
||||
.agent {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.intro,
|
||||
.panel,
|
||||
.agent-panel {
|
||||
border: 1px solid var(--line);
|
||||
background: rgb(255 253 250 / 0.92);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.intro {
|
||||
padding: 18px 20px;
|
||||
}
|
||||
|
||||
.intro p {
|
||||
margin-bottom: 0;
|
||||
color: var(--muted);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.panel,
|
||||
.agent-panel {
|
||||
padding: 22px;
|
||||
}
|
||||
|
||||
.panel-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.business-form {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
padding: 11px 12px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 92px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid var(--ink);
|
||||
background: var(--ink);
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
min-height: 42px;
|
||||
padding: 10px 14px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.primary {
|
||||
width: 100%;
|
||||
background: var(--blue);
|
||||
border-color: var(--blue);
|
||||
}
|
||||
|
||||
.code {
|
||||
overflow: auto;
|
||||
margin: 18px 0 0;
|
||||
border: 1px solid #303a47;
|
||||
background: var(--code);
|
||||
color: #d7e2ef;
|
||||
padding: 16px;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.order-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.order-grid article {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.order-grid span,
|
||||
.order-grid small {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.order-grid strong {
|
||||
display: block;
|
||||
margin: 8px 0 6px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.action-row button {
|
||||
min-height: 56px;
|
||||
background: #fff;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.agent {
|
||||
position: sticky;
|
||||
top: 18px;
|
||||
}
|
||||
|
||||
.tool-list {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
border: 1px solid var(--line);
|
||||
background: var(--panel);
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.tool-card h3 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tool-card p {
|
||||
margin-bottom: 10px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.schema {
|
||||
overflow: auto;
|
||||
max-height: 150px;
|
||||
border: 1px solid var(--line);
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
color: #384551;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.steps {
|
||||
margin: 0 0 16px;
|
||||
padding-left: 20px;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.log {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
max-height: 370px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.entry {
|
||||
border-left: 4px solid var(--blue);
|
||||
background: #fff;
|
||||
padding: 10px 12px;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.entry strong {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.entry pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.topbar,
|
||||
.layout {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.topbar,
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.order-grid,
|
||||
.action-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
const canvas = document.getElementById('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const drawElement = document.getElementById('draw-element');
|
||||
const statusPanel = document.getElementById('support-status');
|
||||
const demoForm = document.getElementById('demo-form');
|
||||
|
||||
const hasHtmlInCanvas =
|
||||
typeof ctx.drawElementImage === 'function' &&
|
||||
typeof canvas.requestPaint === 'function';
|
||||
|
||||
function setStatus(state, message) {
|
||||
statusPanel.dataset.state = state;
|
||||
statusPanel.textContent = message;
|
||||
}
|
||||
|
||||
function resizeCanvas(entry) {
|
||||
const dpcSize = entry.devicePixelContentBoxSize?.[0];
|
||||
const nextWidth = dpcSize?.inlineSize ?? Math.round(entry.contentRect.width * window.devicePixelRatio);
|
||||
const nextHeight = dpcSize?.blockSize ?? Math.round(entry.contentRect.height * window.devicePixelRatio);
|
||||
|
||||
if (canvas.width !== nextWidth) {
|
||||
canvas.width = nextWidth;
|
||||
}
|
||||
|
||||
if (canvas.height !== nextHeight) {
|
||||
canvas.height = nextHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function paint() {
|
||||
ctx.reset();
|
||||
ctx.fillStyle = '#101820';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const x = canvas.width / 25;
|
||||
const y = canvas.height / 25;
|
||||
const transform = ctx.drawElementImage(drawElement, x, y);
|
||||
drawElement.style.transform = transform.toString();
|
||||
}
|
||||
|
||||
demoForm.addEventListener('submit', (event) => {
|
||||
event.preventDefault();
|
||||
setStatus('ok', '表单提交事件来自真实 DOM:发射指令已拦截,demo 仍停留在当前页面。');
|
||||
});
|
||||
|
||||
if (!hasHtmlInCanvas) {
|
||||
document.body.dataset.api = 'fallback';
|
||||
setStatus(
|
||||
'fail',
|
||||
'当前浏览器未暴露 drawElementImage/requestPaint。请使用 Chrome Canary 149+ 并开启 canvas-draw-element flag。'
|
||||
);
|
||||
} else {
|
||||
document.body.dataset.api = 'native';
|
||||
setStatus('ok', '已检测到 HTML-in-Canvas 实验 API,可以操作 canvas 中绘制的 DOM 表单。');
|
||||
canvas.onpaint = paint;
|
||||
|
||||
const supportsDevicePixelContentBox =
|
||||
typeof ResizeObserverEntry !== 'undefined' &&
|
||||
'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
|
||||
const resizeOptions = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
|
||||
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
resizeCanvas(entry);
|
||||
canvas.requestPaint();
|
||||
});
|
||||
|
||||
observer.observe(canvas, resizeOptions);
|
||||
canvas.requestPaint();
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>HTML-in-Canvas API Demo</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<main class="page-shell">
|
||||
<section class="intro" aria-labelledby="page-title">
|
||||
<p class="eyebrow">Chrome Origin Trial Demo</p>
|
||||
<h1 id="page-title">HTML-in-Canvas API</h1>
|
||||
<p class="lede">
|
||||
这个页面基于 WICG 官方示例改写,演示把真实 DOM 表单绘制进
|
||||
<code><canvas></code>,同时保留输入、选择、滑块和按钮的交互能力。
|
||||
</p>
|
||||
|
||||
<div class="status-panel" id="support-status" role="status">
|
||||
正在检测当前浏览器是否支持实验 API...
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="demo-grid" aria-label="HTML-in-Canvas 演示">
|
||||
<div class="canvas-stage">
|
||||
<canvas id="canvas" width="638" height="318" layoutsubtree="true">
|
||||
<div id="draw-element" class="control-panel">
|
||||
<form id="demo-form" action="#" method="get">
|
||||
<fieldset>
|
||||
<legend>飞船控制台</legend>
|
||||
<label class="field-row" for="ship-name">
|
||||
<span>飞船名称</span>
|
||||
<input type="text" id="ship-name" value="Canvas 远航者" />
|
||||
</label>
|
||||
|
||||
<label class="check-row" for="hyperdrive">
|
||||
<input type="checkbox" id="hyperdrive" checked />
|
||||
<span>启动超空间引擎</span>
|
||||
</label>
|
||||
|
||||
<fieldset class="nested-fieldset">
|
||||
<legend>目标星系</legend>
|
||||
<label class="check-row" for="alpha">
|
||||
<input type="radio" id="alpha" name="system" value="alpha" checked />
|
||||
<span>半人马座 Alpha</span>
|
||||
</label>
|
||||
<label class="check-row" for="beta">
|
||||
<input type="radio" id="beta" name="system" value="beta" />
|
||||
<span>参宿四 Betelgeuse</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
<label class="field-row" for="shield-level">
|
||||
<span>护盾强度</span>
|
||||
<input type="range" id="shield-level" min="0" max="100" value="75" />
|
||||
</label>
|
||||
|
||||
<button type="submit">发射</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</canvas>
|
||||
<div class="fallback-preview" aria-hidden="true">
|
||||
<p class="fallback-label">当前浏览器未启用实验 API,下面显示同一 DOM 表单的静态预览。</p>
|
||||
<div class="control-panel preview-panel">
|
||||
<fieldset>
|
||||
<legend>飞船控制台</legend>
|
||||
<label class="field-row">
|
||||
<span>飞船名称</span>
|
||||
<input type="text" value="Canvas 远航者" disabled />
|
||||
</label>
|
||||
<label class="check-row">
|
||||
<input type="checkbox" checked disabled />
|
||||
<span>启动超空间引擎</span>
|
||||
</label>
|
||||
<fieldset class="nested-fieldset">
|
||||
<legend>目标星系</legend>
|
||||
<label class="check-row">
|
||||
<input type="radio" name="preview-system" checked disabled />
|
||||
<span>半人马座 Alpha</span>
|
||||
</label>
|
||||
<label class="check-row">
|
||||
<input type="radio" name="preview-system" disabled />
|
||||
<span>参宿四 Betelgeuse</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<label class="field-row">
|
||||
<span>护盾强度</span>
|
||||
<input type="range" min="0" max="100" value="75" disabled />
|
||||
</label>
|
||||
<button type="button" disabled>发射</button>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="notes" aria-label="运行说明">
|
||||
<h2>运行条件</h2>
|
||||
<p>
|
||||
Chrome 官方文章说明:该 API 在 Chrome 148-150 处于源试用阶段,本地测试需要
|
||||
Chrome Canary 149+,并开启 <code>chrome://flags/#canvas-draw-element</code>。
|
||||
</p>
|
||||
<h2>可验证点</h2>
|
||||
<ul>
|
||||
<li>表单控件来自真实 DOM,不是 canvas 手绘控件。</li>
|
||||
<li>输入框、复选框、单选项、滑块和按钮可直接操作。</li>
|
||||
<li>JS 每次 paint 后同步 DOM transform,让命中区域与 canvas 绘制位置对齐。</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script src="./app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,292 +0,0 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--ink: #17202a;
|
||||
--muted: #5d6670;
|
||||
--panel: #f7f4ec;
|
||||
--canvas: #101820;
|
||||
--accent: #ffb000;
|
||||
--accent-strong: #e04f2f;
|
||||
--line: #263241;
|
||||
--ok: #1d7f4f;
|
||||
--warn: #9a5b00;
|
||||
font-family: "Avenir Next", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
color: var(--ink);
|
||||
background:
|
||||
linear-gradient(90deg, rgba(23, 32, 42, 0.06) 1px, transparent 1px),
|
||||
linear-gradient(rgba(23, 32, 42, 0.05) 1px, transparent 1px),
|
||||
#fffaf0;
|
||||
background-size: 28px 28px;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
code {
|
||||
border: 1px solid rgba(23, 32, 42, 0.16);
|
||||
border-radius: 4px;
|
||||
padding: 0.08rem 0.28rem;
|
||||
background: rgba(255, 255, 255, 0.74);
|
||||
}
|
||||
|
||||
.page-shell {
|
||||
width: min(1120px, calc(100vw - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 44px 0 56px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0;
|
||||
color: var(--accent-strong);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 760px;
|
||||
font-family: Georgia, "Times New Roman", serif;
|
||||
font-size: clamp(3rem, 6vw, 5.8rem);
|
||||
line-height: 0.92;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.lede {
|
||||
max-width: 760px;
|
||||
color: var(--muted);
|
||||
font-size: 1.06rem;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
border: 1px solid rgba(23, 32, 42, 0.18);
|
||||
border-left: 6px solid var(--warn);
|
||||
border-radius: 6px;
|
||||
padding: 10px 14px;
|
||||
color: var(--warn);
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.status-panel[data-state="ok"] {
|
||||
border-left-color: var(--ok);
|
||||
color: var(--ok);
|
||||
}
|
||||
|
||||
.status-panel[data-state="fail"] {
|
||||
border-left-color: var(--accent-strong);
|
||||
color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(280px, 0.8fr);
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.canvas-stage {
|
||||
position: relative;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 480px;
|
||||
border: 1px solid rgba(23, 32, 42, 0.24);
|
||||
border-radius: 8px;
|
||||
padding: 22px;
|
||||
background:
|
||||
radial-gradient(circle at 24% 18%, rgba(255, 176, 0, 0.34), transparent 28%),
|
||||
linear-gradient(135deg, #1a2633 0%, #0d141b 100%);
|
||||
box-shadow: 0 24px 70px rgba(23, 32, 42, 0.18);
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: min(638px, 100%);
|
||||
height: auto;
|
||||
aspect-ratio: 638 / 318;
|
||||
border: 2px solid rgba(255, 176, 0, 0.9);
|
||||
border-radius: 6px;
|
||||
background: var(--canvas);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.16);
|
||||
}
|
||||
|
||||
.fallback-preview {
|
||||
display: none;
|
||||
position: absolute;
|
||||
inset: 42px;
|
||||
place-items: center;
|
||||
gap: 14px;
|
||||
border: 1px dashed rgba(255, 176, 0, 0.72);
|
||||
border-radius: 6px;
|
||||
padding: 22px;
|
||||
background: rgba(16, 24, 32, 0.9);
|
||||
}
|
||||
|
||||
body[data-api="fallback"] .fallback-preview {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
body[data-api="fallback"] canvas {
|
||||
opacity: 0.34;
|
||||
}
|
||||
|
||||
.fallback-label {
|
||||
color: #ffd27c;
|
||||
font-size: 0.86rem;
|
||||
font-weight: 800;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
width: 578px;
|
||||
color: #101820;
|
||||
}
|
||||
|
||||
.control-panel fieldset {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
border: 2px solid #17202a;
|
||||
border-radius: 6px;
|
||||
padding: 18px;
|
||||
background: #fffaf0;
|
||||
}
|
||||
|
||||
.control-panel legend {
|
||||
padding: 0 8px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.field-row,
|
||||
.check-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.field-row input[type="text"] {
|
||||
width: min(310px, 58%);
|
||||
border: 1px solid #17202a;
|
||||
border-radius: 4px;
|
||||
padding: 7px 9px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.field-row input[type="range"] {
|
||||
width: min(300px, 58%);
|
||||
accent-color: var(--accent-strong);
|
||||
}
|
||||
|
||||
.nested-fieldset {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.nested-fieldset legend {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.control-panel button {
|
||||
justify-self: end;
|
||||
border: 2px solid #17202a;
|
||||
border-radius: 4px;
|
||||
padding: 8px 18px;
|
||||
color: #17202a;
|
||||
background: var(--accent);
|
||||
font-weight: 900;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.preview-panel {
|
||||
width: min(578px, 100%);
|
||||
}
|
||||
|
||||
.preview-panel input:disabled,
|
||||
.preview-panel button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.notes {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 14px;
|
||||
border-left: 4px solid var(--line);
|
||||
padding: 20px 0 20px 22px;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.notes h2 {
|
||||
color: var(--ink);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.notes ul {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 0;
|
||||
padding-left: 1.2rem;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.page-shell {
|
||||
width: min(100vw - 22px, 680px);
|
||||
padding-top: 28px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.1rem;
|
||||
}
|
||||
|
||||
.demo-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.canvas-stage {
|
||||
min-height: 360px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.fallback-preview {
|
||||
inset: 24px;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.nested-fieldset {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.notes {
|
||||
border-left: 0;
|
||||
border-top: 4px solid var(--line);
|
||||
padding: 18px 0 0;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
QUERY=${1:-"animate a dialog modal backdrop"}
|
||||
GUIDE_ID=${2:-"animate-to-from-top-layer"}
|
||||
|
||||
echo "== Modern Web Guidance: search =="
|
||||
echo "npx modern-web-guidance@latest search \"$QUERY\""
|
||||
npx modern-web-guidance@latest search "$QUERY"
|
||||
|
||||
echo
|
||||
echo "== Modern Web Guidance: retrieve =="
|
||||
echo "npx modern-web-guidance@latest retrieve \"$GUIDE_ID\""
|
||||
npx modern-web-guidance@latest retrieve "$GUIDE_ID"
|
||||
Binary file not shown.
@@ -3,8 +3,8 @@ theme: penguin
|
||||
colorSchema: light
|
||||
title: What's New in Chrome
|
||||
info: |
|
||||
## Chrome 新特性分享
|
||||
面向 Web 开发者的 Agentic Web 与 Chrome 新能力科普
|
||||
## What's New in Chrome
|
||||
面向 Agentic Web / WebMCP 的 Chrome 新特性分享
|
||||
transition: slide-left
|
||||
mdc: true
|
||||
drawings:
|
||||
@@ -17,9 +17,19 @@ layout: intro
|
||||
|
||||
# What's New in Chrome
|
||||
|
||||
AI Agent 要操作网页了,Chrome 正在做什么?
|
||||
Web 正在进入 Agentic 时代
|
||||
|
||||
<div class="pt-4 text-sm op-60">Part 1 科普 · Part 2 官方优先 demo 演示</div>
|
||||
<div class="pt-4 text-sm op-60">WebMCP · Task Flows · Modern Web Guidance · DevTools for agents · Baseline</div>
|
||||
|
||||
<!--
|
||||
大家好,今天这场分享的主题是 What's New in Chrome,但我不会把它讲成一串 release notes,也不会逐条念 Chrome 又新增了哪些 API。
|
||||
|
||||
我想先给大家一个更大的判断:Web 正在进入 Agentic 时代。以前我们默认网页是给人用的,人打开页面,阅读内容,理解按钮和表单,然后一步一步点击完成任务。但现在,AI Agent 也开始成为网页的一类使用者。它不是像人一样慢慢看页面,而是要理解页面意图,拆解用户任务,调用能力,甚至跨多个页面完成一组流程。
|
||||
|
||||
所以今天我们从开发者视角看一个问题:Chrome 最近这些方向,到底在帮 Web 发生什么变化?我会把它分成三条线。第一条线,是让网站更容易被 Agent 理解和调用,核心代表是 WebMCP。第二条线,是让开发工具和编码 Agent 更懂现代 Web,代表是 Modern Web Guidance 和 DevTools for agents。第三条线,是继续提升 Web 自身的体验,比如 HTML in Canvas、View Transitions、Navigation API 和 Baseline。
|
||||
|
||||
这场分享结束以后,我希望大家带走的不是某个单点 API,而是一个问题意识:我们现在写的网页,未来不仅要被人看懂,也要被 Agent 安全、稳定、结构化地使用。
|
||||
-->
|
||||
|
||||
---
|
||||
src: ./pages/overview.md
|
||||
@@ -29,58 +39,42 @@ src: ./pages/overview.md
|
||||
src: ./pages/outline.md
|
||||
---
|
||||
|
||||
---
|
||||
layout: new-section
|
||||
---
|
||||
|
||||
# Part 2:Demo 演示
|
||||
|
||||
PPT 讲新增功能,项目代码配合现场演示
|
||||
|
||||
---
|
||||
src: ./pages/demo/release-scan.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/webmcp-contract.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/webmcp-project.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/webmcp-script.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/devtools-workflow.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/modern-web-guidance.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/devtools-agents.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/html-in-canvas.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/view-transitions.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/adoption-plan.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/platform-api.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/devtools-workflow.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/demo/adoption-plan.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/chrome-flash.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/modern-web-guidance.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/devtools-agents.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/platform-shifts.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/adoption-now.md
|
||||
---
|
||||
|
||||
---
|
||||
src: ./pages/closing.md
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user