diff --git a/web/default/AGENTS.md b/web/default/AGENTS.md index 4af5e86b..fcb732f9 100644 --- a/web/default/AGENTS.md +++ b/web/default/AGENTS.md @@ -125,6 +125,41 @@ - 以 Tailwind 工具类为主,动态类名用 `cn()` 合并;非动态场景避免内联样式。 - 响应式采用移动优先与 Tailwind 断点(`sm:`、`md:`、`lg:` 等);主题与暗色用 CSS 变量与 `dark:`,自定义样式集中在 `src/styles/`,组件内尽量少写自定义 CSS。 +#### 3.10.1 设计系统纪律(强约束) + +以下规则为硬约束,代码评审与 AI 改动一律遵循;违反即为缺陷。 + +**颜色** + +- 后台界面(除 `features/home/` 营销页外)**禁止硬编码 Tailwind 调色板类**(如 `text-emerald-600`、`bg-amber-50`、`border-rose-200`),一律使用语义令牌:`success` / `warning` / `destructive` / `info` / `neutral`、`primary` / `muted` / `accent` / `border` / `ring`。暗色适配由令牌完成,禁止 `dark:text-emerald-400` 之类逐处覆写。 +- 状态色仅表达状态(成功/警告/错误/信息),不得用于装饰或分类;分类信息(模型名、分组名、渠道名等)一律中性呈现,身份靠文本与图标传达。`StatusBadge` 的 `autoColor` 已废弃为 neutral,禁止恢复字符串哈希取色。 +- 需要浅底状态胶囊时使用 `status-badge.tsx` 的 `tintedBadgeClassMap`,不要再手写 `bg-xxx-50 text-xxx-700 dark:...` 组合。 +- 图表用 `--chart-1..5`;用户头像身份色走 `getAvatarColorClass`。这两处是仅有的多色场景。 +- 禁止用 `!important`(`!text-*` 等)压制文字颜色/字号;若需要覆盖,说明层级设计有误,先修组件。 + +**排版** + +- 字体族只有两轨:正文 `--font-sans`(含 CJK 回退,勿删栈中中文字体)与 `--font-serif`(serif 主题轴)。`font-mono` 仅用于密钥、ID、代码、原始 JSON;**数字对齐一律 `tabular-nums`,不得再加 `font-mono`**。 +- 字号只用 `text-xs/sm/base/lg/xl/2xl`,**禁止任意值字号**(`text-[10px]`、`text-[11px]` 等;营销页除外)。 +- 字重只用 `font-medium`(强调/表头)与 `font-semibold`(标题/关键数值);后台正文与标题**禁用 `font-bold`**。 +- 标题三档契约:页题 `text-lg font-semibold tracking-tight`;卡片/区块题 `text-base font-semibold`;面板/小节题 `text-sm font-medium`。禁止响应式跳字号的标题(如 `text-base sm:text-lg`)。 +- 表头不使用 `uppercase + tracking-wider`。 + +**圆角与阴影** + +- 圆角单源 `--radius`(默认 0.625rem),组件用派生档 `rounded-md/lg/xl`;禁止在业务代码里写死圆角像素或混用 `rounded-2xl`/`rounded-4xl` 表达同类容器。 +- 卡片边界「描边或投影二选一」:默认 `border`(或 Card 自带 ring),禁止再叠 `shadow-*`;`shadow` 只保留给浮层(popover/dropdown/dialog)。 + +**动效** + +- 后台界面禁止入场动画:无 stagger、无 translate/scale/blur 入场、无按钮按压缩放。页面切换只允许纯透明度 fade(见 `lib/motion.ts` 的 `pageEnter`)。`page-transition.tsx` 的 Stagger 系列组件已固化为纯容器,禁止恢复动画。 +- 允许的动效上限:颜色/透明度过渡 ≤150ms、骨架屏 shimmer、`Collapsible/Accordion` 展开、加载 spinner。装饰性动效只允许出现在 `features/home/`(landing)。 + +**徽章与图标** + +- 文本徽章统一 `StatusBadge`(五种语义 voice);模型/分组/渠道等实体徽章统一中性底(`border-border/60 bg-muted/30`)。不要新造第三种徽章样式。 +- 业务代码图标一律 `lucide-react`;`components/ui/` 由 shadcn 生成器维护(Hugeicons),不要手改基础组件图标库;AI 品牌图标用 `@lobehub/icons`(经 `getLobeIcon`)。 + ### 3.11 文件组织 - **功能模块**:置于 `src/features//`,内含 `components/`、`lib/`、`hooks/`,以及按需的 `api.ts`、`types.ts`、`constants.ts`、入口组件等。 @@ -178,3 +213,4 @@ - **2026-01-29**:重组文档结构,合并重复内容,明确主次与交叉引用。 - **2026-01-31**:在 3.2 中补充「类型检查」要求:改动 TS/TSX 后须执行 typecheck 并修复至无错。 - **2026-06-21**:在 3.2 中补充「Lint 检查」要求:完成代码改动前须修复所涉及文件的所有 lint error。 +- **2026-07-11**:新增 3.10.1「设计系统纪律」:语义色硬约束(去彩虹徽章/硬编码调色板)、排版契约(字号/字重/标题三档/mono 边界)、圆角单源、后台零入场动效、徽章与图标单轨。 diff --git a/web/default/src/components/ai-elements/code-block.tsx b/web/default/src/components/ai-elements/code-block.tsx index df70915f..daff49a5 100644 --- a/web/default/src/components/ai-elements/code-block.tsx +++ b/web/default/src/components/ai-elements/code-block.tsx @@ -413,7 +413,7 @@ export const CodeBlockFrame = ({ {showToolbar && (
-
+
{title}
diff --git a/web/default/src/components/data-table/layout/card-row-content.tsx b/web/default/src/components/data-table/layout/card-row-content.tsx index 59af3ce5..2e96c3f2 100644 --- a/web/default/src/components/data-table/layout/card-row-content.tsx +++ b/web/default/src/components/data-table/layout/card-row-content.tsx @@ -110,7 +110,7 @@ function CompactContent({ row }: { row: Row }) { return (
{label && ( -
+
{label}
)} @@ -177,7 +177,7 @@ function FallbackContent({ row }: { row: Row }) { key={cell.id} className='flex items-start justify-between gap-2 overflow-hidden' > - + {label}
diff --git a/web/default/src/components/data-table/static/static-data-table-classnames.ts b/web/default/src/components/data-table/static/static-data-table-classnames.ts index 9282ea70..4d35cc77 100644 --- a/web/default/src/components/data-table/static/static-data-table-classnames.ts +++ b/web/default/src/components/data-table/static/static-data-table-classnames.ts @@ -24,10 +24,9 @@ export const staticDataTableClassNames = { compactHeaderRow: 'hover:bg-transparent', mutedHeaderRow: '[background-color:var(--table-header)] hover:[background-color:var(--table-header-hover)]', - compactHeaderCell: - 'text-muted-foreground py-2 text-[10px] font-medium tracking-wider uppercase', + compactHeaderCell: 'text-muted-foreground py-2 text-xs font-medium', compactHeaderCellRight: - 'text-muted-foreground py-2 text-right text-[10px] font-medium tracking-wider uppercase', + 'text-muted-foreground py-2 text-right text-xs font-medium', compactCell: 'py-2.5', compactTopCell: 'py-2.5 align-top', compactTopNumericCell: 'py-2.5 text-right align-top font-mono', diff --git a/web/default/src/components/group-badge.tsx b/web/default/src/components/group-badge.tsx index b153d59f..7e0f350c 100644 --- a/web/default/src/components/group-badge.tsx +++ b/web/default/src/components/group-badge.tsx @@ -98,7 +98,7 @@ export function GroupBadge(props: GroupBadgeProps) { {badge} diff --git a/web/default/src/components/html-content.tsx b/web/default/src/components/html-content.tsx index c9f9f294..677bd97e 100644 --- a/web/default/src/components/html-content.tsx +++ b/web/default/src/components/html-content.tsx @@ -97,10 +97,7 @@ function hardenIsolatedHtml(html: string): string { template.content.querySelectorAll('a[target="_blank"]').forEach((link) => { const rel = new Set( - link - .getAttribute('rel') - ?.split(/\s+/) - .filter(Boolean) ?? [] + link.getAttribute('rel')?.split(/\s+/).filter(Boolean) ?? [] ) rel.add('noopener') @@ -182,10 +179,7 @@ function IsolatedHtmlContent(props: { }, [props.html]) return ( -
+
) } diff --git a/web/default/src/components/json-code-editor.tsx b/web/default/src/components/json-code-editor.tsx index e15d53d6..e88e41ab 100644 --- a/web/default/src/components/json-code-editor.tsx +++ b/web/default/src/components/json-code-editor.tsx @@ -229,7 +229,7 @@ export function JsonCodeEditor({ {jsonStatus.valid ? ( diff --git a/web/default/src/components/layout/components/nav-group.tsx b/web/default/src/components/layout/components/nav-group.tsx index be6a1521..57f4b82e 100644 --- a/web/default/src/components/layout/components/nav-group.tsx +++ b/web/default/src/components/layout/components/nav-group.tsx @@ -66,7 +66,7 @@ export function NavGroup({ title, items }: NavGroupProps) { return ( - + {title} diff --git a/web/default/src/components/layout/components/section-page-layout.tsx b/web/default/src/components/layout/components/section-page-layout.tsx index 11e9255b..34ef85ea 100644 --- a/web/default/src/components/layout/components/section-page-layout.tsx +++ b/web/default/src/components/layout/components/section-page-layout.tsx @@ -85,7 +85,7 @@ export function SectionPageLayout(props: SectionPageLayoutProps) { )}
-

+

{title}

diff --git a/web/default/src/components/model-group-selector.tsx b/web/default/src/components/model-group-selector.tsx index a85f3aac..e4ca15b6 100644 --- a/web/default/src/components/model-group-selector.tsx +++ b/web/default/src/components/model-group-selector.tsx @@ -253,7 +253,7 @@ export const ModelSelector: React.FC = React.memo(
{t('{{category}} Models', { category })} @@ -274,7 +274,7 @@ export const ModelSelector: React.FC = React.memo(
{model.label} @@ -400,7 +400,7 @@ export const GroupSelector: React.FC = React.memo( className={isMobile ? '!max-h-full flex-1 p-2' : 'max-h-[240px]'} > -
+
{t('Model Group')}
{groups.map((group) => ( @@ -417,7 +417,7 @@ export const GroupSelector: React.FC = React.memo( >
- + {group.label} {(group.desc || group.description) && ( @@ -658,7 +658,7 @@ export const ModelGroupSelector: React.FC = ({ {currentModel?.label || t('Model')} - + {currentGroup?.label || t('Group')} @@ -672,7 +672,7 @@ export const ModelGroupSelector: React.FC = ({ !isMobile && modelGroupSelectorLayoutClasses.groupColumn )} > -
+
{t('Model Group')}
0 ? ( {unreadCount > 99 ? '99+' : unreadCount} diff --git a/web/default/src/components/page-transition.tsx b/web/default/src/components/page-transition.tsx index ffd44d9b..be75a50a 100644 --- a/web/default/src/components/page-transition.tsx +++ b/web/default/src/components/page-transition.tsx @@ -20,16 +20,7 @@ import { Outlet, useRouterState } from '@tanstack/react-router' import { motion, useReducedMotion, type Variants } from 'motion/react' import type { ReactNode } from 'react' -import { - CARD_ITEM_VARIANTS, - CARD_STAGGER_VARIANTS, - MOTION_TRANSITION, - MOTION_VARIANTS, - STAGGER_ITEM_VARIANTS, - STAGGER_VARIANTS, - TABLE_ROW_VARIANTS, - TABLE_STAGGER_VARIANTS, -} from '@/lib/motion' +import { MOTION_TRANSITION, MOTION_VARIANTS } from '@/lib/motion' interface PageTransitionProps { children: ReactNode @@ -92,23 +83,13 @@ interface StaggerContainerProps { variants?: Variants } +/* Entrance choreography is retired on admin surfaces: content appears + * immediately so tables and dashboards read as instant. The components stay + * as plain wrappers so call sites keep working; decorative motion remains + * available to the landing page via its dedicated landing-* utilities. */ + export function StaggerContainer(props: StaggerContainerProps) { - const shouldReduce = useReducedMotion() - - if (shouldReduce) { - return
{props.children}
- } - - return ( - - {props.children} - - ) + return
{props.children}
} interface StaggerItemProps { @@ -118,68 +99,23 @@ interface StaggerItemProps { } export function StaggerItem(props: StaggerItemProps) { - return ( - - {props.children} - - ) + return
{props.children}
} export function TableStaggerContainer(props: StaggerContainerProps) { - const shouldReduce = useReducedMotion() - - if (shouldReduce) { - return <>{props.children} - } - - return ( - - {props.children} - - ) + return {props.children} } export function TableStaggerRow(props: StaggerItemProps) { - return ( - - {props.children} - - ) + return {props.children} } export function CardStaggerContainer(props: StaggerContainerProps) { - const shouldReduce = useReducedMotion() - - if (shouldReduce) { - return
{props.children}
- } - - return ( - - {props.children} - - ) + return
{props.children}
} export function CardStaggerItem(props: StaggerItemProps) { - return ( - - {props.children} - - ) + return
{props.children}
} interface FadeInProps { diff --git a/web/default/src/components/profile-dropdown.tsx b/web/default/src/components/profile-dropdown.tsx index f34c7556..a151ddc1 100644 --- a/web/default/src/components/profile-dropdown.tsx +++ b/web/default/src/components/profile-dropdown.tsx @@ -63,7 +63,7 @@ export function ProfileDropdown() { > {avatarFallback} diff --git a/web/default/src/components/rich-content.tsx b/web/default/src/components/rich-content.tsx index a84a8188..61bdffeb 100644 --- a/web/default/src/components/rich-content.tsx +++ b/web/default/src/components/rich-content.tsx @@ -16,10 +16,7 @@ along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ -import { - HtmlContent, - type HtmlContentVariant, -} from '@/components/html-content' +import { HtmlContent, type HtmlContentVariant } from '@/components/html-content' import { Markdown } from '@/components/ui/markdown' type RichContentMode = 'markdown' | 'html' diff --git a/web/default/src/components/search.tsx b/web/default/src/components/search.tsx index 0c8feb4c..500738b7 100644 --- a/web/default/src/components/search.tsx +++ b/web/default/src/components/search.tsx @@ -50,7 +50,7 @@ export function Search({ className = '', placeholder }: SearchProps) { size={16} /> {resolvedPlaceholder} - + {t('K')} diff --git a/web/default/src/components/status-badge.tsx b/web/default/src/components/status-badge.tsx index fd2e90f1..bb16f6e7 100644 --- a/web/default/src/components/status-badge.tsx +++ b/web/default/src/components/status-badge.tsx @@ -21,30 +21,34 @@ For commercial licensing, please contact support@quantumnous.com import * as React from 'react' import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' -import { stringToColor } from '@/lib/colors' import { cn } from '@/lib/utils' +/* Color discipline: badges speak exactly five voices — success / warning / + * danger / info / neutral. Legacy hue keys (blue, purple, teal, ...) remain + * valid variant names for compatibility, but they all resolve to one of the + * five semantic tokens so tables stop reading as rainbows. Chart hues stay + * reserved for data visualization. */ export const dotColorMap = { success: 'bg-success', warning: 'bg-warning', danger: 'bg-destructive', info: 'bg-info', neutral: 'bg-neutral', - purple: 'bg-chart-4', + purple: 'bg-neutral', amber: 'bg-warning', - blue: 'bg-chart-1', - cyan: 'bg-chart-2', + blue: 'bg-neutral', + cyan: 'bg-neutral', green: 'bg-success', grey: 'bg-neutral', - indigo: 'bg-chart-1', + indigo: 'bg-neutral', 'light-blue': 'bg-info', - 'light-green': 'bg-emerald-400', - lime: 'bg-chart-3', + 'light-green': 'bg-success', + lime: 'bg-neutral', orange: 'bg-warning', - pink: 'bg-chart-5', + pink: 'bg-neutral', red: 'bg-destructive', - teal: 'bg-chart-2', - violet: 'bg-chart-4', + teal: 'bg-neutral', + violet: 'bg-neutral', yellow: 'bg-warning', } as const @@ -54,26 +58,38 @@ export const textColorMap = { danger: 'text-destructive', info: 'text-info', neutral: 'text-muted-foreground', - purple: 'text-chart-4', + purple: 'text-muted-foreground', amber: 'text-warning', - blue: 'text-chart-1', - cyan: 'text-chart-2', + blue: 'text-muted-foreground', + cyan: 'text-muted-foreground', green: 'text-success', grey: 'text-muted-foreground', - indigo: 'text-chart-1', + indigo: 'text-muted-foreground', 'light-blue': 'text-info', - 'light-green': 'text-emerald-500 dark:text-emerald-300', - lime: 'text-chart-3', + 'light-green': 'text-success', + lime: 'text-muted-foreground', orange: 'text-warning', - pink: 'text-chart-5', + pink: 'text-muted-foreground', red: 'text-destructive', - teal: 'text-chart-2', - violet: 'text-chart-4', + teal: 'text-muted-foreground', + violet: 'text-muted-foreground', yellow: 'text-warning', } as const export type StatusVariant = keyof typeof dotColorMap +/** Soft tinted pill surface per semantic voice — the one sanctioned recipe + * for badges that need a filled background (timing pills, duration pills). + * Border/background/text always come from the same token so light and dark + * modes stay consistent without `!important` overrides. */ +export const tintedBadgeClassMap = { + success: 'border border-success/25 bg-success/10 text-success', + warning: 'border border-warning/30 bg-warning/10 text-warning', + danger: 'border border-destructive/25 bg-destructive/10 text-destructive', + info: 'border border-info/25 bg-info/10 text-info', + neutral: 'border border-border/60 bg-muted/30 text-muted-foreground', +} as const + /** Controls the visual style of the badge. * - `badge` — default pill with background and padding (default) * - `text` — plain text, no background or padding, only color @@ -112,6 +128,8 @@ export interface StatusBadgeProps extends Omit< size?: 'sm' | 'md' | 'lg' | null copyable?: boolean copyText?: string + /** Deprecated: categorical identity is conveyed by label/icon, not hue. + * Accepted for call-site compatibility; renders as `neutral`. */ autoColor?: string /** Visual style. Defaults to 'badge'. Can be overridden via StatusBadgeTypeContext. */ type?: StatusBadgeType @@ -137,9 +155,10 @@ export function StatusBadge({ const contextType = React.useContext(StatusBadgeTypeContext) const type = typeProp ?? contextType - const computedVariant: StatusVariant = autoColor - ? (stringToColor(autoColor) as StatusVariant) - : (variant ?? 'neutral') + // String-hash rainbow coloring is retired; `autoColor` intentionally falls + // through to the neutral voice (see prop doc above). + void autoColor + const computedVariant: StatusVariant = variant ?? 'neutral' const handleClick = (e: React.MouseEvent) => { if (copyable) { @@ -166,7 +185,7 @@ export function StatusBadge({ className={cn( 'inline-flex w-fit max-w-full min-w-0 shrink items-center font-medium tracking-normal whitespace-nowrap transition-colors', isBadge - ? cn('rounded-4xl', sizeMap[size ?? 'sm']) + ? cn('rounded-md', sizeMap[size ?? 'sm']) : cn( textSizeMap[size ?? 'sm'], type === 'underline' && 'border-b border-current pb-px' diff --git a/web/default/src/components/table-id.tsx b/web/default/src/components/table-id.tsx index 7fc699e1..bc8a69ba 100644 --- a/web/default/src/components/table-id.tsx +++ b/web/default/src/components/table-id.tsx @@ -27,7 +27,7 @@ export function TableId(props: TableIdProps) { return ( diff --git a/web/default/src/components/ui/chart.tsx b/web/default/src/components/ui/chart.tsx index 02d2ccc6..c7eddbf0 100644 --- a/web/default/src/components/ui/chart.tsx +++ b/web/default/src/components/ui/chart.tsx @@ -269,7 +269,7 @@ function ChartTooltipContent({
{item.value != null && ( - + {typeof item.value === 'number' ? item.value.toLocaleString() : String(item.value)} diff --git a/web/default/src/components/ui/titled-card.tsx b/web/default/src/components/ui/titled-card.tsx index 131ffb05..142f9103 100644 --- a/web/default/src/components/ui/titled-card.tsx +++ b/web/default/src/components/ui/titled-card.tsx @@ -63,7 +63,7 @@ export function TitledCard({ className={cn('gap-0 overflow-hidden py-0', className)} >
@@ -80,7 +80,7 @@ export function TitledCard({
@@ -88,7 +88,7 @@ export function TitledCard({ {description != null && ( {description} @@ -100,7 +100,7 @@ export function TitledCard({ )}
- + {children} diff --git a/web/default/src/config/fonts.ts b/web/default/src/config/fonts.ts deleted file mode 100644 index dfb4d9a3..00000000 --- a/web/default/src/config/fonts.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright (C) 2023-2026 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ -/** - * List of available font names (visit the url `/settings/appearance`). - * This array is used to generate dynamic font classes (e.g., `font-inter`, `font-manrope`). - * - * 📝 How to Add a New Font (Tailwind v4+): - * 1. Add the font name here. - * 2. Update the `` tag in 'index.html' to include the new font from CDN or other source. - * 3. Add the new font family to 'theme.css' using the `@theme inline` and `font-family` CSS variable. - * - * Example: - * fonts.ts → Add 'roboto' to this array. - * index.html → Add font link for Roboto. - * theme.css → Add the new font in the CSS, e.g.: - * @theme inline { - * // ... other font families - * --font-roboto: 'Roboto', var(--font-sans); - * } - */ -export const fonts = ['inter', 'manrope', 'system'] as const diff --git a/web/default/src/context/font-provider.tsx b/web/default/src/context/font-provider.tsx deleted file mode 100644 index e18e9348..00000000 --- a/web/default/src/context/font-provider.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright (C) 2023-2026 QuantumNous - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as -published by the Free Software Foundation, either version 3 of the -License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . - -For commercial licensing, please contact support@quantumnous.com -*/ -import { createContext, useContext, useEffect, useState } from 'react' - -import { fonts } from '@/config/fonts' -import { getCookie, setCookie, removeCookie } from '@/lib/cookies' - -type Font = (typeof fonts)[number] - -const FONT_COOKIE_NAME = 'font' -const FONT_COOKIE_MAX_AGE = 60 * 60 * 24 * 365 // 1 year - -type FontContextType = { - font: Font - setFont: (font: Font) => void - resetFont: () => void -} - -const FontContext = createContext(null) - -export function FontProvider({ children }: { children: React.ReactNode }) { - const [font, _setFont] = useState(() => { - const savedFont = getCookie(FONT_COOKIE_NAME) - return fonts.includes(savedFont as Font) ? (savedFont as Font) : fonts[0] - }) - - useEffect(() => { - const applyFont = (font: string) => { - const root = document.documentElement - root.classList.forEach((cls) => { - if (cls.startsWith('font-')) root.classList.remove(cls) - }) - root.classList.add(`font-${font}`) - } - - applyFont(font) - }, [font]) - - const setFont = (font: Font) => { - setCookie(FONT_COOKIE_NAME, font, FONT_COOKIE_MAX_AGE) - _setFont(font) - } - - const resetFont = () => { - removeCookie(FONT_COOKIE_NAME) - _setFont(fonts[0]) - } - - return ( - {children} - ) -} - -// eslint-disable-next-line react-refresh/only-export-components -export const useFont = () => { - const context = useContext(FontContext) - if (!context) { - throw new Error('useFont must be used within a FontProvider') - } - return context -} diff --git a/web/default/src/features/channels/components/channel-card.tsx b/web/default/src/features/channels/components/channel-card.tsx index 7198b5e2..384b5f32 100644 --- a/web/default/src/features/channels/components/channel-card.tsx +++ b/web/default/src/features/channels/components/channel-card.tsx @@ -78,7 +78,7 @@ function ChannelCardComponent({ const responseCell = renderCell('response_time') const testCell = renderCell('test_time') - const labelClass = 'text-muted-foreground text-[11px] font-medium select-none' + const labelClass = 'text-muted-foreground text-xs font-medium select-none' // In card view the enable/disable state is already conveyed by the inline // power toggle, so the plain "Enabled"/"Disabled" badge is redundant. Keep diff --git a/web/default/src/features/channels/components/channels-columns.tsx b/web/default/src/features/channels/components/channels-columns.tsx index 12c03cc0..54d9c984 100644 --- a/web/default/src/features/channels/components/channels-columns.tsx +++ b/web/default/src/features/channels/components/channels-columns.tsx @@ -638,7 +638,7 @@ export function useChannelsColumns( + } /> diff --git a/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx b/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx index cccf59fb..5046c617 100644 --- a/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/codex-usage-dialog.tsx @@ -30,7 +30,11 @@ import { useTranslation } from 'react-i18next' import { ConfirmDialog } from '@/components/confirm-dialog' import { Dialog } from '@/components/dialog' -import { StatusBadge, type StatusBadgeProps } from '@/components/status-badge' +import { + StatusBadge, + textColorMap, + type StatusBadgeProps, +} from '@/components/status-badge' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { @@ -400,32 +404,11 @@ function formatLabelValue(label: string, value: string) { return label.endsWith(':') ? `${label}${value}` : `${label} ${value}` } +// Single source of truth for variant → text color lives in status-badge. const percentTextClassName: Record< NonNullable, string -> = { - success: 'text-success', - warning: 'text-warning', - danger: 'text-destructive', - info: 'text-info', - neutral: 'text-muted-foreground', - purple: 'text-chart-4', - amber: 'text-warning', - blue: 'text-chart-1', - cyan: 'text-chart-2', - green: 'text-success', - grey: 'text-muted-foreground', - indigo: 'text-chart-1', - 'light-blue': 'text-info', - 'light-green': 'text-emerald-500 dark:text-emerald-300', - lime: 'text-chart-3', - orange: 'text-warning', - pink: 'text-chart-5', - red: 'text-destructive', - teal: 'text-chart-2', - violet: 'text-chart-4', - yellow: 'text-warning', -} +> = textColorMap type RateLimitWindowProps = { title: string @@ -464,7 +447,7 @@ function RateLimitWindow(props: RateLimitWindowProps) { > {hasData ? `${percent}%` : '-'}
-
+
{t('Used')}
@@ -482,7 +465,7 @@ function RateLimitWindow(props: RateLimitWindowProps) { )}
-
+
{t('Reset at:')}
@@ -490,7 +473,7 @@ function RateLimitWindow(props: RateLimitWindowProps) {
-
+
{t('Resets in:')}
@@ -565,9 +548,7 @@ function RateLimitGroupSection(props: RateLimitGroupSectionProps) { {props.meteredFeature ? (
- - metered_feature - + metered_feature {props.meteredFeature} @@ -600,7 +581,7 @@ function InfoField(props: { props.className )} > -
+
{props.label}
@@ -635,7 +616,7 @@ function ResetCreditTimeField(props: { }) { return (
-
+
{props.label}
) : null} {props.credit.id ? ( -
+
{props.credit.id}
) : null}
-
+
{t('Expires in')}
} + render={} > {t('From model redirect, not yet added to models list')} diff --git a/web/default/src/features/channels/components/dialogs/param-override-editor-dialog.tsx b/web/default/src/features/channels/components/dialogs/param-override-editor-dialog.tsx index 9ded59b8..a6dd07f3 100644 --- a/web/default/src/features/channels/components/dialogs/param-override-editor-dialog.tsx +++ b/web/default/src/features/channels/components/dialogs/param-override-editor-dialog.tsx @@ -553,17 +553,11 @@ const getOperationSummary = ( } const getModeTagTailwind = (mode: string): string => { - if (mode.includes('header')) - return 'bg-cyan-500/15 text-cyan-700 dark:text-cyan-300 border-cyan-500/20' - if (mode.includes('replace') || mode.includes('trim')) - return 'bg-violet-500/15 text-violet-700 dark:text-violet-300 border-violet-500/20' - if (mode.includes('copy') || mode.includes('move')) - return 'bg-blue-500/15 text-blue-700 dark:text-blue-300 border-blue-500/20' + // Only destructive-ish operations get a color voice; the rest stay neutral — + // the mode label itself carries the identity. if (mode.includes('error') || mode.includes('prune')) - return 'bg-red-500/15 text-red-700 dark:text-red-300 border-red-500/20' - if (mode.includes('sync')) - return 'bg-green-500/15 text-green-700 dark:text-green-300 border-green-500/20' - return 'bg-muted text-muted-foreground' + return 'bg-destructive/10 text-destructive border-destructive/20' + return 'bg-muted text-muted-foreground border-border/60' } const getModePathLabel = (mode: string): string => { @@ -1849,7 +1843,7 @@ export function ParamOverrideEditorDialog( @@ -1935,24 +1929,21 @@ export function ParamOverrideEditorDialog( #{index + 1} - + {operation.conditions.length}
-

+

{getOperationSummary(operation, index)}

{operation.description.trim() && ( -

+

{operation.description}

)} @@ -2214,7 +2205,7 @@ function RuleEditor(ruleEditorProps: RuleEditorProps) { - + {operation.description.length}/180
@@ -2485,7 +2476,7 @@ function ConditionEditor(conditionEditorProps: ConditionEditorProps) {
- + C{conditionEditorProps.conditionIndex + 1} @@ -2522,9 +2513,7 @@ function ConditionEditor(conditionEditorProps: ConditionEditorProps) {
- + @@ -2539,9 +2528,7 @@ function ConditionEditor(conditionEditorProps: ConditionEditorProps) { />
- +
-