- migrate the classic frontend from Vite to Rsbuild with JSX, Semi UI, proxy, and production build config.
- update make dev-web to run both default and classic frontends for local theme switching.
- fix classic public page height, footer, CORS proxy, error handling, and constant export warnings.
- update Dockerfile and release workflow to install from the web workspace root with the shared lockfile.
- add a web workspace catalog to manage dependency versions shared by default and classic frontends.
- switch shared dependencies including @lobehub/icons to catalog references and align @lobehub/icons on 5.10.0.
- replace separate frontend Bun lockfiles with a unified web/bun.lock to reduce duplicate maintenance.
- add the icon field to the pricing model type to consume model-level icons returned by the backend.
- prefer model icons in cards, table model cells, and detail headers while falling back to vendor icons.
The idx_created_at_id composite index on the logs table was defined as
(id, created_at) because the GORM `priority` values on Id and CreatedAt were
swapped. Since `id` is the auto-increment primary key, a secondary composite
index leading with `id` is redundant with the PK and cannot accelerate
`created_at` range scans (a range column must sit at the index prefix).
This defeats the common log-listing queries
(`WHERE created_at BETWEEN ? AND ? ORDER BY id DESC LIMIT n` in
GetAllLogs/GetUserLogs) that the index name implies it should serve — the
optimizer falls back to scanning the primary key, degrading to near full-table
scans on large logs tables.
Swap the priorities so the column order becomes (created_at, id), matching the
index name and its intended purpose. idx_user_id_id and idx_created_at_type are
unaffected.
Note: GORM AutoMigrate does not change the column order of an already-existing
index with the same name, so existing deployments must rebuild the index
manually (see PR description for per-database DDL).
Co-authored-by: wuyupeng <wuyupeng@floatmiracle.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- scope validation queries with a form root id so feedback stays inside the submitted form.
- scroll to the earliest invalid control or message and move focus without fighting scroll position.
- avoid handling the same failed submit twice to keep retries from jumping unexpectedly.
Implement the Simple Large-font theme preset and xl font scale options to enhance interface accessibility. Remove status indicator dots from channel badges in logs to keep the table layout visual and clean.
System settings forms that used flat dotted API keys (e.g.
`performance_setting.monitor_cpu_threshold`) with React Hook Form were
broken: RHF stores dotted paths as nested objects on update, while dirty
checks and submit comparisons still read flat keys from defaults. Users
could edit values but always saw "No changes to save".
Refactor affected sections to use nested Zod schemas and default values
for RHF, with explicit helpers to convert between nested form state and
flat API keys. Track a normalized baseline in refs for accurate change
detection and post-save resets.
Add `safeNumberFieldProps` to prevent native `<input type="number">`
from writing NaN into form state when cleared. NaN caused Zod validation
to fail silently and made the save button appear unresponsive. The
helper ignores non-finite updates so controlled inputs snap back to the
last valid value, matching legacy Semi InputNumber behavior.
Sections refactored for dotted-key handling:
- maintenance/performance-section
- models/grok-settings-card
- auth/passkey-section
- auth/oauth-section
- auth/section-registry (pass attachment_preference raw; normalize in section)
Sections migrated to safeNumberFieldProps:
- maintenance/performance-section
- models/grok-settings-card
- integrations/monitoring-settings-section
- integrations/payment-settings-section
- integrations/creem-product-dialog
- general/pricing-section (USD exchange rate)
- general/system-behavior-section
- content/dashboard-section
Optional numeric fields (e.g. custom currency exchange rate) keep their
existing empty-to-undefined semantics and are intentionally unchanged.
- Remove explicit 'font-mono' and custom size classes from model and token
badges in usage logs.
- Allow model name and token badges to naturally inherit the active theme's
font family (Sans or Serif) and text size from the parent container.
- Restore visual consistency and proportion across all table badge components.
- Re-introduce the custom translucent background color and thin border scheme
for timing and duration badges in common, drawing, and task logs.
- Remove strict max-width constraints on model badges to ensure complete
names (with version suffixes) are always visible and wrap gracefully.
- Adjust spacing on model and token badges (h-6 height, larger gaps, and
proper padding) to prevent crowded elements and restore a balanced,
high-quality look in the log tables.
Adjust PRESET_DEFAULT_FONT so that the shipped 'default' preset falls back to the humanist 'sans' (Public Sans) out-of-the-box instead of forcing the editorial 'serif' (Lora). Keeps the 'anthropic' preset on 'serif' as intended.
- Restore StatusBadge sizes to h-5/text-xs (sm, md) and h-6/text-xs (lg).
- Restore classic textColorMap coloring and status indicator dot.
- Embed channel type icon directly inside StatusBadge as custom children.
- Re-align status badge colors: danger for manual disabled, warning for auto disabled.
Introduce a switchable Anthropic-inspired color preset and a new Font customization axis so users can adopt the editorial serif look across the entire UI, including sidebar navigation, tabs, form controls, buttons, and table headers.
Theme preset
Add anthropic to the theme preset registry with warm cream canvas, slate foreground, and clay/coral accent tokens for light and dark modes
Define explicit surface colors for the Anthropic preset instead of relying on the semantic surface bridge
Exclude anthropic from the primary-color surface bridge so bespoke warm neutrals are not overridden by accent-tinted mixes
Typography system
Add @fontsource-variable/lora and a global --font-serif token with CJK serif fallbacks (Noto Serif SC, Source Han Serif, Songti SC, etc.)
Introduce a --font-body token and drive <body> font-family from it
Add a Font axis (default | sans | serif) parallel to radius/scale
Resolve font: 'default' against preset defaults (anthropic → serif)
Persist font preference via cookie and apply data-theme-font on <body>
Apply serif OpenType features (kern, liga, calt, tnum) and heading display tuning when serif is active
Remove per-component sans opt-outs so serif inherits through sidebar, tabs, inputs, buttons, badges, and table headers via natural CSS cascade
Keep monospace contexts unchanged via Tailwind preflight and .font-mono
UI and i18n
Add Font selector to the theme config drawer (Auto / Sans / Serif)
Add "Font" and "Select body font" translations for en, zh, fr, ja, ru, vi
Misc
Tighten group and status badge sizing for better balance with serif text
Restructure the default-theme channel create/edit experience to match classic
frontend behavior, improve form UX, and align with the project's Base UI design
system.
Channel editor architecture:
- Split the monolithic channel mutate drawer into focused section components
(basic, API access, auth, models, advanced) with shared drawer layout
primitives
- Extract submission, toast handling, and react-query cache invalidation into
`useChannelMutateForm`
- Add a dedicated loading skeleton for channel detail fetch during edit mode
- Remove the top-level configuration summary block
Form validation and data handling:
- Strengthen `channel-form` Zod schema with JSON, model mapping, status code
mapping, Codex credential, and Vertex AI key refinements
- Move type-specific conditional validation into `superRefine`
- Normalize base URL formatting and tighten model mapping value validation
Model mapping editor:
- Add Visual/JSON tabbed editing with inline JSON and duplicate-key feedback
- Improve accessibility for icon-only actions and add model suggestion datalists
MultiSelect component:
- Replace the custom cmdk-based implementation with Base UI Combobox chips
- Align focus, border, ring, disabled, and invalid states with standard Input
styling via `ComboboxChips`
- Preserve existing API for all current callers (`options`, `selected`,
`onChange`, `allowCreate`, `createLabel`)
- Support inline custom value creation and comma/newline batch input
- Limit visible chips with a compact "+N more" overflow summary via
`maxVisibleChips` (8 in the channel editor)
- Anchor the dropdown to the full chips container via `useComboboxAnchor` so
the popup matches input width and long model names are no longer truncated
Models & groups UX:
- Integrate manual custom model entry directly into the model MultiSelect
- Remove the separate manual model input/button block
- Keep selected-model count badge and existing model-mapping guardrail behavior
i18n:
- Add and sync translation keys for section descriptions, validation messages,
model mapping UI, and MultiSelect labels across en, zh, fr, ja, ru, and vi
- Fix missing translations for "Name, provider type, and availability.",
"Endpoint, provider-specific settings, and credentials.", and "Published
models, groups, and model remapping rules."
- Remove obsolete keys tied to the deprecated summary and manual model entry UI
Restructure the default-theme channel create/edit experience to align with
classic frontend behavior, modern form UX patterns, and the project's Base UI
design system.
Channel editor architecture:
- Split the monolithic channel mutate drawer into focused section components
(basic, API access, auth, models, advanced) with shared drawer layout
primitives
- Extract submission, toast handling, and react-query cache invalidation into
`useChannelMutateForm`
- Add a dedicated loading skeleton for channel detail fetch during edit mode
- Remove the top-level configuration summary block per UX feedback
Form validation and data handling:
- Strengthen `channel-form` Zod schema with JSON, model mapping, status code
mapping, Codex credential, and Vertex AI key refinements
- Move type-specific conditional validation into `superRefine`
- Normalize base URL formatting and tighten model mapping value validation
Model mapping editor:
- Add Visual/JSON tabbed editing with inline JSON and duplicate-key feedback
- Improve accessibility for icon-only actions and add model suggestion datalists
MultiSelect component:
- Replace the custom cmdk-based implementation with Base UI Combobox chips
- Align focus, border, ring, disabled, and invalid states with standard Input
styling via `ComboboxChips`
- Preserve existing API (`options`, `selected`, `onChange`, `allowCreate`,
`createLabel`) for all current callers
- Support inline custom value creation, comma/newline batch input, searchable
options, portal-based dropdown positioning, and chip removal
Models & groups UX:
- Integrate manual custom model entry directly into the model MultiSelect
- Remove the separate manual model input/button block
- Keep selected-model count and existing model-mapping guardrail behavior
i18n:
- Add and sync translation keys for new editor sections, validation messages,
model mapping UI, and MultiSelect empty/create labels across en, zh, fr, ja,
ru, and vi
- Remove obsolete keys tied to the deprecated summary and manual model entry UI
Affected areas:
- `web/default/src/features/channels/components/drawers/`
- `web/default/src/features/channels/hooks/use-channel-mutate-form.ts`
- `web/default/src/features/channels/lib/channel-form.ts`
- `web/default/src/features/channels/lib/model-mapping-validation.ts`
- `web/default/src/features/channels/components/model-mapping-editor.tsx`
- `web/default/src/components/multi-select.tsx`
- `web/default/src/i18n/locales/*.json`
Redesigns the hero section into a balanced horizontal dual-column layout:
- Left Column: Features title, clean legal-compliant descriptions, CTA buttons with BookOpen Docs link, and enlarged supported apps buttons (Cherry Studio and CC Switch with lobe icons)
- Right Column: Smoothly integrates the terminal API demo with top horizontal alignment
- i18n: Configures compliance translations for en, zh, fr, ja, ru, and vi locales
Refactor the usage log filter toolbar into a shared reusable component for common, drawing, and task logs. Optimize desktop filters with a responsive grid, move secondary filters into a mobile drawer, standardize filter typography, remove redundant filter icons, and add the missing i18n translations for the new drawer description.
Resolve verified V1 frontend feedback by improving channel workflows, auth behavior, API key interactions, user filtering, layout persistence, subscription quota handling, i18n text, pricing metadata, and stale frontend cache recovery.
- Add a global frontend cache version cleanup to prevent old frontend localStorage from causing page errors after upgrades.
- Fix channel copy refresh, model mapping input focus loss, create-channel fetch-model title state, upstream model update confirmation, and batch test toast behavior.
- Respect password login settings and improve Turnstile, forgot-password, registration, and invite-link flows.
- Make user role/status filtering server-side and preserve table page size in URLs.
- Improve API key edit validation feedback and prefetch real keys for reliable copy actions.
- Fix rankings access fail-open behavior, double scrollbars, subscription received amount conversion/display, token i18n wording, model deletion confirmation grammar, and Claude pricing context inference.
- Add clearer Playground model/group loading errors.
Validation:
- bun run typecheck
- bun run i18n:sync
- gofmt on modified Go files
- go test ./controller ./model -run '^$'
Upgrade all web/default dependencies to their latest versions and refresh the Bun lockfile. Add dependency overrides for vulnerable transitive packages so bun audit reports no known vulnerabilities.
Update TypeScript configuration for TypeScript 6 by removing deprecated baseUrl usage and explicitly enabling Node types where needed. Adapt the calendar component to react-day-picker v10 by replacing the removed table class key with month_grid.
Validation:
- bun outdated: no outdated dependencies
- bun audit: no vulnerabilities found
- bun run typecheck: passed
- bun run build: passed
Redesign the system settings interface to align with the rest of the console experience by using fixed header actions, removing redundant subtitles, respecting global content width, and standardizing responsive form layouts.
Introduce reusable settings layout primitives for forms, switch rows, grouped controls, nested control sections, title status indicators, and page action portals. Replace duplicated card-style switch markup with explicit compact components, improve nested switch readability, and reduce visual noise across authentication, billing, content, integrations, maintenance, models, and request-limit settings.
Also complete missing i18n translations, remove obsolete subtitle translation keys, refine i18n sync reporting, fix sidebar truncation for long labels, and verify the frontend with type checking and lint diagnostics.
Replace the ad-hoc "workspace" abstraction with a focused, URL-driven
"sidebar view" registry that implements the modern Vercel / Cloudflare
drill-in pattern: clicking a top-level entry (e.g. System Settings)
swaps the sidebar to a contextual workspace, with a `← Back to
Dashboard` affordance, instead of stacking sub-navigation in the root.
Architecture
------------
- types.ts
+ SidebarView — declarative nested view config
(id, pathPattern, parent, getNavGroups)
+ SidebarViewParent — back-navigation descriptor
+ ResolvedSidebarView — { key, view, navGroups } returned by hook
+ SidebarData — slimmed to { navGroups } only
- Workspace — removed (logo/plan never rendered)
- lib/sidebar-view-registry.ts (new, replaces workspace-registry.ts)
+ SIDEBAR_VIEWS array — single source of truth for nested views
+ resolveSidebarView(pathname)
+ getNavGroupsForPath(pathname, t) — back-compat helper for the
command palette
- config/system-settings.config.ts
Refactored to export a single SYSTEM_SETTINGS_VIEW (SidebarView)
with parent `/dashboard/overview` + label `Back to Dashboard`.
- components/sidebar-view-header.tsx (new)
Renders only the back affordance (chevron + label). Uses the
default SidebarMenuButton size so its typography matches the
nav items below; collapses gracefully into icon mode via the
existing tooltip behavior. The redundant "title + icon" row was
removed — workspace context is already carried by the nav groups.
- hooks/use-sidebar-view.ts (new)
Encapsulates view resolution and root-nav filtering:
· matched view → returns its nav groups verbatim (route-level
beforeLoad guards already enforce access);
· no match → returns root nav groups, narrowed by user
role (admin gate) and useSidebarConfig
(admin × user sidebar_modules overlay).
- components/app-sidebar.tsx
Now a thin presentation layer: reads { key, view, navGroups }
from useSidebarView() and orchestrates the view transition via
AnimatePresence + MOTION_VARIANTS.sidebarSlide (respects
prefers-reduced-motion). No logic, no role checks, no path
matching — those live in the hook.
- components/command-menu.tsx
Switched to the new getNavGroupsForPath() API; behavior preserved.
Cleanup
-------
- Deleted layout/context/workspace-context.tsx (zero consumers).
- Deleted layout/lib/workspace-registry.ts and its
workspace-registry.example.ts companion (over-abstracted: name/id
metadata, isInWorkspace / getAllWorkspaces / WORKSPACE_IDS were
registered but never read).
- Removed `workspaces` field from useSidebarData (never consumed
after the top-switcher was dropped).
- Dropped WorkspaceProvider from authenticated-layout.tsx.
- Trimmed dead `Manage and configure` translation key from all six
locale files and from static-keys.ts.
i18n
----
Added the `Back to Dashboard` key to en, zh, fr, ja, ru, vi, and
registered it in static-keys.ts under "Sidebar views".
Verification
------------
- bun run typecheck: passes
- Lint: no new warnings/errors on the touched files
- Adding a new drill-in workspace now only requires registering a
SidebarView in SIDEBAR_VIEWS — no changes to AppSidebar required.
* 🐛 fix(channel): evict auto-disabled multi-key channels from cache
Ensure multi-key channels are removed from the in-memory routing cache when all keys become auto-disabled, preventing subsequent requests from repeatedly selecting channels with no available keys.
Also make multi-key status updates more robust by handling missing key matches, checking actual enabled key availability, and restoring the channel status when a key is re-enabled. Add regression coverage for disabled cached channels and multi-key cache eviction.
Three layered optimizations targeting Gemini-style 5MB base64 payloads where
RSS could balloon to tens of GB under concurrent load:
1. Byte-based param override (relay/common/override.go)
- Switch legacy/operations hot paths from common.Marshal round-trips and
map[string]any conversions to gjson/sjson on []byte directly.
- Avoids cloning 5MB strings during each Set/Delete operation.
2. strings.Builder for Gemini response markdown (relay/channel/gemini/relay-gemini.go)
- Replace string concatenation + strings.Join when assembling
"" content for inline image responses.
- Pre-allocates capacity from inline_data byte sizes.
3. Outbound BodyStorage + streaming Decoder (this commit's core)
- New relay/common/outbound_body.go helper wraps marshaled upstream bodies
in common.BodyStorage, allowing disk-cache mode to offload jsonData to
a temp file while waiting for upstream TTFB. The original []byte can
then be GC'd, removing ~5MB/req of heap residency during the longest
window of a request.
- All 7 relay handlers (gemini/claude/responses/embedding/image/compatible/
rerank) plus chat_completions_via_responses adopt the helper with
defer closer.Close() and explicit jsonData = nil.
- relay/common/relay_info.go: new UpstreamRequestBodySize so
relay/channel/api_request.go can populate req.ContentLength (lost when
body becomes a type-erased io.Reader).
- common/gin.go UnmarshalBodyReusable: when storage is disk-backed and
content-type is JSON, decode via DecodeJson(storage) instead of
storage.Bytes()+Unmarshal, removing one transient 5MB copy per request.
memory mode and form/multipart paths unchanged.