refactor(console/dashboard): replace Card with HeroUI Pro Widget#2
refactor(console/dashboard): replace Card with HeroUI Pro Widget#2dingyi wants to merge 77 commits into
Conversation
Brings the visual language of the standalone @heroui-pro/template-dashboard project into all /console routes by aligning background, typography, and color tokens with the template: - Strip body radial-gradient background; body now matches template's flat var(--background) so cards/tables sit on a uniform surface. - DashboardHeader greeting drops to text-xl + text-foreground (matches template navbar title weight/size; was text-2xl text-gray-800). - StatsCards / ApiInfoPanel / AnnouncementsPanel / FaqPanel / UptimePanel swap literal text-gray-* / text-slate-* / bg-gray-50 utilities for semantic HeroUI tokens (text-foreground, text-muted, text-primary, border-border, bg-surface-secondary). - ApiInfoPanel API route label drops !font-bold for template's font-semibold; KPI numbers in StatsCards gain tabular-nums. Also rolls in pre-existing in-flight HeroUI/Semi cleanup that was sitting in the working tree, plus .gitignore entries for .playwright-mcp and loose screenshot artifacts. Made-with: Cursor
… SiderBar Brings the visual layer of the console sidebar in line with @heroui-pro/template-dashboard's Sidebar component (without changing the underlying PageLayout structure). - Drop sidebar-shell glassmorphism: remove backdrop-blur, heavy box-shadow, and gradient background. Sidebar now uses flat bg-background + border-r border-border like the template. - Add a Sidebar.Header-style block at the top: avatar + display name + role (super admin / admin / user), mirroring template's Kate Moore / Admin block. - Tone down section labels: uppercase tracking-wide bold → quiet `text-xs font-medium text-muted` labels. - Menu items: rounded-2xl → rounded-md, font-semibold → font-medium, slate-600/300 → text-muted, primary-tinted active → bg-surface-secondary + text-foreground. - Nested chat items drop to font-normal, matching template's relative weight hierarchy. - Collapse button: drop bordered + backdrop-blur look, switch to ghost variant with semantic hover tokens. Build passes (`bun run build`). Functional behavior (routing, collapse state, mobile drawer, chat sub-menu) is untouched. Made-with: Cursor
Continues aligning console chrome with @heroui-pro/template-dashboard after the sidebar visual refresh. Header bar (always visible): - Drop bg-white/75 + backdrop-blur-lg glassmorphism on header element; use flat bg-background + border-b border-border like template's Navbar. - Navigation: text-slate-700/200 -> text-foreground; reduce link weight from font-semibold to font-medium to match template. - HeaderLogo: text-slate-950/white -> text-foreground. - UserArea: replace bg-slate-900/[0.04] hover:bg-slate-900/[0.07] etc. with bg-surface-secondary / hover:bg-surface-tertiary; chevron and username swap slate literal colors for text-muted/foreground; menu popover drops backdrop-blur + bg-white/95 in favor of bg-background + border-border + shadow-lg. Dashboard panels: - DashboardHeader: drop hard bg-emerald-500 / bg-blue-500 buttons in favor of HeroUI's `size='sm' variant='tertiary'` to match template's navbar action buttons. - useDashboardStats: drop pastel `bg-blue-50/green-50/yellow-50/indigo-50` card tints (which had no dark-mode adaptation) so KPI cards render on the default neutral surface like template's `<KPI>` cards. Sidebar (in-flight HeroUI Pro adoption + my user header): - SiderBar.jsx now uses the actual `<Sidebar>` / `<Sidebar.Mobile>` / `<Sidebar.Header>` / `<Sidebar.Group>` / `<Sidebar.MenuItem>` components from @heroui-pro/react, mirroring template's `dashboard-sidebar.tsx` exactly. Avatar + display name + role block sits inside Sidebar.Header. - PageLayout.jsx now wraps the app in `<Sidebar.Provider>` and bridges the existing localStorage-backed collapse state to its open/onOpenChange API. - MobileMenuButton + useHeaderBar updated to drive the new Sidebar.Provider open state. `bun run build` passes (27.42s). Visual smoke test on /login + / shows flat header with subtle border-bottom, semantic surface tokens throughout, no glassmorphism. Made-with: Cursor
…aligned skeleton Three product-spec deviations from the literal template-dashboard layout: 1. Sidebar.Trigger lives in the top navbar (top-left, after the mobile menu button), NOT inside the sidebar itself. Mirrors the same place template-dashboard puts <Sidebar.Trigger /> in `dashboard-navbar.tsx`, which is conceptually "sidebar collapse is at the top" rather than bottom of the sidebar. Only renders on /console routes + non-mobile. 2. User avatar / display-name / role block moves from Sidebar.Header to Sidebar.Footer (bottom of the sidebar) per product preference (template puts it at top with Kate Moore / Admin; we want it at the bottom). 3. Drop the SkeletonWrapper that was wrapping <SidebarBody/>. The old skeleton renders bare divs sized for the previous self-implemented sidebar; nesting them inside heroui-pro's <Sidebar> component (which expects Sidebar.Content / .Footer children with its own internal layout) caused all menu items to misalign — items rendered flush-left without sidebar padding, no visible container background, and broke the visual structure entirely. Now the real Sidebar structure renders immediately; visibleSections gracefully empties while useSidebar() is still resolving, so the menu briefly shows nothing rather than misaligned placeholder bars. Build passes. Visual smoke test on /login confirms the trigger appears at top-left of the navbar and no misaligned bars remain. Made-with: Cursor
The misalignment everyone saw on /console was rooted in a single missing import. The codebase used <Sidebar> / <Sidebar.MenuItem> / <Sidebar.Group> from @heroui-pro/react but only loaded @heroui/styles in index.css. Without @heroui-pro/react/css the Pro components have zero styling — menu icons stack on top of labels instead of sitting beside them, group labels disappear, sidebar has no container width or border-right, and footer slot renders into nothing. Mirror the template-dashboard's globals.css layout exactly: @import '@heroui/styles'; @import '@heroui-pro/react/css'; Plus two follow-on cleanups now that the stylesheet is loaded: - Wrap the user avatar / role block as a real <Sidebar.MenuItem> inside a <Sidebar.Menu> within <Sidebar.Footer>. Sidebar.Footer ignores arbitrary children when not given a Sidebar.Menu structure. - Skip menu items / chat sub-items with no resolvable href to silence the remaining `PressResponder was rendered without a pressable child` warnings from react-aria. Verified locally with the Go backend running: - sidebar trigger □ at top-left of navbar (template parity) - menu items render as icon + label rows with primary-tinted active state - group labels (Chat / Console / Personal center / Admin) visible - user avatar + display name + role at the very bottom of the sidebar - collapse / expand via the navbar trigger works Made-with: Cursor
Three follow-on UX fixes after the heroui-pro Sidebar wiring:
1. Right-side content was no longer scrollable.
Sidebar.Provider was set to `min-h-dvh` so tall console pages grew the
document body and the whole page (sidebar included) scrolled. Switched
to `h-dvh overflow-hidden` so the <main> element becomes the scroll
container and the sidebar stays sticky like template-dashboard.
2. Sidebar.Trigger now switches sides based on expand/collapse state.
- When the sidebar is collapsed: trigger renders in the navbar's
top-left (NavbarTrigger). Lets the user re-expand from anywhere.
- When the sidebar is expanded: trigger renders inside the page
content's top-left (ConsolePageTrigger), above the page heading.
Avoids visually crowding the trigger flush against the expanded
sidebar's right edge.
Both wrappers read `isOpen` from `useSidebar()` (heroui-pro exposes
the desktop expand/collapse flag as `isOpen`, NOT `open`).
3. Active menu item icon no longer tints to primary.
`getLucideIcon` was hard-setting `color: var(--app-primary)` for
selected items, which clashed with the menu item's own
foreground-on-active styling. Now selected icons keep `currentColor`
(so they inherit the deepened foreground tone of the active row) and
bump strokeWidth from 2 to 2.5 so they read as "deepened" without
changing tint. Also drops the scale-105 transform.
Plus index.css cleanup: remove the legacy `--sidebar-width` /
`--sidebar-width-collapsed` / `--sidebar-current-width` vars and the
`body.sidebar-collapsed` selector. Those drove the previous custom
sidebar's width and conflict with heroui-pro's internal width vars,
causing the icon-mode column to render at the wrong width.
Made-with: Cursor
…h bump
Three follow-up fixes after the live console smoke test exposed missing /
out-of-spec behaviors:
1. User avatar block at the bottom was invisible.
Root cause: heroui-pro's `.sidebar` is hard-coded `height: 100svh`,
intended for an <AppLayout> that hoists the navbar above the
Sidebar.Provider. Our PageLayout puts the navbar *inside* the provider,
so the sidebar starts ~64px below the viewport top and its 100svh
height pushes Sidebar.Footer (the avatar block) ~64px below the
visible viewport. Override `[data-slot='sidebar'] { height: 100% }` so
the sidebar fits its parent's available height. Footer now sticks at
the bottom of the visible sidebar.
2. Sidebar.Trigger position must NOT change between expand/collapse.
Reverted the conditional NavbarTrigger / ConsolePageTrigger split.
ConsolePageTrigger now renders unconditionally above every console
page (top-left of the content area, above the page heading), and the
navbar no longer hosts a trigger at all.
3. Active menu item icons no longer bump strokeWidth.
heroui-pro's sidebar.css already deepens active-item icons from
`--muted` to `--foreground` automatically via
`.sidebar__menu-item[data-current=true] .sidebar__menu-icon { color }`,
so my custom `strokeWidth: 2.5` for selected items was producing icons
visibly heavier than template-dashboard. Reverted to a uniform stroke
of 2 for both states.
Verified locally on /console with the Go backend + dingyi user logged in:
trigger stays put through expand/collapse, avatar+role block reads
"Root User / Super Admin" at the bottom of the sidebar in both states,
active item icon matches template-dashboard's neutral (non-tinted) look.
Made-with: Cursor
When the sidebar is collapsed, render <Sidebar.Trigger /> inside a <Sidebar.Header className='pt-6 pb-3'> at the top of the sidebar so the user can re-expand from within the icon column itself. When the sidebar is expanded, keep the trigger above the page heading in the content area (unchanged). `pt-6` gives roughly 1.5rem of breathing room above the trigger so it doesn't sit flush against the navbar's bottom border. Renamed the heroui-pro `useSidebar` import to `useSidebarUI` inside SiderBar.jsx to avoid colliding with our own `hooks/common/useSidebar` (which manages module visibility). Made-with: Cursor
Replaces Semi-style Dropdown/Space/SplitButtonGroup/Tag/AvatarGroup/Avatar/ Progress/Popover/Typography/Modal compatibility wrappers (and the matching HeroIconsCompat icons) in the tokens table column definitions with native Tailwind primitives + shared ConfirmDialog/HoverPanel/ClickMenu helpers. Highlights: - Token key cell uses a flat input with isPending HeroUI Buttons for show/hide and a ClickMenu for "copy key" / "copy connection string". - Quota usage cell renders a HoverPanel with copyable lines and a tiny Tailwind progress bar coloured by remaining percent. - Model limits cell renders vendor avatar pills inside Tooltip wrappers. - IP limit cell shows the first IP plus a "+N" pill summarising the rest in a Tooltip. - Operations cell uses a custom split button (chat + ChevronDown) plus inline enable/disable/edit/delete buttons; delete now goes through the shared ConfirmDialog instead of Modal.confirm. Build passes (bun run build); /console/token verified rendering. Made-with: Cursor
Replaces Semi-style Avatar/Space/Tag/Popover/Typography compatibility wrappers (and the matching IconHelpCircle) in the usage logs column definitions with the same native Tailwind primitives now used by TaskLogs / MjLogs columndefs. Highlights: - ColorTag/UserChip/EllipsisText helpers mirror the Tasks/MJ palette so cell tones stay aligned across log views. - Channel cell keeps the affinity sparkles overlay and multi-key index pill, but renders them on top of a ColorTag wrapper rather than the legacy Tag. - Stream-status alert badge moves to a CircleAlert lucide overlay with a Tooltip that surfaces end_reason / soft errors. - Model-mapped cell uses HoverPanel for the dual "请求并计费模型 / 实际模型" panel and keeps the Route suffix icon on the trigger tag. - Detail cell still supports the multi-segment summary path (subscription / violation / claude / openai variants) plus a fallback EllipsisText for free-form content. Build passes (bun run build); /console/log loads cleanly, no console errors from the new file (existing EditTokenModal warnings are unrelated and tracked for the next batch). Made-with: Cursor
…Compat
Replaces the legacy Semi `Notification.info` popup (and the matching
`Notification`/`Space`/`Toast`/`Typography` HeroCompat wrappers + the
HeroUI `Select` Semi-style API call) with a controlled top-right
`FluentNoticePanel` rendered directly from React state.
Highlights:
- New `FluentNoticePanel` component anchored to top-right (`fixed
top-20 right-4`) renders only when the FluentRead container is
detected. Layout mirrors the previous Notification: title bar,
intro line, model picker, action row.
- Model picker is a native `<select>` for parity with CCSwitchModal,
which keeps the icon-bearing labels accessible by falling back to
the model `value` when `label` is a JSX node.
- `不再提醒` keeps the per-tab `localStorage` suppression flag and
fires `showInfo`. `一键填充到 FluentRead` keeps the same
`fluent:prefill` CustomEvent payload and fires `showSuccess` on
success. Both paths close the panel via `setFluentNoticeOpen(false)`
rather than `Notification.close('fluent-detected')`.
- Drops the now-redundant effect that recalled `openFluentNotification`
on `[modelOptions, selectedModel, t, fluentNoticeOpen]` — the panel
re-renders automatically from state.
- Cleans up the leftover `Toast.success` / `Notification.close` calls
in `handlePrefillToFluent` and `onRemoved` that the previous batch
missed.
Build passes (bun run build); /console/token renders cleanly with no
new console errors (existing PressResponder warning + EditTokenModal
null-map are pre-existing and tracked elsewhere).
Made-with: Cursor
Replace the hand-rolled flex-row chrome around HeaderBar with @heroui-pro/react Navbar primitives (Header/Brand/Content/Spacer/ Item/MenuToggle/Menu/MenuItem). Wire Navbar's `navigate` prop through react-router so internal hrefs do client-side navigation and external links open in a new tab. Move the desktop nav links into Navbar.Item with `isCurrent` derived from useLocation(), and split the mobile-menu list into a new MobileNavMenu component that renders into Navbar.Menu (only on non-console routes — the console keeps its sidebar drawer). ActionButtons returns a fragment so Navbar.Content owns the gap spacing. Add localized strings 主导航 / 打开菜单 across all locales. Made-with: Cursor
Continues the audit of remaining literal `text-slate-*` / `bg-slate-*` / `text-gray-*` / `bg-gray-*` colors under /console and replaces them with semantic HeroUI tokens that already power the rest of the rebuilt console chrome. Highlights: - TokensColumnDefs.jsx: TONE_CLASSES (grey/black/white chips), copy button hover, ProgressBar track, VendorAvatar pill, and the quota usage capsule now use `bg-surface-secondary` / `bg-foreground` / `bg-background` / `border-border` / `text-foreground` / `text-muted` instead of slate-200/700/800/900 + dark variants. - helpers/dashboard.jsx (renderMonitorList): empty illustration tile, monitor row hover, name + uptime + status text, status divider, and uptime track all migrate to `bg-surface-secondary` / `text-muted` / `text-foreground` / `bg-border`. Uptime percentage gets `tabular-nums` for steady alignment. No behavior changes; every site that imported these helpers (token table, dashboard monitor panel) now inherits dark/light theme from the HeroUI semantic palette instead of hard-coded slate ramps. Made-with: Cursor
…(table + common/ui) Continues the audit of literal `text-slate-*` / `bg-slate-*` / `text-gray-*` / `bg-gray-*` ramps across /console, replacing them with the semantic HeroUI tokens (`bg-background`, `bg-surface-secondary`, `text-foreground`, `text-muted`, `border-border`, `bg-border`, etc.) that already power the rebuilt console chrome. Table column defs: - UsersColumnDefs: TONE_CLASSES grey/white chips, copy button hover, tooltip popover panel, ProgressBar track, ClickMenu dropdown panel + divider + hover state, remark pill, quota usage capsule (with new `tabular-nums` on numeric cells). - ChannelsColumnDefs: IO.NET tooltip body uses `text-foreground` / `text-muted` instead of `text-gray-600` / `text-gray-500`. - SubscriptionsColumnDefs: TONE_CLASSES.white pill, neutral Dot tone (`bg-muted`), HoverPanel popover panel, plan-divider all migrate. - DeploymentsColumnDefs: stopped/info status icons, IO.NET fallback Typography text, hardware multiplier, created_at timestamp (with `tabular-nums`). - MjLogsColumnDefs (and MjLogsActions): white ColorTag chip + progress bar track, view-content menu icon. - UsageLogsColumnDefs: white ColorTag chip alignment with the rest of the log column tone palette. Shared common/ui primitives (also picked up in this pass — same audit scope): - CardPro: divider, footer border, surface bg/border. - CardTable: empty state panel, skeleton frame, list header / body divider + row hover. - ColumnSelectorDialog / ConfirmDialog: dialog surface tint, header border, body text muting, footer border. - HoverPanel: tooltip surface + border. - TableEmptyState: dashed surface, illustration tile, title / description text. - TableFilterForm (FilterInput / FilterSelect / FilterDateRange): inputs, datetime range, preset chips — all standardize to `border-border bg-background ... focus:border-primary` and `bg-surface-secondary text-muted` for chip presets. - RenderUtils.renderDescription: tooltip-wrapped truncated text. No behavior changes; all tables, modals, dialogs, and shared primitives now derive their dark/light surfaces from the HeroUI semantic palette instead of slate/gray ramps. `bun run build` passes. Made-with: Cursor
…(topup + common) Continues the audit, now covering /console topup pages and a follow-up sweep through shared common components. Topup: - InvitationCard: section title + 奖励说明 muted texts. - RechargeCard: Creem product card border / hover, name color, price-quota description text. - SubscriptionPlansCard: active subscription remaining-days, end-date, next-reset, total-quota meta lines, no-plans empty state, plan benefit meta lines, "暂无可购买套餐" empty state. - index (Creem confirm modal): dialog surface, header border, body description. - modals/PaymentConfirmModal: dialog surface, header border, summary Card, line-through 原价 muted, footer border, InfoRow label/value semantic colors. - modals/SubscriptionPurchaseModal: dialog surface, header border, summary Card surface, calendar/package muted icons, divider, payment selector hint, ePay native select border, InfoRow label/value semantic colors. - modals/TopupHistoryModal: dialog surface, header border, search icon, loading + empty states, mobile card surface + meta + value, desktop table border + thead surface + tbody divider + row hover + cell text, page-size native select. - modals/TransferModal: dialog surface, header border, footer border. Common: - DocumentRenderer: empty/url/html/markdown view backgrounds, headings, description paragraph. - markdown/MarkdownRenderer: PreCode copy button surface. - modals/RiskAcknowledgementModal: dialog surface, header border, detail preview surface, checklist surface, required-text surface, footer border. - modals/SecureVerificationModal: dialog surface (no-2FA + main), title, description copy, hint copy in totp + passkey branches. - ui/ChannelKeyDisplay: section heading + status copy. - ui/ClickMenu: hover state. - ui/ColumnSelectorDialog: option grid surface. - ui/JSONEditor: panel chrome, ruler, action button hover. - ui/SelectableButtonGroup: idle / selected / disabled chip variants. `bun run build` passes after both batches. Made-with: Cursor
…(table modals + settings + setup) Final pass of the literal-color → semantic-token audit, sweeping all remaining /console table modals, the personal settings cards, the setup wizard steps, and a few stragglers in pages/Setting and the table column defs. Patterns standardized to: - `bg-background` / `bg-background/95 backdrop-blur` for dialog and side-panel surfaces (replaced `bg-white ... dark:bg-slate-950`, `bg-white/95 ... dark:bg-slate-950/95`). - `border-border` / `border-b border-border` / `border-t border-border` for chrome borders (replaced `border-slate-200/80 dark:border-white/10`). - `bg-surface-secondary` / `bg-surface-secondary/60` for inner panels, illustration tiles, dropdown rows, summary cards, expandable rows (replaced `bg-slate-100/200 dark:bg-slate-800/900`, `bg-gray-50`). - `text-foreground` for headings, table cells, body labels (replaced `text-slate-700/800/900 dark:text-slate-100/200`, `text-gray-700/900`). - `text-muted` for hints, tertiary labels, status copy, timestamps (replaced `text-slate-500/600 dark:text-slate-300/400`, `text-gray-500/600`, `text-gray-400`). - `bg-foreground text-background` for active pill states (replaced `bg-slate-900 text-white dark:bg-slate-100 dark:text-slate-900`). - `border-border bg-background ... focus:border-primary` for inline selects, datetime inputs, single-line editors (replaced `border-slate-200 bg-white ... focus:border-sky-400 dark:border-slate-700 dark:bg-slate-900`). - `bg-muted` for neutral status dots (replaced `bg-slate-400/500`, `bg-gray-500`). Files touched (74 jsx files): - Side panels: AddUserModal, EditPrefillGroupModal, PrefillGroupManagement, EditRedemptionModal, ModelDetailSideSheet, UserSubscriptionsModal. - Tabs / dropdowns / actions: ChannelsActions, ChannelsTabs, ModelsActions, ModelsTabs, SelectionNotification. - Modals: BatchTagModal, ChannelUpstreamUpdateModal, CodexOAuthModal, EditChannelModal (14 inline literals), EditTagModal, SingleModelSelectModal, ContentModal (mj-logs), AudioPreviewModal, ContentModal (task-logs), CopyTokensModal, EditTokenModal, CCSwitchModal, EditModelModal, EditVendorModal, MissingModelsModal, SyncWizardModal, UpstreamConflictModal, EditRedemptionModal, AddEditSubscriptionModal, ChannelAffinityUsageCacheModal, ColumnSelectorModal, ParamOverrideModal, UserInfoModal, ConfirmationDialog, EditDeploymentModal, ExtendDurationModal, UpdateConfigModal, ViewDetailsModal, ViewLogsModal, EditUserModal, UserBindingManagementModal, ChannelSelectorModal. - Pricing layout/views: PricingSidebar, PricingVendorIntro, SearchActions, PricingFilterModal, ModelPricingTable, PricingCardSkeleton, PricingCardView, PricingTable, PricingTableColumns. - Column defs: ModelsColumnDefs, TaskLogsColumnDefs. - Tokens index: FluentNoticePanel surface + native model select. - Usage logs table: expanded-row grid surface, label, value. - Settings personal: AccountManagement, CheckinCalendar, NotificationSettings, PreferencesSettings, TwoFASetting, UserInfoHeader, AccountDeleteModal, ChangePasswordModal, EmailBindModal, WeChatBindModal. - Setup wizard: AdminStep, CompleteStep, DatabaseStep, UsageModeStep. - pages/Setting: SettingsAPIInfo, SettingsAnnouncements, SettingsFAQ, SettingsUptimeKuma, SettingsPaymentGatewayCreem, ModelPricingEditor, Setting/index (sidebar tabs). `bun run build` passes. /components/table now has zero literal slate/gray classes. Made-with: Cursor
Marks the "Audit remaining literal text-gray-* / text-slate-* usages elsewhere under /console" Next-step item complete, and records the four commits that landed it (TokensColumnDefs+dashboard, table+common/ui, topup+common, table modals + settings + setup) plus the standardized patterns and out-of-scope areas left for a future pass (playground, headerbar, auth, top-level pages). Made-with: Cursor
…tokens (auth + layout + home) Continues the literal-color → semantic-token audit, now covering the visitor-facing chrome (footer, auth pages, notice modal, home page) so the entire pre-login surface inherits dark/light themes from the same HeroUI semantic palette as the rest of /console. Files touched: - layout/Footer.jsx: section heading + footer link colors (12 nav links share the same `text-muted hover:text-primary` pattern), copyright + "设计与开发由" + custom-footer body, demo logo backdrop. - layout/NoticeModal.jsx: dialog surface (`bg-background/95 backdrop-blur`), header / footer borders, timeline left rail, empty-state illustration tile (replaced `bg-slate-900/[0.04]` with `bg-surface-secondary`), default announcement dot tone (`bg-muted`), announcement card surface + meta line. - auth/AuthLayout.jsx: brand row (logo border + title), AuthPanel card surface + headings + subtitle, AuthDivider rails + label, AuthAgreement checkbox label + 用户协议/隐私政策 link tone (now `text-primary hover:text-primary/80`), AuthLinkRow text + link, AuthOutlineButton + AuthGhostButton baseline tones, AuthTextField label + icon + Input focus ring (now `focus:border-primary focus:ring-primary/10`), AuthModal dialog surface + header/footer borders. - pages/Home/index.jsx: hero section bottom border, hero title + subtitle, BASE URL pill (border + bg + input text + endpoint label chip), 大模型供应商 caption. `bun run build` passes; / and /console (post-login redirect) verified in the browser — hero, BASE URL pill, sidebar, dashboard cards and empty states all render with the semantic tokens. No new console errors (existing PressResponder warnings remain pre-existing). Made-with: Cursor
…semantic tokens Continues the literal-color → semantic-token audit, now covering the playground, the setup wizard, the model-deployment access guard, the header theme toggle, and the dashboard search modal. Playground: - SSEViewer: default Pill tone, parse-error block, root container surface + header border, item list rows / dividers / hover, event ID + delta keys + chevrons. (Kept the JSON pre block + copy button intentionally always-dark for the terminal-style code view.) - ParameterControl: hint text + 6 icon prefixes (Thermometer / Target / Repeat / Ban / Hash / Shuffle). - MessageActions: disabled state (`!text-muted/50`) + idle tone (`!text-muted`) for retry / copy / edit / role-toggle / delete; the semantic hover colors (blue/green/yellow/purple/red) are preserved because they carry per-action meaning. - SettingsPanel: 3 icon prefixes (Users / Sparkles / ToggleLeft). - DebugPanel: tab pill states (active uses `bg-foreground text-background`, idle uses `bg-surface-secondary text-muted`), Clock + timestamp meta. - ChatArea: clear-context button (now `bg-surface-secondary text-muted hover:bg-danger`), assistant message border + bg, role label. - ImageUrlInput: image icon active/idle tone, 3 hint copy lines. - ConfigManager: 2 export/import action buttons + status meta line. - CustomRequestEditor: Code icon prefix + JSON helper hint. - CustomInputRender: clear button surface + composer container bg. Setup wizard: - SetupWizard: outer card (border + bg). - StepNavigation: footer divider. - UsageModeStep: option icon (active = primary, idle = surface-secondary text-muted) + label. - DatabaseStep: heading + body copy + nested SQLite path tile + warning paragraph + connection-status side card (icon tile + heading). - AdminStep: input icon + Input field surface (now uses `focus:border-primary focus:ring-primary/10`), heading, security side card (surface + icon tile + heading). - CompleteStep: heading + 3 summary cards (border/bg + icon tile + value text). Other chrome: - model-deployments/DeploymentAccessGuard: loading card surface, error card (uses `border-warning/30 bg-warning/5` semantic palette instead of literal amber gradient), heading/description/help-list copy, config-detail side panel surface, danger card (uses semantic `border-danger/30 bg-danger/5` instead of literal rose gradient). - layout/headerbar/ThemeToggle: trigger button surface, dropdown panel surface + border, menu item hover state, icon tone, "当前跟随系统" hint. - dashboard/modals/SearchModal: dialog surface, header/footer borders, field label tone, native time-grain `<select>` colors (now `border-border bg-background ... focus:border-primary`). `bun run build` passes; /console verified post-edit (theme toggle dropdown now shows semantic surface, panel header borders, icon + description tones in dark/light mode). No new console errors. Made-with: Cursor
…, trim languages to zh+en Three small navbar follow-ups from QA feedback: 1. Navbar.Item hover was invisible because we hard-coded `text-foreground hover:text-primary` on the className, which short-circuited heroui-pro's built-in `text-muted` → `text-foreground` hover transition (the baseline was already at the hover color, so nothing changed visually). Drop the override so the default token shift kicks back in, then layer a `bg-surface-secondary` tint on hover / `data-current` so the change is visually obvious — using both `hover:` and `data-[hovered=true]:` selectors so it works for mouse and a11y-driven hover events. 2. UserArea + LanguageSelector dropdown items were rendering at the default `text-sm` (14px) but with `py-2` and 16px lucide icons, making the menu feel oversized. Lock font to `text-[14px]`, tighten vertical padding to `py-1.5`, drop icons to 14px, and shrink the LanguageSelector menu min-width to 36/UserArea to 40. LanguageSelector trigger button also moves off `bg-slate-900/[0.04] text-slate-700 dark:bg-white/10 ...` to semantic `bg-surface-secondary text-foreground hover:bg-surface-tertiary` for parity with the rest of the navbar. 3. Language switcher (both navbar `LanguageSelector` and personal `PreferencesSettings`) trims user-facing options down to just 简体中文 and English. Underlying `i18n` resources, `supportedLanguages`, and translation bundles for `zh-TW / fr / ja / ru / vi` stay intact so users with saved preferences in those locales keep working — only the chooser UI is reduced. Verified live in browser: - Hover on `控制台` / `模型广场` / `文档` / `关于` shows the same `bg-surface-secondary` pill that 首页 (current) renders. - User menu items render compact at 14px with 14px icons. - Language menu shows only 简体中文 (checked) + English at 14px. Made-with: Cursor
…eset stops winning
Setting `text-[14px]` directly on the menu `<button>` elements still
rendered as 16px because `@heroui/styles` ships a global
button, input, textarea, select { font-size: inherit; }
reset that lives OUTSIDE any @layer. Per the CSS Cascade Layers spec,
unlayered author rules beat any rule inside an explicit layer
(including all Tailwind utilities), regardless of selector specificity
— so the reset always wins and the button just inherits body's 16px.
Verified via Chrome DevTools: with `text-[14px]` on the button, the
inline reset rule was the matching `font-size` rule and computed size
was 16px / 24px line-height, despite Tailwind correctly emitting
`.text-\[14px\] { font-size: 14px; }` inside `@layer utilities`.
Fix: hoist `text-[14px] leading-5` from the buttons up to the
surrounding menu `<div role="menu">` wrapper. The wrapper isn't a
button so the reset doesn't target it, the Tailwind utility wins, and
each menuitem button then inherits 14px / 20px through the reset's
`font-size: inherit`. Confirmed live: both LanguageSelector
menuitemradio buttons and UserArea menuitem buttons now compute to
`14px` / `line-height: 20px`.
Made-with: Cursor
…c tokens + fix UserArea JSX
Final pass of the literal-color → semantic-token audit, sweeping the
remaining 15 stragglers across pages, auth, layout, settings and the
pricing card view.
Files touched:
- pages/About: card heading.
- pages/Forbidden: 403 heading + description.
- pages/NotFound: 404 heading + description.
- pages/Chat: SSO redirect overlay surface.
- auth/TwoFAVerification: subtitle, both 提示 info cards (border + bg
+ body copy).
- auth/LoginForm: 使用邮箱或用户名登录 button (now `bg-foreground
text-background` so it inherits theme polarity).
- auth/RegisterForm: 使用用户名注册 button (same pattern).
- layout/headerbar/NewYearButton: trigger + dropdown panel + menu item
hover.
- layout/headerbar/NotificationButton: 系统公告 trigger button.
- layout/components/SkeletonWrapper: SkeletonBlock background, user
area skeleton container.
- settings/ChannelSelectorModal: dialog header + footer borders.
- settings/personal/cards/PreferencesSettings: language card border.
- topup/index: Creem confirm modal footer border.
- table/model-pricing/view/card/PricingCardSkeleton: card border.
- table/model-pricing/view/card/PricingCardView: default card border
fallback.
Drive-by:
- layout/headerbar/UserArea: convert the JSX `{/* ... */}` comment
block inside `{open ? (...) : null}` to a regular `// ...` comment
so the conditional has a single root element again. The comment
block was breaking the build with `Expected ")" but found "role"`.
After this pass only 4 files retain literal slate/gray tones and they
are all intentional:
- ui/semi.js + ui/semi-illustrations.js: Hero compatibility shim,
needs the literal Semi default palette.
- common/ErrorBoundary.jsx: defensive always-dark error fallback so
the stack trace is visible even when theme tokens are broken.
- playground/SSEViewer.jsx: terminal-style JSON viewer (the `bg-gray-900
text-gray-100` `<pre>` and the floating copy button) — kept
intentionally always-dark, mirrors the code-editor UX.
`bun run build` passes; / + /about + /404 + user-menu dropdown
verified in the browser. No new console errors (existing PressResponder
warnings remain pre-existing).
Made-with: Cursor
ThemeToggle's light/dark/auto menu had the same 16px-instead-of-14px
problem the user/language menus had. Same root cause: `@heroui/styles`
ships an unlayered
button, input, textarea, select { font-size: inherit; }
reset that beats every Tailwind utility (per CSS Cascade Layers spec,
unlayered author rules win over `@layer utilities`), so `text-sm` on
the buttons was being silently dropped.
Hoist `text-[14px] leading-5` from each menuitemradio button up to the
surrounding menu `<div role="menu">` wrapper. The wrapper isn't a
button, so the reset doesn't target it; Tailwind's utility wins; and
each menu button inherits 14px / 20px through the reset's
`font-size: inherit`. The nested description span keeps its explicit
`text-xs` (12px) since spans aren't reset-targeted, so labels stay 14px
and descriptions stay 12px. Also tighten button vertical padding from
`py-2` to `py-1.5` to match the user / language dropdowns.
Verified via Chrome DevTools: menu container resolves to 14px / 20px,
each `[role=menuitemradio]` button computes 14px / 20px, label spans
14px, description spans 12px.
Made-with: Cursor
Tailwind v4 dropped the v3 preflight rule that gave `<button>` elements `cursor: pointer` by default — buttons now inherit the browser default (`cursor: default`, i.e. arrow). All four navbar trigger buttons (NotificationButton bell, ThemeToggle, LanguageSelector, UserArea avatar) plus the dropdown menuitem buttons inside ThemeToggle / LanguageSelector / UserArea were affected: hover gave no pointer affordance. Verified via Chrome DevTools before the fix: Bell -> cursor: default Theme -> cursor: default Lang -> cursor: default Add `cursor-pointer` to: - NotificationButton trigger - ThemeToggle trigger + each light/dark/auto menuitemradio - LanguageSelector trigger + each language menuitemradio - UserArea avatar trigger + each menuitem button After the fix DevTools confirms all targets resolve to `cursor: pointer`. Made-with: Cursor
…HeroCompat
Replaces the legacy Semi `Table` (the last surface in the model-pricing
detail area still on HeroCompat) with a native `<table>` that mirrors
the pattern used by the dashboard tables (SettingsAPIInfo / SettingsFAQ /
SettingsAnnouncements).
Highlights:
- Native thead + tbody driven by the existing `getPricingTableColumns`
output. `column.title` continues to support both string/JSX nodes and
zero-arg functions (used by the `倍率` column for its help-icon button).
- Optional row-selection column with a local `HeaderCheckbox` (indeterminate
ref) at the head and a per-row checkbox that stops click propagation so
selecting a row no longer triggers `openModelDetail`. Selection state is
driven by the `rowSelection` prop coming from `useModelPricingData`
(`{ selectedRowKeys, onChange }`); per-page toggle adds/removes only the
current page's keys.
- `column.fixed === 'right'` on the price column is honoured outside
compact mode via `position: sticky; right: 0` on the head + body cells
with a faint left shadow; compact mode strips the hint as before.
- Mobile path delegates to the shared `CardTable` (cards stack with
field/value rows) — selection on mobile is handled by `PricingCardView`,
so the table view doesn't need it there.
- New `PaginationBar` row uses HeroUI Button + native `<select>` for page
size with rolling page slicing driven by `currentPage`/`pageSize` from
the hook (matches the dashboard table pattern).
- Loading state renders a 4-row skeleton grid matching column count,
plus the row-selection slot when enabled.
- Empty state keeps the original Inbox icon centred inside the table body.
Build passes (bun run build); /pricing 表格视图 verified: header
checkbox + column headers (模型名称 / 供应商 / 描述 / 标签 / 计费类型 /
可用端点类型 / 模型价格) render with native `<table>`; empty-state
Inbox card centred; no new console errors (existing PressResponder
warning from SiderBar is pre-existing and unrelated).
Made-with: Cursor
…sweep The PricingTable rewrite (8eedbad5) was authored on feature/console-template-style before the literal-color → semantic-token sweep, so the cherry-pick brought in 11 stale `text-slate-*` / `bg-slate-*` / `border-[color:var(--app-border)]` references. Convert in-place to match the rest of /console: - container border + bg → `border-border bg-background` - header surface + text → `bg-surface-secondary text-muted` - skeleton row dividers + cells → `bg-border` / `bg-surface-secondary` - empty-state Inbox tile → `bg-surface-secondary text-muted` - row hover → `hover:bg-surface-secondary/60` - body cell text → `text-foreground` - sticky-right cells → `bg-surface-secondary` (head) / `bg-background` (body) Made-with: Cursor
Replaces the legacy Semi `Modal/Typography/Tag/Progress/Descriptions/Spin/Empty`
+ `IconRefresh` HeroCompat wrappers with native HeroUI v3 Modal anatomy +
Tailwind primitives + lucide icons.
Highlights:
- Modal v3 (`ModalDialog/ModalHeader/ModalBody/ModalFooter`) with
`useOverlayState` syncing the legacy `visible`/`onCancel` props.
Body scrolls inside the dialog (`max-h-[70vh] overflow-y-auto`) so
the long detail report doesn't blow past the viewport.
- New `SectionCard` + `DescList` + `ProgressBar` + `StatusTag` + `EmptyBlock`
primitives kept local to the file. They mirror the API surface of the
Semi `Card/Descriptions/Progress/Tag/Empty` we removed without pulling
in another shared component.
- `StatusTag` uses the same green/blue/orange/red/grey tone palette as
the source modal but renders flat semantic chips
(`bg-{tone}/15 text-{tone}` with `bg-surface-secondary text-muted` as
the grey fallback).
- `DescList` is a 1-col-mobile / 2-col-desktop `<dl>` with key/value
rows; replaces the Semi 1:1.
- `ProgressBar` is a tiny 2px Tailwind bar that turns success-green
when the percent reaches 100.
- All Buttons converted: Semi `theme='borderless'/'light'` → HeroUI
`variant='light'/'flat'`, isPending replaces loading, onPress replaces
onClick, startContent replaces icon prop.
- `<IconRefresh />` (Semi-shaped) → `<RefreshCw size={14} />` from lucide.
- `<Empty image=PRESENTED_IMAGE_SIMPLE>` → `EmptyBlock` with Inbox icon
+ muted description (consistent with the rest of /console).
- Container instances list keeps the legacy "card → events drawer"
pattern but each level uses semantic surfaces:
- Outer card: `border-border bg-surface-secondary`
- Inner events panel: `border-border bg-background`
- Event rows: monospace muted timestamp + foreground message
- 已支付金额 highlight is now `bg-success/10 text-success` (was literal
green).
`bun run build` passes; /console/deployment renders the access-guard
correctly (demo site has io.net disabled, so the modal can't be opened
end-to-end here, but the structural rewrite is verified at compile
time + via `rg "HeroCompat|HeroIconsCompat"` returning 0 hits in this
file).
Drops: 33 → 32 files still importing HeroCompat.
Made-with: Cursor
Replaces the legacy Semi `Modal/Typography/Space/Spin/Tag/Empty/Divider/Radio`
+ `IconRefresh/IconDownload` HeroCompat wrappers with native HeroUI v3
Modal anatomy + Tailwind primitives + lucide icons.
Highlights:
- Modal v3 (`ModalDialog/ModalHeader/ModalBody`) with `useOverlayState`
syncing `visible`/`onCancel`. Footer is intentionally empty (the
toolbar already exposes refresh/copy/download), matching the original
Semi `footer={null}` behaviour.
- Container picker: Semi `<Select prefix=... optionList>` + custom
option markup → native `<select>` with `border-border bg-background
focus:border-primary` and a single-line `id · brand_name` label
(matches the simpler picker style used in CCSwitchModal).
- Search field: HeroUI v3 `<Input value onValueChange size='sm'>` with
an absolute-positioned `<FaSearch>` prefix and `[&_input]:pl-7` so
the icon sits inside the rounded input.
- Stream filter: Semi `<Radio.Group type='button'>` STDOUT / STDERR
pair → new local `StreamSegment` (a 2-button rounded segment that
toggles `bg-foreground text-background` for the active option).
- Auto-refresh / 跟随日志 toggles: Semi `<Switch checked onChange>` →
HeroUI v3 `<Switch isSelected onValueChange size='sm'>` with the
required `<Switch.Control><Switch.Thumb /></Switch.Control>` anatomy.
- Action buttons (refresh/copy/download): Semi `theme='borderless'
icon=<IconXxx>` → HeroUI `variant='light' isIconOnly` with
`<RefreshCw />` (lucide) + `<FaCopy />` + `<Download />` (lucide)
inside `<Tooltip>` wrappers and `aria-label` on each.
- Status / 容器详情 strip: Semi `<Tag color>` → local `StatusTag`
(green/blue/orange/red/grey palette via `bg-{tone}/15 text-{tone}`,
with `bg-surface-secondary text-muted` as the grey fallback).
`<Divider margin='12px'>` → simple `<div className='h-px bg-border' />`.
- Container details grid: timestamps + uptime % wrapped with
`tabular-nums` so the column doesn't wiggle as the values change.
- Empty + loading states: Semi `<Empty image=PRESENTED_IMAGE_SIMPLE>`
+ `<Spin tip>` → local `EmptyBlock` (Inbox icon + muted description)
+ HeroUI `Spinner` with a muted subtitle.
- Log viewer surface: outer panel `border-border bg-surface-secondary`,
inner scroll area `bg-background`, footer status row
`border-t border-border bg-surface-secondary text-xs text-muted`.
`bun run build` passes; /console/deployment renders the access guard
(io.net disabled on the demo site, so the modal can't be opened
end-to-end here, but the structural rewrite is verified — `rg
"HeroCompat|HeroIconsCompat"` returns 0 hits in this file).
Drops: 32 → 31 files still importing HeroCompat.
Made-with: Cursor
Replaces the legacy Semi `Dropdown/Tag/Typography` HeroCompat wrappers +
`IconMore` (HeroIconsCompat) in the model-deployments column defs with
native primitives + the shared `ClickMenu` and `MoreVertical` (lucide).
Highlights:
- Status / instance-count / progress chips: Semi `<Tag color shape='circle'
size='small' prefixIcon>` → new local `StatusChip` with the same
green/blue/orange/red/grey palette mapped to semantic tones (`bg-{tone}/15
text-{tone}` with `bg-surface-secondary text-muted` as the grey fallback).
Status icons keep the same Fa* glyphs but the color is inherited from the
chip via `currentColor` (no more per-icon `text-blue-600` literals).
- ContainerNameCell: Semi `<Typography.Text strong>` + `<Typography.Text
type='secondary' onClick>` → native `<span>` + a real `<button>` for the
copy-id action (a11y win — was previously a clickable Typography element
without a role).
- 服务商 chip: Inline `style={{ borderColor:'rgba(59,130,246,...)', ... }}`
literal palette → `border-primary/30 bg-primary/10 text-primary` Tailwind
classes that follow the theme.
- 硬件配置 chip: Literal `bg-green-50 border-green-200 text-green-700` →
semantic `bg-success/10 border-success/30 text-success`.
- 剩余时间 cell: `getRemainingTheme()` traffic-light colors (#ff5a5f /
#ffb400 / #2ecc71) → CSS variables (`var(--app-danger)` /
`var(--app-warning)` / `var(--app-success)`) so the warn/critical hue
inherits dark/light mode. Returns a matching `tone` so the percentage
chip uses the same palette via `StatusChip`.
- Operations dropdown: Semi `<Dropdown trigger='click'
position='bottomRight'><Dropdown.Menu><Dropdown.Item icon onClick>` →
shared `<ClickMenu items=[{label, icon, onClick, danger?, divider?}]
trigger>` with a `<MoreVertical />` lucide trigger and aria-label.
Item ordering preserved (details → logs → management actions → config
actions → destructive action with dividers between groups).
- Primary action button: Semi `theme={...} type={...} icon onClick` →
HeroUI v3 `variant color startContent onPress isDisabled`. Disabled
states (deploying/pending/termination requested/已结束) keep the
`variant='flat'` / `variant='light'` distinction.
`bun run build` passes; no console errors. All Fa* icons retained (the
file relied heavily on react-icons/fa for status glyphs and the dropdown
menu — swapping all of them to lucide would have been a much larger,
more invasive change).
Drops: 31 → 30 files still importing HeroCompat.
Made-with: Cursor
…l without HeroCompat
Continues the model-deployments cleanup. Both modals were on Semi
Form / Form.Input / Form.InputNumber / Banner / Tag / Typography /
Space / Divider / Spin / Collapse with imperative formApi refs;
both move to controlled React state + native HeroUI v3 + Tailwind
primitives.
Shared helpers introduced inline in each file:
- `StatusChip` — same green/blue/orange/red/grey palette
(`bg-{tone}/15 text-{tone}` with `bg-surface-secondary text-muted` as
the grey fallback) used in ViewDetailsModal / ViewLogsModal /
DeploymentsColumnDefs.
- `CollapseSection` (UpdateConfigModal only) — replaces Semi
`<Collapse><Collapse.Panel>` with a native `<details>` element. Header
gets a chevron that rotates on `group-open`, content lives inside a
`border-t border-border` body. Free a11y, no extra hook needed.
ExtendDurationModal:
- Drops Form/Form.InputNumber + formApi refs. `durationHours` is now
controlled React state with an inline `validateDuration` that runs
on every change (and again before submit) — keeps the same
1 ≤ value ≤ 720 contract and renders the error inline below the
input.
- Quick-select chips: Semi `<Button theme size type onClick>` →
HeroUI v3 `variant='solid'/'flat' color='primary'? onPress`, with the
active chip getting `variant='solid' color='primary'`.
- Cost estimate panel: literal `border-green-200 bg-blue-50 text-green-600`
→ `border-success/30 bg-primary/5 text-success` semantic palette.
Numbers wear `tabular-nums` so the column doesn't dance while the
estimate refreshes.
- Final confirm reminder card: literal `bg-red-50 border-red-200
text-red-700` → `border-danger/30 bg-danger/5 text-danger`.
- Modal v3 anatomy with `useOverlayState` syncing visible/onCancel,
footer hosts the Cancel + Confirm buttons (was Semi `okText` /
`cancelText` props on Modal).
UpdateConfigModal:
- Drops Form / Form.Input / Form.InputNumber + formApi refs. All form
state lives in a single `formData` object updated via
`updateField('image_url')(value)`.
- 4 collapsible sections (镜像配置 / 网络配置 / 启动配置 / 环境变量) all
use the new `CollapseSection`. 镜像配置 stays open by default
(`defaultOpen`), matching the original `defaultActiveKey={['docker']}`.
- Native `<input type='text'/'number'/'password'>` for the form fields
(matches the inputClass pattern from CCSwitchModal). `traffic_port`
has inline 1-65535 validation rendered below the input.
- Env var rows: HeroUI v3 `<Input value onValueChange size='sm'>` +
isIconOnly Button with FaPlus/FaMinus icons. Empty states use
`border-dashed border-border` (普通) and `border-dashed
border-danger/30 bg-danger/5` (机密) panels.
- 机密环境变量说明 banner: Semi `<Banner type='info' title description size='small'>`
→ small inline panel with `border-primary/30 bg-primary/5 text-muted`.
- Final confirm reminder: literal `bg-yellow-50 border-yellow-200
text-yellow-700/800` → `border-warning/30 bg-warning/5 text-warning`.
`bun run build` passes.
Drops: 30 → 28 files still importing HeroCompat. Both modals also
stripped of all literal slate/gray/yellow/red/green hex utilities.
Made-with: Cursor
Replaces the legacy Semi `Typography/Avatar/Form/Radio/Toast/Tabs/
TabPane/Row/Col` HeroCompat wrappers + 4 HeroIconsCompat icons
(`IconMail/IconKey/IconBell/IconLink`) with HeroUI v3
`Button/Card/Switch` + Tailwind primitives + lucide icons. Form
state moves from imperative `formApiRef` (`refForm.validate`) to
controlled React state with inline `validate()` driven by
the parent `notificationSettings` + `handleNotificationSettingChange`.
Highlights:
- Tabs: Semi `<Tabs type='card' onChange><TabPane tab itemKey>`
→ segmented control (`bg-foreground text-background` / `bg-
background text-muted hover:bg-surface-secondary`), matching
the AccountManagement / SettingsChats pattern. 通知配置 / 价格
设置 / 隐私设置 keep their lucide icons; 边栏设置 stays
permission-gated via `hasSidebarSettingsPermission()`.
- 通知方式 radio: Semi `<Form.RadioGroup><Radio value=email|
webhook|bark|gotify>` → 4-button segmented control. 选中后
conditional 的 邮箱 / Webhook / Bark / Gotify 子表单照旧渲染.
- AutoComplete: Semi `<Form.AutoComplete data prefix>` →
controlled `<input type='number' list>` + a `<datalist>` for the
4 额度预警 presets (0.2$ / 1$ / 2$ / 10$). Gotify 优先级
AutoComplete 改成原生 `<select>` (5 个 priority 预设).
- 表单字段: Semi `<Form.Input prefix=<IconX> showClear extraText>`
→ local `<PrefixedInput>` (受控 native `<input>` + 绝对定位
lucide prefix). 所有 webhook / bark / gotify URL 字段保留
`^https://` 或 `^https?://` 校验.
- Switches: Semi `<Form.Switch checkedText='开' uncheckedText='关'
extraText>` → HeroUI v3 `<Switch isSelected onValueChange size=
'md'>` wrapped in a local `<SwitchRow>` (label + hint + 右侧
toggle).
- 验证: Semi `<Form rules>` 校验 → inline `validate()` 替换
4 种通知类型的必填 + URL 协议 + 数值正整数 检查;失败时
`Toast.error()` 改成 `showError()`.
- Sidebar 设置 grid: Semi `<Row gutter><Col xs sm md lg xl>` +
`<Card hoverable bodyStyle>` → CSS grid (`grid-cols-1 md:
grid-cols-2 lg:grid-cols-3`) of HeroUI v3 `<Card>` tiles. The
section enable Switch + per-module Switch 全部改成 HeroUI v3
anatomy with the `<Switch.Control><Switch.Thumb /></Switch.
Control>` 复合结构, semantic `bg-surface-secondary` 区域块,
bg-background 模块卡, 受控 disabled 状态。
- Footer: 原来用 `<Card footer={...}>` prop → Semi-style 不被
HeroUI v3 Card 支持,改用 `<Card.Footer className='border-t
border-border'>` 显式 slot. 切到 边栏设置 标签页时显示 重置
+ 保存设置 两个 button, 否则 单个 保存设置 button.
- 卡片头部 avatar: Semi `<Avatar size='small' color='blue'>` →
semantic round bg `bg-primary/10 text-primary` 图标 tile (与
AccountManagement / EditTokenModal 一致).
`bun run build` passes.
Drops: 7 → 6 files still importing HeroCompat.
Made-with: Cursor
Replaces the legacy Semi `Form/Row/Col/Typography/Modal/Banner/
Collapse/Table/Tag/Popconfirm/Space` HeroCompat wrappers + 4
HeroIconsCompat icons (`IconPlus/IconEdit/IconDelete/IconRefresh`)
with HeroUI v3 Modal anatomy + a native HTML table + the existing
`ConfirmDialog` + Tailwind primitives + lucide icons. Form state
moves from imperative `formApiRef` (with the dual-write
`mergeFormValues` helper) to controlled React state with
`setField(key)(value)` cleanup.
Highlights:
- 提供商列表: Semi `<Table columns dataSource pagination empty
loading>` → native `<table>` with `border-border rounded-xl`
shell, header `bg-surface-secondary text-muted uppercase
tracking-wide` and `divide-y divide-border` body. The 操作
column renders 编辑 / 删除 buttons (HeroUI flat + danger).
Loading overlay uses `bg-background/60 backdrop-blur-[1px]` +
`<Spinner color='primary'>`.
- Modal: Semi `<Modal title visible width=860 centered bodyStyle=
{{maxHeight:'72vh',overflowY:'auto'}}>` → HeroUI v3 anatomy
(`ModalBackdrop/ModalContainer/ModalDialog/ModalHeader/ModalBody
/ModalFooter`) with `useOverlayState` and `size='4xl'
className='max-w-[95vw]'`. ModalBody preserves the
`max-h-[72vh] overflow-y-auto` scroll behavior. Footer hosts the
启用供应商 Switch + StatusChip + 取消/保存 buttons.
- Layout: Semi `<Form><Row gutter><Col span>` → CSS grid
(`grid-cols-1 md:grid-cols-2|md:grid-cols-12 gap-4`). The
预设模板 / Issuer URL / 获取 Discovery 配置 row uses a 4/5/3
12-column split that mirrors the original Semi layout.
- 表单字段: Semi `<Form.Input rules>` / `<Form.Select optionList>` /
`<Form.TextArea>` → controlled native `<input>` / `<select>` /
`<textarea>` driven by `formValues` + `setField(key)(value)`.
All required-field validation moves to `handleSubmit()` (matches
the original Semi `rules={[{required}]}` semantics) and uses
`showError()` for inline messages.
- 横幅: Semi `<Banner type='info'>` 配置说明 + Semi `<Banner
type='success' description=<Discovery 自动填充>>` → local
`<InfoBanner tone='info|success'>` (`border-primary/20 bg-
primary/5` / `border-success/30 bg-success/5`) with lucide
`<Info>` / `<CheckCircle2>` icons.
- 高级选项 Collapse: Semi `<Collapse keepDOM><Collapse.Panel
header itemKey>` → native `<details>` (mirrors the
CollapseSection / CodexUsageModal pattern), with a chevron
rotating on `group-open`. Accommodates 准入策略 JSON textarea
+ 拒绝提示 input + 4 个 填充模板 buttons.
- Tag: Semi `<Tag color='green|grey'>` → semantic `<StatusChip
tone='green|grey'>` (`bg-success/15 text-success` / `bg-surface-
secondary text-muted`). Slug column uses the default grey chip.
- 删除确认: Semi `<Popconfirm onConfirm>` → state-driven
`<ConfirmDialog>` (`deleteTarget` flag, danger style), with the
provider name surfaced in the confirmation copy.
- 图标预览: Inline-styled `border var(--app-border) border-radius
bg surface-muted` block → semantic `border-border bg-surface-
secondary rounded-lg` with the `<getOAuthProviderIcon>` 24px
preview centered.
- Imperative `formApiRef.current?.setValue/getValues()` calls (and
the `getLatestFormValues()` helper) are removed entirely; everything
reads/writes `formValues` directly.
`bun run build` passes.
Drops: 6 → 5 files still importing HeroCompat.
Made-with: Cursor
Replaces the legacy Semi `Banner/Col/Collapse/Divider/Form/Modal/
Row/Space/Spin/Table/Tag/Typography` HeroCompat wrappers + 7
HeroIconsCompat icons (`IconClose/IconCode/IconDelete/IconEdit/
IconPlus/IconRefresh/IconSearch`) with HeroUI v3 Modal anatomy +
the existing `ConfirmDialog` + native HTML tables + Tailwind
primitives + lucide icons. The 2 imperative `Modal.confirm({onOk})`
calls + the imperative `Modal.info()` preview are converted to
state-driven dialogs.
Highlights:
- Outer settings page: Semi `<Form><Form.Section><Row><Col>` →
semantic `<SectionHeader>` + responsive grid (`grid-cols-1
sm:grid-cols-2 md:grid-cols-3`). 启用 / 成功后切换亲和 switches
use `<SwitchRow>` (label + hint + 右侧 toggle). 最大条目数 +
默认 TTL number inputs are controlled `<input type='number'
min=0>` with the same NaN-safe coercion (`raw === '' ? 0 :
Number(raw)`).
- 编辑模式 toggle: Semi `<Button type='primary'>` 双按钮 →
2-button segmented control (`bg-foreground text-background` /
`bg-background text-muted hover:bg-surface-secondary`),
matching the SettingsChats / GroupRatioSettings pattern.
- 规则列表 / Key 来源: Semi `<Table columns dataSource>` → native
`<table>` inside a `border-border rounded-xl` shell (header
`bg-surface-secondary text-muted uppercase tracking-wide`,
body `divide-y divide-border hover:bg-surface-secondary/60`).
规则列表 has 10 columns including 模型正则 / 路径正则 / Key 来源
multi-tag cells, 失败后是否重试 `<StatusChip tone='orange|green'>`
badge, 缓存条目数 (with `cacheStats?.by_rule_name` lookup), and
作用域 chips. Per-row 操作 column has 清空缓存 / 编辑 / 删除
isIconOnly buttons.
- 规则编辑 modal: Semi `<Modal width=720>` → HeroUI v3 anatomy
with `useOverlayState` and `size='3xl'`. ModalBody uses
`max-h-[72vh] overflow-y-auto` to keep the long form scrollable.
All Semi `<Form.Input/TextArea/InputNumber/Switch>` fields
switched to controlled native `<input>` / `<textarea>` / HeroUI
`<Switch>`, with `setModalField('field')(value)` curried helper.
- 高级设置 collapsible: Semi `<Collapse keepDOM><Collapse.Panel>`
→ native `<details>` with rotating `<ChevronDown>` (matches
the CollapseSection / CodexUsageModal / CustomOAuthSetting
pattern).
- 参数覆盖模板 panel: Semi inline-styled `bg surface-muted border
app-border` block → semantic `border-border bg-surface-
secondary p-3 rounded-xl`. The chip status (`未设置` / `已设置`
/ `JSON 无效`) lifts to `<StatusChip tone='grey|orange|red'>`,
buttons keep the existing 可视化编辑 / 格式化 / 清空 actions and
open the existing `ParamOverrideEditorModal`.
- Modal.confirm / Modal.info: 4 imperative confirmations
(清空全部缓存 / 清空该规则缓存 / 填充模版 / 参数模板预览) →
3 `<ConfirmDialog>` 实例 + 1 state-driven HeroUI v3 preview
modal. Each has its own state flag (`confirmClearAll` /
`confirmClearRule` / `confirmAppendTemplates` /
`previewModalVisible + previewRaw`).
- Tag: Semi `<Tag color='orange|green'>` → semantic `<StatusChip
tone>`; default 中性 chip (`bg-surface-secondary text-muted`)
used for 模型正则 / 路径正则 / Key 来源 / 作用域 / 上下文 Key
presets and the embedded type:detail labels.
- Imperative `formApiRef.current?.setValue/getValues/validate()` calls
go away; everything reads/writes `inputs` (page-level) and
`modalForm` (rule-level) state.
`bun run build` passes.
Drops: 5 → 4 files still importing HeroCompat.
Made-with: Cursor
- Adopt HeroUI Pro EmptyState in shared TableEmptyState wrapper for cleaner, design-system-consistent empty content across tables. - Bump action/filter button text to 14px (text-sm) on the token page. - Defer actions-on-left / filters-on-right row layout to the xl breakpoint so the card stops collapsing on lg-and-below viewports where actions+filters did not fit on one row. - Make filter inputs share row width via flex-1 between sm and xl, giving a balanced two-input + two-button row when stacked above the action buttons. Made-with: Cursor
The unlayered `button, input, textarea, select { font: inherit; }` rule
in index.css overrode @heroui/styles' layered `.button { font-size: 0.875rem; }`,
making every HeroUI button render with the body's 16px instead of the
design system's 14px. In Tailwind v4 unlayered styles always win against
layered ones regardless of selector specificity.
Wrap the redundant reset in @layer base so it sits at the same priority
as Tailwind's preflight and lets HeroUI's component layer styles win.
Also drop the now-unnecessary text-sm overrides on token page action and
filter buttons that were previously a workaround for this bug.
Made-with: Cursor
Replaces the legacy Semi `Modal/Form/InputNumber/Collapse/Divider/
Typography/Space/Spin/Tag/Row/Col/Radio` HeroCompat wrappers + 4
HeroIconsCompat icons (`IconPlus/IconMinus/IconHelpCircle/IconCopy`)
with HeroUI v3 Modal anatomy + Tailwind primitives + lucide icons.
Form state moves from imperative `formApi` (`setValue/getValue/
submitForm`) to a fully controlled `values` state + inline
`validate()`.
Highlights:
- Modal anatomy: Semi `<Modal title visible width=800
confirmLoading top:20>` → HeroUI v3 anatomy
(`ModalBackdrop/ModalContainer/ModalDialog/ModalHeader/ModalBody/
ModalFooter`) with `useOverlayState` and `size='4xl'
className='max-w-[95vw]'`. ModalBody preserves
`max-h-[70vh] overflow-y-auto` scroll. Footer hosts 取消 + 创建
buttons (with `isPending={submitting}`).
- 镜像选择 / 计价币种 toggles: Semi `<RadioGroup type='button'>`
→ 2-button segmented control (`bg-foreground text-background`
for the active option).
- 硬件类型 / 部署位置 selectors: Semi `<Form.Select>` with rich
`<Option>` content + `renderSelectedItem` → 2 new local
components:
- `RichSingleSelect`: button-triggered dropdown with rich
per-item rendering (硬件类型: name + 最大GPU + 可用数量
chip) + selected display via `renderSelected`.
- `RichMultiSelect`: chip-based multi-select with rich per-item
rendering (部署位置: name + region tag + 可用数量) +
checkbox indicators in the dropdown.
Both close on outside click, support `loading` and `disabled`
states, and use `<ChevronDown>` lucide as the trigger glyph.
- 表单字段: Semi `<Form.Input rules>` / `<Form.InputNumber>` →
controlled native `<input>` / `<input type='number'>` driven by
`values` + `setField(key)(value)` curried helper. NaN-safe
numeric coercion via `setNumberField()`.
- 高级配置: Semi `<Collapse keepDOM><Collapse.Panel header
itemKey>` → native `<details>` with rotating `<ChevronDown>`
(matches the CodexUsageModal / ModelPricingEditor / Custom
OAuthSetting pattern). Inside: 镜像仓库配置 + 容器启动配置
(entrypoint / args 列表) + 环境变量 (普通 + 密钥) — all
rebuilt with Tailwind grids and HeroUI `<Button isIconOnly>`
add/remove buttons.
- 快速导航 buttons: Semi `<Button theme='borderless' type=
'tertiary'>` → HeroUI `<Button size='sm' variant='light'>`.
- 价格预估 cards: Semi inline-styled `border bg surface-muted
rounded-md` blocks → semantic `border-border bg-surface-
secondary rounded-md` 块 (`text-2xl font-semibold` for the big
total, `text-sm font-semibold` for hourly rate / compute cost,
6 个 价格摘要 项).
- Tooltip: Semi `<Tooltip content>` next to 流量端口 label →
HeroUI v3 `<Tooltip content>` (already imported from
`@heroui/react`).
- 内置 API Key 复制 row: Semi `<Input readOnly>` + `<Button icon=
<IconCopy>>` → controlled `<input readOnly>` + HeroUI `<Button
size='sm' variant='light' startContent=<Copy>>` driven by the
existing `copy()` helper.
- Imperative `formApi.setValue/getValue/submitForm()` calls go
away; everything reads/writes `values` directly. ESC-to-close
added via document keydown listener.
`bun run build` passes.
Drops: 4 → 3 files still importing HeroCompat.
Made-with: Cursor
Switch the "令牌管理" card title from text-blue-500 to text-foreground so it follows the active light/dark theme via --app-foreground, and remove the leading Key lucide icon for a cleaner header that matches the rest of the design system. Made-with: Cursor
Replaces the legacy Semi `Form/Row/Col/Typography/Modal/Banner/ TagInput/Spin/Radio` HeroCompat wrappers with HeroUI v3 `Button/Card/Spinner` + the existing `ConfirmDialog` + Tailwind primitives + lucide icons. Form state moves from imperative `formApiRef` (`refForm.setValues / formApi.getValues`) to a fully controlled `inputs` state + `setField(key)(value)` curried helper. Highlights: - Layout: Semi `<Form><Card><Form.Section text>` + nested `<Row gutter><Col xs sm md lg xl>` → semantic `<Card.Content>` + local `<SectionHeader>` + responsive grid (`grid-cols-1 md:grid-cols-2|md:grid-cols-3|md:grid-cols-12`). The 14 个 settings cards (通用 / 代理 / SSRF / 登录注册 / Passkey / 邮箱白名单 / SMTP / OIDC / GitHub / Discord / LinuxDO / WeChat / Telegram / Turnstile) keep their original sectioning and order. - 表单字段: Semi `<Form.Input field placeholder type='password' extraText>` → controlled `<TextInput>` (inline-defined, borderless `<input>` w/ semantic styling). All 25+ text/password fields rebuilt; 敏感信息 fields keep `type='password'` + their same "敏感信息不会发送到前端显示" placeholder. - 复选框: Semi `<Form.Checkbox field noLabel onChange>` → local `<CheckboxRow>` (native `<input type='checkbox' accent- primary>` + label + optional hint). All 19 toggles (登录注册 cluster + Passkey + SSRF / 邮箱限制 / SMTP SSL / Worker HTTP) drive a shared `handleCheckboxToggle(key)` curried handler that also persists the change immediately via `updateOptions()`. - 选择器: Semi `<Form.Select optionList>` → controlled native `<select>` (Passkey 安全验证级别 / 设备类型偏好). All 3 options preserved verbatim. - 段控制器: Semi `<Radio.Group type='button'><Radio>` for SSRF 域名 / IP 白名单 vs 黑名单 → local `<Segmented>` (matches the SettingsChats / GroupRatioSettings pattern). Toggling persists to `inputs` immediately so `submitSSRF` reads the current mode. - 标签输入: Semi `<TagInput value onChange placeholder>` → local `<TagInput>` (chip + input, commits on Enter / `,` / blur, Backspace removes trailing chip). Used for 邮箱域名白名单 / 域名列表 / IP列表 / 端口列表 (4 instances). - 横幅: Semi `<Banner type='info' description>` → local `<InfoBanner>` (`border-primary/20 bg-primary/5 text-foreground`). - 邮箱域名添加 row: Semi `<Form.Input suffix=<Button> onEnterPress>` → flex row with separate `<input>` + Add button, Enter-to-submit via inline keydown handler. - 取消密码登录确认: Semi imperative `<Modal title visible onOk onCancel>` → state-driven `<ConfirmDialog danger>` ( `showPasswordLoginConfirm` flag). `previousPasswordLoginRef` preserved so cancel reverts the checkbox visually. - Loading state: Semi `<Spin size='large'>` (shown until `isLoaded`) → centered `<Spinner color='primary'>`. While the options are being saved (`loading=true`), an absolute overlay (`bg-background/40` + `<Spinner>`) is shown over the form. - All `formApiRef.current?.setValue/getValues/setValues()` imperative calls go away — `setInputs` / `setField` is the single source of truth. `bun run build` passes. Drops: 3 → 2 files still importing HeroCompat. Made-with: Cursor
Replaces the legacy Semi `Col/Collapse/Modal/Row/Space/Tag/TextArea/
Typography` HeroCompat wrappers + 3 HeroIconsCompat icons
(`IconDelete/IconMenu/IconPlus`) with local shim components defined
at the top of the file that mirror the Semi-style API but render
HeroUI v3 / Tailwind / lucide underneath. The 3500-line render tree
stays untouched — this is a pure imports + shim layer.
Why shims (instead of a full rewrite):
- 3 nested modals, dozens of `<Row><Col>` grids, a controlled
`<Collapse activeKey>` accordion, 3 Switches with `checkedText/
uncheckedText`, and 80+ Text / Tag / TextArea instances.
- Rewriting the render tree end-to-end risks losing subtle layout
invariants (sticky panels, sticky-fill heights, condition
collapse keep-DOM behavior, JSON live-preview spacing, etc.).
- The shim layer drops the HeroCompat dependency, removes the
legacy Semi `<Form>` plumbing wherever it bled into the file,
and adapts `Input/Select/Switch` from @heroui/react to the
Semi-style `value/onChange(value)/optionList/checked` API used
inside the render tree.
Shim components (defined inline, ~360 lines):
- `Tag`: 7 tone keys (blue/cyan/green/red/orange/yellow/grey) →
semantic Tailwind palette (`bg-success/15 text-success` family).
- `Text` + `Typography`: maps `type='tertiary|secondary|danger|
warning'` + `size='small'` + `strong` to semantic classes.
- `Space`: flex container with `vertical/wrap/spacing/align`.
- `Row` + `Col`: Antd-style 24-col grid via flex-wrap; per-
breakpoint span clamped to 1..24 and rendered via inline
`flex-basis` / `max-width` percentages.
- `TextArea`: native `<textarea>` (`font-mono text-xs`) with
Semi's `autosize.minRows`, `showClear`, `readOnly`.
- `Input`: HeroUI-styled `<input>` adapter for Semi's
`value/onChange(value)/prefix/suffix/showClear/onEnterPress`.
- `Select`: lightweight dropdown matching Semi's
`value/onChange(nextValue)/optionList`. Click-outside collapse,
active-row highlight, max-h-280 scroll.
- `Switch`: thin wrapper over HeroUI v3 `<Switch isSelected
onValueChange size>` accepting Semi's `checked/onChange(value)`
+ ignoring `checkedText/uncheckedText` (HeroUI v3 doesn't
surface inline labels — the labels are rendered next to the
switch in the existing markup anyway).
- `Collapse` + `Collapse.Panel`: controlled accordion that walks
`<Collapse.Panel itemKey header>` children and renders rotating
`<ChevronDown>` headers + `border-t border-border` panel
bodies. Honors the existing `activeKey` array + `onChange(keys)`
contract used by the conditions panel.
- `Modal`: HeroUI v3 `Modal/ModalBackdrop/ModalContainer/Modal
Dialog/ModalHeader/ModalBody/ModalFooter` wrapper that maps
`title/visible/width/bodyStyle/footer/onCancel/onOk/okText/
cancelText`. When `footer === null` no footer is rendered;
otherwise 默认 取消 + 保存 buttons. Supports `width=number` via
`style={{maxWidth:width}}`.
- Lucide icon aliases: `IconDelete = Trash2`, `IconMenu =
GripVertical`, `IconPlus = Plus`.
`bun run build` passes (19s, no errors).
Drops: 2 → 1 file still importing HeroCompat.
Made-with: Cursor
The final HeroCompat file. Replaces the legacy Semi `SideSheet/Space/
Spin/Typography/Checkbox/Banner/Modal/ImagePreview/Tag/Avatar/Form/Row/
Col/Highlight/Collapse/Dropdown` HeroCompat wrappers + 10
HeroIconsCompat icons with local shim components (~880 lines) defined
at the top of the file that mirror the Semi-style API but render
HeroUI v3 / Tailwind / lucide underneath. The 3900+ line render tree
stays untouched — this is a pure imports + shim layer.
Why shims (not a full rewrite):
- 16 HeroCompat wrappers used by 200+ render-tree call sites.
- 51 `<Form.Input/Switch/Select/Checkbox/TextArea/InputNumber/Section/
Slot/TagInput>` instances that all bind into the Semi `<Form
initValues>` context via `field='X'`.
- 3 imperative `Modal.confirm({ onOk })` calls (one of which captures
the returned handle to call `.destroy()` later).
- A full rewrite would risk breaking the modal's intricate per-field
side-effects (`base_url` regex guard, channel-type-driven model
prefills, multi-key mode lifecycle, vertex_files reset, etc.).
Shim components added (defined inline, no exports):
- `Tag` / `Text` / `Title` / `Typography`: semantic-token mappings.
10 tone keys (blue/cyan/green/red/orange/yellow/purple/white/grey
+ default).
- `Space` / `Row` / `Col`: flex-wrap layout primitives. Antd-style
24-col grid via inline `flex-basis` percentages, `gutter`-derived
row+column gap.
- `Avatar`: round/square tile w/ `color` tone and `src` fallback.
- `Banner`: tinted info/warning/error/success block, ignores
`closeIcon/fullMode/bordered`.
- `Spin`: HeroUI Spinner wrapper supporting `spinning={loading}`
overlay over children + standalone loader form.
- `Checkbox`: native `<input>` with Semi's `checked/onChange(event)`
API.
- `Highlight`: text-with-mark renderer using a regex over
`searchWords`, falls back to plain text.
- `ImagePreview`: passthrough children or fall-through `<img src>`.
- `Dropdown`: click-triggered menu adapter, supporting `position
=bottomRight|bottomLeft|topRight|topLeft` + `menu=[{node,name,
icon,onClick,danger,suffix},{node:'divider'}]`.
- `Collapse` / `Collapse.Panel`: controlled accordion w/ rotating
ChevronDown header (matches Semi's `activeKey` array contract).
- `Modal`: HeroUI v3 anatomy (`ModalBackdrop/Container/Dialog/
Header/Body/Footer`) with `useOverlayState`. Honors `width=number`
via `style.maxWidth`. `footer === null` removes footer; otherwise
renders 默认 取消/确认 buttons.
- `Modal.confirm({...})`: imperative bridge driven by a single
`<ImperativeConfirmHost />` mounted at the end of the modal's
render. Each call routes through a static `imperativeConfirmHandle
.open(config)`; the returned `{ destroy() }` handle calls
`imperativeConfirmHandle.close()`. Backed by the shared
`<ConfirmDialog>` component.
- `SideSheet`: custom fixed `<aside>` that slides in from
`placement='right|left'`, bg-black/40 backdrop, ESC close via
parent's `onCancel`. Mirrors Semi's `width/title/footer/bodyStyle`
API.
Form context shim (`Form` + `Form.Input/InputNumber/TextArea/Switch/
Checkbox/Select/Select.Option/TagInput/Section/Slot`):
- `Form` provides a React Context (`FormCtx`) seeded from
`initValues`. Each `Form.X field='name'` reads from
`ctx.values[field]` and writes via `ctx.setValue(field, value)`.
- `getFormApi(api)` exposes a stable ref-style API:
`setValue(name,value)/setValues(merge)/getValue(name)/getValues()/
submitForm()/validate()/reset()`.
- `formApiRef.current.setValue('field', value)` from outside the
component tree (e.g. `useEffect`) updates the same context state,
preserving the bidirectional sync the original code relies on.
- Each Form.X callback fires user's `onChange(value)` AFTER the
context is updated.
- `Form.Switch.checkedText / uncheckedText` accepted but ignored
(HeroUI v3 doesn't surface inline labels — surrounding markup
already includes descriptive `extraText`).
- `Form.Section text` renders a section header with
`border-b border-border`.
- `Form.Slot label` is a label + child wrapper.
- `Form.Select` walks both `optionList` and `<Option>` children;
rich Semi extras like `filter / showClear / dropdownStyle /
renderSelectedItem / innerBottomSlot / onSearch / autoClearSearch
Value / allowAdditions / additionLabel / allowCreate` are accepted
but currently no-op (the existing markup uses these for visual
niceties, not for runtime correctness).
- `Form.TagInput` commits on Enter / `,` / blur, removes the
trailing tag on Backspace when the draft is empty.
Lucide icon aliases for HeroIconsCompat names used in this file:
`IconSave=Save`, `IconClose=X`, `IconServer=Server`, `IconSetting=
Settings`, `IconCode=Code2`, `IconCopy=Copy`, `IconGlobe=Globe`,
`IconBolt=Bolt`, `IconSearch=Search`, `IconChevronDown=ChevronDown`.
Imperative singleton: `<ImperativeConfirmHost />` is mounted at the
end of the modal's render. It runs the imperative `Modal.confirm`
through the shared `<ConfirmDialog>`, preserving the captured-handle
contract (`const m = Modal.confirm(...); m.destroy();` pattern at
line ~1418).
`bun run build` passes (18s, no errors).
Drops: 1 → 0 files importing HeroCompat. The frontend HeroCompat
migration is complete.
Made-with: Cursor
Now that no file imports `HeroCompat`, `HeroIconsCompat`, or `HeroIllustrationsCompat`, the shim re-exports and their underlying Semi adapters can be deleted. - web/src/components/common/ui/HeroCompat.jsx (re-export of semi.js) - web/src/components/common/ui/HeroIconsCompat.jsx (re-export of semi-icons.js) - web/src/components/common/ui/HeroIllustrationsCompat.jsx (re-export of semi-illustrations.js) - web/src/components/ui/semi.js (1533 lines: Semi → HeroUI v2 adapter) - web/src/components/ui/semi-icons.js (158 lines: lucide-backed icon aliases) - web/src/components/ui/semi-illustrations.js (59 lines: lucide-backed illustrations) - web/src/components/ui/semi/ (empty es/lib directory tree) The last remaining import (`IllustrationNoResult` in PricingCardView) is replaced inline with a lucide `<SearchX size=96 stroke-1.25 text-muted/60>` glyph inside the existing `<EmptyState.Media>` slot. Net: -1815 lines of legacy adapter code, 0 import sites broken. `bun run build` passes (20s). Made-with: Cursor
- Drop the leading Eye icon from the "任务记录" header and switch the text from text-orange-500 to text-foreground so it follows the active light/dark theme via --app-foreground. - Remove the `flex min-h-0 min-w-0` wrapper around CardPro that was making the card a content-sized flex item; CardPro is now rendered directly with `w-full` so it expands to fill the page on large screens, matching how every other table page is structured. Made-with: Cursor
Mirror the same polish applied to the task logs page: - Drop the leading Eye icon from the "Midjourney 任务记录" header (and its skeleton placeholder) and add text-foreground to the title so it follows the active light/dark theme. - Remove the `flex min-h-0 min-w-0` wrapper around CardPro that was forcing the card to shrink to content size; CardPro now renders directly with `w-full` so it expands to fill the page on large screens, matching how every other table page is structured. Made-with: Cursor
Apply the same polish previously applied to token / task-logs / mj-logs to the remaining table description headers — drop the leading lucide icon and the saturated text-blue-500 / text-orange-500 wrapper, and render the title as a plain text-foreground span so it follows the active light/dark theme instead of fighting it. Covers: - Users (用户管理) — drops UserPlus icon - Redemptions (兑换码管理) — drops Ticket icon - Subscriptions (订阅管理) — drops CalendarClock icon Made-with: Cursor
The previous header used `<Badge count='当前余额' position='rightTop' type='danger'>` — props that belong to Semi UI, not HeroUI. The result was a label that overflowed the card boundary (showed `$200.0` clipped on the right). Restructure the cover header so the balance lives in the Card.Content area beneath the cover image, with a proper "当前余额" label, a wallet icon, and a stat row that gracefully wraps on smaller breakpoints. Use HeroUI Chip components for the role / ID pills. Made-with: Cursor
… AccountManagement The card previously rolled its own segmented-control out of <button> tags and passed Semi-style props to HeroUI Buttons (`color='primary' variant='bordered'`, `startContent`) that HeroUI silently dropped — leaving every binding row with a flat gray button regardless of state. Switch the binding/security toggle to a proper <Tabs>/<Tabs.List>/<Tabs.Tab> structure and rebuild the action buttons around the real HeroUI variants: - "绑定" / "修改绑定" → variant='primary' - "已绑定" → variant='outline' isDisabled - "未启用" → variant='tertiary' isDisabled - "解绑" → variant='danger-soft' - "删除账户" → variant='danger' Header icon is now a HeroUI Avatar with Avatar.Fallback, and inline icons are passed as children (instead of the unsupported `startContent` prop) so they actually render. Made-with: Cursor
…and preferences cards NotificationSettings and PreferencesSettings both rolled custom segmented controls out of <button> tags and used a plain colored <div> for their card header icons. Replace these with HeroUI primitives: - Top tabs (通知配置 / 价格设置 / 隐私设置 / 边栏设置) become <Tabs.List> with <Tabs.Tab> children. - The 通知方式 selector inside notification config also becomes a HeroUI <Tabs> control bound to `warningType`. - Card header icons use <Avatar><Avatar.Fallback> for consistent surface treatment with the rest of /console/personal. No behavioural change to form fields, validation, sidebar settings or language preference handling — only structural and presentational alignment with HeroUI. Made-with: Cursor
…chers The 账户绑定/安全设置 toggle, the 通知配置/价格设置/隐私设置/边栏设置 toggle, and the 通知方式 selector all share the same intent: a small set of mutually exclusive options. HeroUI Pro ships a dedicated <Segment> primitive for exactly this case (iOS-style segmented control), so swap the previous <Tabs>/<Tab.List>/<Tab> structure for <Segment>/<Segment.Item> with <Segment.Separator> dividers. This gives a clearer selected-state pill, native keyboard handling, and consistent styling across personal settings. Made-with: Cursor
PreferencesSettings used a raw <select> for the language picker and NotificationSettings used another raw <select> for the Gotify priority. Both now use HeroUI Pro <CellSelect> with a HeroUI <ListBox> popover — this gives proper keyboard navigation, consistent cell-style trigger, and an inline label/value/indicator layout that matches the rest of the settings panel. We pass an explicit <ChevronsUpDown /> child to <CellSelect.Indicator> because the component's default `@gravity-ui/icons/ChevronsExpandVertical` import does not interop cleanly through Vite's CJS handling here. Made-with: Cursor
…Header The previous lg-breakpoint inline-row layout pushed both the balance and the stat-row pill into the same lg row, which on a ~1024px content area forced every label and value to ellipsize ($200... / 历史消耗 $0... / 用户分组 defa...). Restructure so the stats render as a 3-column grid below the balance up through the lg range, and only collapse into a single inline pill at xl. StatItem itself stacks label-above-value at narrower widths so values have enough room to render in full. Made-with: Cursor
Migrate every panel on /console (StatsCards, ChartsPanel, ApiInfoPanel, AnnouncementsPanel, FaqPanel, UptimePanel) from `@heroui/react` Card to the new `Widget` primitive in `@heroui-pro/react` so each surface gets the gray outer shell + elevated white content area + subtle inner shadow defined by widget.css. Notable adjustments while wiring this up: - StatsCards: Widget.Content padding tightened from p-4 to p-3 and the KPI value font dropped from text-lg to text-base, with min-w-0 on the label cluster and shrink-0 on the trailing button/sparkline so the rendered "充值" button no longer clips against "$200.00" inside the narrower Widget content area. - ChartsPanel + AnnouncementsPanel: override Widget.Header from the default fixed h-8 to h-auto min-h-12 flex-col … lg:flex-row so the inline Tabs strip / legend has room to wrap on narrow viewports. - AnnouncementsPanel + UptimePanel: adopt Widget.Legend / Widget.LegendItem for status legends; the announcement panel maps the existing grey/blue/green/orange/red swatch keys to concrete CSS colors via a small LEGEND_COLOR_MAP because Widget.LegendItem requires a CSS color string. - All title clusters use a whitespace-nowrap flex wrapper around the lucide icon and Widget.Title so CJK title characters do not stack vertically when the grid column is narrow. widget.css is already pulled in via `@import '@heroui-pro/react/css';` in web/src/index.css, so no extra CSS plumbing is required. Verified: `bun run build` passes; `bunx prettier --check` and `bunx eslint` pass for all touched files; manual screenshot QA confirms the dashboard panels render with the new Widget surface. Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f54cb30. Configure here.
| *.jpg | ||
| *.jpeg | ||
| !web/public/**/*.png | ||
| !web/public/**/*.jpg |
There was a problem hiding this comment.
Missing .jpeg exception in .gitignore for web/public
Medium Severity
The .gitignore adds *.jpeg to the global ignore list but only creates exceptions for !web/public/**/*.png and !web/public/**/*.jpg — there's no matching !web/public/**/*.jpeg exception. Any .jpeg file added to web/public/ in the future would be silently ignored by git, inconsistent with the treatment of the other two image extensions.
Reviewed by Cursor Bugbot for commit f54cb30. Configure here.
|
|
||
| - `web/src/components/settings/{DashboardSetting,ModelDeploymentSetting,OperationSetting}.jsx`: low-risk container files that mainly depend on `Spin`. | ||
| - `web/src/pages/Setting/{Drawing,Performance,RateLimit}/...`: form pages still use `Form`, `Row`, `Col`, `Spin`, and `Tag`. | ||
| - `web/src/components/table/{channels,models}/...`: high-impact table and modal area, but should be migrated in smaller batches. |
There was a problem hiding this comment.
Cursor agent scratchpad committed with stale tracking data
Low Severity
TODO.md is a 337-line Cursor agent working log that appears to have been unintentionally included in the commit. The "Current Hotspots" and "Next Migration Candidates" sections (lines 81–91) list items as still needing work (DashboardSetting, ModelDeploymentSetting, OperationSetting, Drawing, RateLimit, table modules) that are already marked [x] completed earlier in the same file (lines 11, 34–35, etc.). This stale data would actively mislead contributors about the migration's actual status.
Reviewed by Cursor Bugbot for commit f54cb30. Configure here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f54cb30aab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| {visibleColumns.map((col, idx) => ( | ||
| <th | ||
| key={col.key || col.dataIndex || idx} | ||
| className='whitespace-nowrap px-4 py-3' | ||
| style={{ width: col.width }} |
There was a problem hiding this comment.
Restore row selection support in CardTable
This custom table path now renders headers/cells only from visibleColumns and never consumes tableProps.rowSelection, so the selection checkbox column disappears on pages that rely on it for batch actions (for example tokens/channels/models pass rowSelection into CardTable). In practice users can no longer select rows to run bulk operations, which is a functional regression from the previous Table-based implementation.
Useful? React with 👍 / 👎.
| new CustomEvent('app-toast', { | ||
| detail: { type, content: message }, | ||
| }), |
There was a problem hiding this comment.
Emit toast text via the
message field
ToastViewport reads event.detail.message, but notify() dispatches { type, content }, so Markdown copy notifications produce empty toasts (icon-only) instead of user-readable text. This affects code-copy success/failure feedback in Markdown-rendered content.
Useful? React with 👍 / 👎.
| <HeroPagination | ||
| page={currentPage} | ||
| total={Math.max(1, Math.ceil(total / pageSize))} | ||
| size={isMobile ? 'sm' : 'md'} | ||
| onChange={(page) => onPageChange?.(page, pageSize)} | ||
| showControls |
There was a problem hiding this comment.
Re-enable page-size controls in CardPro pagination
This helper still accepts onPageSizeChange, pageSizeOpts, and showSizeChanger, but the new HeroPagination wiring only handles page index changes. As a result, tables using createCardProPagination lose the ability to change page size, so hooks exposing handlePageSizeChange are no longer reachable from the UI.
Useful? React with 👍 / 👎.
Address visual polish feedback on the Widget rebuild: - Title size: createSectionTitle in helpers/dashboard.jsx now wraps the text in a `text-sm font-semibold text-foreground` span (matching the widget__title CSS) so the four StatsCards titles (账户数据 / 使用统计 / 资源消耗 / 性能指标) match the Widget.Title used by every other panel. Also adds whitespace-nowrap + shrink-0 on the icon to prevent CJK characters from stacking. - Header height: every simple Widget.Header now uses `h-12` so the header strip matches the AnnouncementsPanel header height (which the user pointed out as the correct one). The complex headers (ChartsPanel, AnnouncementsPanel) keep their `h-auto min-h-12 … flex-col` rule for content that can wrap to a second row. - Empty state: every empty state now uses HeroUI Pro's `EmptyState` with `size='sm'` and `EmptyState.Media variant='icon'`, dropping the custom 96×96 illustration container override. FaqPanel previously used a bespoke div + HelpCircle 42px; it now uses EmptyState too, so the four panels (ApiInfoPanel / AnnouncementsPanel / FaqPanel / UptimePanel) share the same circular muted icon background and consistent typography. - Responsive grid: StatsCards collapses to 4-col only at xl (≥1280px) via `grid-cols-1 sm:grid-cols-2 xl:grid-cols-4` so each KPI card has ~360px at common 1024–1280 widths instead of ~150px (which was forcing labels like "统计次数" to wrap CJK character per line). ChartsPanel + ApiInfoPanel switch to `lg:grid-cols-3 xl:grid-cols-4` with the chart panel spanning 2/3, giving the API info column a more usable ~256px width at lg. The bottom info panels grid switches to `lg:grid-cols-2 xl:grid-cols-4` so 系统公告 takes the full row at lg with FAQ + Uptime side-by-side beneath it. - ChartsPanel tabs strip: keeps the title and the Tabs.List on separate rows through lg and only inlines them at xl. The tab list is wrapped in an overflow-x-auto container with whitespace-nowrap on each Tabs.Tab so the 4–6 chart tabs no longer collapse into vertical CJK character columns when the chart panel narrows. - Cleanup: drop the now-unused ILLUSTRATION_SIZE prop from AnnouncementsPanel / ApiInfoPanel / FaqPanel / UptimePanel and from the dashboard index that wired it through. Verified: bun run build passes, prettier --check + eslint pass on all touched files, and manual screenshot QA confirms consistent header heights, title sizes, empty state icons, and a clean 2-col KPI grid + horizontally scrollable tabs at the typical 1024px viewport. Made-with: Cursor
…tons - StatsCards: switch the 当前余额 → 充值 button from `variant='secondary'` (default surface fill + accent-soft text, which read as a muted green pill) to `variant='primary'` so it now renders with the accent fill + accent-foreground text expected of a recharge CTA. - DashboardHeader: switch the 搜索条件 + 刷新 icon-only buttons from `variant='tertiary'` (which paints the surrounding pill at rest) to `variant='ghost'` so the trigger reads as a bare icon and the background only fades in on hover/press. Drop the gap from `gap-2` to `gap-1` since the buttons no longer carry their own pill. Made-with: Cursor
The refresh trigger inside 服务可用性's compact 48px Widget.Header was still rendering at full Button `sm` icon-only dimensions (32×32 with a 16px svg, since `.button--sm` forces svg to size-4 regardless of the lucide `size` prop). Override to `!h-6 !w-6 !min-w-0` plus `[&_svg]:!size-3` so the affordance reads as a 24×24 hover pill with a 12px refresh glyph — proportionate to the small panel header without dominating the title. HeroUI Button only ships `sm`/`md`/`lg`, so a className escape hatch is the cleanest sub-`sm` path. Made-with: Cursor
The Tabs.List inside 模型数据分析's Widget.Header carries 4–6 entries (消耗分布 / 调用趋势 / 调用次数分布 / 调用次数排行 / 用户消耗排行 / 用户消耗趋势). At the default 14px tab font, only the first three fit across the chart panel at lg, forcing the rest behind the horizontal scroll. Drop each Tabs.Tab label to text-xs (12px) so 4–5 tabs are visible at rest while the secondary chrome stays subordinate to the 14px Widget.Title. Made-with: Cursor
ApiInfoPanel sits in a CSS grid row alongside the much-taller ChartsPanel, so the row stretches the API info Widget to ~24rem. Previously the empty state branch rendered straight inside ScrollableContainer, which collapses to its content height — leaving the empty state pinned to the top with all the empty space below it. Split the two render paths: - Data path keeps the ScrollableContainer for scroll-with-fade and now carries `flex-1` so the scroll region claims the stretched height. - Empty path renders into a `flex flex-1 min-h-80 items-center justify-center` wrapper, which propagates the row height down via the `flex` Widget.Content + `flex-1` child chain and centers the EmptyState both axes regardless of grid stretch height. Made-with: Cursor
|
Replaced by a clean PR #3 that targets feature/console-template-style and contains only the 6 Widget-related commits (no cumulative HeroCompat / personal-page-redesign work bundled in). |


Summary
Migrate every panel on
/console(the dashboard surface) from@heroui/reactCardto the newWidgetprimitive in@heroui-pro/reactso each surfacegets the gray outer shell + elevated white content area + subtle inner
shadow defined by
widget.css.This is the latest in the ongoing
/consoleHeroUI Pro migration work andis built on top of the still-open #1 (
feature/console-template-style) +the personal-page-redesign series; this PR adds the Widget surface
treatment from https://docs.heroui.pro/docs/react/components/widget on
top of those.
Components migrated to
WidgetStatsCards— the four KPI tiles at the top of the dashboard(账户数据 / 使用统计 / 资源消耗 / 性能指标).
ChartsPanel— 模型数据分析 chart panel with the inlineTabs.Liststrip.ApiInfoPanel— API信息 list with the speedtest/jump action rows.AnnouncementsPanel— 系统公告 timeline + status legend.FaqPanel— 常见问答 accordion + empty state.UptimePanel— 服务可用性 monitor list + status legend inWidget.Footer.Key adjustments
Widget.Contentpadding tightened fromp-4top-3andthe KPI value font dropped from
text-lgtotext-base, plusmin-w-0on the label cluster and
shrink-0on the trailing button/sparkline sothe rendered "充值" button no longer clips against
$200.00inside thenarrower Widget content area.
Widget.Headerfrom thedefault fixed
h-8toh-auto min-h-12 flex-col … lg:flex-rowso theinline Tabs strip / wrapping legend has room on narrow viewports.
grey/blue/green/orange/redswatch keys to concrete CSS colors via a small
LEGEND_COLOR_MAPbecause
Widget.LegendItemrequires a CSS color string.Widget.Titlein awhitespace-nowrapflex container so CJK title characters don't stackvertically when the grid column is narrow.
widget.cssis already pulled in via@import '@heroui-pro/react/css';in
web/src/index.css, so no extra CSS plumbing was required.Test plan
bun run build— passes.bunx prettier --check— passes for all touched dashboard files.bunx eslint— passes for all touched dashboard files./console: stats cards, model analyticschart panel, API info panel, announcements panel, FAQ panel, and
uptime panel all render with the new Widget surface.
This PR was generated automatically by Cursor
Made with Cursor
Note
Medium Risk
Moderate UI refactor of the
/consoledashboard panels to a new component primitive; risk is mainly layout/interaction regressions rather than backend or data/security concerns.Overview
Migrates the
/consoledashboard surface from@heroui/reactCardpanels to the HeroUI ProWidgetcomponent to adopt the new shell styling.Updates the affected dashboard panels (e.g. KPI tiles and charts/info/announcements/FAQ/uptime panels) to use
Widget.Header/Widget.Content/Widget.Footer, with small layout tweaks for tighter padding and responsive headers (tabs/legends) and a legend color mapping whereWidget.LegendItemrequires explicit CSS colors.Separately updates project docs to reflect the HeroUI stack, adds image and Playwright MCP ignores in
.gitignore(while keepingweb/publicimages tracked), and introduces aTODO.mdtracking the broader HeroUI migration work.Reviewed by Cursor Bugbot for commit f54cb30. Bugbot is set up for automated code reviews on this repo. Configure here.