From 31b68323142eebcd0580a384046a90ac80cbcfd4 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sun, 12 Jul 2026 15:30:25 +0800 Subject: [PATCH] refactor(web): make identity tints theme-aware and restore colorful default preset Derive --color-identity-* tokens from theme palette variables (chart and status hues) instead of fixed oklch hues, so group names, channel ids and model badges re-tint automatically when a color preset overrides the palette. The monochrome simple-large preset collapses identity tints to the foreground via --identity-color-mix: 0%. Also port main's blue default palette (light/dark), preview the default preset as a rainbow swatch, and show the token group as a tinted GroupBadge on the usage-logs page. --- web/default/src/components/config-drawer.tsx | 4 +- .../columns/common-logs-columns.tsx | 40 +++-- web/default/src/lib/theme-customization.ts | 2 +- web/default/src/styles/theme-presets.css | 6 + web/default/src/styles/theme.css | 145 +++++++----------- 5 files changed, 93 insertions(+), 104 deletions(-) diff --git a/web/default/src/components/config-drawer.tsx b/web/default/src/components/config-drawer.tsx index 1a3d6f1516..d37f1cf0e3 100644 --- a/web/default/src/components/config-drawer.tsx +++ b/web/default/src/components/config-drawer.tsx @@ -323,10 +323,12 @@ function PresetConfig() { aria-hidden='true' className='absolute inset-0 rounded-md' style={ + // The default preset is the full-color theme; two swatch + // stops cannot convey that, so it previews as a rainbow. preset.value === 'default' ? { background: - 'linear-gradient(135deg, var(--background) 0%, var(--muted) 50%, var(--foreground) 100%)', + 'linear-gradient(135deg, oklch(0.68 0.2 25) 0%, oklch(0.8 0.17 85) 25%, oklch(0.72 0.18 155) 50%, oklch(0.66 0.19 245) 75%, oklch(0.68 0.2 315) 100%)', } : { background: `linear-gradient(135deg, ${preset.swatches[0]} 0%, ${preset.swatches[1] ?? preset.swatches[0]} 100%)`, diff --git a/web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx b/web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx index baadc791a7..0963e708b2 100644 --- a/web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx +++ b/web/default/src/features/usage-logs/components/columns/common-logs-columns.tsx @@ -21,6 +21,7 @@ import { GitBranch, Sparkles } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' +import { GroupBadge } from '@/components/group-badge' import { CopyableStatusBadge, StatusBadge } from '@/components/status-badge' import { Avatar, AvatarFallback } from '@/components/ui/avatar' import { @@ -567,31 +568,33 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef[] { let group = log.group if (!group) group = other?.group || '' - const metaParts: string[] = [] const groupRatioText = getGroupRatioText( other, group ? groupRatios[group] : undefined ) - if (group) { - metaParts.push(sensitiveVisible ? group : '••••') - } - if (groupRatioText) metaParts.push(groupRatioText) + const tokenBadgeClassName = + 'max-w-full min-w-0 overflow-hidden [&>[data-slot=status-badge-label]]:max-w-full [&>[data-slot=status-badge-label]]:min-w-0 [&>[data-slot=status-badge-label]]:overflow-hidden [&>[data-slot=status-badge-label]]:text-ellipsis' return ( -
+
- }> + }> {sensitiveVisible ? ( {displayName} ) : ( - + {displayName} )} @@ -603,12 +606,21 @@ export function useCommonLogsColumns(isAdmin: boolean): ColumnDef[] { )} - {metaParts.length > 0 && ( - - {metaParts.join(' · ')} + {(group || groupRatioText) && ( + + {group && ( + + )} + {groupRatioText && ( + + {groupRatioText} + + )} )}
diff --git a/web/default/src/lib/theme-customization.ts b/web/default/src/lib/theme-customization.ts index 24d8c3d10f..2d55a2568c 100644 --- a/web/default/src/lib/theme-customization.ts +++ b/web/default/src/lib/theme-customization.ts @@ -27,7 +27,7 @@ export const THEME_PRESETS = [ { value: 'default', name: 'Default', - swatches: ['oklch(0.13 0 0)', 'oklch(0.95 0 0)'], + swatches: ['oklch(0.692 0.141 243.716)', 'oklch(0.7 0.12 280)'], }, { // Inspired by Anthropic's official brand language: warm cream canvas diff --git a/web/default/src/styles/theme-presets.css b/web/default/src/styles/theme-presets.css index c1ecd25d90..c7a80f8328 100644 --- a/web/default/src/styles/theme-presets.css +++ b/web/default/src/styles/theme-presets.css @@ -342,6 +342,12 @@ For commercial licensing, please contact support@quantumnous.com --skeleton-base: oklch(0.9 0 0); --skeleton-highlight: oklch(0.97 0 0); + /* Identity tints (group names, channel ids, model badges…) are decorative, + * so this monochrome preset collapses them to the foreground by mixing 0% + * of the palette hue. Status colors above stay colored on purpose: they + * carry meaning (error/success), which accessibility asks us to keep. */ + --identity-color-mix: 0%; + --radius: 0.5rem; --text-xs: 0.9rem; --text-sm: 1rem; diff --git a/web/default/src/styles/theme.css b/web/default/src/styles/theme.css index 52d670e918..5e933a9145 100644 --- a/web/default/src/styles/theme.css +++ b/web/default/src/styles/theme.css @@ -105,94 +105,66 @@ For commercial licensing, please contact support@quantumnous.com /* Metric identity accent; intentionally distinct from status semantics. */ --color-metric-rpm: color-mix( in oklch, - oklch(0.68 calc(0.2 * var(--identity-chroma-scale)) 350) - var(--identity-color-mix), + oklch(0.68 0.2 350) var(--identity-color-mix), var(--foreground) ); - /* Stable entity hues used by deterministic string-to-color identity tints. - * Mixing toward the achromatic foreground dilutes chroma along with - * lightness, so each hue pre-scales its chroma by the per-mode - * `--identity-chroma-scale` (see :root / .dark). */ + /* Stable entity tints used by deterministic string-to-color identity + * coloring. Each tint derives from a theme palette token (chart and status + * hues) instead of a fixed hue, so identity colors re-tint automatically + * when a theme preset overrides the palette. Mixing toward the foreground + * (via the per-mode `--identity-color-mix`) keeps compact text legible in + * both light and dark modes. */ --color-identity-amber: color-mix( in oklch, - oklch(0.72 calc(0.17 * var(--identity-chroma-scale)) 75) - var(--identity-color-mix), + var(--warning) var(--identity-color-mix), var(--foreground) ); --color-identity-blue: color-mix( in oklch, - oklch(0.62 calc(0.18 * var(--identity-chroma-scale)) 250) - var(--identity-color-mix), + var(--chart-1) var(--identity-color-mix), var(--foreground) ); --color-identity-cyan: color-mix( in oklch, - oklch(0.7 calc(0.14 * var(--identity-chroma-scale)) 205) - var(--identity-color-mix), + var(--chart-2) var(--identity-color-mix), var(--foreground) ); --color-identity-green: color-mix( in oklch, - oklch(0.64 calc(0.16 * var(--identity-chroma-scale)) 145) - var(--identity-color-mix), + var(--success) var(--identity-color-mix), var(--foreground) ); --color-identity-grey: var(--muted-foreground); - --color-identity-indigo: color-mix( - in oklch, - oklch(0.6 calc(0.2 * var(--identity-chroma-scale)) 275) - var(--identity-color-mix), - var(--foreground) - ); + --color-identity-indigo: var(--color-identity-blue); --color-identity-light-blue: color-mix( in oklch, - oklch(0.74 calc(0.12 * var(--identity-chroma-scale)) 225) - var(--identity-color-mix), + var(--info) var(--identity-color-mix), var(--foreground) ); --color-identity-lime: color-mix( in oklch, - oklch(0.75 calc(0.17 * var(--identity-chroma-scale)) 125) - var(--identity-color-mix), + var(--chart-3) var(--identity-color-mix), var(--foreground) ); - --color-identity-orange: color-mix( + --color-identity-orange: var(--color-identity-amber); + --color-identity-pink: color-mix( in oklch, - oklch(0.7 calc(0.19 * var(--identity-chroma-scale)) 50) - var(--identity-color-mix), + var(--chart-5) var(--identity-color-mix), var(--foreground) ); - --color-identity-pink: var(--color-metric-rpm); --color-identity-purple: color-mix( in oklch, - oklch(0.62 calc(0.2 * var(--identity-chroma-scale)) 305) - var(--identity-color-mix), + var(--chart-4) var(--identity-color-mix), var(--foreground) ); --color-identity-red: color-mix( in oklch, - oklch(0.64 calc(0.21 * var(--identity-chroma-scale)) 25) - var(--identity-color-mix), - var(--foreground) - ); - --color-identity-teal: color-mix( - in oklch, - oklch(0.66 calc(0.14 * var(--identity-chroma-scale)) 175) - var(--identity-color-mix), - var(--foreground) - ); - --color-identity-violet: color-mix( - in oklch, - oklch(0.65 calc(0.2 * var(--identity-chroma-scale)) 325) - var(--identity-color-mix), - var(--foreground) - ); - --color-identity-yellow: color-mix( - in oklch, - oklch(0.78 calc(0.16 * var(--identity-chroma-scale)) 95) - var(--identity-color-mix), + var(--destructive) var(--identity-color-mix), var(--foreground) ); + --color-identity-teal: var(--color-identity-cyan); + --color-identity-violet: var(--color-identity-purple); + --color-identity-yellow: var(--color-identity-amber); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); @@ -233,19 +205,19 @@ For commercial licensing, please contact support@quantumnous.com * `[data-theme-font='...']` blocks in theme-presets.css. Default mirrors * `--font-sans` so behavior is identical when no font preference is set. */ --font-body: var(--font-sans); - /* Light mode uses a bright pure-white canvas (matching the original - * palette) with soft light-grey borders and accents, so the UI reads - * crisp instead of collapsing into muddy greys. */ + /* Light mode uses a bright pure-white canvas with the blue brand palette: + * blue primary/ring/charts and primary-tinted accents, over soft + * light-grey borders, so the default theme reads colorful but calm. */ --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.13 0 0); - --primary-foreground: oklch(0.985 0 0); + --primary: oklch(0.692 0.141 243.716); + --primary-foreground: oklch(1 0 0); --secondary: oklch(0.95 0 0); - --secondary-foreground: oklch(0.145 0 0); + --secondary-foreground: oklch(0.42 0.16 250); --muted: oklch(0.97 0 0); --muted-foreground: oklch(0.49 0 0); /* Low-emphasis text tiers below `muted-foreground`, used for secondary @@ -263,7 +235,7 @@ For commercial licensing, please contact support@quantumnous.com var(--muted-foreground) 40%, transparent ); - --accent: oklch(0.95 0 0); + --accent: color-mix(in oklch, var(--primary) 12%, var(--background)); --accent-foreground: oklch(0.145 0 0); --destructive: oklch(0.577 0.245 27.325); --destructive-foreground: oklch(0.985 0 0); @@ -277,20 +249,20 @@ For commercial licensing, please contact support@quantumnous.com --neutral-foreground: oklch(0.145 0 0); --border: oklch(0.93 0 0); --input: oklch(0.93 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.975 0 0); + --ring: oklch(0.708 0.16 249.003); + --chart-1: oklch(0.72 0.18 250); + --chart-2: oklch(0.65 0.15 200); + --chart-3: oklch(0.7 0.12 280); + --chart-4: oklch(0.68 0.19 325); + --chart-5: oklch(0.68 0.16 155); + --sidebar: color-mix(in oklch, var(--foreground) 0.5%, var(--background)); --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.13 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.92 0 0); - --sidebar-accent-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.64 0.197 253.892); + --sidebar-primary-foreground: oklch(1 0 0); + --sidebar-accent: color-mix(in oklch, var(--primary) 12%, var(--background)); + --sidebar-accent-foreground: oklch(0.579 0.196 255.909); --sidebar-border: oklch(0.93 0 0); - --sidebar-ring: oklch(0.708 0 0); + --sidebar-ring: oklch(0.58 0.2 250); --skeleton-base: oklch(0.97 0 0); --skeleton-highlight: oklch(0.985 0 0); /* 100% keeps the original vivid semantic hues (matching the old palette @@ -298,7 +270,6 @@ For commercial licensing, please contact support@quantumnous.com * of darkening them toward the near-black foreground. */ --status-color-mix: 100%; --identity-color-mix: 100%; - --identity-chroma-scale: 1; --identity-surface-mix: 13%; --identity-border-mix: 28%; --table-row: var(--card); @@ -333,13 +304,13 @@ For commercial licensing, please contact support@quantumnous.com --card-foreground: oklch(0.965 0 0); --popover: oklch(0.305 0 0); --popover-foreground: oklch(0.965 0 0); - --primary: oklch(0.965 0 0); - --primary-foreground: oklch(0.155 0 0); + --primary: oklch(0.54 0.142 248.516); + --primary-foreground: oklch(1 0 0); --secondary: oklch(0.335 0 0); - --secondary-foreground: oklch(0.965 0 0); + --secondary-foreground: oklch(0.9 0.05 250); --muted: oklch(0.305 0 0); --muted-foreground: oklch(0.78 0 0); - --accent: oklch(0.365 0 0); + --accent: color-mix(in oklch, var(--primary) 20%, var(--background)); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --destructive-foreground: oklch(0.985 0 0); @@ -347,32 +318,30 @@ For commercial licensing, please contact support@quantumnous.com --success-foreground: oklch(0.145 0 0); --warning: oklch(0.769 0.188 70.08); --warning-foreground: oklch(0.145 0 0); - --info: oklch(0.68 0.17 237.323); + --info: oklch(0.613 0.14 239.919); --info-foreground: oklch(0.145 0 0); --neutral: oklch(0.76 0 0); --neutral-foreground: oklch(0.155 0 0); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 17%); - --ring: oklch(0.68 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); + --ring: oklch(0.554 0.148 250.726); + --chart-1: oklch(0.76 0.14 250); + --chart-2: oklch(0.76 0.13 200); + --chart-3: oklch(0.76 0.13 285); + --chart-4: oklch(0.75 0.16 325); + --chart-5: oklch(0.76 0.14 155); --sidebar: oklch(0.225 0 0); --sidebar-foreground: oklch(0.95 0 0); - --sidebar-primary: oklch(0.965 0 0); - --sidebar-primary-foreground: oklch(0.155 0 0); - --sidebar-accent: oklch(0.355 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.588 0.123 246.488); + --sidebar-primary-foreground: oklch(1 0 0); + --sidebar-accent: color-mix(in oklch, var(--primary) 20%, var(--background)); + --sidebar-accent-foreground: oklch(0.9 0.05 250); --sidebar-border: oklch(1 0 0 / 11%); - --sidebar-ring: oklch(0.68 0 0); + --sidebar-ring: oklch(0.7 0.17 250); --skeleton-base: oklch(0.335 0 0); --skeleton-highlight: oklch(0.44 0 0); --status-color-mix: 55%; --identity-color-mix: 55%; - /* Dark mode keeps the soft macaron pastels unchanged. */ - --identity-chroma-scale: 1; --identity-surface-mix: 10%; --identity-border-mix: 20%; --table-row: var(--background);