- utils.jsx: Replace input with textarea in copy function to preserve line breaks in multi-line content, preventing formatting loss mentioned in #1828
- api.js: Fix duplicate 'group' property in buildApiPayload to resolve syntax issues
- MarkdownRenderer.jsx: Refactor code text extraction using textContent for accurate copying
Closes#1828
Signed-off-by: Zhaokun Zhang <zhaokunzhang@Zhaokuns-Air.lan>
- 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
- Rename React components/pages/utilities that contain JSX to `.jsx` across `web/src`
- Update import paths and re-exports to match new `.jsx` extensions
- Fix Vite entry by switching `web/index.html` from `/src/index.js` to `/src/index.jsx`
- Verified remaining `.js` files are plain JS (hooks/helpers/constants) and do not require JSX
- No runtime behavior changes; extension and reference alignment only
Context: Resolves the Vite pre-transform error caused by the stale `/src/index.js` entry after migrating to `.jsx`.
- Add new Forbidden page at /forbidden (`web/src/pages/Forbidden/index.js`)
- Use Semi-UI Empty with IllustrationNoAccess (250x250)
- Update i18n description to: '您无权访问此页面,请联系管理员~'
- Align visual style with existing 404 page
- Introduce `AdminRoute` in `web/src/helpers/auth.js`
- Use `UserContext`/localStorage; redirect to `/forbidden` when `!user` or `user.role < 10`
- Protect console/admin routes with `AdminRoute` and register `/forbidden` in `web/src/App.js`
- Update `web/src/i18n/locales/en.json`
- Add English translation for the new forbidden message
- Remove legacy "没有权限" entry
- Lint passes; no runtime errors observed
Summary
-------
1. **Backend**
• `controller/model_meta.go`
– For prefix/suffix/contains rules, aggregate endpoints, bound channels, enable groups, and quota types across all matched models.
– When mixed billing types are detected, return `quota_type = -1` (unknown) instead of defaulting to volume-based.
2. **Frontend**
• `web/src/helpers/utils.js`
– `calculateModelPrice` now handles `quota_type = -1`, returning placeholder `'-'`.
• `web/src/components/table/model-pricing/view/card/PricingCardView.jsx`
– Billing tag logic updated: displays “按次计费” (times), “按量计费” (volume), or `'-'` for unknown.
• `web/src/components/table/model-pricing/view/table/PricingTableColumns.js`
– `renderQuotaType` shows “未知” for unknown billing type.
• `web/src/components/table/models/ModelsColumnDefs.js`
– Unified `renderQuotaType` to return `'-'` when type is unknown.
• `web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
– Group price table honors unknown billing type; pricing columns show `'-'` and neutral tag color.
3. **Utilities**
• Added safe fallback colours/tags for unknown billing type across affected components.
Impact
------
• Ensures correct data aggregation for non-exact model matches.
• Prevents UI from implying volume billing when actual type is ambiguous.
• Provides consistent placeholder display (`'-'` or “未知”) across cards, tables and modals.
No breaking API changes; frontend gracefully handles legacy values.
Summary:
• Updated `helpers/utils.js` to display the “group” label without a colon, ensuring consistent typography with other price elements.
Details:
1. `formatPriceInfo`
– Changed `{t('分组')}:` to `{t('分组')}` for a cleaner look.
– Keeps spacing intact between label and selected group name.
2. No functional impact; purely visual polish.
Overview:
• Introduced a new “Model Tag” filter across pricing screens
• Refactored `usePricingFilterCounts` to eliminate duplicated logic
• Improved tag handling to be case-insensitive and deduplicated
• Extended utilities to reset & persist the new filter
Details:
1. Added `filterTag` state to `useModelPricingData` and integrated it into all filtering paths.
2. Created reusable `PricingTags` component using `SelectableButtonGroup`.
3. Incorporated tag filter into `PricingSidebar` and mobile `PricingFilterModal`, including reset support.
4. Enhanced `resetPricingFilters` (helpers/utils) to restore tag filter defaults.
5. Refactored `usePricingFilterCounts.js`:
• Centralized predicate `matchesFilters` to remove redundancy
• Normalized tag parsing via `normalizeTags` helper
• Memoized model subsets with concise filter calls
6. Updated lints – zero errors after refactor.
Result:
Users can now filter models by custom tags with consistent UX, and internal logic is cleaner, faster, and easier to extend.
- **Backend Changes:**
- Refactor pricing API to return separate vendors array with ID-based model references
- Remove redundant vendor_name/vendor_icon fields from pricing records, use vendor_id only
- Add vendor_description to pricing response for frontend display
- Maintain 1-minute cache protection for pricing endpoint security
- **Frontend Data Flow:**
- Update useModelPricingData hook to build vendorsMap from API response
- Enhance model records with vendor info during data processing
- Pass vendorsMap through component hierarchy for consistent vendor data access
- **UI Component Replacements:**
- Replace PricingCategories with PricingVendors component for vendor-based filtering
- Replace PricingCategoryIntro with PricingVendorIntro in header section
- Remove all model category related components and logic
- **Header Improvements:**
- Implement vendor intro with real backend data (name, icon, description)
- Add text collapsible feature (2-line limit with expand/collapse functionality)
- Support carousel animation for "All Vendors" view with vendor icon rotation
- **Model Detail Modal Enhancements:**
- Update ModelHeader to use real vendor icons via getLobeHubIcon()
- Move tags from header to ModelBasicInfo content area to avoid SideSheet title width constraints
- Display only custom tags from backend with stringToColor() for consistent styling
- Use Space component with wrap property for proper tag layout
- **Table View Optimizations:**
- Integrate RenderUtils for description and tags columns
- Implement renderLimitedItems for tags (max 3 visible, +x popover for overflow)
- Use renderDescription for text truncation with tooltip support
- **Filter Logic Updates:**
- Vendor filter shows disabled options instead of hiding when no models match
- Include "Unknown Vendor" category for models without vendor information
- Remove all hardcoded vendor descriptions, use real backend data
- **Code Quality:**
- Fix import paths after component relocation
- Remove unused model category utilities and hardcoded mappings
- Ensure consistent vendor data usage across all pricing views
- Maintain backward compatibility with existing pricing calculation logic
This refactor provides a more scalable vendor-based architecture while eliminating
data redundancy and improving user experience with real-time backend data integration.
• Removed obsolete `sidebarIconColors` map and `getItemColor` util from
SiderBar/render; all selected states now use the single CSS variable
`--semi-color-primary` for both text and icons.
• Simplified `getLucideIcon`:
– Added `Package` to Lucide imports.
– Switched “models” case to `<Package />`, avoiding duplication with
the Layers glyph.
– Replaced per-key color logic with `iconColor` derived from the new
uniform highlight color.
• Stripped any unused imports / dead code paths after the refactor.
• Lint passes; sidebar hover/focus behavior unchanged while visual
consistency is improved.
Backend
• Add `model/model_meta.go` and `model/vendor_meta.go` defining Model & Vendor entities with CRUD helpers, soft-delete and time stamps
• Create corresponding controllers `controller/model_meta.go`, `controller/vendor_meta.go` and register routes in `router/api-router.go`
• Auto-migrate new tables in DB startup logic
Frontend
• Build complete “Model Management” module under `/console/models`
- New pages, tables, filters, actions, hooks (`useModelsData`) and dynamic vendor tabs
- Modals `EditModelModal.jsx` & unified `EditVendorModal.jsx`; latter now uses default confirm/cancel footer and mobile-friendly modal sizing (`full-width` / `small`) via `useIsMobile`
• Update sidebar (`SiderBar.js`) and routing (`App.js`) to surface the feature
• Add helper updates (`render.js`) incl. `stringToColor`, dynamic LobeHub icon retrieval, and tag color palettes
Table UX improvements
• Replace separate status column with inline Enable / Disable buttons in operation column (matching channel table style)
• Limit visible tags to max 3; overflow represented as “+x” tag with padded `Popover` showing remaining tags
• Color all tags deterministically using `stringToColor` for consistent theming
• Change vendor column tag color to white for better contrast
Misc
• Minor layout tweaks, compact-mode toggle relocation, lint fixes and TypeScript/ESLint clean-up
These changes collectively deliver end-to-end model & vendor administration while unifying visual language across management tables.
Summary
• Introduced a unified `selectFilter` helper that matches both `option.value` and `option.label`, ensuring all `<Select>` components support intuitive search (fixes channel “type” dropdown not filtering).
• Replaced all usages of the old `modelSelectFilter` with `selectFilter` in:
• `EditChannelModal.jsx`
• `SettingsPanel.js`
• `EditTokenModal.jsx`
• `EditTagModal.jsx`
• Removed the deprecated `modelSelectFilter` export from `utils.js` (no backward-compat alias).
• Updated documentation comments accordingly.
Why
The old filter only inspected `option.value`, causing searches to fail when `label` carried the meaningful text (e.g., numeric IDs for channel types). The new helper searches both fields, covering all scenarios and unifying the API across the codebase.
Notes
No functional regressions expected; all components have been migrated.
- Extract default values to DEFAULT_PRICING_FILTERS constant for centralized configuration
- Replace verbose type checks with optional chaining operator (?.) for cleaner code
- Eliminate redundant function type validations and comments
- Reduce code lines by ~50% (from 60 to 25 lines) while maintaining full functionality
- Improve code readability and follow modern JavaScript best practices
This refactoring enhances code quality without changing the function's behavior,
making it easier to maintain and modify default filter values in the future.
- Extract default values to DEFAULT_PRICING_FILTERS constant for centralized configuration
- Replace verbose type checks with optional chaining operator (?.) for cleaner code
- Eliminate redundant function type validations and comments
- Reduce code lines by ~50% (from 60 to 25 lines) while maintaining full functionality
- Improve code readability and follow modern JavaScript best practices
This refactoring enhances code quality without changing the function's behavior,
making it easier to maintain and modify default filter values in the future.
- Remove K/M switch from model price column header in pricing table
- Add "Display in K units" option to pricing display settings panel
- Update parameter passing for tokenUnit and setTokenUnit across components:
- PricingDisplaySettings: Add tokenUnit toggle functionality
- PricingSidebar: Pass tokenUnit props to display settings
- PricingFilterModal: Include tokenUnit in mobile filter modal
- Enhance resetPricingFilters utility to reset token unit to default 'M'
- Clean up PricingTableColumns by removing unused setTokenUnit parameter
- Add English translation for "按K显示单位" as "Display in K units"
This change improves UX by consolidating all display-related controls
in the filter settings panel, making the interface more organized and
the token unit setting more discoverable alongside other display options.
Affected components:
- PricingTableColumns.js
- PricingDisplaySettings.jsx
- PricingSidebar.jsx
- PricingFilterModal.jsx
- PricingTable.jsx
- utils.js (resetPricingFilters)
- en.json (translations)
- Create PricingEndpointTypes.jsx component for endpoint type filtering
- Add filterEndpointType state management in useModelPricingData hook
- Integrate endpoint type filtering logic in filteredModels computation
- Update PricingSidebar.jsx to include endpoint type filter component
- Update PricingFilterModal.jsx to support endpoint type filtering on mobile
- Extend resetPricingFilters utility function to include endpoint type reset
- Support filtering models by endpoint types (OpenAI, Anthropic, Gemini, etc.)
- Display model count for each endpoint type with localized labels
- Ensure filter state resets to first page when endpoint type changes
This enhancement allows users to filter models by their supported endpoint types,
providing more granular control over model selection in the pricing interface.
- Increase skeleton card count from 6 to 10 for better visual coverage
- Extend minimum skeleton display duration from 500ms to 1000ms for smoother UX
- Add circle shape to all pricing tags for consistent rounded design
- Apply circle styling to billing type, popularity, endpoint, and context tags
This commit improves the visual consistency and user experience of the pricing
card view by standardizing tag appearance and optimizing skeleton loading timing.
Centralize filter-reset logic to improve maintainability and consistency.
- Add `resetPricingFilters` helper to `web/src/helpers/utils.js`, encapsulating all reset actions (search, category, currency, ratio, group, quota type, etc.).
- Update `PricingFilterModal.jsx` and `PricingSidebar.jsx` to import and use the new utility instead of keeping their own duplicate `handleResetFilters`.
- Removes repeated code, ensures future changes to reset behavior require modification in only one place, and keeps components lean.
- Add left-right pagination layout for desktop (total info on left, controls on right)
- Keep mobile layout centered with pagination controls only
- Implement proper i18n support for pagination text using react-i18next
- Add pagination translations for Chinese and English
- Standardize t function usage across all table components to use xxxData.t pattern
- Update CardPro footer layout to support justify-between on desktop
- Use CSS variable --semi-color-text-2 for consistent text styling
- Disable built-in Pagination showTotal to avoid duplication
Components updated:
- CardPro: Enhanced footer layout with responsive design
- createCardProPagination: Added i18n support and custom total text
- All table components: Unified t function usage pattern
- i18n files: Added pagination-related translations
The pagination now displays "Showing X to Y of Z items" on desktop
and maintains existing centered layout on mobile devices.
## Overview
Refactored the monolithic dashboard page (~1200 lines) into a modular architecture following the project's global layout pattern. The main `Detail/index.js` is now simplified to match other page entry files like `Midjourney/index.js`.
## Changes Made
### 🏗️ Architecture Changes
- **Before**: Single large file `pages/Detail/index.js` containing all dashboard logic
- **After**: Modular structure with dedicated hooks, components, and helpers
### 📁 New Files Created
- `hooks/dashboard/useDashboardData.js` - Core data management and API calls
- `hooks/dashboard/useDashboardStats.js` - Statistics computation and memoization
- `hooks/dashboard/useDashboardCharts.js` - Chart specifications and data processing
- `constants/dashboard.constants.js` - UI config, time options, and chart defaults
- `helpers/dashboard.js` - Utility functions for data processing and UI helpers
- `components/dashboard/index.jsx` - Main dashboard component integrating all modules
- `components/dashboard/modals/SearchModal.jsx` - Search modal component
### 🔧 Updated Files
- `constants/index.js` - Added dashboard constants export
- `helpers/index.js` - Added dashboard helpers export
- `pages/Detail/index.js` - Simplified to minimal wrapper (~20 lines)
### 🐛 Bug Fixes
- Fixed SearchModal DatePicker onChange to properly convert Date objects to timestamp strings
- Added missing localStorage update for `data_export_default_time` persistence
- Corrected data flow between search confirmation and chart updates
- Ensured proper chart data refresh after search parameter changes
### ✨ Key Improvements
- **Separation of Concerns**: Data, stats, and charts logic isolated into dedicated hooks
- **Reusability**: Components and hooks can be easily reused across the application
- **Maintainability**: Smaller, focused files easier to understand and modify
- **Consistency**: Follows established project patterns for global folder organization
- **Performance**: Proper memoization and callback optimization maintained
### 🎯 Functional Verification
- ✅ All dashboard panels (model analysis, resource consumption, performance metrics) update correctly
- ✅ Search functionality works with proper parameter validation
- ✅ Chart data refreshes properly after search/filter operations
- ✅ User interface remains identical to original implementation
- ✅ All existing features preserved without regression
### 🔄 Data Flow
```
User Input → SearchModal → useDashboardData → API Call → useDashboardCharts → UI Update
```
## Breaking Changes
None. All existing functionality preserved.
## Migration Notes
The refactored dashboard maintains 100% API compatibility and identical user experience while providing a cleaner, more maintainable codebase structure.
Fix "Rendered fewer hooks than expected" error caused by conditional hook calls
in createCardProPagination function. The issue occurred when paginationArea was
commented out, breaking React's hooks rules.
**Problem:**
- createCardProPagination() internally called useIsMobile() hook
- When paginationArea was disabled, the hook was not called
- This violated React's rule that hooks must be called in the same order on every render
**Solution:**
- Refactor createCardProPagination to accept isMobile as a parameter
- Move useIsMobile() hook calls to component level
- Ensure consistent hook call order regardless of pagination usage
**Changes:**
- Update createCardProPagination function to accept isMobile parameter
- Add useIsMobile hook calls to all table components
- Pass isMobile parameter to createCardProPagination in all usage locations
**Files modified:**
- web/src/helpers/utils.js
- web/src/components/table/channels/index.jsx
- web/src/components/table/redemptions/index.jsx
- web/src/components/table/usage-logs/index.jsx
- web/src/components/table/tokens/index.jsx
- web/src/components/table/users/index.jsx
- web/src/components/table/mj-logs/index.jsx
- web/src/components/table/task-logs/index.jsx
Fixes critical runtime error and ensures stable pagination behavior across all table components.
This patch standardises how all “model” (and related) `<Select>` components handle searching.
Highlights
• Added a shared helper `modelSelectFilter` to `helpers/utils.js` – performs case-insensitive value-based matching, independent of ReactNode labels.
• Removed the temporary `helpers/selectFilter.js`; all components now import from the core helpers barrel.
• Updated Selects to use the new filter *and* set `autoClearSearchValue={false}` so the query text is preserved after a choice is made.
Affected components
• Channel editor (EditChannelModal) – channel type & model lists
• Tag editor (EditTagModal) – model list
• Token editor (EditTokenModal) – model-limit list
• Playground SettingsPanel – model selector
Benefits
✓ Consistent search behaviour across the app
✓ Better user experience when selecting multiple items
✓ Cleaner codebase with one canonical filter implementation
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
Summary
-------
1. Introduced a reusable `toBoolean` utility (`web/src/helpers/boolean.js`) that converts
strings (`'true'/'false'`, `'1'/'0'`), numbers, and native booleans to a proper boolean.
2. Re-exported `toBoolean` via `web/src/helpers/index.js` for simple one-line imports.
Refactors
---------
• Systematically replaced all legacy `item.value === 'true'` checks with `toBoolean(item.value)` in
the following components:
– `SystemSetting.js`
– `OperationSetting.js`
– `PaymentSetting.js`
– `RatioSetting.js`
– `RateLimitSetting.js`
– `ModelSetting.js`
– `DrawingSetting.js`
– `DashboardSetting.js`
– `ChatsSetting.js`
• Unified import statements to
`import { …, toBoolean } from '../../helpers';`
removing redundant `../../helpers/boolean` paths.
Why
---
SQLite sometimes returns `1/0` or boolean literals instead of the string `'true'/'false'`, causing
checkbox states to reset on page reload. The new utility guarantees consistent boolean parsing,
fixing the issue across all environments (SQLite, MySQL, etc.) while improving code clarity.