Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const descriptionKeys: Readonly<Record<string, MessageKey>> = {
ExploreGroup: "component.ExploreGroup.description",
ContextLoadGroup: "component.ContextLoadGroup.description",
FlowChatRuntimeStatus: "component.FlowChatRuntimeStatus.description",
FlowChatTurnMetrics: "component.FlowChatTurnMetrics.description",
OpenBitFunSolidMark: "component.OpenBitFunSolidMark.description",
OpenBitFunAppIcon: "component.OpenBitFunAppIcon.description",
OpenBitFunMark: "component.OpenBitFunMark.description",
Expand Down
27 changes: 27 additions & 0 deletions design-system/apps/design-lab/src/i18n/flowChatMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ export const flowChatEn = {
"component.ThinkingBlock.description": "Reasoning and summaries with controlled disclosure, shared with the conversation.",
"component.ExploreGroup.description": "A shared exploration group with controlled disclosure and natural-height content.",
"component.FlowChatRuntimeStatus.description": "A resident status slot whose label and visibility are controlled by the application.",
"component.FlowChatTurnMetrics.description": "Turn token usage, cache hits and output speed with accessible details for each metric.",
"flowChat.modelRound.metrics.label": "Turn usage",
"flowChat.modelRound.metrics.total": "Total tokens",
"flowChat.modelRound.metrics.input": "Input tokens",
"flowChat.modelRound.metrics.output": "Output tokens",
"flowChat.modelRound.metrics.cache": "Cache hit rate",
"flowChat.modelRound.metrics.averageRate": "Average output rate",
"flowChat.modelRound.metrics.rateValue": "{value} tokens/s",
"flowChat.modelRound.metrics.rateBasis": "Output tokens divided by the whole turn duration, including tools and waits.",
"detail.option.collapsed": "Collapsed",
"detail.option.streaming": "Streaming",
"detail.option.summary": "Summary",
Expand Down Expand Up @@ -225,6 +234,15 @@ export const flowChatZhCN = {
"component.ThinkingBlock.description": "对话共用的思考与摘要组件,统一展开收起和内容区域。",
"component.ExploreGroup.description": "对话共用的探索组,统一折叠与分组摘要,内容自然展开。",
"component.FlowChatRuntimeStatus.description": "常驻运行状态区域,由应用提供文案并控制显示。",
"component.FlowChatTurnMetrics.description": "回合 Token 用量、缓存命中与输出速度,每项指标均提供无障碍详情。",
"flowChat.modelRound.metrics.label": "回合用量",
"flowChat.modelRound.metrics.total": "总 Token",
"flowChat.modelRound.metrics.input": "输入 Token",
"flowChat.modelRound.metrics.output": "输出 Token",
"flowChat.modelRound.metrics.cache": "缓存命中率",
"flowChat.modelRound.metrics.averageRate": "平均输出速度",
"flowChat.modelRound.metrics.rateValue": "{value} tokens/s",
"flowChat.modelRound.metrics.rateBasis": "输出 Token 除以整个回合耗时,包含工具执行与等待。",
"detail.option.collapsed": "已收起",
"detail.option.streaming": "流式更新",
"detail.option.summary": "思考摘要",
Expand Down Expand Up @@ -372,6 +390,15 @@ export const flowChatZhTW = {
"component.ThinkingBlock.description": "對話共用的思考與摘要元件,統一展開收起與內容區域。",
"component.ExploreGroup.description": "對話共用的探索組,統一折疊與分組摘要,內容自然展開。",
"component.FlowChatRuntimeStatus.description": "常駐執行狀態區域,由應用提供文案並控制顯示。",
"component.FlowChatTurnMetrics.description": "回合 Token 用量、快取命中與輸出速度,每項指標均提供無障礙詳情。",
"flowChat.modelRound.metrics.label": "回合用量",
"flowChat.modelRound.metrics.total": "總 Token",
"flowChat.modelRound.metrics.input": "輸入 Token",
"flowChat.modelRound.metrics.output": "輸出 Token",
"flowChat.modelRound.metrics.cache": "快取命中率",
"flowChat.modelRound.metrics.averageRate": "平均輸出速度",
"flowChat.modelRound.metrics.rateValue": "{value} tokens/s",
"flowChat.modelRound.metrics.rateBasis": "輸出 Token 除以整個回合耗時,包含工具執行與等待。",
"detail.option.collapsed": "已收合",
"detail.option.streaming": "串流更新",
"detail.option.summary": "思考摘要",
Expand Down
8 changes: 6 additions & 2 deletions design-system/apps/design-lab/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export const enUSMessages = {
"component.GitToolCard.description": "A prominent Git command card with output, warning, failure, and execution metadata.",
"component.GlobSearchToolCard.description": "An ambient file-pattern search card with reusable result-list anatomy.",
"component.GrepSearchToolCard.description": "An ambient text-search card with query metadata and a scrollable result preview.",
"component.IconButton.description": "A labeled icon-only action with quiet, filled, and primary presentation variants.",
"component.IconButton.description": "A labeled icon-only action with annotation, quiet, outlined, filled, and primary variants. An annotation provides a small, muted entry to supplemental help.",
"component.Input.description": "A native text field with independent leading and trailing content slots.",
"component.KeyHint.description": "A semantic keyboard hint with optional decorative icon content.",
"component.LoadingState.description": "A centered loading composition with an activity indicator and optional support copy.",
Expand Down Expand Up @@ -556,6 +556,7 @@ export const enUSMessages = {
"detail.option.fill": "Filled",
"detail.option.primary": "Primary",
"detail.option.quiet": "Quiet",
"detail.option.annotation": "Annotation",
"detail.option.text": "Text",
"detail.option.sm": "Small",
"detail.option.start": "Start",
Expand Down Expand Up @@ -968,7 +969,7 @@ export const zhCNMessages = {
"component.GitToolCard.description": "关注型 Git 命令卡片,统一呈现输出、警告、失败与执行元数据。",
"component.GlobSearchToolCard.description": "复用结果列表结构的低关注文件模式搜索卡片。",
"component.GrepSearchToolCard.description": "带查询元数据和可滚动结果预览的低关注文本搜索卡片。",
"component.IconButton.description": "带无障碍标签的纯图标操作组件,支持安静、填充和主要呈现方式。",
"component.IconButton.description": "带无障碍标签的纯图标组件,支持标注、安静、描边、填充和主要呈现方式。标注以小尺寸、弱化颜色提供补充说明入口。",
"component.Input.description": "使用原生输入语义,并提供独立前置与后置内容槽的文本字段。",
"component.KeyHint.description": "使用键盘提示语义,并支持可选装饰图标的基础组件。",
"component.LoadingState.description": "由活动指示器与可选说明文字组成的居中加载状态。",
Expand Down Expand Up @@ -1361,6 +1362,7 @@ export const zhCNMessages = {
"detail.option.fill": "填充",
"detail.option.primary": "主要",
"detail.option.quiet": "安静",
"detail.option.annotation": "标注",
"detail.option.text": "文字",
"detail.option.sm": "小",
"detail.option.start": "起始",
Expand Down Expand Up @@ -1770,6 +1772,7 @@ export const zhTWMessages = {
"meta.category.primitive": "基礎元件",
"meta.category.other": "元件",
"component.ActionCard.description": "可獨立設定前置內容、標題、說明、選取狀態和同級尾端操作的可互動卡片。",
"component.IconButton.description": "帶無障礙標籤的純圖示元件,支援標註、安靜、描邊、填滿和主要呈現方式。標註以小尺寸、弱化顏色提供補充說明入口。",
"component.MobileActionSheet.description": "用於統一呈現一般和危險操作清單的可關閉行動端彈層。",
"component.MobileBadge.description": "用於狀態、身分與中繼資料的緊湊行動端標籤。",
"component.MobileBanner.description": "支援語義色調和可選操作的行動端行內狀態橫幅。",
Expand Down Expand Up @@ -2109,6 +2112,7 @@ export const zhTWMessages = {
"detail.icon": "圖示",
"detail.iconPosition": "圖示位置",
"detail.option.outline": "描邊",
"detail.option.annotation": "標註",
"detail.option.fill": "填滿",
"detail.option.sm": "小",
"detail.option.start": "起始",
Expand Down
28 changes: 17 additions & 11 deletions design-system/apps/design-lab/src/pages/ComponentDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ type PageHeaderAlign = "center" | "start";
type PageHeaderSize = "display" | "lg" | "md" | "sm";

const buttonVariants = ["outline", "fill", "secondary", "primary", "text"] as const;
const iconButtonVariants = ["quiet", "outline", "fill", "primary"] as const;
const iconButtonVariants = ["annotation", "quiet", "outline", "fill", "primary"] as const;
const iconButtonSizes = ["xs", "standard", "sm", "md", "lg"] as const;
const buttonInspectorStates = ["default", "hover", "active"] as const;
const fieldOrientations = ["vertical", "horizontal"] as const;
Expand Down Expand Up @@ -230,6 +230,7 @@ const optionLabelKeys: Readonly<Record<string, MessageKey>> = {
on: "detail.option.on",
open: "detail.option.open",
outline: "detail.option.outline",
annotation: "detail.option.annotation",
primary: "detail.option.primary",
quiet: "detail.option.quiet",
raised: "detail.option.raised",
Expand Down Expand Up @@ -538,19 +539,20 @@ export function ComponentDetailPage({

if (component.name === "IconButton") {
const stateProps = `${inspectorDisabled ? " disabled" : ""}${inspectorLoading ? " loading" : ""}`;
return `import { IconButton } from "@openbitfun/ui";\nimport { List } from "lucide-react";\n\n<IconButton\n aria-label="${t("components.preview.listView")}"\n icon={<List />}\n variant="${iconButtonVariant}"\n size="${iconButtonSize}"\n shape="${iconButtonShape}"${stateProps}\n/>`;
const annotation = iconButtonVariant === "annotation";
return `import { Icon, IconButton } from "@openbitfun/ui";${annotation ? "" : '\nimport { List } from "lucide-react";'}\n\n<IconButton\n aria-label="${t(annotation ? "components.preview.fieldHelp" : "components.preview.listView")}"\n icon={<Icon ${annotation ? 'name="info"' : 'glyph={List}'} />}\n variant="${iconButtonVariant}"\n size="${iconButtonSize}"\n shape="${iconButtonShape}"${stateProps}\n/>`;
}
if (component.name === "Field") {
const labelAction = fieldShowLabelAction
? `\n labelAction={<IconButton aria-label="${t("components.preview.fieldHelp")}" icon={<Icon name="info" />} size="xs" />}`
? `\n labelAction={\n <Tooltip content="${t("components.preview.fieldDescription")}" trigger="hover-focus" openOnClick>\n <IconButton aria-label="${t("components.preview.fieldHelp")}" icon={<Icon name="info" />} size="xs" variant="annotation" />\n </Tooltip>\n }`
: "";
const controlLeading = fieldShowControlLeading
? `\n controlLeading={<Switch aria-label="${t("components.preview.notifications")}" />}`
: "";
const controlTrailing = fieldShowControlTrailing
? `\n controlTrailing={<IconButton aria-label="${t("components.preview.more")}" icon={<Icon name="more" />} size="xs" />}`
: "";
return `import { Icon, Field, IconButton, Input, Switch } from "@openbitfun/ui";\n\n<Field\n description="${t("components.preview.fieldDescription")}"\n label="${t("components.preview.appearance")}"${labelAction}${controlLeading}${controlTrailing}\n orientation="${fieldOrientation}"\n required\n>\n <Input defaultValue="${t("components.preview.fieldValue")}" trailing={<Icon name="chevron-down" />} />\n</Field>`;
return `import { Icon, Field, IconButton, Input, Switch, Tooltip } from "@openbitfun/ui";\n\n<Field\n description="${t("components.preview.fieldDescription")}"\n label="${t("components.preview.appearance")}"${labelAction}${controlLeading}${controlTrailing}\n orientation="${fieldOrientation}"\n required\n>\n <Input defaultValue="${t("components.preview.fieldValue")}" trailing={<Icon name="chevron-down" />} />\n</Field>`;
}
if (component.name === "Input") {
const stateProps = previewState === "disabled"
Expand Down Expand Up @@ -867,13 +869,14 @@ export function ComponentDetailPage({
previewVariant = iconButtonVariant,
applyInspectorControls = false,
) {
const annotation = previewVariant === "annotation";
return (
<IconButton
aria-label={t("components.preview.listView")}
aria-label={t(annotation ? "components.preview.fieldHelp" : "components.preview.listView")}
className={state === "focus-visible" ? "lab-force-focus" : undefined}
data-openbitfun-preview-state={state === "hover" || state === "active" ? state : undefined}
disabled={state === "disabled" || applyInspectorControls && inspectorDisabled}
icon={<List aria-hidden="true" />}
icon={annotation ? <Icon name="info" /> : <Icon glyph={List} />}
loading={state === "loading" || applyInspectorControls && inspectorLoading}
size={iconButtonSize}
shape={iconButtonShape}
Expand Down Expand Up @@ -1364,11 +1367,14 @@ export function ComponentDetailPage({
error={state === "invalid" ? t("components.preview.inputError") : undefined}
label={t("components.preview.appearance")}
labelAction={fieldShowLabelAction ? (
<IconButton
aria-label={t("components.preview.fieldHelp")}
icon={<Icon name="info" size="lg" aria-hidden="true" />}
size="xs"
/>
<Tooltip content={t("components.preview.fieldDescription")} trigger="hover-focus" openOnClick>
<IconButton
aria-label={t("components.preview.fieldHelp")}
icon={<Icon name="info" size="xs" />}
size="xs"
variant="annotation"
/>
</Tooltip>
) : undefined}
orientation={applyInspectorControls ? fieldOrientation : "vertical"}
required
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ import {
DirectoryListToolCard,
FileDiffToolCard,
FileOperationToolCard,
FlowChatMetricDetails,
FlowChatTurnMetrics,
GetToolSpecToolCard,
GitToolCard,
GlobSearchToolCard,
Expand Down Expand Up @@ -142,6 +144,34 @@ type FlowChatPreviewDefinitionMap = {

type PreviewProps = FlowChatPreviewRenderOptions;

function TurnMetricsPreview({ state, interactive }: PreviewProps) {
const t = usePresentationTranslate();
const formatNumber = usePresentationFormatNumber();
const unknown = state === "unknown";
const tokenValue = formatNumber(24800);
const rateValue = t("modelRound.metrics.rateValue", { value: formatNumber(42) });
const tokenRows = [
{ label: t("modelRound.metrics.total"), value: tokenValue },
{ label: t("modelRound.metrics.input"), value: formatNumber(22400) },
{ label: t("modelRound.metrics.output"), value: formatNumber(2400) },
...(!unknown ? [{ label: t("modelRound.metrics.cache"), value: `${formatNumber(62.5)}%` }] : []),
];
const rateRows = [{ label: t("modelRound.metrics.averageRate"), value: rateValue }];

return <FlowChatTurnMetrics
label={t("modelRound.metrics.label")}
tokenValue={tokenValue}
tokenDescription={tokenRows.map(row => `${row.label}: ${row.value}`).join(" · ")}
tokenDetails={<FlowChatMetricDetails rows={tokenRows} />}
cacheHitRate={unknown ? null : 0.625}
rateValue={unknown ? null : rateValue}
rateDescription={rateRows.map(row => `${row.label}: ${row.value}`).join(" · ")}
rateDetails={<FlowChatMetricDetails rows={rateRows} note={t("modelRound.metrics.rateBasis")} />}
speedLevel={unknown ? null : 3}
focusable={interactive}
/>;
}

function RelationPreview({ state, interactive }: PreviewProps) {
const t = usePresentationTranslate();
const result = state === 'error' ? t('toolCards.default.failed') : state === 'loading'
Expand Down Expand Up @@ -981,6 +1011,11 @@ export const flowChatPreviewDefinitions = {
codeSample: () => '<FlowChatRuntimeStatus label={hint} visible={visible} />',
render: (options) => <RuntimeStatusPreview {...options} />,
},
FlowChatTurnMetrics: {
attention: "ambient", icon: Cpu, section: "framework", specimens: [],
codeSample: () => '<FlowChatTurnMetrics label={label} tokenValue={tokens} tokenDescription={tokenDescription} cacheHitRate={cacheHitRate} rateValue={rate} rateDescription={rateDescription} speedLevel={speedLevel} />',
render: (options) => <TurnMetricsPreview {...options} />,
},
AgentControlToolCard: {
attention: "prominent",
codeSample: concreteCodeSample("AgentControlToolCard"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,11 +201,11 @@ test("IconButton preview exposes its icon-only presentation contract", async ()
assert.ok(declaration);
assert.deepEqual(
[...declaration[1].matchAll(/"([^"]+)"/g)].map((match) => match[1]),
["quiet", "outline", "fill", "primary"],
["annotation", "quiet", "outline", "fill", "primary"],
);
assert.match(source, /data-component="icon-button"/);
assert.match(source, /aria-label=\{t\("components\.preview\.listView"\)\}/);
assert.match(source, /icon=\{<List aria-hidden="true" \/>\}/);
assert.match(source, /aria-label=\{t\(annotation \? "components\.preview\.fieldHelp" : "components\.preview\.listView"\)\}/);
assert.match(source, /icon=\{annotation \? <Icon name="info" \/> : <Icon glyph=\{List\} \/>\}/);
assert.match(source, /size=\{iconButtonSize\}/);
assert.match(source, /shape=\{iconButtonShape\}/);
});
Expand Down
Loading
Loading