Context:
Clicking a vendor tab triggered “setActivePage is not a function” from ModelsTabs.jsx:43.
Root cause:
ModelsTabs expects `setActivePage` via props (spread from `useModelsData`), but the hook did not expose it in its return object, so the prop resolved to `undefined`.
Fix:
Export `setActivePage` from `useModelsData`’s return object so `ModelsTabs` receives a valid function.
Result:
Tab switching now correctly resets pagination to page 1 and reloads models without runtime errors.
Files:
- web/src/hooks/models/useModelsData.jsx
Test plan:
- Open the Models page
- Click different vendor tabs
- Verify no crash occurs and the list reloads with page reset to 1
Refs: web/src/components/table/models/ModelsTabs.jsx:43
When syncing official models, clicking "Apply overwrite" with zero selected
conflict fields resulted in no request being sent and the modal simply closing.
This blocked creation of missing models/vendors even though the backend
supports an empty `overwrite` array and will still create missing items.
Changes:
- Remove the early-return guard in `UpstreamConflictModal.handleOk`
- Always call `onSubmit(payload)` even when `payload` is empty
- Keep closing behavior when the request succeeds
Behavior:
- Users can now proceed with upstream sync without selecting any conflict fields
- Missing models/vendors are created as expected
- Existing models are not overwritten unless fields are explicitly selected
Affected:
- web/src/components/table/models/modals/UpstreamConflictModal.jsx
Quality:
- Lint passes
- No breaking changes
- No visual/UI changes beyond the intended behavior
Test plan:
1) Open official models sync and trigger a conflicts preview
2) Click "Apply overwrite" without selecting any fields
3) Expect the sync to proceed and a success toast indicating created models
4) Re-try with some fields selected to confirm overwrites still work
Ensure UpstreamConflictModal submits { overwrite: payload, locale } instead of spreading an array into an object
Remove numeric-key fallback from applyUpstreamOverwrite for simpler and explicit logic
Effect: selected fields are now actually updated; success message shows updated model count
Refs: backend SyncUpstreamModels expects overwrite: overwriteField[]
Frontend (web)
- ModelsActions.jsx
- Replace “Sync Official” with “Sync” and open a new two-step SyncWizard.
- Pass selected locale through to preview, sync, and overwrite flows.
- Keep conflict resolution flow; inject locale into overwrite submission.
- New: models/modals/SyncWizardModal.jsx
- Two-step wizard: (1) method selection (config-sync disabled for now), (2) language selection (en/zh/ja).
- Horizontal, centered Radio cards; returns { option, locale } via onConfirm.
- UpstreamConflictModal.jsx
- Add search input (model fuzzy search) and native pagination.
- Column header checkbox now only applies to rows in the current filtered result.
- Fix “Cannot access ‘filteredDataSource’ before initialization”.
- Refactor with useMemo/useCallback; extract helpers to remove duplicated logic:
- getPresentRowsForField, getHeaderState, applyHeaderChange
- Minor code cleanups and stability improvements.
- i18n (en.json)
- Add strings for the sync wizard and related actions (Sync, Sync Wizard, Select method/source/language, etc.).
- Adjust minor translations.
Hooks
- useModelsData.jsx
- Extend previewUpstreamDiff, syncUpstream, applyUpstreamOverwrite to accept options with locale.
- Send locale via query/body accordingly.
Backend (Go)
- controller/model_sync.go
- Accept locale from query/body and resolve i18n upstream URLs.
- Add SYNC_UPSTREAM_BASE for upstream base override (default: https://basellm.github.io/llm-metadata).
- Make HTTP timeouts/retries/limits configurable:
- SYNC_HTTP_TIMEOUT_SECONDS, SYNC_HTTP_RETRY, SYNC_HTTP_MAX_MB
- Add ETag-based caching and support both envelope and pure array JSON formats.
- Concurrently fetch vendors and models; improve error responses with locale and source URLs.
- Include source meta (locale, models_url, vendors_url) in success payloads.
Notes
- No breaking changes expected.
- Lint passes for touched files.
- Set sidebar skeleton background to use theme variable (--semi-color-bg-0) instead of hardcoded white for better dark mode compatibility
- Apply consistent background to both collapsed and expanded skeleton states
- Ensure sidebar container uses theme background when skeleton is loading
- Remove duplicate margin-right classes from skeleton wrapper components that conflicted with CSS definitions
- Simplify navigation text structure by removing unnecessary div wrappers to improve text truncation
- Add proper text layout styles for better truncation handling when menu items have long names
- Standardize icon-to-text spacing across all sidebar navigation items
The Linux/Vite build failed with:
“Could not resolve "./headerbar" from "src/components/layout/PageLayout.jsx"”
On Linux and with stricter ESM/rollup resolution, directory index files (index.jsx)
may not be auto-resolved reliably. Explicitly importing the index file ensures
cross-platform stability.
Changes:
- Update PageLayout import from "./headerbar" to "./headerbar/index.jsx"
Impact:
- Fixes build on Linux
- No runtime behavior changes
Verification:
- Linter passes for web/src/components/layout/PageLayout.jsx
Notes:
- Prefer explicit index file imports (and extensions) to avoid platform differences.
Backend (controller/ratio_sync.go):
- Add built‑in official upstream to GetSyncableChannels (ID: -100, BaseURL: https://basellm.github.io)
- Support absolute endpoint URLs; otherwise join BaseURL + endpoint (defaults to /api/ratio_config)
- Harden HTTP client:
- IPv4‑first with IPv6 fallback for github.io
- Add ResponseHeaderTimeout
- 3 attempts with exponential backoff (200/400/800ms)
- Validate Content-Type and limit response body to 10MB (safe decode via io.LimitReader)
- Robust parsing: support type1 ratio_config map and type2 pricing list
- Use net.SplitHostPort for host parsing
- Use float tolerance in differences comparison to avoid false mismatches
- Remove unused code (tryDirect) and improve warnings
Frontend:
- UpstreamRatioSync.jsx: auto-assign official endpoint to /llm-metadata/api/newapi/ratio_config-v1-base.json
- ChannelSelectorModal.jsx:
- Pin the official source at the top of the list
- Show a green “官方” tag next to the status
- Refactor status renderer to accept the full record
Notes:
- Backward compatible; no API surface changes
- Official ratio_config reference: https://basellm.github.io/llm-metadata/api/newapi/ratio_config-v1-base.json
- Ran: bun run eslint:fix && bun run lint:fix
- Inserted AGPL license header via eslint-plugin-header
- Enforced no-multiple-empty-lines and other lint rules
- Formatted code using Prettier v3 (@so1ve/prettier-config)
- No functional changes; formatting-only baseline across JS/JSX files
- Replace copy button icon from semi-ui IconCopy to lucide-react Copy in PricingCardView
- Add conditional tooltip functionality to SelectableButtonGroup that only shows when text overflows
- Implement responsive table column behavior in PricingTableColumns with mobile-aware fixed positioning
- Use DOM-based overflow detection (scrollWidth vs clientWidth) for better performance
- Apply useIsMobile hook to conditionally set fixed: 'right' only on desktop devices
These changes improve user experience across different screen sizes and provide more consistent iconography throughout the pricing interface.
This commit updates the default pagination settings across the model pricing
components to improve user experience by reducing the need for frequent
page navigation when browsing large model catalogs.
Changes made:
- Update initial pageSize state from 10 to 100 in useModelPricingData hook
- Set defaultPageSize to 100 in PricingTable pagination configuration
- Increase default skeletonCount from 10 to 100 in PricingCardSkeleton
Files modified:
- web/src/hooks/model-pricing/useModelPricingData.jsx
- web/src/components/table/model-pricing/view/table/PricingTable.jsx
- web/src/components/table/model-pricing/view/card/PricingCardSkeleton.jsx
This change affects both card and table view modes of the model pricing page,
ensuring consistent pagination behavior across different display formats.
This commit introduces a comprehensive responsive design system for the SelectableButtonGroup component that adapts to container width changes, particularly optimized for dynamic sidebar layouts.
## Key Features
### 1. Container Width Detection
- Added `useContainerWidth` hook using ResizeObserver API
- Real-time container width monitoring for responsive calculations
- Automatic layout adjustments based on available space
### 2. Intelligent Column Layout
Implements a 4-tier responsive system:
- **≤280px**: 1 column + tags (mobile portrait)
- **281-380px**: 2 columns + tags (narrow screens)
- **381-460px**: 3 columns - tags (general case, prioritizes readability)
- **>460px**: 3 columns + tags (wide screens, full feature display)
### 3. Dynamic Tag Visibility
- Tags automatically hide in medium-width containers (381-460px) to improve text readability
- Tags show in narrow and wide containers where space allows for optimal UX
- Responsive threshold ensures content clarity across all viewport sizes
### 4. Adaptive Grid Spacing
- Compact spacing `[4,4]` for containers ≤400px
- Standard spacing `[6,6]` for larger containers
- Additional `.sbg-compact` CSS class for fine-tuned styling in narrow layouts
### 5. Sidebar Integration
- Perfectly compatible with dynamic sidebar width: `clamp(280px, 24vw, 520px)`
- Automatically adjusts as sidebar scales with viewport changes
- Maintains optimal button density and information display at all sizes
## Technical Implementation
- **Hook**: `useContainerWidth.js` - ResizeObserver-based width detection
- **Component**: Enhanced `SelectableButtonGroup.jsx` with responsive logic
- **Styling**: Added `.sbg-compact` mode in `index.css`
- **Performance**: Efficient span calculation using `Math.floor(24 / perRow)`
## Benefits
- Improved UX across all screen sizes and sidebar configurations
- Better text readability through intelligent tag hiding
- Seamless integration with existing responsive sidebar system
- Maintains component functionality while optimizing space utilization
Closes: Responsive design implementation for model marketplace sidebar components
- Add `filter={selectFilter}` to enable search filtering in group Select component
- Add `autoClearSearchValue={false}` to preserve search input value
- Maintain consistency with existing model selector search behavior
- Improve user experience by allowing quick filtering of group options
Files modified:
- web/src/components/playground/SettingsPanel.jsx
- Update error-state rendering to use white text in the playground chat
- Remove Typography.Text `type="danger"` and the red background for consistency with official behavior
- Preserve layout and other message states (loading/success/system) unchanged
- No linter issues introduced
Files touched:
- web/src/components/playground/MessageContent.jsx
- Set TopUp page outer wrapper to "w-full max-w-7xl mx-auto px-2"
to match PersonalSetting and ensure consistent layout width and padding.
- No functional changes; UI-only adjustment.
- Lint checks passed.
- Verified that pages/TopUp only re-exports the component (no extra wrapper).
Affected: web/src/components/topup/index.jsx
- Switch Semi UI Timeline to mode="left" in:
- web/src/components/layout/NoticeModal.jsx
- web/src/components/dashboard/AnnouncementsPanel.jsx
- Show both relative and absolute time in the `time` prop (e.g. "3 days ago 2025-02-18 10:30")
- Move auxiliary description to the `extra` prop and remove duplicate rendering from content area
- Keep original `extra` data intact; compute and pass:
- `time`: absolute time (yyyy-MM-dd HH:mm)
- `relative`: relative time (e.g., "3 days ago")
- Update data assembly to expose `time` and `relative` without overwriting `extra`:
- web/src/components/dashboard/index.jsx
- No i18n changes; no linter errors introduced
Why: Aligns Timeline layout across the app and clarifies time context by combining relative and absolute timestamps while preserving auxiliary notes via `extra`.
- Add mobile filter-button placeholder in skeleton when `isMobile` is true
- Plumb `isMobile` from `PricingVendorIntroWithSkeleton` to `PricingVendorIntroSkeleton`
- Rename skeleton key from 'button' to 'copy-button' for consistency
- Neutralize copy-button skeleton color to match input (use neutral palette)
- Keep “Total x models” tag inline with title on mobile; wrap only when space is insufficient
- Mirror the same title+tag layout in the skeleton (flex-row flex-wrap items-center)
- No linter errors introduced
Affected files:
- web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx
- web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx
- web/src/components/table/model-pricing/layout/header/PricingVendorIntroWithSkeleton.jsx
- Re-introduce and forward `sidebarProps` from PricingTopSection to PricingFilterModal
- Fix TypeError: “Cannot destructure property 'showWithRecharge' of 'sidebarProps' as it is undefined”
- Keep modal state managed at top section; no other behavioral changes
- Lint passes
Files touched:
- web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx