Cloudflare Agents 前端视觉体系实战:基于 Kumo 设计系统构建 Agent Playground 界面
2026/9/17 22:03:01 网站建设 项目流程

Cloudflare Agents 前端视觉体系实战:基于 Kumo 设计系统构建 Agent Playground 界面

【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents

本文以仓库中的 design/visuals.md 为核心,系统讲解 Cloudflare Agents 项目(GitHub_Trending/agents1/agents)如何基于 Cloudflare 内部设计系统Kumo@cloudflare/kumo)统一构建所有示例与 Playground 的前端界面。你将掌握 Kumo 与 Tailwind v4 的集成方式、基于data-mode的自动明暗模式切换、LinkProvider与 React Router 的类型适配方案,以及设计系统能力不足时如何用自定义组件补齐缺口的工程决策。

一、设计系统选型:为什么使用 Kumo

Playground(以及后续所有示例)统一采用 Cloudflare 内部设计系统 Kumo(@cloudflare/kumo),而不是自建组件原语。它带来了三层核心收益:

  • 语义化颜色令牌(semantic color tokens):如bg-kumo-basetext-kumo-defaultborder-kumo-line,颜色含义由语义驱动,而非固定色值;
  • 无障碍组件:按钮、输入框、表单控件等均内置 ARIA 支持;
  • 自动明暗模式:无需在每个组件上手动维护主题逻辑。

从仓库看,这一决策已贯穿整个示例体系:examples/下几乎所有示例的src/client.tsxsrc/styles.css都导入了 Kumo,而 Playground 是落地最完整的参考实现(入口见 examples/playground/src/client.tsx)。

依赖清单

在 examples/playground/package.json 中可以确认以下关键依赖:

依赖版本作用
@cloudflare/kumo^2.6.0设计系统组件库(monorepo 根目录安装为 devDependency)
@phosphor-icons/react^2.1.10Kumo 的配套图标库(peer 依赖)
@tailwindcss/vite^4Tailwind v4 的 Vite 插件

图标使用规范:永远使用*Icon后缀导出

@phosphor-icons/reactv2 中,带Icon后缀的导出(如TrashIconShieldIcon)是推荐用法,裸名(TrashShield)已废弃。仓库中的真实调用印证了这一点:

  • examples/playground/src/components/LogPanel.tsx 中使用了TrashIcon
  • examples/playground/src/layout/Sidebar.tsx 中大量使用CaretDownIconCaretRightIconCubeIconChatDotsIconMoonIconSunIcon等。

二、环境搭建:Tailwind v4 与 Kumo 的集成

Kumo 自带 Tailwind 插件,需要在 Vite 构建链路中挂载@tailwindcss/vite,并在 CSS 入口中显式导入。

Vite 配置

examples/playground/vite.config.ts 中的插件顺序为:

import { cloudflare } from "@cloudflare/vite-plugin"; import tailwindcss from "@tailwindcss/vite"; import react from "@vitejs/plugin-react"; import agents from "agents/vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [agents(), react(), tailwindcss(), cloudflare()], define: { __filename: "'index.ts'" } });

tailwindcss()插件与@vitejs/plugin-react@cloudflare/vite-plugin并列注册。注意这里的agents()是 agents SDK 自己的 Vite 插件,负责在本地开发时注入 Worker 运行时。

styles.css 的三行关键配置

examples/playground/src/styles.css 顶部:

@import "tailwindcss"; @import "@cloudflare/kumo/styles/tailwind"; /* Tailwind ignores node_modules by default, so we source Kumo for class extraction. */ @source "../node_modules/@cloudflare/kumo/dist/**/*.{js,jsx,ts,tsx}";

这里有一个重要的工程细节:Tailwind 默认忽略node_modules,因此必须用@source指令将 Kumo 的产物目录纳入类名提取范围,否则 Kumo 组件内部使用的语义类(bg-kumo-base等)不会被生成。

关于@source路径,文档特别强调了一个易踩的坑:@source路径是相对于src/styles.css的(即../node_modules),它指向示例自己的node_modules。这个写法在 pnpm workspace 中(每个包都有自己的带符号链接的node_modules)与示例被单独复制出去独立安装两种场景下都能正确解析,因此不要写成../../../node_modules这类 monorepo 根目录相对路径。

其他示例的对比

以 examples/channels/src/styles.css 等为代表的示例同样遵循这一模式,验证了这是整个示例体系的统一约定,而非 Playground 独有。

三、明暗模式:data-mode属性驱动的主题系统

与常见的 Tailwinddark:类前缀方案不同,Kumo 使用<html>元素上的data-mode属性控制主题。所有 Kumo 语义令牌(bg-kumo-basetext-kumo-defaultborder-kumo-line等)会自动响应这个属性,整个代码库中不需要任何dark:前缀

这套机制由两个部分配合完成:

1. index.html 中的首屏防闪烁脚本

examples/playground/index.html 的<head>内联脚本在页面加载前同步执行:

<script> (() => { const mode = localStorage.getItem("theme") || "light"; document.documentElement.setAttribute("data-mode", mode); document.documentElement.style.colorScheme = mode; })(); </script>

它在 React 应用挂载之前就把data-mode写入<html>,避免了明暗主题切换时的白屏/闪烁(FOUC)。同时设置了style.colorScheme,让浏览器原生控件(滚动条、表单控件)也跟随主题。

2. 内联 ModeToggle 组件

每个示例内置一份ModeToggle组件(不共享自公共包),负责运行时切换。examples/playground/src/layout/Sidebar.tsx 中的实现如下:

function ModeToggle() { const [mode, setMode] = useState( () => localStorage.getItem("theme") || "light" ); useEffect(() => { document.documentElement.setAttribute("data-mode", mode); document.documentElement.style.colorScheme = mode; localStorage.setItem("theme", mode); }, [mode]); return ( <Button variant="ghost" shape="square" aria-label="Toggle theme" onClick={() => setMode((m) => (m === "light" ? "dark" : "light"))} icon={mode === "light" ? <MoonIcon size={16} /> : <SunIcon size={16} />} /> ); }

工作流程是:useState读取localStorage中的theme作为初始值 →useEffect将模式写入data-modecolorScheme并持久化 → 点击按钮在light/dark间切换,图标也随之从MoonIcon变为SunIcon

色彩主题(Color Themes)

所有示例目前均使用 Kumo 的默认主题,没有任何自定义主题覆盖。Kumo 支持通过父元素上的data-theme属性进行主题定制,但现有示例全部省略该属性。这意味着如果你的 Agent 应用需要品牌色主题,可以在未来通过data-theme扩展,而无需改动组件代码。

四、标准 UI 模式清单

每个示例都包含以下 UI 元素,且是内联在每个示例中(而非从公共包导出):

模式来源用途
PoweredByCloudflare@cloudflare/kumo"Powered by Cloudflare" 页脚徽标——每个示例都应包含
CloudflareLogo@cloudflare/kumoCloudflare Logo 组件,含 glyph / full 两种变体
ModeToggle每个示例内联基于localStorage+data-mode属性的明暗切换
ConnectionIndicator每个示例内联彩色圆点 + 标签,展示 WebSocket 状态(connecting/connected/disconnected

ConnectionIndicator 的实际实现

examples/playground/src/components/ConnectionStatus.tsx 给出了完整的连接状态指示器实现。它用一个statusConfig配置表把三种状态映射到语义类与颜色:

const statusConfig = { connected: { label: "Connected", dot: "bg-green-500", text: "text-kumo-success", bg: "bg-green-500/10" }, connecting: { label: "Connecting…", dot: "bg-kumo-warning animate-pulse", text: "text-kumo-warning", bg: "bg-kumo-warning-tint" }, disconnected: { label: "Disconnected", dot: "bg-kumo-danger", text: "text-kumo-danger", bg: "bg-kumo-danger-tint" } } as const;

组件渲染为一个带彩色圆点的圆角徽章,连接成功后还会追加显示agentName/instanceName,让用户直观看到自己连接到了哪个 Agent 实例。

五、路由集成:KumoLinkProvider与 React Router 的类型适配

Kumo 的<LinkProvider>允许注入自定义链接组件,让<Link>通过你的路由库渲染。但在接入 React Router 时存在一个类型不匹配问题:

  • Kumo 的LinkComponentProps定义to?: string(可选);
  • React Router 的Link要求to: To(必选,且To = string | Partial<Path>)。

这两个类型在任一方向上都不互相可赋值,直接把RouterLink传给LinkProvider会报类型错误。

解决方案:AppLink 适配器

仓库在 examples/playground/src/client.tsx 中用一个极薄的适配组件桥接两者:

import { LinkProvider, type LinkComponentProps } from "@cloudflare/kumo"; const AppLink = forwardRef<HTMLAnchorElement, LinkComponentProps>( ({ to, ...props }, ref) => { if (to) { return <RouterLink ref={ref} to={to} {...props} />; } // oxlint-disable-next-line jsx-a11y/anchor-has-content -- content comes from spread props return <a ref={ref} {...props} />; } ); function App() { return ( <LinkProvider component={AppLink}> <BrowserRouter> <Routes>...</Routes> </BrowserRouter> </LinkProvider> ); }

这个适配器做了两件事:

  1. 兜底可选性缺口to存在时渲染为RouterLinkto缺失时退化为普通<a>,与 Kumo 实际只会传入字符串to的行为对齐;
  2. 收窄类型:将To收窄为string,这正是 Kumo 在实际调用中始终传入的类型。

此外,仓库还保留了oxlint-disable注释来说明裸<a>无内容时的无障碍检查豁免——内容通过展开的 props 提供。

上游修复建议

文档指出这个问题值得向 Kumo 团队提出,因为每个使用 React Router 的开发者都会遇到。可能的修复方向有三个:

  • Kumo 将LinkComponentPropsto改为必填(组件实际被调用时总是提供该值);
  • Kumo 直接接受 React Router 的To类型;
  • React Router 放宽Link,接受to?: string

六、Kumo 组件选用清单

以下是 Playground 中实际使用的 Kumo 组件与其替换的自研组件:

Kumo 组件替换对象 / 用途
Button所有按钮(primary / secondary / destructive / ghost 操作)
Input文本输入框;用内置labelprop 作为 Field 包装
InputArea多行文本框
Surface卡片 / 面板容器
Text标题与正文(注意:不接受className,需要 margin/spacing 时外层包<div>
Badge状态指示与标签
Banner告警 / 警告横幅
CodeBlock静态代码示例与动态 JSON 展示
Tabs标签页切换(如 inbox/outbox)
Switch布尔开关(内置 label)
Checkbox多选复选框
Table数据表格
Empty空状态占位
Loader加载 spinner
LinkProvider/Link路由感知链接

以 examples/playground/src/demos/core/RoutingDemo.tsx 为例,可以同时看到多个组件的组合用法:Surface承载控制面板,Text variant="heading3"渲染小节标题(外层用<div className="mb-4">控制间距,正是文档提到的Text不接受className的应对方式),Input label="User ID ..."提供带标签输入框,Radio.Group/Radio.Item实现策略选择。

七、自定义实现及其原因(Kumo 能力边界)

Kumo 并非覆盖一切场景,Playground 在以下几处保留了自研实现,每个决策都有明确理由:

1. 侧边栏分类折叠(Sidebar category toggle)

examples/playground/src/layout/Sidebar.tsx 中的CategorySection使用原生<button>实现分类的展开/收起。原因是 Kumo 的Collapsible只接受label: string,而导航分类需要"图标 + 文本"的组合内容。该按钮还通过aria-expandedaria-controls维护无障碍语义,配合CaretDownIcon/CaretRightIcon指示折叠状态。

2. 路由策略选择器(RoutingDemo)

RoutingDemo需要一个"每项既有标题又有描述"的类单选 UI,而 Kumo 的Radio.Item只支持label: string,无法容纳 per-option 的描述。从 examples/playground/src/demos/core/RoutingDemo.tsx 可以看到它的折中方案:在Radio.Item的 label 中用拼接标题与描述(如"Per-User — Each user ID gets their own agent instance"),并在侧栏用"标题 + 副文案"结构展示四种策略的完整说明。

3. 交互式列表项

房间列表(ChatRoomsDemo)、表格列表(SqlDemo)、邮件列表(ReceiveDemo、SecureDemo)以及审批预设按钮(ApprovalDemo)都使用被样式化为列表行的原生<button>。这些是带复杂 active/hover 状态的"选择驱动型列表行",不属于标准按钮模式,而 Kumo 没有可复用的可选列表组件。

4. 范围滑块(BasicDemo)

Workflow 步骤数滑块使用原生<input type="range">,因为 Kumo 未提供 range/slider 组件。

5. 日志面板(LogPanel)

事件日志使用styles.css中定义的少量自定义 CSS 工具类(.log-entry.log-entry-in.log-entry-out.log-entry-error),它们是整个代码库中仅有的自定义 CSS 类。原因在于日志条目是高密度、领域特定的模式,Kumo 没有对应组件。

examples/playground/src/styles.css 中的实际定义展示了如何用@layer components@apply组合语义令牌:

@layer components { .log-entry { @apply px-3 py-1.5 text-xs font-mono text-kumo-default border-b border-kumo-fill last:border-0; } .log-entry-in { @apply bg-green-500/10; } .log-entry-out { @apply bg-kumo-info-tint; } .log-entry-error { @apply bg-kumo-danger-tint text-kumo-danger; } .log-entry-info { @apply bg-kumo-base; } }

对应消费方 examples/playground/src/components/LogPanel.tsx 根据日志方向(in/out/error/info)选择不同的类组合,并以分别标记方向,同时自动滚动到底部。

6. 语义色令牌缺口(当前已知限制)

有几个期望存在的 Kumo 语义令牌目前尚不存在,仓库中的回退方案是:

  • bg-kumo-success-tint/bg-kumo-success→ 回退为bg-green-500/10/bg-green-500
  • border-l-kumo-success→ 回退为border-l-green-500

文档明确警示:这些原生 Tailwind 绿色不会随data-theme变化(它们绕过了令牌系统),应在 Kumo 上游补齐对应令牌后替换。这个"回退但不完美"的处理思路也值得借鉴:先用可用的类保证功能与视觉,同时在代码中留下待替换标记。

八、整体架构与布局实践

从 examples/playground/src/layout/Layout.tsx 可以看到完整的页面骨架:

  • 移动端顶栏(md:hidden)包含打开侧栏的 ghost 按钮、PoweredByCloudflare徽标;
  • 桌面端为静态侧栏,移动端为覆盖式抽屉(带遮罩层);
  • 主内容区flex-1 overflow-y-auto bg-kumo-base承载路由出口Outlet

侧栏导航数据定义在 examples/playground/src/layout/Sidebar.tsx 顶部的navigation数组,按 Core / AI / Durable Execution / MCP / Workflows / Multi-Agent / Voice / Email / Product Integrations 分类组织,每个分类带图标;条目用 React Router 的NavLink渲染,根据isActive切换高亮样式。移动端侧栏在路由变化时自动关闭(useEffect监听location.pathname)。

九、实践要点总结

  • 统一设计系统:Kumo 提供语义令牌、无障碍组件与自动明暗模式,examples/全部示例统一使用,可参考 examples/playground/src/styles.css 作为基线;
  • 工具链组合@tailwindcss/vite+@cloudflare/kumo/styles/tailwind+@source提取声明,三者缺一不可;
  • 主题不写dark::通过data-mode+localStorage+ 首屏内联脚本实现无闪烁的主题切换;
  • 路由类型桥接AppLink适配器是 Kumo × React Router 组合的标准解法,完整代码在 examples/playground/src/client.tsx;
  • 承认边界:当设计系统缺少所需组件(滑块、可折叠带图标、可选列表等)时,保留原生元素 + 语义类的自研方案,并注意避免非语义色值破坏主题一致性。

【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询