Agentaily design system —— 用法 skill
Agentaily 是一个 AI chatbot。品牌一句话,原文照录:极客风格,简约,大气,科技感(geek-flavored, minimal, expansive, technological)。它先用平实的语言回答,再展示推导:结论 → 推导。不浮夸,不卖萌。
本 skill 是已发布设计系统的消费者指南。你不需要仓库内部细节 —— 在包和下面的规则之上构建即可。
- 包:
@agentaily/design-system(npm) · 目录: https://agentaily.github.io/design-system/(Storybook) · 完整品牌指南: https://github.com/agentaily/design-system/blob/main/DESIGN.md
在生产级 React 中使用
npm i @agentaily/design-system # 外部仓库,从 npm 装(冻结在最后发布的版本)在
agentailymonorepo 内部(pnpm + Turbo)设计系统作为一个工作区包存在 (design-system/)—— 无需安装。以"@agentaily/design-system": "workspace:*"依赖它 并就地编辑;跑pnpm --filter @agentaily/design-system dev在保存时重建dist。 npm 发布已暂停 —— monorepo 是唯一真相源。
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,控件照样能用。
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 受控)。纯拼装辅助函数(assembleFilePreview、assembleInlinePreview、previewableEntries、workbenchFileTree)已导出供复用。预览跑在 <iframe sandbox="allow-scripts">(无 allow-same-origin)里。
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)左导航(集成 / 通用 / 账户 …)由 SettingsSheet 的 nav 配置;footer 是按 tab的。保存是显式的(非自动保存):集成分区用调用方持有的状态 + 显式 dirty;普通表单 tab 用 Form.useForm。两者共用同一个 SettingsSaveBar。
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> 来组合 —— 旧的一体式 IntegrationSettings 和 FeishuCard 已被移除,所以配置 + 持久化 + 保存(经 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() 辅助函数),否则会显示英文默认。 DeepSeekCard 的 copy 会顺着 ConnectionCard → TestRow 一路传下去,所以一个对象就能本地化整张卡:
<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 或导航。你来注入这些副作用,把真正的校验、持久化、以及(防开放重定向的)重定向决策留在你的产品里。
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。
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 只路由点击、并维护抽屉的开合状态。
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/strike、inline code、围栏代码块、有序/无序/嵌套/任务列表、引用块、GFM 表格(逐列对齐)、链接 + 裸 URL 自动链接、#/##/### 标题。构造上即安全(无 dangerouslySetInnerHTML、链接 href 经 scheme 净化、图片惰性化)且流式容错(半截表格 / 未闭合标记降级为部分 / 字面量,绝不抛错)。React-node children 仍照常渲染(向后兼容)。对任何模型输出面,单独用 <Markdown content={…} />。
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" reveal 的 Input,会在标准表单字段外框内长出同样的眼睛切换)、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 引入它。
import {
ThemeProvider,
useTheme,
themeInitScript,
createI18n,
createStorage,
persistentState,
} from "@agentaily/design-system";- 主题。 把你的 app 包进
<ThemeProvider>。它解析主题(light | dark,system经prefers-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 组件经它们的copyprops 保持 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 构建:
<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(上方链接)。