首页
/ Cline @cline/ui 主题与 Agent 对话组件采用指南:从 Token 到 React 原语的分层接入实践

Cline @cline/ui 主题与 Agent 对话组件采用指南:从 Token 到 React 原语的分层接入实践

2026-09-06 18:00:07作者:秋泉律Samson

本文基于 Cline 仓库中 ADOPTION.md 的采用首稿(adoption primer)展开,介绍 @cline/ui 这个共享 Web 主题与 Agent 对话 UI 组件包的分层设计、五种接入级别、三种主题集成方案(完整 Tailwind v4 主题 / 仅映射 / 框架无关 Token)、agent-chat React 原语的实际用法,以及该包在生产消费方(Desktop 应用)中的验证方式与版本兼容契约。读完后,你能够判断一个 Cline 系 Web 应用应以哪一级别接入该包、按什么顺序导入 CSS、如何在消费边界映射自有消息 Schema,以及如何在升级时保持契约稳定。

包定位:两层可选项,明确不越界

@cline/ui(位于 sdk/packages/ui)面向的目标是:让 Web 应用共享 Cline 的视觉语言与 Agent 会话呈现方式,而无需复制 Desktop 端样式,也不需采纳 Desktop 的产品结构。它提供两个"可选启用"(opt-in)的层:

  1. 一套基于标准 shadcn/Tailwind 语义命名的共享 CSS 主题;
  2. 面向常见 Agent 对话界面的可复用 React 表现层原语(presentation primitives)。

主题层提供:

  • Cline 自有的 Slate、Violet、Ruby、Green、Amber、Sky 六组实色/alpha 色板(--neutral-*--accent-*--error-*--success-*--warning-*--info-*
  • 亮色/暗色语义颜色
  • 标准 shadcn token 命名(--background--foreground--card--primary--border--ring、charts、sidebar 等)
  • 字体族、字号、字重、行高与字间距
  • 边框、圆角、卡片、导航、侧边栏、图表颜色
  • 选区(selection)与滚动条取值
  • 一小组用于品牌物料的 --brand-* 色板
  • Tailwind v4 映射
  • 可选的全局、交互与 Markdown 样式

agent-chat 组件面(当前第一版)提供:

  • 粘性 Agent 会话结构与"滚动到最新消息"的浮动按钮
  • 用户、助手、系统、状态、错误消息的呈现
  • 带可访问标签与焦点行为的消息操作(Message actions)
  • 受控或非受控的推理(reasoning)折叠展开
  • 静态或可展开的工具活动(tool activity),含 running、success、error 状态
  • 空会话(empty-conversation)呈现

同时,ADOPTION 文档划定了消费方继续自行拥有的职责清单:运行时消息与工具 Schema、会话/Provider/传输/流式/持久化行为、Markdown 渲染与外链/图片策略、审批与追问的编排、checkpoint/fork/剪贴板/toast 行为、页面布局导航与产品工作流、字体文件加载与框架集成、产品特定动画与刻意的视觉覆盖。这条边界的意义在于:让 Cline 各产品共享一套视觉与交互语言,而不让 @cline/ui 变成第二个 Agent 运行时

共享模块:tool-summary 与 tool-diff

文档同时说明,这条边界约束的是运行时耦合,而不是包要保持小型。当一个以上的产品需要相同的呈现行为时,目标是把它抽取为共享模块,而不是让每个应用各自维护一份拷贝——这正是 @cline/ui 的发展方向,后续会持续增加共享模块。当前已存在两个:

  • @cline/ui/components/agent-chat/tool-summary —— 纯函数、无框架依赖(buildToolSummarybuildGroupedToolLabel 及底层解析器),把原始 { toolName, input, result } 载荷转换为各端都应显示的标签、逐项明细、diff 计数与 before/after 文本。"unknown in, data out"——没有 React、没有图标、不依赖 @cline/core 或传输事件。从源码 tool-summary/index.ts 可以看到:buildToolSummary 接受 unknown 类型的 input/result,解析是防御式的,畸形载荷会退化为通用标签而不会抛异常;它内置了 TOOL_NAME_ALIASES 别名表(如 bash → run_commandsedit_file → editor)与 classifyTool 的类型归类(read / edit / command / search / web / spawn / team / skill / mcp / question / other),并支持 pathStyleshortened/basename/full)、maxOutputChars(默认 64 KiB)等选项。单文件读取会生成 Read app.tsx (10–80) 这样的行内标签,多条目调用则聚合为 Read 3 files 并把逐项明细放进 details
  • @cline/ui/components/agent-chat/tool-diff —— ToolFileDiff 组件,是对 @pierre/diffs(可选 peer 依赖,见 package.jsonpeerDependenciesMetaoptional: true)的薄封装,把 tool-summary 的文件条目渲染为带语法高亮、主题感知的 diff。

这类共享模块让各产品的 read/edit/command 行保持一致,同时消费方仍拥有自己的消息 Schema、图标资产和整体渲染。文档给出的工作习惯是:在应用里手写呈现逻辑之前,先检查它是否应该放到这里

当前状态与版本策略

@cline/ui 已配置为可公开发布到 npm,拥有独立版本号与手动发布流程(publishConfig.access: public)。可用 npm view @cline/ui version 检查可用性,返回 E404 说明首个正式版仍在待发布状态。API 处于 pre-stable 阶段,生产消费方应固定精确版本,并在升级时审查兼容性说明。仓库内 package.json 当前版本为 0.2.0-next.8,包为 ESM("type": "module"),其 React 入口面向浏览器应用。

Desktop 应用(apps/examples/desktop-app)是主题与共享会话原语的首个生产级消费方;Storybook 是隔离组件状态的参考目录;Hub 与其他 Agent 界面是下一轮采用的候选对象,前提是其运行时与 Markdown 适配器被显式映射。

选择接入级别

ADOPTION 文档给出了一张按目标选择导入方式的对照表(这是接入决策的核心依据):

目标 导入 需要 Tailwind 需要 React
只使用亮/暗 CSS 变量 @cline/ui/theme/tokens.css
渲染共享根 React 原语且不做宿主 reset scoped-tokens.csscomponents.css@cline/ui Tailwind v4 React 18.3 或 19
通过 Tailwind 工具类使用 token tokens.css 然后 theme.css Tailwind v4
使用完整主题与共享基础行为 @cline/ui/theme/index.css Tailwind v4
组合共享 agent-chat 呈现 @cline/ui/components/agent-chat 及其 CSS 若 token 已通过纯 CSS 映射则不需要 React 18.3 或 19

另外两个要点:

  • 包单独导出了 base.css,供想要全局、Markdown、滚动条、选区、光标与原生 color-scheme 行为的消费方使用;
  • 不存在 @cline/ui/theme 简写,必须使用文档中的显式路径,以保持依赖可见。

package.jsonexports 字段对照,实际可用的公共入口为:.(React 原语)、./components.css./components/agent-chat./components/agent-chat/tool-summary./components/agent-chat/tool-diff./components/agent-chat.css./components/markdown./components/markdown.css./theme/{index,palette,scoped-tokens,tokens,theme,base}.css。所有 peer 依赖(React、Tailwind、Streamdown、Shiki、@pierre/diffs 等)都标记为 optional,即"只安装你所采用层的前置条件"。

安装

Cline monorepo 内部(workspace 方式)

添加 workspace 依赖:

{
  "dependencies": {
    "@cline/ui": "workspace:*"
  }
}

更新清单与锁文件后,运行仓库正常的包安装流程即可。Desktop 应用正是如此接入的——apps/examples/desktop-app/package.json 中声明了 "@cline/ui": "workspace:*",并通过 "build:ui": "bun -F @cline/ui build" 在构建链中先构建该包。

其他仓库(npm 方式)

首个正式版可用后,安装最新生产版本。--exact 会记录解析到的具体版本而非版本区间:

bun add --exact @cline/ui

只为被采用的层安装前置依赖:

# React 组件需要
bun add react@^19 react-dom@^19

# 文档所述 Tailwind 支撑的主题与 Cline 字体需要
bun add @fontsource-variable/inter @fontsource-variable/geist-mono
bun add --dev tailwindcss

已在 React 18.3 上的应用可以保留该兼容版本(包声明的 peer 范围是 react >=18.3.0 <20)。仅用 token 的消费方不需要 React 或 Tailwind。提交消费仓库的锁文件,使构建始终使用同一解析版本;团队有意升级到新发布版本时使用包管理器的更新命令:

bun update @cline/ui

UI 版本与运行时 SDK 包独立演进。对于有意预览,UI 发布会发布不稳定的 next npm tag:

bun add --exact @cline/ui@next

生产应用不要使用 next

方案一:完整 Tailwind v4 主题

在完整主题之前先导入字体和 Tailwind:

@import "@fontsource-variable/inter";
@import "@fontsource-variable/geist-mono";
@import "tailwindcss";
@import "@cline/ui/theme/index.css";

从源码看,theme/index.css 本身就是三行聚合:tokens.css(框架无关变量)→ theme.css(Tailwind 语义映射与暗色变体)→ base.css(全局基础样式)。这个完整入口提供:

  • 框架无关的 token 值
  • Tailwind 语义映射与暗色变体
  • 全局排版与 body 样式
  • Markdown 与代码块样式
  • 滚动条与选区样式
  • 一致的光标/指针提示
  • 原生亮/暗 color-scheme

应用自有 CSS 应放在这些导入之后。Desktop 应用的 globals.css 就是这一模式的真实落地:字体与 Tailwind 之后依次 @import "@cline/ui/theme/index.css"components.cssagent-chat.cssmarkdown.css

方案二:仅 Tailwind 映射,不带基础样式

当应用想要共享 token 与工具类、但已自行管理文档、Markdown、滚动条或光标行为时使用:

@import "@fontsource-variable/inter";
@import "@fontsource-variable/geist-mono";
@import "tailwindcss";
@import "@cline/ui/theme/tokens.css";
@import "@cline/ui/theme/theme.css";

对于必须保留自身宿主外壳的既有 Tailwind v4 界面,保持 Tailwind 配置宿主所有,追加 Cline 的作用域化导入:

@import "@cline/ui/theme/scoped-tokens.css";
@import "@cline/ui/components.css";

共享组件渲染在 .cline-ui-theme 内。暗色值在该包裹元素或其祖先上存在 .dark 时生效。components.css 只注册包命名空间内的 Tailwind 映射,因此宿主自有的通用工具(如 bg-backgroundtext-foregroundrounded-lg)在包裹之外保持宿主定义的含义不变。这一点有源码佐证:scripts/generate-theme.ts 会生成 scoped-tokens.csscomponent-theme.css,并把 --font-*--text-* 等词法 token 改写为 --font-cline-ui-*--text-cline-ui-* 这样的包内专属名,避免污染宿主的通用工具语义。

该作用域化配置下不要导入 theme.css:该入口有意注册完整的、通用的 Cline 工具词汇,只面向希望由 Cline 全局拥有这些工具名的应用。根原语使用包所有的语义颜色、词法、圆角与暗色变体名;结构类工具仍走 Tailwind 标准 spacing/shadow/breakpoint 尺度,宿主自定义这些尺度时可能改变组件尺寸,但其工具名与 token 值不受影响。若应用日后改采 Cline 全局工具词汇与基础行为,切换到方案一即可。

方案三:框架无关 token

不使用 Tailwind 的应用可以只导入变量:

@import "@cline/ui/theme/tokens.css";

仅用 token 的消费方必须自行提供:

  • 字体文件
  • reset 与文档默认值
  • 原生 color-scheme(如需)
  • 从 CSS 变量到框架工具类的自有映射
  • 自有的暗色模式类激活

让原生控件跟随所选主题:

:root {
  color-scheme: light;
}

.dark {
  color-scheme: dark;
}

tokens.css 头部注释可以看到它的自我约束:"Keep this file framework-neutral: imports and variable blocks only, with no Tailwind directives, resets, font imports, or app-specific selectors." 文件内定义了品牌扩展(--brand-violet 等)、标准 Tailwind 词法变量(--text-xs--text-6xl 及各自的行高/字距配套变量)与两组"可读视觉角色"(--surface-1--text-2--border-1--focus-ring 以及 success/warning/error/info 的 surface/border/solid/text/contrast 五件套),供框架无关的组件 CSS 使用。

主题三层结构与生成物校验

包内主题源码分三层,且有两层"生成物"需要特别理解(见 README.mdgenerate-theme.ts):

  1. Cline 自有 12 级实色与 alpha 色板:Slate 为 --neutral-*,Violet 为 --accent-*,Ruby 为 --error-*,Green 为 --success-*,Amber 为 --warning-*,Sky 为 --info-*theme/palette.css);色值派生自 Radix Colors 3.0.0(随包附带 MIT 许可,见 RADIX-COLORS-LICENSE),运行时不依赖 Radix Colors。
  2. 可读视觉角色--surface-1--text-2--border-1--success-surface 等。
  3. 稳定的 shadcn 兼容变量:供组件消费。

新写框架无关组件 CSS 时优先使用视觉/状态角色;在 shadcn 兼容组件中继续使用标准 shadcn 命名。品牌物料才允许使用 --brand-*palette.csstokens.css规范源scoped-tokens.css 与内部组件 Tailwind 映射是生成物,由 bun run generate:theme 从源文件产生——package.jsonbuildtest 脚本都先执行 check:theme-generated--check 模式),测试与 CI 会拒绝任何生成物漂移;配合 scripts/validate-theme.tsscripts/smoke-package.ts,构成"改动源 → 重新生成 → 构建/测试校验"的完整闭环。

接入 agent-chat React 原语

CSS 顺序

使用完整 Tailwind 主题时,在其后导入组件样式:

@import "@cline/ui/theme/index.css";
@import "@cline/ui/components/agent-chat.css";

无 Tailwind 时,导入框架无关 token 与组件样式,并在应用或会话根上应用共享字体族(token 只定义字体值,不应用文档排版):

@import "@cline/ui/theme/tokens.css";
@import "@cline/ui/components/agent-chat.css";

.agent-chat-root {
  font-family: var(--font-sans);
}

组合示例:围绕自有数据构建会话

ADOPTION 文档给出了一个完整可复制的组合范例(消费方自有 ProductMessage 模型,renderMarkdown 由消费方注入):

import type { ReactNode } from "react";
import {
  type AgentMessageRole,
  Conversation,
  ConversationContent,
  ConversationScrollButton,
  ConversationViewport,
  Message,
  MessageActions,
  MessageAction,
  MessageContent,
  Reasoning,
  ReasoningContent,
  ReasoningTrigger,
  ToolActivity,
  ToolActivityContent,
  ToolActivityTrigger,
} from "@cline/ui/components/agent-chat";

type ProductMessage = {
  id: string;
  role: "human" | "agent" | "system" | "error";
  content: string;
  reasoning?: string;
  isStreaming?: boolean;
};

const roleMap: Record<ProductMessage["role"], AgentMessageRole> = {
  human: "user",
  agent: "assistant",
  system: "system",
  error: "error",
};

type AgentTranscriptProps = {
  conversationId: string;
  messages: ProductMessage[];
  onCopy: (message: ProductMessage) => void;
  renderMarkdown: (content: string) => ReactNode;
};

export function AgentTranscript({
  conversationId,
  messages,
  onCopy,
  renderMarkdown,
}: AgentTranscriptProps) {
  return (
    <Conversation
      className="agent-chat-root"
      key={conversationId}
      style={{ height: "32rem" }}
    >
      <ConversationViewport aria-label="Agent conversation">
        <ConversationContent>
          {messages.map((message) => (
            <Message from={roleMap[message.role]} key={message.id}>
              <MessageContent>
                {message.reasoning ? (
                  <Reasoning isStreaming={message.isStreaming}>
                    <ReasoningTrigger />
                    <ReasoningContent>
                      {renderMarkdown(message.reasoning)}
                    </ReasoningContent>
                  </Reasoning>
                ) : null}

                {renderMarkdown(message.content)}
              </MessageContent>

              <MessageActions>
                <MessageAction label="Copy message" onClick={() => onCopy(message)}>
                  Copy
                </MessageAction>
              </MessageActions>
            </Message>
          ))}

          <ToolActivity expandable>
            <ToolActivityTrigger
              label="Edited 2 files"
              additions={24}
              deletions={8}
              status="success"
            />
            <ToolActivityContent>Normalized tool details</ToolActivityContent>
          </ToolActivity>
        </ConversationContent>
      </ConversationViewport>
      <ConversationScrollButton />
    </Conversation>
  );
}

示例中有几个刻意的产品决策,值得对照源码理解:

  • 显式高度让独立示例可滚动。在真实外壳中等价的有界 flex 布局也可以:高度链上的每个祖先都必须允许收缩(通常是 min-height: 0),会话要填满可用高度。
  • renderMarkdown 由消费方注入:不同产品的 Streamdown 插件、语法高亮预算、链接确认行为与图片策略各不相同。共享包标准化外围呈现,不悄悄改变这些安全与产品决策。
  • React key 绑定会话标识:活跃会话切换时重置会话局部状态。
  • 在消费边界映射运行时角色与工具状态:不要让 UI 包依赖 @cline/core、Vercel AI SDK、Desktop Schema 或传输事件。

源码级实现细节:粘性滚动与滚动按钮

Conversation 原语(components/agent-chat/index.tsx)内部实现了示例中"scroll-to-latest"体验的具体机制,两个阈值常量定义了行为边界:

  • STICK_TO_BOTTOM_THRESHOLD_PX = 24:距底部 24px 以内视为"钉住底部";
  • SCROLL_BUTTON_THRESHOLD_PX = 120:距底部超过 120px 时显示 ConversationScrollButton

从源码结构看,"钉住"被实现为意图而非位置:内容在钉住的视口下增长时会短暂拉大 distance,这段时间内的 scroll 事件不被解读为用户离开底部,只有真正的向上滚动才解除钉住,触底则总是恢复钉住。程序化滚动(smooth 行为)期间通过 isProgrammaticScroll 标记与 1500ms 定时器区分用户滚动,且尊重 prefers-reduced-motion: reduce(把 smooth 降级为 auto)。此外,包导出 useConversation() hook,供消费方(例如提交新消息时)强制滚动到最新消息——这是组件面中很少见但实用的可编程接口,对应文档中"Message actions with accessible labels and focus behavior"之外的滚动控制契约。相关行为有测试覆盖(tests/agent-chat.test.tsx)。

在 Storybook 中探索组件

从 Cline 仓库根目录启动交互式组件目录:

bun -F @cline/ui storybook

然后打开 http://localhost:6006。工具栏可切换亮/暗模式,并提供代表性的桌面/移动视口。Stories 覆盖(见 stories 目录):

  • 主题色板、词法、圆角与控件
  • 完整会话与空会话
  • 用户、助手与错误消息
  • 折叠、展开与流式中的推理
  • pending、running、success、failed 的工具活动
  • 可展开与静态的工具摘要

在仓库的 agent sandbox 中,绑定转发宿主与未占用端口:

bun -F @cline/ui storybook -- --host 0.0.0.0 --port 3490 --exact-port

生产构建 Storybook bundle:

bun -F @cline/ui build-storybook

注意:Storybook 目前是隔离的组件参考,真实应用构建才是运行时适配器与产品 CSS 的集成测试。目录当前只能在 Cline monorepo checkout 中运行——story 源码与配置不包含在 npm 包里,目录也尚未托管。

Token 使用规范

产品组件应使用语义 token:

.card {
  color: var(--card-foreground);
  background: var(--card);
  border-color: var(--border);
}

.primary-action {
  color: var(--primary-foreground);
  background: var(--primary);
}

主题作者可以在不改写组件语义的前提下调整视觉层级:

:root,
.dark {
  --surface-1: var(--neutral-1);
  --surface-2: var(--neutral-2);
  --focus-ring: var(--accent-8);
}

--brand-* 小组只用于品牌物料。普通控件应优先视觉/状态角色或 shadcn 语义 token,以便在亮色、暗色与未来的主题层中继续工作。

产品覆盖的正确姿势

先导入包,再覆盖标准语义值:

@import "@cline/ui/theme/index.css";

:root {
  --primary: /* product-specific value */;
}

.dark {
  --primary: /* dark product-specific value */;
}

不要把 tokens.css 或组件 CSS 复制进消费应用。显式覆盖使产品差异可审查,也允许未来升级包版本。

消费方自有行为清单

以下内容保持留在 @cline/ui 之外:

  • Next、Tauri、VS Code 与运行时特定行为
  • #__next、视口锁定与外壳布局
  • 应用路由与信息架构
  • 会话、工作区、Provider、sidecar 行为
  • 运行时事件归一化与持久化
  • 审批与提问请求的编排
  • 产品特定动作与动画
  • 尚未被多个产品验证可复用的组件

该包应标准化重复的视觉与交互语言,而不是抹平产品边界。

采用检查清单

ADOPTION 文档给出了一份可直接执行的检查清单(建议作为接入 PR 的自查项):

  • [ ] 选择 workspace:* 或固定 npm 版本
  • [ ] 提交消费项目的锁文件
  • [ ] 选择 tokens-only、Tailwind 映射或完整主题
  • [ ] 加载所需字体文件
  • [ ] 按文档顺序导入文件
  • [ ] 使用 React 原语时导入 agent-chat.css
  • [ ] 使用 React 原语时安装 React 18.3 或 19
  • [ ] 在包边界映射产品消息/工具模型
  • [ ] 使用稳定的会话标识作为 Conversation 的 React key
  • [ ] Markdown 与链接/图片策略在消费方显式保留
  • [ ] 确认应用的 .dark 行为
  • [ ] 刻意的覆盖放在包导入之后
  • [ ] 在开发模式与生产模式下都构建应用
  • [ ] 亮/暗模式下比对代表性屏幕
  • [ ] 演练 focus、hover、disabled、streaming 与 error 状态
  • [ ] 在 Storybook 中检查相同状态
  • [ ] 记录所需覆盖与缺失的共享行为

Desktop 应用侧还有集成层面的验证入口:theme-integration.test.ts 专门测试主题在真实消费方中的集成,这是"Storybook 是隔离参考、真实构建才是集成测试"这一原则的落地。

契约与兼容性预期

在包达到稳定版本之前,契约变更应满足:

  • 附兼容性说明
  • 运行包构建、类型检查与测试
  • 构建 Storybook
  • 构建每个活跃消费方
  • 值变化时附亮/暗视觉证据
  • 避免重命名标准 shadcn/Tailwind 变量
  • Cline 自有色板值保留在 palette.css,UI 通过视觉/状态角色编写
  • 保持 tokens.css 框架无关
  • 组件 props 与产品运行时 Schema 解耦
  • 产品特定布局与编排留在包外

删除或改变某个语义 token 或组件 prop 的含义,最终应被视为破坏性变更;增量式地新增 token、props 与入口则是兼容的。仓库通过 tests/theme.test.tstests/package.test.tstests/tool-summary.test.tscheck:theme-generated 漂移检查来把这些约定固化到 CI。

发布与稳定路线图

npm 包解决了跨仓库分发问题;剩余工作是验证与稳定化公共契约。ADOPTION 文档建议的顺序:

  1. 在第二个生产级 Cline 应用中采用主题与会话原语
  2. 记录该应用需要的适配器或刻意变体
  3. 指定设计与工程负责人
  4. 定义浏览器、React、Tailwind 的兼容与弃用策略
  5. 为代表性 Storybook 状态增加截图回归覆盖
  6. 随着框架被验证,扩展干净消费方(clean-consumer)fixture
  7. 定义 API 可被视为稳定的兼容性节点

后续候选组件应由重复需求驱动:审批卡片、追问、附件、提示词输入框都是候选,但标准化之前应先比较它们当前的产品契约(包内已存在 AgentApprovalCardAgentAskQuestionAgentPromptQueue 等受控呈现组件,见 components/index.tstests,其宿主状态与提交回调仍由消费方拥有)。

关键参考路径

主题 路径
采用首稿(本文主体) sdk/packages/ui/ADOPTION.md
包 README sdk/packages/ui/README.md
包清单(exports / peer / scripts) sdk/packages/ui/package.json
agent-chat 组件实现 sdk/packages/ui/components/agent-chat/index.tsx
agent-chat 样式 sdk/packages/ui/components/agent-chat/agent-chat.css
tool-summary 纯函数模块 sdk/packages/ui/components/agent-chat/tool-summary/index.ts
ToolFileDiff 组件 sdk/packages/ui/components/agent-chat/tool-diff.tsx
Token 源文件 sdk/packages/ui/theme/tokens.css
色板源文件 sdk/packages/ui/theme/palette.css
完整主题入口 sdk/packages/ui/theme/index.css
主题生成脚本 sdk/packages/ui/scripts/generate-theme.ts
Desktop 主题集成(globals.css) apps/examples/desktop-app/webview/app/globals.css
Desktop 集成测试 apps/examples/desktop-app/webview/styles/theme-integration.test.ts
登录后查看全文
热门项目推荐
相关项目推荐