Skip to content

Agentaily design system —— 用法 skill

Agentaily 是一个 AI chatbot。品牌一句话,原文照录:极客风格,简约,大气,科技感(geek-flavored, minimal, expansive, technological)。它先用平实的语言回答,再展示推导:结论 → 推导。不浮夸,不卖萌。

本 skill 是已发布设计系统的消费者指南。你不需要仓库内部细节 —— 在包和下面的规则之上构建即可。

在生产级 React 中使用

bash
npm i @agentaily/design-system   # 外部仓库,从 npm 装(冻结在最后发布的版本)

agentaily monorepo 内部(pnpm + Turbo)设计系统作为一个工作区包存在 (design-system/)—— 无需安装。以 "@agentaily/design-system": "workspace:*" 依赖它 并就地编辑;跑 pnpm --filter @agentaily/design-system dev 在保存时重建 dist。 npm 发布已暂停 —— monorepo 是唯一真相源。

jsx
import "@agentaily/design-system/styles.css"; // once, at the app root — loads design tokens
import { Button, Composer, Reasoning } from "@agentaily/design-system";
  • 122 个组件 / 15 个类别 —— buttons、inputs、display、feedback、overlay、layout、chat、ai、code、voice、workflow、utilities,外加产品域图层 settings、auth、review。组合它们;绝不重新实现一个原语。
  • 找组件及其 props: 浏览 Storybook(每个变体 / 状态都是一个 story);TypeScript 契约随包发布(.d.ts)。
  • 亮色主题(paper)是默认(在 :root 上)。要暗色,就在某个包裹元素上设 data-theme="dark"(如 <html data-theme="dark">)。

表单 (Forms)

分层且与库无关。控件(Field/Input/Select/…)自持布局,绝不依赖某个表单引擎。Form + FormActions 只加纯结构。Form.useForm 是一个可选、零依赖、与 react-hook-form 对齐的 hook(values/errors/touched、含 async 的逐字段规则、handleSubmit、完整 formState);Form.useFieldArray 覆盖动态列表。把这个 hook 换成 RHF/TanStack,控件照样能用。

jsx
import { Form, FormActions, Input, Button } from "@agentaily/design-system";

const form = Form.useForm({ initialValues: { email: "" }, mode: "onBlur" });
<Form onSubmit={form.handleSubmit}>
  <Input label="Email" {...form.field("email", { required: "Email is required." })} />
  <FormActions bordered>
    <Button type="submit" onClick={form.handleSubmit}>
      Save
    </Button>
  </FormActions>
</Form>;

form.field(name, rules) 展开 value/onChange/onBlur/error;checkbox 用 { type: "checkbox" }。在沙箱化的 iframe 里还要给提交按钮加 onClick={form.handleSubmit} —— 那里原生表单提交被禁。

整页 shells 与 frames

整页布局是活组件,不是拷贝模板 —— 每个区域都是一个 slot,所以你只填内容、外框(chrome)在包升级时自动保持同步。AppShell(侧栏 + 顶栏 + 内容)、DesignerShell(双栏 chat/preview、可拖分隔条)、DocsLayout(nav · article · TOC)、SettingsSheet(浮动设置页:左侧分区导航 + 内容,构建在全屏的 PanelSheet 浮层外壳之上 —— 见下文 Settings)、SignInPage(品牌分屏鉴权页)、以及 VerifyEmailPage(品牌分屏整页邮箱验证 —— verifying → ok / error 状态机;注入 verifyToken/onResend,见下文 Auth)。栏高是 token(--topbar-h / --bar-h),所以各栏对得齐。

要预览一个生成的多文件 app(一个零构建 VFS —— path → content),用 PreviewWorkbench(code):一棵分组文件树(页面/脚本/样式,复用 FileTree)挨着一个实时预览(复用 WebPreview)。点击某个文件就切换预览 —— index.html→整页,.js→把该脚本放进一个宿主页,.css→带样式的页面;它绝不显示代码。传 files;选中态 + 侧栏自管理(或通过 selected/onSelect 受控)。纯拼装辅助函数(assembleFilePreviewassembleInlinePreviewpreviewableEntriesworkbenchFileTree)已导出供复用。预览跑在 <iframe sandbox="allow-scripts">(无 allow-same-origin)里。

jsx
import { AppShell, AccountControl, Icon } from "@agentaily/design-system";

<AppShell
  nav={[{ id: "overview", label: "Overview", icon: <Icon name="layout" size={16} /> }]}
  crumb={
    <>
      workspace / <b>Overview</b>
    </>
  }
  account={<AccountControl user={user} onLogin={openAuth} onLogout={signOut} />}
>
  {/* your screen */}
</AppShell>;

Settings(浮动面板 + 连接卡)

设置 UI 是一条四层链,每层都构建在下一层之上、且各自可单独复用:

PanelSheet            full-screen rise-up overlay shell (Header + body + optional Footer slot)
  └ SettingsSheet     floating settings page: left section nav + right content
      └ PageSection   the 「集成」 (or any) section: eyebrow + title + body slot ← you compose it
          └ DeepSeekCard   connection card(s) composed in via children (built on ConnectionCard ← Card)
PanelFooter           footer-content component (status-left / actions-right) for a sheet's footer slot
SettingsSaveBar       per-tab bottom save bar (explicit save, GitHub model) — composes PanelFooter
PageSection           generic eyebrow + title + description + body section (alias SettingsSection)

左导航(集成 / 通用 / 账户 …)由 SettingsSheetnav 配置;footer 是按 tab的。保存是显式的(非自动保存):集成分区用调用方持有的状态 + 显式 dirty;普通表单 tab 用 Form.useForm。两者共用同一个 SettingsSaveBar

jsx
import {
  SettingsSheet,
  PageSection,
  DeepSeekCard,
  SettingsSaveBar,
} from "@agentaily/design-system";

// caller owns cfg + persistence; cards are pure-display (props in, events out)
<SettingsSheet
  nav={[
    { id: "integrations", label: "集成", icon: "plug" },
    { id: "general", label: "通用", icon: "settings" },
  ]}
  active={tab}
  onNavigate={setTab}
  onClose={close}
  footer={
    tab === "integrations" ? (
      <SettingsSaveBar dirty={cfg !== saved} saving={saving} onSave={save} onReset={revert} />
    ) : null
  }
>
  {tab === "integrations" && (
    <PageSection eyebrow="集成 · INTEGRATIONS" title="连接你的服务">
      <DeepSeekCard {...deepSeekProps} />
    </PageSection>
  )}
</SettingsSheet>;

从手搓的浮层迁移?删掉任何本地的 .s-overlay / .s-modal / .s-wrap 外壳 CSS、挂上 <PanelSheet>;集成分区你自己用一个装着连接卡的 <PageSection> 来组合 —— 旧的一体式 IntegrationSettingsFeishuCard 已被移除,所以配置 + 持久化 + 保存(经 footer 的 SettingsSaveBar)归你管;若想要「改完即存」,跳过 SettingsSaveBar、在 onChange 里持久化即可。

locale 无关文案(settings/account 组件是 headless 的)。 DS 不自带 i18n —— 这些组件不再硬编码中文;每条用户可见字符串现在都默认英文,并可经 copy prop 覆盖(与 SignInPage / AuthDialog 同一个 DEFAULT_COPY idiom)。受影响的:DeepSeekCard · ConnectionCard · TestRow · SettingsSaveBar · AccountControl(各接 copy),以及 SettingsSheet(crumb / navLabel 默认为 "Settings")。一个中文 locale 的产品必须传 copy(如经你的 L() 辅助函数),否则会显示英文默认。 DeepSeekCardcopy 会顺着 ConnectionCardTestRow 一路传下去,所以一个对象就能本地化整张卡:

jsx
<DeepSeekCard {...deepSeekProps} copy={zh ? { title: "DeepSeek", desc: "驱动对话式交互…", connected: "已连接", disconnected: "未连接", apiKeyLabel: "API KEY", idleHint: "填写密钥后测试连通性", test: "测试连接", retest: "重新测试", testing: "正在握手…", help: { /* … */ } } : undefined} />
<SettingsSaveBar /* … */ copy={zh ? { save: "保存", reset: "放弃更改", saving: "正在保存…", saved: "已保存", cleanHint: "全部更改已保存", dirtyHint: "有未保存的更改" } : undefined} />
<AccountControl user={user} copy={zh ? { signIn: "登录", menuLabel: "账户菜单", signedIn: "已登录账户", signOut: "退出登录" } : undefined} />

每个组件的 .prompt.md 列出它完整的 copy key 集。(StatusPill 的 badge 文案仍默认中文 —— 经它既有的 labels prop 本地化;一个转发 seam 是后续 follow-up。)

Auth(整页邮箱验证)

SignInPage(登录 / 注册)和 VerifyEmailPage 是品牌分屏鉴权页;AuthDialog 是它的弹窗版兄弟。VerifyEmailPage 负责 verifying → ok / error 流程 —— 它绝不校验 token、写 session、弹 toast 或导航。你来注入这些副作用,把真正的校验、持久化、以及(防开放重定向的)重定向决策留在你的产品里。

jsx
import { VerifyEmailPage } from "@agentaily/design-system";

<VerifyEmailPage
  email="lin@agentaily.dev"
  returnTo={{ label: "your workspace", href: "/app" }}
  verifyToken={async () => {
    const res = await fetch("/api/verify?token=" + token);
    if (!res.ok) throw new Error("This link has expired."); // reject/throw/network → error
    // success: persist the session HERE, then resolve
  }}
  onResend={() => fetch("/api/verify/resend", { method: "POST" })}
  onContinue={(rt) => {
    location.href = isSameOrigin(rt?.href) ? rt.href : "/app";
  }} // YOU guard the redirect
  onBackToSignIn={() => navigate("/signin")}
  copy={
    {
      /* every string is localizable, shallow-merged per group */
    }
  }
/>;

组件强制执行的硬规则(让产品没法弄错):error 状态绝不自动重定向;success 倒计时(redirectDelay,默认 5s;noRedirect 关掉它)然后调 onContinue(returnTo);resend 受冷却门控 + 幂等 + 原地确认,并带一条「resend ≠ verified」的提醒。传 status(+ 可选 error)自己驱动状态机,或用 headless 的 VerifyEmailPage.useVerify({ verifyToken })(见下)配你自己的 UI。

Headless hooks(只有逻辑、没有 UI)

有些逻辑作为 headless hook 发布,作为静态属性挂在与它配对的组件上(镜像 Form.useForm)。hook 持有状态;调用方注入它,所以一份状态能同时驱动多处 UI。

  • Queue.useQueue({ onFirst, onBatch }){ queue, busy, enqueue, remove, reset } —— 忙时仍可继续发送的缓冲区;配 <ConversationThread controller={q} />(纯渲染聊天面)。
  • AuthDialog.useAuth(storageKey?){ user, signIn, signOut } —— localStorage 持久化的 session;配 <AuthDialog> + <AccountControl>
  • VerifyEmailPage.useVerify({ verifyToken }){ status, error, retry } —— 把一个注入的 token 检查跑成 verifying → ok / error 状态机(reject/throw/network → error,绝不静默挂起);配 <VerifyEmailPage> 或自带 UI。
jsx
import { ConversationThread, Queue } from "@agentaily/design-system";

const q = Queue.useQueue({
  onFirst: async (text) => {
    push({ role: "user", text });
    await run(text);
  },
  onBatch: async (texts) => texts.forEach((t) => push({ role: "user", text: t })),
});
<ConversationThread controller={q} messages={messages} draft={draft} onDraftChange={setDraft} />;

整个聊天 app —— 把 thread 包进 <ChatApp> 得到完整应用框架:一个会话历史侧栏(brand · new-chat · 分组历史 · account)+ 作为 children 的活动 <ConversationThread>,在手机上折叠为抽屉。和 thread 一样纯渲染 —— 你持有 conversations / activeId / onSelect / onNewChat;ChatApp 只路由点击、并维护抽屉的开合状态。

jsx
import { ChatApp, ConversationThread, AccountControl } from "@agentaily/design-system";

<ChatApp
  conversations={[{ id: "c1", title: "活动报名表", group: "今天" }]}
  activeId={activeId}
  onSelect={setActiveId}
  onNewChat={newChat}
  account={<AccountControl user={user} onLogout={logout} />}
>
  <ConversationThread controller={q} messages={messages} draft={draft} onDraftChange={setDraft} />
</ChatApp>;

聊天消息正文渲染 markdown。 <Message> 接收一个 markdown 字符串(markdown prop,或一个普通字符串作为 children),经 <Markdown> 渲染它 —— 段落、bold/italic/strikeinline code、围栏代码块、有序/无序/嵌套/任务列表、引用块、GFM 表格(逐列对齐)、链接 + 裸 URL 自动链接、#/##/### 标题。构造上即安全(无 dangerouslySetInnerHTML、链接 href 经 scheme 净化、图片惰性化)且流式容错(半截表格 / 未闭合标记降级为部分 / 字面量,绝不抛错)。React-node children 仍照常渲染(向后兼容)。对任何模型输出面,单独用 <Markdown content={…} />

jsx
import { Message, Markdown } from "@agentaily/design-system";

<Message role="assistant" streaming markdown={modelText} />; // chat turn, typeset
<Markdown content={modelText} />; // standalone

对于凭证 / 连接类 UI,还有 SecretField(掩码的 mono key/secret 输入 + 显示/隐藏 —— 给 API key 之类用;登录/注册的密码框则改用带 type="password" revealInput,会在标准表单字段外框内长出同样的眼睛切换)、StatusPill(连接状态 chip)、以及 TestRow / HelpSteps(连接卡原子)。ConnectionCard 是共享的连接卡外壳(构建在 Card 之上:可折叠头部 + 正文 + 测试行 + 状态色调);DeepSeekCard(LLM key)把它组合成一张纯展示卡 —— props 进、events 出,零 state / localStorage / 门控;调用方持有配置、持久化、保存、后端错误、以及任何就绪门控(与 Form.useForm / Queue.useQueue 同一套 headless 理念)。给其它服务做卡片,就照样组合 ConnectionCard。已连接的卡默认折叠成一行摘要(collapsible)。AccountControl(auth)接 onProfile,让它的邮箱行跳转到个人资料 / 账户页。Icon 是统一的 Lucide 集(Icon.names 列出全部);BrandMark 是 agentaily 标 + 字标;RotatingTagline 是动画品牌标语(打字短语 + 流动彩虹渐变 + 拖尾光标 —— 用于鉴权品牌面板和营销 hero)。MarkupLayer(review)是一个由 data-mk-label 驱动的点选式标注浮层。

运行时 —— 主题切换、i18n、跨子域持久化(非视觉)

包还 ship 了从 @agentaily/web-kit 吸收来的浏览器运行时(Providers / hooks / 工具函数,无渲染 UI)—— 用这些,别手搓主题切换、locale 状态或偏好存储。@agentaily/web-kit 正在被弃用;从 @agentaily/design-system 引入它。

jsx
import {
  ThemeProvider,
  useTheme,
  themeInitScript,
  createI18n,
  createStorage,
  persistentState,
} from "@agentaily/design-system";
  • 主题。 把你的 app 包进 <ThemeProvider>。它解析主题(light | dark,systemprefers-color-scheme 解析)并应用到 <html data-theme="…"> —— 这正是组件 token 所读取的(所以这就是你驱动暗色模式的方式)。useTheme(){ theme, resolvedTheme, setTheme } 给主题切换器用。要消除首屏的「闪错主题」,在任何绘制前于 <head> 内联同步执行 themeInitScript()(一个小巧、XSS 安全、零依赖的字符串)。
  • i18n。 createI18n({ catalogs, defaultLocale }) 返回 { LocaleProvider, useLocale, useMessages }。机制是共享的;每个产品注入自己的 message catalog。初始 locale 解析顺序为 持久化 → navigator.language → fallback;useMessages() 按 catalog 形状定型。(DS 组件经它们的 copy props 保持 locale 无关 —— 这是给那些 props 喂数据的 app 级 locale 状态。)
  • 持久化。 createStorage({ backend, cookieDomain, keyPrefix }) 构建一个 PreferenceStorage,默认走跨 .agentaily.com 子域的 cookie(让主题/locale 跨子域保持一致),依次降级 localStorage → 无 domain 的 cookie → 内存。它绝不抛错 —— SSR 和隐私模式静默降级。persistentState({ key, defaultValue, storage, decode, encode }) 把一个定型值绑到它上。ThemeProvider / createI18n 接受一个 storage 配置来定制它。

用于一次性产物(幻灯片、mock、静态 HTML)

链上已发布的样式表和 logo,然后用 token / class 构建:

html
<link rel="stylesheet" href="https://unpkg.com/@agentaily/design-system/dist/styles.css" />

Logo 标:@agentaily/design-system/assets/logo/agentaily-mark-{white,black}.svg

品牌规则(保持在品牌内)

  • 颜色 —— 单色,无色相 accent。 两套阶:paper(亮色,默认在 :root)和 ink(暗色,在 [data-theme="dark"] 下)。「accent」是反相:primary = 亮色下黑底白字、暗色下白底黑字。绿/琥珀/红(--ok/--warn/--danger)只作状态,绝不装饰。无渐变(点阵 mask fade 除外)。
  • 字体。 Space Grotesk(display + UI)和 JetBrains Mono(代码、时间戳、标签)。CJK 回退到系统 sans。display 尺寸用紧字距(−0.02em)、中等字重 —— 绝不 bold-black。正文 15px / 1.6,最大约 76ch。招牌是 mono ALL-CAPS 标签(12px,+0.08em):CONVERSATIONS
  • 间距 —— 4px 栅格。 外部空间宽裕(区块 72–96px,大气);内部节奏紧凑(8–16px,极客)。聊天列最大 760px,容器 1120px,侧栏 272px。
  • 圆角 —— 硬边。 2px chips · 4px buttons/inputs · 8px cards/dialogs。默认头像是方的(4px)。
  • 层级靠 1px hairline,不靠阴影。 阴影只给浮层(菜单、弹窗)。
  • 交互。 Hover = 背景填充或边框升级,绝不对文字做透明度淡化。Press = translateY(1px)。Focus = 双环(键盘);输入框换 border-color。
  • 动效。 一条缓动 cubic-bezier(0.2,0,0,1),三档时长 120/200/320ms。决断、无回弹。机械例外:光标在 steps(1) 闪烁,spinner 在 steps(8) 旋转。尊重 prefers-reduced-motion
  • 母题(每视图用 1 个,最多 2 个): block cursor ▍ · 点阵 · 角标刻线 · mono ALL-CAPS 标签。
  • 图标: Lucide(24px 栅格,stroke 2,currentColor,渲染在 14–20px)。Unicode ⌘ ⌥ ⇧ ⏎ ▣ ▍ 是打出来的文本,设为 mono。绝不用填充图标、双色、或 emoji 当图标。

语气 (Voice)

平实、精确、略带干燥 —— 一位优秀资深工程师的语气:短陈述句、具体数字、零废话。绝不用 emoji(状态由方点 + 颜色 token 承载)。数字即文案:「0.4s」「128k」「Retry in 18s」。双语设计(英文承载技术身份,中文承载正文),但每个元素只选一种语言。产品文案与按钮用 sentence case;标签母题用 mono ALL-CAPS;字标永远小写 agentaily

  • ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong 😅"
  • ✓ "有什么要解决的?" ✗ "✨ 开启你的奇妙旅程!"

被无具体要求地调用时

先问用户想构建什么,问一两个划定范围的问题,然后以一名 Agentaily 设计专家的身份行动 —— 视需要产出生产级 React(消费这个包)静态 HTML 产物。需要超出这些规则的深度时,读完整的 DESIGN.md(上方链接)。