Skip to content

🤓feat: the model management module - #1452

Merged
t0ng7u merged 64 commits into
alphafrom
refactor/model-pricing
Aug 7, 2025
Merged

🤓feat: the model management module#1452
t0ng7u merged 64 commits into
alphafrom
refactor/model-pricing

Conversation

@t0ng7u

@t0ng7u t0ng7u commented Jul 26, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added comprehensive model and vendor management with UI for creating, editing, deleting models, vendors, and prefill groups.
    • Launched advanced model pricing page featuring multi-dimensional filters (group, quota type, endpoint type, vendor), card/table views, and detailed model info modals.
    • Introduced reusable UI components including selectable button groups, JSON editor, skeleton loaders, and selection notifications.
    • Added admin-only "Model Management" menu item and route.
    • Extended backend APIs with full CRUD support for models, vendors, prefill groups, and exposed vendor and endpoint metadata.
  • Enhancements

    • Improved loading skeleton timing across components for smoother UX.
    • Enriched pricing data with vendor details, model descriptions, tags, and enhanced endpoint support.
    • Implemented responsive, mobile-friendly layouts for pricing and model management interfaces.
    • Unified icon color handling and updated translations in navigation and UI.
  • Bug Fixes

    • Fixed footer visibility conditions on pricing pages.
  • Chores

    • Added and refined CSS styles for new layouts and scrollbar behavior.
    • Updated internal data models, API routes, and hooks to support new features and improve performance.

t0ng7u added 25 commits July 23, 2025 01:58
* Re-architected model-pricing page into modular components:
  * PricingPage / PricingSidebar / PricingContent
  * Removed obsolete `ModelPricing*` components and column defs
* Introduced reusable `SelectableButtonGroup` in `common/ui`
  * Supports Row/Col grid (3 per row)
  * Optional collapsible mode with gradient mask & toggle
* Rebuilt filter panels with the new button-group:
  * Model categories, token groups, and quota types
  * Added dynamic `tagCount` badges to display item totals
* Extended `useModelPricingData` hook
  * Added `filterGroup` and `filterQuotaType` state and logic
* Updated PricingTable columns & sidebar reset logic to respect new states
* Ensured backward compatibility via re-export in `index.jsx`
* Polished styling, icons and i18n keys
…oup, fixed column, scroll tweaks (#1365)

• SelectableButtonGroup
  • Added optional collapsible support with gradient mask & toggle
  • Dynamic tagCount badge support for groups / quota types
  • Switched to responsive Row/Col (`xs 24`, `sm 24`, `lg 12`, `xl 8`) for fluid layout
  • Shows expand button only when item count exceeds visible rows

• Sidebar filters
  • PricingGroups & PricingQuotaTypes now pass tag counts to button-group
  • Counts derived from current models & quota_type

• PricingTableColumns
  • Moved “Availability” column to far right; fixed via `fixed: 'right'`
  • Re-ordered columns and preserved ratio / price logic

• PricingTable
  • Added `compactMode` prop; strips fixed columns and sets `scroll={compactMode ? undefined : { x: 'max-content' }}`
  • Processes columns to remove `fixed` in compact mode

• PricingPage & index.css
  • Added `.pricing-scroll-hide` utility to hide Y-axis scrollbar for `Sider` & `Content`

• Responsive / style refinements
  • Sidebar width adjusted to 460px
  • Scrollbars hidden uniformly across pricing modules

These changes complete the model-pricing UI refactor, ensuring clean scrolling, responsive filters, and fixed availability column for better usability.
…1365)

* Added `position: sticky; top: 0; z-index: 5;` to search bar container
  – keeps the bar fixed while the table body scrolls
* Preserves previous padding, border and background styles
* Improves usability by ensuring quick access to search & actions during long list navigation

• PricingTable
  • Added `compactMode` prop; strips fixed columns and sets `scroll={compactMode ? undefined : { x: 'max-content' }}`
  • Processes columns to remove `fixed` in compact mode

• PricingPage & index.css
  • Added `.pricing-scroll-hide` utility to hide Y-axis scrollbar for `Sider` & `Content`

• Responsive / style refinements
  • Sidebar width adjusted to 460px
  • Scrollbars hidden uniformly across pricing modules

These changes complete the model-pricing UI refactor, ensuring clean scrolling, responsive filters, and fixed availability column for better usability.
…e layout (#1365)

* **PricingDisplaySettings.jsx**
  • Extracted display settings (recharge price, currency, ratio toggle) from PricingSidebar
  • Maintains complete styling and functionality as standalone component

* **SelectableButtonGroup.jsx**
  • Added isMobile detection with conditional Col spans
  • Mobile: `span={12}` (2 buttons per row) for better touch experience
  • Desktop: preserved responsive grid `xs={24} sm={24} md={24} lg={12} xl={8}`

* **PricingSidebar.jsx**
  • Updated imports to use new PricingDisplaySettings component
  • Simplified component structure while preserving reset logic

These changes enhance code modularity and provide optimized mobile UX for filter button groups across the pricing interface.
… eliminate duplication (#1365)

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.
…eColumns (#1365)

Summary
• Swapped out the old availability UI for clearer icon-based feedback.
• Users now see a green check icon when their group can use a model and a red × icon (with tooltip) when it cannot.

Details
1. Imports
   • Removed deprecated `IconVerify`.
   • Added `IconCheckCircleStroked` ✅ and `IconClose` ❌ for new states.

2. Availability column
   • `renderAvailable` now
     – Shows a green `IconCheckCircleStroked` inside a popover (“Your group can use this model”).
     – Shows a red `IconClose` inside a popover (“你的分组无权使用该模型”) when the model is inaccessible.
     – Eliminates the empty cell/grey tag fallback.

3. Group tag
   • Updated selected-group tag to use `IconCheckCircleStroked` for visual consistency.

Result
Improves UX by providing explicit visual cues for model availability and removes ambiguous blank cells.
…tor pricing display settings (#1365)

- Add withCheckbox prop to SelectableButtonGroup component for checkbox-prefixed buttons
- Support both single value and array activeValue for multi-selection scenarios
- Refactor PricingDisplaySettings to use consistent SelectableButtonGroup styling
- Replace Switch components with checkbox-enabled SelectableButtonGroup
- Replace Select dropdown with SelectableButtonGroup for currency selection
- Maintain unified UI/UX across all pricing filter components
- Add proper JSDoc documentation for new withCheckbox functionality

This improves visual consistency and provides a more cohesive user experience
in the model pricing filter interface.
…nent (#1365)

Add comprehensive loading state support with skeleton animations for the SelectableButtonGroup component, improving user experience during data loading.

Key Changes:
- Add loading prop to SelectableButtonGroup with minimum 500ms display duration
- Implement skeleton buttons with proper Semi-UI Skeleton wrapper and active animation
- Use fixed skeleton count (6 items) to prevent visual jumping during load transitions
- Pass loading state through all pricing filter components hierarchy:
  - PricingSidebar and PricingFilterModal as container components
  - PricingDisplaySettings, PricingCategories, PricingGroups, PricingQuotaTypes as filter components

Technical Details:
- Reference CardTable.js implementation for consistent skeleton UI patterns
- Add useEffect hook for 500ms minimum loading duration control
- Support both checkbox and regular button skeleton modes
- Maintain responsive layout compatibility (mobile/desktop)
- Add proper JSDoc parameter documentation for loading prop

Fixes:
- Prevent skeleton count sudden changes that caused visual discontinuity
- Ensure proper skeleton animation with Semi-UI active parameter
- Maintain consistent loading experience across all filter components
)

Filter out the special empty string group ("": "用户分组") from the
usable groups in PricingGroups component. This empty group represents
"user's current group" but contains no data and should not be displayed
in the group filter options.

- Add filter condition to exclude empty string keys from usableGroup
- Prevents displaying invalid empty group option in UI
- Improves user experience by showing only valid selectable groups
…ure (#1365)

- Replace model count with group ratio display (x2.2, x1) in group filter
- Remove redundant "Available Groups" column from pricing table
- Remove "Availability" column and related logic completely
- Move "Supported Endpoint Types" column to fixed right position
- Clean up unused parameters and variables in PricingTableColumns.js
- Optimize variable declarations (let → const) and simplify render logic
- Improve code readability and reduce memory allocations

This refactor enhances user experience by:
- Providing clearer group ratio information in filters
- Simplifying table layout while maintaining essential functionality
- Improving performance through better code organization

Breaking changes: None
#1365)

- 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.
- 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.
- 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)
…maintainability (#1365)

- 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.
…maintainability (#1365)

- 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.
Optimize grid column breakpoints to account for 460px sidebar width:
- Change from sm:grid-cols-2 lg:grid-cols-3 to xl:grid-cols-2 2xl:grid-cols-3
- Ensures adequate space for card display after subtracting sidebar width
- Improves layout on medium-sized screens where previous breakpoints caused cramped display

Breakpoint calculation:
- 1280px screen - 460px sidebar = 820px → 2 columns
- 1536px screen - 460px sidebar = 1076px → 3 columns
…stency

- **Fix SideSheet double-click issue**: Remove early return for null modelData to prevent rendering blockage during async state updates
- **Component modularization**:
  - Split ModelDetailSideSheet into focused sub-components (ModelHeader, ModelBasicInfo, ModelEndpoints, ModelPricingTable)
  - Refactor PricingFilterModal with FilterModalContent and FilterModalFooter components
  - Remove unnecessary FilterSection wrapper for cleaner interface
- **Improve visual consistency**:
  - Unify avatar/icon logic between ModelHeader and PricingCardView components
  - Standardize tag colors across all pricing components (violet/teal for billing types)
  - Apply consistent dashed border styling using Semi UI theme colors
- **Enhance data accuracy**:
  - Display raw endpoint type names (e.g., "openai", "anthropic") instead of translated descriptions
  - Remove text alignment classes for better responsive layout
  - Add proper null checks to prevent runtime errors
- **Code quality improvements**:
  - Reduce component complexity by 52-74% through modularization
  - Improve maintainability with single responsibility principle
  - Add comprehensive error handling for edge cases

This refactoring improves component reusability, reduces bundle size, and provides a more consistent user experience across the model pricing interface.
This commit introduces a unified, maintainable solution for all model-pricing filter buttons and removes redundant code.

Key points
• Added `usePricingFilterCounts` hook
  - Centralises filtering logic and returns:
    - `quotaTypeModels`, `endpointTypeModels`, `dynamicCategoryCounts`, `groupCountModels`
  - Keeps internal helpers private (removed public `modelsAfterCategory`).

• Updated components to consume the new hook
  - `PricingSidebar.jsx`
  - `FilterModalContent.jsx`

• Improved button UI/UX
  - `SelectableButtonGroup.jsx` now respects `item.disabled` and auto-disables when `tagCount === 0`.
  - `PricingGroups.jsx` counts models per group (after all other filters) and disables groups with zero matches.
  - `PricingEndpointTypes.jsx` enumerates all endpoint types, computes filtered counts, and disables entries with zero matches.

• Removed obsolete / duplicate calculations and comments to keep components lean.

The result is consistent, real-time tag counts across all filter groups, automatic disabling of unavailable options, and a single source of truth for filter computations, making future extensions straightforward.
@coderabbitai

coderabbitai Bot commented Jul 26, 2025

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@t0ng7u has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 473f3b6 and d96f846.

📒 Files selected for processing (1)
  • web/src/components/common/ui/JSONEditor.js (1 hunks)

Walkthrough

This update introduces a comprehensive model and vendor management system to both the frontend and backend. On the backend, new RESTful APIs and database models are added for managing models, vendors, and prefill groups, along with associated metadata and advanced querying. The frontend implements a full-featured UI for model and vendor management, model pricing, filtering, editing, and batch operations, including reusable UI components and hooks. Supporting utilities, hooks, and styles are also added or refactored for consistency and maintainability.

Changes

Cohort / File(s) Change Summary
Backend: Model & Vendor Management APIs
controller/model_meta.go, controller/vendor_meta.go, controller/prefill_group.go, controller/missing_models.go, model/model_meta.go, model/vendor_meta.go, model/prefill_group.go, model/missing_models.go, model/model_extra.go, model/pricing_refresh.go
Introduces CRUD HTTP handlers and database models for models, vendors, and prefill groups. Adds advanced querying, duplicate checks, and metadata enrichment. Implements missing models detection and pricing refresh utilities.
Backend: Pricing & Endpoint Enhancements
model/pricing.go, controller/pricing.go, common/endpoint_defaults.go
Extends pricing data with vendor and endpoint metadata, enriches pricing cache and endpoint info, and exposes new API fields.
Backend: Migration & Routing
model/main.go, router/api-router.go
Adds new models to DB migrations and registers new API routes for models, vendors, and prefill groups.
Frontend: Model & Vendor Management UI
web/src/components/table/models/*, web/src/components/common/ui/RenderUtils.jsx, web/src/components/table/models/modals/*, web/src/hooks/models/useModelsData.js, web/src/pages/Model/index.js
Implements model/vendor management UI: tables, tabs, filters, batch actions, modals for editing, missing models, prefill groups, and selection notifications. Adds reusable render utilities.
Frontend: Model Pricing Redesign
web/src/components/table/model-pricing/*, web/src/helpers/utils.js, web/src/hooks/model-pricing/useModelPricingData.js, web/src/hooks/model-pricing/usePricingFilterCounts.js
Refactors model pricing to support multi-dimensional filtering (group, vendor, endpoint, quota), new layout, card/table views, modal details, and vendor info. Adds new hooks and utilities for filtering and price formatting.
Frontend: Common UI & Utilities
web/src/components/common/ui/SelectableButtonGroup.jsx, web/src/components/common/ui/JSONEditor.js, web/src/components/common/ui/RenderUtils.jsx, web/src/components/common/ui/CardPro.js, web/src/components/common/ui/CardTable.js
Adds or updates reusable UI components: selectable button groups, JSON editor, card/table utilities, and render helpers.
Frontend: Layout, Navigation, and Styles
web/src/components/layout/SiderBar.js, web/src/components/layout/HeaderBar.js, web/src/components/layout/PageLayout.js, web/src/App.js, web/src/index.css, web/src/helpers/render.js
Adds new sidebar menu for model management, updates header and footer logic, unifies icon coloring, and introduces new CSS for model/pricing pages.
Frontend: Loading State Refactor
web/src/hooks/common/useMinimumLoadingTime.js, web/src/hooks/dashboard/useDashboardData.js, web/src/components/table/usage-logs/UsageLogsActions.jsx, web/src/components/common/ui/CardTable.js, web/src/components/layout/HeaderBar.js
Refactors minimum loading time logic to a reusable hook, replacing manual timing in several components and hooks.
Frontend: Internationalization
web/src/i18n/locales/en.json
Updates translation keys: adds, renames, and removes entries for new/updated UI.
Frontend: Routing and Entry Points
web/src/pages/Pricing/index.js, web/src/App.js
Updates routing to point to new pricing and model management pages.
Frontend: Miscellaneous
web/src/components/table/channels/modals/EditChannelModal.jsx, web/src/components/table/channels/modals/ModelTestModal.jsx
Minor refactors to imports and styling for consistency.

Sequence Diagram(s)

Model Management: Create/Edit Flow

sequenceDiagram
    participant AdminUser
    participant ModelsPage
    participant EditModelModal
    participant BackendAPI
    participant DB

    AdminUser->>ModelsPage: Click "Add/Edit Model"
    ModelsPage->>EditModelModal: Open modal
    AdminUser->>EditModelModal: Fill form & submit
    EditModelModal->>BackendAPI: POST/PUT /api/models
    BackendAPI->>DB: Create/Update Model
    DB-->>BackendAPI: Success/Error
    BackendAPI-->>EditModelModal: Response
    EditModelModal-->>ModelsPage: Close modal, refresh list
Loading

Model Pricing: Filtering and Detail Modal

sequenceDiagram
    participant User
    participant PricingPage
    participant PricingSidebar
    participant PricingContent
    participant BackendAPI

    User->>PricingSidebar: Set filters (group, vendor, etc.)
    PricingSidebar->>PricingPage: Update filter state
    PricingPage->>PricingContent: Pass filtered data
    User->>PricingContent: Click model card/table row
    PricingContent->>PricingPage: openModelDetail(model)
    PricingPage->>ModelDetailSideSheet: Show model details
    ModelDetailSideSheet->>BackendAPI: (if needed) Fetch more info
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Poem

A rabbit hops through fields anew,
With models, vendors, pricing too!
Tables, cards, and filters bright,
Batch actions hopping left and right.
Prefill groups and JSON cheer,
Admin menus now appear.
In this garden, code’s in bloom—
Reviewers, sharpen up your plume! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/model-pricing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (24)
web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (1)

26-26: Add PropTypes for better type safety.

The component is missing prop validation which would help catch bugs and improve maintainability.

Add PropTypes at the end of the file:

+import PropTypes from 'prop-types';

// ... existing code ...

+ModelEndpoints.propTypes = {
+  modelData: PropTypes.shape({
+    supported_endpoint_types: PropTypes.arrayOf(PropTypes.string)
+  }),
+  t: PropTypes.func.isRequired
+};
+
export default ModelEndpoints;
web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx (1)

23-50: LGTM! Well-documented component with proper filtering logic.

The component correctly implements quota type filtering with clear JSDoc documentation. The qtyCount function properly filters models based on quota_type values.

Consider extracting the hardcoded quota type definitions to constants for better maintainability:

+const QUOTA_TYPES = {
+  ALL: 'all',
+  PAY_AS_YOU_GO: 0,
+  PAY_PER_USE: 1,
+};

const items = [
-  { value: 'all', label: t('全部类型'), tagCount: qtyCount('all') },
-  { value: 0, label: t('按量计费'), tagCount: qtyCount(0) },
-  { value: 1, label: t('按次计费'), tagCount: qtyCount(1) },
+  { value: QUOTA_TYPES.ALL, label: t('全部类型'), tagCount: qtyCount(QUOTA_TYPES.ALL) },
+  { value: QUOTA_TYPES.PAY_AS_YOU_GO, label: t('按量计费'), tagCount: qtyCount(QUOTA_TYPES.PAY_AS_YOU_GO) },
+  { value: QUOTA_TYPES.PAY_PER_USE, label: t('按次计费'), tagCount: qtyCount(QUOTA_TYPES.PAY_PER_USE) },
];
web/src/components/table/model-pricing/layout/PricingPage.jsx (1)

28-40: LGTM! Clean component structure with good separation of concerns.

The component properly uses hooks for data management and responsive behavior. The local state management for UI options is well-placed.

Consider making prop passing more explicit to improve debugging and maintainability:

-  const allProps = {
-    ...pricingData,
-    showRatio,
-    setShowRatio,
-    viewMode,
-    setViewMode
-  };
+  const sidebarProps = {
+    ...pricingData,
+    showRatio,
+    setShowRatio,
+    viewMode,
+    setViewMode,
+  };
+  
+  const contentProps = {
+    ...pricingData,
+    showRatio,
+    viewMode,
+  };
web/src/components/table/model-pricing/modal/components/FilterModalContent.jsx (1)

28-66: LGTM! Well-organized component composition with proper hook integration.

The component effectively centralizes filter modal content with clean prop destructuring and proper integration of the usePricingFilterCounts hook for dynamic filtering.

Consider grouping related props to improve readability:

const FilterModalContent = ({ sidebarProps, t }) => {
  const {
+    // Display settings
    showWithRecharge,
    setShowWithRecharge,
    currency,
    setCurrency,
    showRatio,
    setShowRatio,
    viewMode,
    setViewMode,
    tokenUnit,
    setTokenUnit,
+    // Filter states
    handleChange,
    setActiveKey,
    filterGroup,
    setFilterGroup,
    filterQuotaType,
    setFilterQuotaType,
    filterEndpointType,
    setFilterEndpointType,
+    // Other props
    loading,
    ...categoryProps
  } = sidebarProps;
web/src/components/table/model-pricing/layout/header/PricingCategoryIntroSkeleton.jsx (1)

70-72: Consider removing redundant Skeleton wrapper.

The Skeleton wrapper with loading={true} is redundant since the individual skeleton components already handle the loading state internally.

-  return (
-    <Skeleton loading={true} active placeholder={placeholder}></Skeleton>
-  );
+  return placeholder;
web/src/components/table/model-pricing/filter/PricingEndpointTypes.jsx (1)

56-59: Consider removing unused label transformation function.

The getEndpointTypeLabel function currently just returns the input unchanged. If no transformation is needed, consider removing this function for simplicity.

-  // 端点类型显示名称映射
-  const getEndpointTypeLabel = (endpointType) => {
-    return endpointType;
-  };

And update the usage:

        value: endpointType,
-        label: getEndpointTypeLabel(endpointType),
+        label: endpointType,
web/src/components/table/model-pricing/modal/PricingFilterModal.jsx (1)

67-68: Add WebKit scrollbar hiding for broader browser support.

The current scrollbar hiding implementation covers Firefox and IE/Edge but misses WebKit-based browsers.

        scrollbarWidth: 'none',
-        msOverflowStyle: 'none'
+        msOverflowStyle: 'none',
+        WebkitScrollbar: { display: 'none' }

Or better yet, use CSS-in-JS or a CSS class for this styling.

web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (1)

52-82: Simplify data preparation logic and improve error handling.

The data preparation logic has some areas for improvement:

  1. Simplify price data extraction:
-        outputPrice: modelData?.quota_type === 0 ? (priceData.completionPrice || priceData.outputPrice) : '-',
+        outputPrice: modelData?.quota_type === 0 ? (priceData.completionPrice ?? priceData.outputPrice ?? '-') : '-',
  1. Consider better fallback for empty groups:
   const availableGroups = Object.keys(usableGroup || {}).filter(g => g !== '');
   if (availableGroups.length === 0) {
-    availableGroups.push('default');
+    // Consider logging a warning or returning early instead of assuming 'default' exists
+    console.warn('No available groups found for pricing display');
+    return <div className="text-gray-500">{t('无可用分组')}</div>;
   }
  1. Extract billing type logic:
+  const getBillingType = (quotaType) => quotaType === 0 ? t('按量计费') : t('按次计费');
+  
   return {
     // ...
-    billingType: modelData?.quota_type === 0 ? t('按量计费') : t('按次计费'),
+    billingType: getBillingType(modelData?.quota_type),
web/src/components/table/model-pricing/view/card/PricingCardSkeleton.jsx (2)

50-64: Extract magic numbers for better maintainability.

The dynamic width calculations use magic numbers that reduce code readability. Consider extracting these values as constants.

+const SKELETON_WIDTHS = {
+  MODEL_NAME_BASE: 120,
+  MODEL_NAME_VARIATION: 30,
+  PRICE_BASE: 160,
+  PRICE_VARIATION: 20
+};
+
 /* 模型名称骨架 */
 <Skeleton.Title
   style={{
-    width: `${120 + (index % 3) * 30}px`,
+    width: `${SKELETON_WIDTHS.MODEL_NAME_BASE + (index % 3) * SKELETON_WIDTHS.MODEL_NAME_VARIATION}px`,
     height: 20,
     marginBottom: 8
   }}
 />
 /* 价格信息骨架 */
 <Skeleton.Title
   style={{
-    width: `${160 + (index % 4) * 20}px`,
+    width: `${SKELETON_WIDTHS.PRICE_BASE + (index % 4) * SKELETON_WIDTHS.PRICE_VARIATION}px`,
     height: 20,
     marginBottom: 0
   }}
 />

31-36: Consider performance optimization for large skeleton counts

The current implementation generates skeleton cards without any performance optimization. For large skeletonCount values, this could impact rendering performance.

Consider memoizing the skeleton card structure:

+const SkeletonCard = React.memo(({ index, rowSelection, showRatio }) => (
+  <Card
+    key={index}
+    className="!rounded-2xl border border-gray-200"
+    bodyStyle={{ padding: '24px' }}
+  >
+    {/* ... card content ... */}
+  </Card>
+));

 const placeholder = (
   <div className="p-4">
     <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
       {Array.from({ length: skeletonCount }).map((_, index) => (
-        <Card
-          key={index}
-          className="!rounded-2xl border border-gray-200"
-          bodyStyle={{ padding: '24px' }}
-        >
+        <SkeletonCard
+          key={index}
+          index={index}
+          rowSelection={rowSelection}
+          showRatio={showRatio}
+        />
web/src/components/table/model-pricing/layout/header/PricingCategoryIntro.jsx (4)

58-84: Consider extracting category descriptions for better maintainability.

The category descriptions are comprehensive but hardcoded within the function. Consider extracting them to a configuration object for easier maintenance and potential localization.

+const CATEGORY_DESCRIPTIONS = {
+  all: '查看所有可用的AI模型,包括文本生成、图像处理、音频转换等多种类型的模型。',
+  openai: '令牌分发介绍:SSVIP 为纯OpenAI官方。SVIP 为纯Azure。Default 为Azure 消费。VIP为近似的复数。VVIP为近似的书发。',
+  // ... other descriptions
+};
+
 const getCategoryDescription = (categoryKey) => {
-  const descriptions = {
-    all: t('查看所有可用的AI模型,包括文本生成、图像处理、音频转换等多种类型的模型。'),
-    // ... other descriptions
-  };
-  return descriptions[categoryKey] || t('该分类包含多种AI模型,适用于不同的应用场景。');
+  return t(CATEGORY_DESCRIPTIONS[categoryKey] || '该分类包含多种AI模型,适用于不同的应用场景。');
 };

50-84: Category descriptions are comprehensive but consider localization impact.

The extensive category descriptions provide good user context, but some contain very specific details that might be challenging to localize effectively across different languages.

Consider if some of the longer, more detailed descriptions could be simplified for better localization and user scanning, while keeping essential information.


87-122: Complex avatar rendering logic needs refactoring

The renderAllModelsAvatar function is quite complex with nested conditions and array manipulations. Consider breaking it down into smaller, more testable functions.

Break down the complex rendering logic:

+  const createFallbackCategories = () => {
+    return Object.entries(modelCategories)
+      .filter(([key]) => key !== 'all')
+      .slice(0, 3)
+      .map(([key, category]) => ({
+        key,
+        label: category.label,
+        text: category.label.slice(0, 2) || key.slice(0, 2).toUpperCase()
+      }));
+  };
+
+  const renderFallbackAvatars = () => {
+    const fallbackCategories = createFallbackCategories();
+    return (
+      <div className="min-w-16 h-16 rounded-2xl bg-white shadow-md flex items-center justify-center px-2">
+        <AvatarGroup size="default" overlapFrom='end'>
+          {fallbackCategories.map((item) => (
+            <Avatar key={item.key} size="default" color="transparent" alt={item.label}>
+              {item.text}
+            </Avatar>
+          ))}
+        </AvatarGroup>
+      </div>
+    );
+  };
+
   const renderAllModelsAvatar = () => {
     const rotatedCategories = validCategories.length > 3 ? [
       ...validCategories.slice(currentOffset),
       ...validCategories.slice(0, currentOffset)
     ] : validCategories;

-    if (validCategories.length === 0) {
-      // ... complex fallback logic
-      return (/* fallback JSX */);
-    }
+    if (validCategories.length === 0) {
+      return renderFallbackAvatars();
+    }

183-187: Consider responsive font sizing consistency

The responsive text sizing (text-lg sm:text-xl) is good, but ensure it's consistent across similar components in the application.

Verify consistent responsive typography patterns across the pricing components.

web/src/helpers/utils.js (1)

705-730: Consider adding JSDoc documentation for better maintainability.

The resetPricingFilters function is well-implemented with proper null safety, but would benefit from JSDoc documentation describing the expected parameter structure.

+/**
+ * Resets all pricing filter states to their default values
+ * @param {Object} options - Object containing setter functions and data
+ * @param {Function} options.handleChange - Handler for search input changes
+ * @param {Function} options.setActiveKey - Setter for active category key
+ * @param {Array} options.availableCategories - Array of available category keys
+ * @param {Function} options.setShowWithRecharge - Setter for recharge display toggle
+ * @param {Function} options.setCurrency - Setter for currency selection
+ * @param {Function} options.setShowRatio - Setter for ratio display toggle
+ * @param {Function} options.setViewMode - Setter for view mode (card/table)
+ * @param {Function} options.setFilterGroup - Setter for group filter
+ * @param {Function} options.setFilterQuotaType - Setter for quota type filter
+ * @param {Function} options.setFilterEndpointType - Setter for endpoint type filter
+ * @param {Function} options.setCurrentPage - Setter for current page
+ * @param {Function} options.setTokenUnit - Setter for token unit (K/M)
+ */
 export const resetPricingFilters = ({
web/src/components/common/ui/SelectableButtonGroup.jsx (2)

52-58: Consider memoizing expensive calculations.

The maxVisibleRows calculation and needCollapse boolean are recalculated on every render. Consider wrapping them in useMemo for better performance.

+ const maxVisibleRows = useMemo(() => Math.max(1, Math.floor(collapseHeight / 32)), [collapseHeight]);
+ const needCollapse = useMemo(() => collapsible && items.length > perRow * maxVisibleRows, [collapsible, items.length, perRow, maxVisibleRows]);
- const maxVisibleRows = Math.max(1, Math.floor(collapseHeight / 32)); // Approx row height 32
- const needCollapse = collapsible && items.length > perRow * maxVisibleRows;

89-131: Improve skeleton rendering maintainability.

The skeleton rendering logic is complex and could benefit from extraction into a separate component or hook for reusability.

Consider extracting the skeleton logic:

+ const SkeletonButton = ({ withCheckbox, index }) => (
+   <div style={{
+     width: '100%',
+     height: '32px',
+     display: 'flex',
+     alignItems: 'center',
+     justifyContent: 'flex-start',
+     border: '1px solid var(--semi-color-border)',
+     borderRadius: 'var(--semi-border-radius-medium)',
+     padding: '0 12px',
+     gap: '8px'
+   }}>
+     {withCheckbox && (
+       <Skeleton.Title active style={{ width: 14, height: 14 }} />
+     )}
+     <Skeleton.Title
+       active
+       style={{
+         width: `${60 + (index % 3) * 20}px`,
+         height: 14
+       }}
+     />
+   </div>
+ );
web/src/hooks/model-pricing/useModelPricingData.js (2)

81-116: Optimize filtering performance with better memoization.

The filtering logic performs multiple sequential filters which could be expensive. Consider optimizing the dependency array and filtering logic.

The current implementation is correct but could be optimized:

const filteredModels = useMemo(() => {
  let result = models;

+ // Early return if no models
+ if (!models.length) return [];

  // Apply all filters in a single pass for better performance
+ return models.filter(model => {
+   // Category filter
+   if (activeKey !== 'all' && !modelCategories[activeKey].filter(model)) {
+     return false;
+   }
+   
+   // Group filter
+   if (filterGroup !== 'all' && !model.enable_groups.includes(filterGroup)) {
+     return false;
+   }
+   
+   // Quota type filter
+   if (filterQuotaType !== 'all' && model.quota_type !== filterQuotaType) {
+     return false;
+   }
+   
+   // Endpoint type filter
+   if (filterEndpointType !== 'all' && 
+       (!model.supported_endpoint_types || !model.supported_endpoint_types.includes(filterEndpointType))) {
+     return false;
+   }
+   
+   // Search filter
+   if (searchValue.length > 0) {
+     const searchTerm = searchValue.toLowerCase();
+     if (!model.model_name.toLowerCase().includes(searchTerm)) {
+       return false;
+     }
+   }
+   
+   return true;
+ });

- // Sequential filtering logic...
}, [activeKey, models, searchValue, filterGroup, filterQuotaType, filterEndpointType, modelCategories]);

243-311: Consider grouping related return values for better organization.

The return object is quite large and could benefit from logical grouping to improve maintainability.

Consider organizing the return object:

return {
- // 状态
- searchValue,
- setSearchValue,
- // ... many individual items
+ // Search and filters
+ search: {
+   searchValue,
+   setSearchValue,
+   handleChange,
+   handleCompositionStart,
+   handleCompositionEnd,
+ },
+ 
+ // Filters
+ filters: {
+   activeKey,
+   setActiveKey,
+   filterGroup,
+   setFilterGroup,
+   filterQuotaType,
+   setFilterQuotaType,
+   filterEndpointType,
+   setFilterEndpointType,
+ },
+ 
+ // Pagination
+ pagination: {
+   currentPage,
+   setCurrentPage,
+   pageSize,
+   setPageSize,
+ },
+ 
+ // Selection and modals
+ selection: {
+   selectedRowKeys,
+   setSelectedRowKeys,
+   rowSelection,
+ },
+ 
+ // ... other groups
};

However, this would be a breaking change, so consider it for future refactoring.

web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx (3)

20-41: Component setup is functional but has high prop dependency.

The component accepts many props which creates tight coupling. Consider if some of these props could be grouped into objects or if the component could be broken down further to reduce complexity.


58-67: Review copy button styling and accessibility

The copy button has custom styling that overrides theme colors and lacks accessibility considerations for disabled state.

Improve accessibility and styling:

      <Button
        theme='outline'
        type='primary'
        icon={<IconCopy />}
        onClick={() => copyText(selectedRowKeys)}
        disabled={selectedRowKeys.length === 0}
-        className="!bg-blue-500 hover:!bg-blue-600 text-white"
+        className="!bg-blue-500 hover:!bg-blue-600 text-white disabled:!bg-gray-300 disabled:!text-gray-500"
+        aria-label={t('复制选中的模型')}
+        title={selectedRowKeys.length === 0 ? t('请先选择要复制的模型') : t('复制选中的模型')}
      >
        {t('复制')}
      </Button>

99-106: Consider prop drilling optimization

The component passes sidebarProps directly to the modal, which may contain many properties. Consider if this creates unnecessary coupling.

Consider extracting only the needed props:

      {isMobile && (
        <PricingFilterModal
          visible={showFilterModal}
          onClose={() => setShowFilterModal(false)}
-          sidebarProps={sidebarProps}
+          filterProps={{
+            handleChange: sidebarProps.handleChange,
+            setActiveKey: sidebarProps.setActiveKey,
+            // ... other specific props needed by modal
+          }}
          t={t}
        />
      )}
web/src/components/table/model-pricing/view/table/PricingTableColumns.js (2)

63-74: Consider destructuring validation for complex parameter object

The function accepts a large parameter object with many properties. Consider adding parameter validation or at least documenting the expected structure.

Add parameter validation or use destructuring with defaults:

-export const getPricingTableColumns = ({
-  t,
-  selectedGroup,
-  groupRatio,
-  copyText,
-  setModalImageUrl,
-  setIsModalOpenurl,
-  currency,
-  tokenUnit,
-  displayPrice,
-  showRatio,
-}) => {
+export const getPricingTableColumns = (params) => {
+  const {
+    t,
+    selectedGroup,
+    groupRatio = {},
+    copyText,
+    setModalImageUrl,
+    setIsModalOpenurl,
+    currency = 'USD',
+    tokenUnit = 'K',
+    displayPrice = true,
+    showRatio = false,
+  } = params || {};
+
+  if (!t || !copyText) {
+    throw new Error('Required parameters t and copyText are missing');
+  }

94-96: Review filter performance for large datasets

The onFilter function uses toLowerCase() and includes() which could be inefficient for large datasets. Consider if this filtering should be moved to the backend or optimized.

Consider optimizing the filter function:

-    onFilter: (value, record) =>
-      record.model_name.toLowerCase().includes(value.toLowerCase()),
+    onFilter: (value, record) => {
+      if (!value) return true;
+      const searchTerm = value.toLowerCase();
+      const modelName = record.model_name?.toLowerCase() || '';
+      return modelName.includes(searchTerm);
+    },
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5d9759 and b41c24d.

📒 Files selected for processing (43)
  • web/src/components/common/ui/CardTable.js (2 hunks)
  • web/src/components/common/ui/SelectableButtonGroup.jsx (1 hunks)
  • web/src/components/layout/HeaderBar.js (3 hunks)
  • web/src/components/layout/PageLayout.js (1 hunks)
  • web/src/components/table/model-pricing/ModelPricingColumnDefs.js (0 hunks)
  • web/src/components/table/model-pricing/ModelPricingFilters.jsx (0 hunks)
  • web/src/components/table/model-pricing/ModelPricingHeader.jsx (0 hunks)
  • web/src/components/table/model-pricing/ModelPricingTabs.jsx (0 hunks)
  • web/src/components/table/model-pricing/filter/PricingCategories.jsx (1 hunks)
  • web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx (1 hunks)
  • web/src/components/table/model-pricing/filter/PricingEndpointTypes.jsx (1 hunks)
  • web/src/components/table/model-pricing/filter/PricingGroups.jsx (1 hunks)
  • web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx (1 hunks)
  • web/src/components/table/model-pricing/index.jsx (0 hunks)
  • web/src/components/table/model-pricing/layout/PricingPage.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/PricingSidebar.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/content/PricingContent.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/content/PricingView.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/header/PricingCategoryIntro.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/header/PricingCategoryIntroSkeleton.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/header/PricingCategoryIntroWithSkeleton.jsx (1 hunks)
  • web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/ModelDetailSideSheet.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/PricingFilterModal.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/FilterModalContent.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/FilterModalFooter.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/ModelBasicInfo.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/ModelHeader.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (1 hunks)
  • web/src/components/table/model-pricing/view/card/PricingCardSkeleton.jsx (1 hunks)
  • web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1 hunks)
  • web/src/components/table/model-pricing/view/table/PricingTable.jsx (2 hunks)
  • web/src/components/table/model-pricing/view/table/PricingTableColumns.js (1 hunks)
  • web/src/components/table/usage-logs/UsageLogsActions.jsx (2 hunks)
  • web/src/helpers/utils.js (2 hunks)
  • web/src/hooks/common/useMinimumLoadingTime.js (1 hunks)
  • web/src/hooks/dashboard/useDashboardData.js (4 hunks)
  • web/src/hooks/model-pricing/useModelPricingData.js (7 hunks)
  • web/src/hooks/model-pricing/usePricingFilterCounts.js (1 hunks)
  • web/src/i18n/locales/en.json (2 hunks)
  • web/src/index.css (3 hunks)
  • web/src/pages/Pricing/index.js (1 hunks)
💤 Files with no reviewable changes (5)
  • web/src/components/table/model-pricing/index.jsx
  • web/src/components/table/model-pricing/ModelPricingFilters.jsx
  • web/src/components/table/model-pricing/ModelPricingTabs.jsx
  • web/src/components/table/model-pricing/ModelPricingHeader.jsx
  • web/src/components/table/model-pricing/ModelPricingColumnDefs.js
🧰 Additional context used
🧬 Code Graph Analysis (16)
web/src/components/common/ui/CardTable.js (1)
web/src/hooks/common/useMinimumLoadingTime.js (3)
  • showSkeleton (29-29)
  • useMinimumLoadingTime (28-50)
  • useMinimumLoadingTime (28-50)
web/src/components/table/model-pricing/layout/content/PricingView.jsx (3)
web/src/components/table/model-pricing/layout/PricingPage.jsx (1)
  • viewMode (33-33)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1)
  • PricingCardView (35-331)
web/src/components/table/model-pricing/view/table/PricingTable.jsx (1)
  • PricingTable (28-128)
web/src/components/table/model-pricing/layout/PricingPage.jsx (4)
web/src/hooks/model-pricing/useModelPricingData.js (2)
  • useModelPricingData (27-312)
  • useModelPricingData (27-312)
web/src/components/table/model-pricing/modal/ModelDetailSideSheet.jsx (2)
  • isMobile (51-51)
  • ModelDetailSideSheet (38-101)
web/src/components/table/model-pricing/layout/PricingSidebar.jsx (1)
  • PricingSidebar (30-154)
web/src/components/table/model-pricing/layout/content/PricingContent.jsx (1)
  • PricingContent (24-38)
web/src/hooks/common/useMinimumLoadingTime.js (8)
web/src/hooks/model-pricing/useModelPricingData.js (1)
  • loading (47-47)
web/src/components/layout/HeaderBar.js (1)
  • loading (71-71)
web/src/hooks/dashboard/useDashboardData.js (1)
  • loading (36-36)
web/src/components/common/ui/SelectableButtonGroup.jsx (1)
  • showSkeleton (58-58)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1)
  • showSkeleton (59-59)
web/src/components/common/ui/CardTable.js (1)
  • showSkeleton (45-45)
web/src/components/table/model-pricing/layout/header/PricingCategoryIntroWithSkeleton.jsx (1)
  • showSkeleton (33-33)
web/src/components/table/usage-logs/UsageLogsActions.jsx (1)
  • showSkeleton (34-34)
web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx (2)
web/src/components/common/ui/SelectableButtonGroup.jsx (1)
  • SelectableButtonGroup (40-255)
web/src/hooks/model-pricing/useModelPricingData.js (1)
  • filterQuotaType (38-38)
web/src/components/table/model-pricing/filter/PricingCategories.jsx (5)
web/src/components/table/model-pricing/filter/PricingGroups.jsx (1)
  • items (36-57)
web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx (1)
  • items (39-66)
web/src/components/table/model-pricing/filter/PricingQuotaTypes.jsx (1)
  • items (34-38)
web/src/hooks/model-pricing/useModelPricingData.js (3)
  • modelCategories (58-58)
  • availableCategories (73-79)
  • categoryCounts (60-71)
web/src/components/common/ui/SelectableButtonGroup.jsx (1)
  • SelectableButtonGroup (40-255)
web/src/components/table/model-pricing/modal/components/ModelHeader.jsx (1)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx (2)
  • CARD_STYLES (28-33)
  • getModelIcon (78-118)
web/src/components/table/model-pricing/view/card/PricingCardSkeleton.jsx (3)
web/src/components/table/model-pricing/layout/header/PricingCategoryIntroSkeleton.jsx (1)
  • placeholder (26-68)
web/src/hooks/model-pricing/useModelPricingData.js (1)
  • rowSelection (118-126)
web/src/components/table/model-pricing/layout/PricingPage.jsx (1)
  • showRatio (32-32)
web/src/components/table/model-pricing/modal/PricingFilterModal.jsx (4)
web/src/components/table/model-pricing/layout/PricingSidebar.jsx (1)
  • handleResetFilters (71-85)
web/src/helpers/utils.js (2)
  • resetPricingFilters (705-730)
  • resetPricingFilters (705-730)
web/src/components/table/model-pricing/modal/components/FilterModalFooter.jsx (1)
  • FilterModalFooter (23-42)
web/src/components/table/model-pricing/modal/components/FilterModalContent.jsx (1)
  • FilterModalContent (28-120)
web/src/components/table/model-pricing/filter/PricingGroups.jsx (2)
web/src/hooks/model-pricing/useModelPricingData.js (3)
  • usableGroup (49-49)
  • groupRatio (48-48)
  • filterGroup (37-37)
web/src/components/common/ui/SelectableButtonGroup.jsx (1)
  • SelectableButtonGroup (40-255)
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (6)
web/src/components/table/model-pricing/modal/ModelDetailSideSheet.jsx (1)
  • Typography (36-36)
web/src/components/table/model-pricing/modal/components/ModelBasicInfo.jsx (1)
  • Typography (24-24)
web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (1)
  • Typography (24-24)
web/src/hooks/model-pricing/useModelPricingData.js (3)
  • usableGroup (49-49)
  • groupRatio (48-48)
  • tokenUnit (45-45)
web/src/components/table/model-pricing/view/table/PricingTable.jsx (1)
  • columns (50-74)
web/src/components/table/model-pricing/layout/PricingPage.jsx (1)
  • showRatio (32-32)
web/src/helpers/utils.js (2)
web/src/hooks/model-pricing/useModelPricingData.js (7)
  • groupRatio (48-48)
  • selectedGroup (34-34)
  • tokenUnit (45-45)
  • displayPrice (128-138)
  • currency (43-43)
  • handleChange (193-199)
  • availableCategories (73-79)
web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx (1)
  • handleChange (73-88)
web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx (3)
web/src/hooks/model-pricing/useModelPricingData.js (10)
  • handleCompositionStart (201-203)
  • handleCompositionEnd (205-210)
  • handleChange (193-199)
  • copyText (185-191)
  • selectedRowKeys (31-31)
  • loading (47-47)
  • activeKey (39-39)
  • modelCategories (58-58)
  • categoryCounts (60-71)
  • availableCategories (73-79)
web/src/components/table/model-pricing/layout/header/PricingCategoryIntroWithSkeleton.jsx (1)
  • PricingCategoryIntroWithSkeleton (25-52)
web/src/components/table/model-pricing/modal/PricingFilterModal.jsx (1)
  • PricingFilterModal (26-74)
web/src/components/table/model-pricing/layout/content/PricingContent.jsx (2)
web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx (1)
  • PricingTopSection (26-109)
web/src/components/table/model-pricing/layout/content/PricingView.jsx (1)
  • PricingView (24-31)
web/src/components/common/ui/SelectableButtonGroup.jsx (1)
web/src/hooks/common/useMinimumLoadingTime.js (3)
  • showSkeleton (29-29)
  • useMinimumLoadingTime (28-50)
  • useMinimumLoadingTime (28-50)
web/src/hooks/model-pricing/usePricingFilterCounts.js (3)
web/src/components/table/model-pricing/modal/components/FilterModalContent.jsx (1)
  • usePricingFilterCounts (52-65)
web/src/components/table/model-pricing/layout/PricingSidebar.jsx (1)
  • usePricingFilterCounts (56-69)
web/src/hooks/model-pricing/useModelPricingData.js (5)
  • modelCategories (58-58)
  • filterGroup (37-37)
  • filterQuotaType (38-38)
  • filterEndpointType (40-40)
  • searchValue (29-29)
🔇 Additional comments (98)
web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (2)

44-46: Verify the hardcoded URL path assumption.

The code assumes all endpoint types use the same URL path /v1/chat/completions, but different endpoint types typically have different paths (e.g., /v1/embeddings, /v1/completions, /v1/chat/completions).

Please verify that all supported endpoint types actually use the same URL path, or consider making the path dynamic based on the endpoint type:

const getEndpointPath = (endpointType) => {
  const pathMap = {
    'chat': '/v1/chat/completions',
    'completion': '/v1/completions', 
    'embedding': '/v1/embeddings',
    // add other mappings as needed
  };
  return pathMap[endpointType] || '/v1/chat/completions';
};

53-67: LGTM! Clean component structure with proper internationalization.

The component structure is well-organized with:

  • Proper use of Semi-UI components
  • Clean CSS styling with responsive design
  • Good internationalization implementation
  • Semantic HTML structure
web/src/hooks/model-pricing/usePricingFilterCounts.js (6)

25-42: LGTM! Well-structured parameter handling and category filtering.

The parameter destructuring with sensible defaults and the category filtering logic are well-implemented:

  • Proper handling of the 'all' category case
  • Safe function checking before calling category.filter
  • Correct memoization dependencies

45-63: LGTM! Comprehensive filtering logic with proper data structure handling.

The filtering implementation correctly handles different data types:

  • Array inclusion checks for enable_groups and supported_endpoint_types
  • Direct comparison for quota_type
  • Case-insensitive search for model names

The chaining approach ensures all filters are applied correctly.


66-77: LGTM! Smart dynamic counting logic for category filters.

The dynamic category counting implementation is well-designed:

  • Properly excludes the 'all' category from iteration
  • Safe function checking before applying category filters
  • Provides meaningful fallback (0) when filter function is missing

This enables responsive UI counts that update based on other active filters.


80-103: LGTM! Correct complementary filtering logic for button counts.

Both quotaTypeModels and endpointTypeModels implement the right logic for filter button counts:

  • Start with category-filtered models
  • Apply all filters except the one being counted
  • This provides accurate counts for what users would see if they selected each option

The parallel structure makes the code maintainable and consistent.


106-123: LGTM! Consistent filtering logic with clear documentation.

The groupCountModels logic correctly implements the complementary filtering pattern:

  • Properly excludes the group filter itself
  • Applies all other relevant filters
  • Clear comments explaining the logic

Consistent with the other count model implementations.


125-131: LGTM! Well-designed hook with clear return values.

The hook returns all the necessary filtered datasets with descriptive names that align with their usage in consuming components. The overall design follows React hook best practices with proper memoization throughout.

web/src/components/layout/PageLayout.js (1)

45-45: LGTM! Appropriate footer hiding for the new pricing page layout.

The addition of the exact /pricing path to the footer hiding condition aligns well with the new pricing page design that likely requires full-screen layout, similar to console pages.

web/src/pages/Pricing/index.js (2)

21-21: LGTM! Updated import path reflects the new modular architecture.

The import path change from the old monolithic component to the new layout/PricingPage structure aligns with the comprehensive refactoring mentioned in the PR objectives.


24-26: LGTM! Simplified wrapper reflects improved component design.

The removal of the styled div wrapper in favor of a React fragment suggests the new PricingPage component properly handles its own layout and spacing internally, which is a good architectural improvement.

web/src/components/common/ui/CardTable.js (2)

26-26: LGTM! Good refactoring to use centralized loading state management.

The import of useMinimumLoadingTime hook aligns with the broader effort to standardize minimum loading duration behavior across components.


45-45: LGTM! Simplified loading state management with custom hook.

The replacement of manual minimum loading time logic with the useMinimumLoadingTime hook improves:

  • Code reusability across components
  • Consistent loading behavior
  • Reduced duplication of timing logic

The hook maintains the same 1000ms default minimum time while providing better abstraction.

web/src/components/table/model-pricing/layout/content/PricingView.jsx (1)

24-31: Clean implementation of view switcher pattern.

The component follows React best practices with proper prop destructuring, default parameters, and conditional rendering. The fallback to table view when viewMode is not 'card' provides good defensive programming.

web/src/i18n/locales/en.json (2)

953-953: Key rename aligns with UI refactor.

The renaming of the Chinese key from "定价" to "模型广场" while maintaining the English translation "Pricing" is consistent with the broader model pricing UI refactor mentioned in the PR objectives.


1198-1198: New translation key supports token unit display feature.

The addition of "按K显示单位": "Display in K units" provides localization support for the new token unit display toggle functionality in the pricing display settings.

web/src/components/table/model-pricing/modal/components/FilterModalFooter.jsx (1)

23-42: Well-structured modal footer with proper button hierarchy.

The component effectively implements a modal footer with appropriate visual hierarchy:

  • Reset button uses outline theme (secondary action)
  • Confirm button uses solid primary theme (primary action)
  • Proper internationalization support
  • Clean flexbox layout with right alignment
web/src/components/table/model-pricing/layout/content/PricingContent.jsx (1)

24-38: Well-structured responsive layout component.

The component effectively implements a responsive layout with:

  • Conditional CSS classes for mobile/desktop differences
  • Clear separation between fixed header and scrollable content areas
  • Proper prop extraction and forwarding (viewMode from sidebarProps)
  • Good component composition with PricingTopSection and PricingView

The layout structure supports the intended UX of having a fixed search/filter header with scrollable pricing data below.

web/src/components/table/model-pricing/layout/header/PricingCategoryIntroWithSkeleton.jsx (1)

25-52: Effective loading wrapper with consistent skeleton timing.

The component implements a clean loading wrapper pattern:

  • Uses the custom useMinimumLoadingTime hook for standardized skeleton display timing
  • Proper conditional rendering between skeleton and actual content
  • Correct prop mapping (activeKey === 'all' to isAllModels)
  • Clean separation of concerns between loading logic and content rendering

This pattern provides a consistent loading experience across the application.

web/src/components/table/usage-logs/UsageLogsActions.jsx (1)

20-34: LGTM! Clean refactor to centralize loading skeleton logic.

The replacement of manual loading timing logic with the useMinimumLoadingTime hook improves code maintainability and reusability while preserving the existing functionality.

web/src/components/table/model-pricing/filter/PricingCategories.jsx (1)

23-42: LGTM! Well-structured filter component with clean data transformation.

The component properly filters available categories and transforms the data structure to match the SelectableButtonGroup component's expectations. The implementation follows good React patterns with appropriate prop passing and data mapping.

web/src/components/table/model-pricing/layout/PricingPage.jsx (1)

42-84: Validation complete: modal props and CSS classes are correctly mapped.

– Confirmed that CSS selectors .pricing-layout, .pricing-scroll-hide, .pricing-sidebar, and .pricing-content are defined in web/src/index.css.
– Verified that useModelPricingData returns modalImageUrl, isModalOpenurl, and setIsModalOpenurl, which are used as src, visible, and onVisibleChange on <ImagePreview>.

No changes required.

web/src/components/table/model-pricing/modal/components/FilterModalContent.jsx (1)

67-119: Excellent component composition with consistent prop passing.

The component properly renders all filter components with appropriate props and maintains consistency in prop passing patterns across all child components.

web/src/hooks/common/useMinimumLoadingTime.js (1)

22-50: LGTM! Well-implemented custom hook with proper React patterns.

The hook correctly manages minimum loading time with:

  • Proper use of useRef to track loading start time
  • Appropriate useEffect cleanup for timeouts
  • Sound logic for calculating remaining display time
  • Good default parameter (1000ms)

The implementation handles edge cases well and follows React best practices.

web/src/index.css (2)

394-416: LGTM! Consistent scrollbar hiding extension.

The addition of .pricing-scroll-hide follows the established pattern for hiding scrollbars across the application. The implementation is consistent with existing scroll hiding utilities.


622-675: LGTM! Comprehensive and well-structured pricing layout styles.

The new pricing layout CSS provides:

  • Proper fixed sidebar (460px) with responsive mobile handling
  • Flexible content area with appropriate overflow management
  • Sticky header positioning with correct z-index layering
  • Consistent theming using CSS custom properties
  • Good mobile responsiveness with dedicated classes

The implementation follows modern CSS practices and supports the new modular pricing page architecture.

web/src/components/layout/HeaderBar.js (4)

55-55: LGTM! Proper integration of the centralized loading hook.

The import of useMinimumLoadingTime aligns with the broader refactoring to standardize loading skeleton behavior across components.


71-72: LGTM! Simplified loading state management.

The refactoring to use useMinimumLoadingTime improves the component by:

  • Centralizing loading time logic in a reusable hook
  • Reducing local state complexity
  • Maintaining consistent UX with simplified code

The loading derivation from statusState?.status === undefined is appropriate.


133-133: LGTM! Navigation text update aligns with the pricing page refactor.

The change from t('定价') to t('模型广场') reflects the evolution from a simple pricing page to a more comprehensive model marketplace/square interface, which aligns with the broader refactoring objectives.


461-461: LGTM! Appropriate header styling enhancement.

The addition of a bottom border using var(--semi-color-border) provides visual separation and maintains theming consistency. The inline style is acceptable for this simple styling enhancement.

web/src/components/table/model-pricing/modal/components/ModelBasicInfo.jsx (1)

26-53: LGTM! Well-structured component with good UX considerations.

The ModelBasicInfo component demonstrates:

  • Proper use of semi-ui components for consistent styling
  • Good conditional logic for model-specific descriptions (gpt-4o-image handling)
  • Appropriate fallback handling when modelData is missing
  • Clean internationalization support
  • Semantic HTML structure with accessible styling

The special case handling for gpt-4o-image models provides valuable user guidance about the model's limitations and costs.

web/src/components/table/model-pricing/modal/ModelDetailSideSheet.jsx (1)

38-101: LGTM! Well-architected modal component with excellent composition.

The ModelDetailSideSheet component demonstrates:

  • Excellent responsive design with mobile-first approach
  • Clean component composition separating concerns (BasicInfo, Endpoints, PricingTable)
  • Proper loading state handling with user-friendly feedback
  • Appropriate prop passing and state management
  • Good use of semi-ui components for consistent styling
  • Proper accessibility with close button and keyboard handling

The responsive width handling (isMobile ? '100%' : 600) and component structure provide a solid foundation for the model detail view.

web/src/hooks/dashboard/useDashboardData.js (1)

27-27: LGTM! Clean refactor to centralize loading state management.

The integration of useMinimumLoadingTime hook successfully replaces manual loading timing logic while maintaining the same API interface. This standardization improves consistency across components and reduces code duplication.

Also applies to: 39-39, 246-246

web/src/components/table/model-pricing/layout/header/PricingCategoryIntroSkeleton.jsx (1)

32-50: Well-implemented conditional avatar skeleton logic.

The conditional rendering for isAllModels provides appropriate skeleton patterns for both single and multiple avatar contexts. The overlapping avatar effect with negative margins creates a realistic loading placeholder.

web/src/components/table/model-pricing/view/table/PricingTable.jsx (2)

26-26: Clean component and import renaming.

The renaming from ModelPricingTable to PricingTable and corresponding column import aligns well with the new pricing architecture.

Also applies to: 28-28


88-93: Effective compact mode implementation for responsive design.

The logic to remove fixed properties and disable horizontal scrolling in compact mode improves mobile usability. The destructuring approach cleanly removes the fixed property while preserving other column properties.

web/src/components/table/model-pricing/filter/PricingEndpointTypes.jsx (2)

35-42: Robust endpoint type extraction with proper fallback.

The logic correctly handles the fallback from allModels to models and safely checks for array properties before processing. The sorting ensures consistent display order.


46-54: Efficient count calculation with proper filtering.

The counting logic correctly handles the 'all' case and properly filters models based on supported endpoint types. The null safety checks prevent runtime errors.

web/src/components/table/model-pricing/modal/PricingFilterModal.jsx (2)

32-46: Well-structured filter reset implementation.

The handleResetFilters function properly maps all the relevant sidebarProps to the resetPricingFilters utility, ensuring comprehensive filter state reset. The parameter mapping aligns with the utility function signature shown in the relevant code snippets.


62-69: Effective full-screen modal styling for mobile use.

The modal styling properly implements full-viewport dimensions with appropriate height calculations and scrollbar hiding. The scrollable content area ensures accessibility on various screen sizes.

web/src/components/table/model-pricing/modal/components/ModelHeader.jsx (1)

106-133: Component render logic looks well-structured.

The JSX render logic is clean and follows React best practices:

  • Proper use of conditional rendering with optional chaining
  • Good accessibility with copyable text and success toast
  • Clean styling with Tailwind classes
  • Proper key usage in map operations
web/src/components/table/model-pricing/filter/PricingGroups.jsx (3)

20-32: Excellent documentation and clean imports.

The JSDoc documentation clearly describes all parameters and their types, making the component interface easy to understand.


59-69: Clean component render with proper prop delegation.

The component render is well-structured, properly delegating to the reusable SelectableButtonGroup component with all necessary props.


34-57: Add null safety to prevent runtime errors.

The current implementation has potential null safety issues that could cause runtime errors:

  1. Line 34: The filter key !== '' doesn't handle null/undefined keys
  2. Line 39: m.enable_groups.includes(g) could throw if enable_groups is undefined

Apply these improvements for better null safety:

-const groups = ['all', ...Object.keys(usableGroup).filter(key => key !== '')];
+const groups = ['all', ...Object.keys(usableGroup).filter(key => key && key.trim() !== '')];

const items = groups.map((g) => {
  const modelCount = g === 'all'
    ? models.length
-   : models.filter(m => m.enable_groups && m.enable_groups.includes(g)).length;
+   : models.filter(m => Array.isArray(m.enable_groups) && m.enable_groups.includes(g)).length;

This ensures:

  • Keys are truthy and not just empty strings
  • enable_groups is actually an array before calling includes()

Likely an incorrect or invalid review comment.

web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (2)

84-171: Well-structured column definition logic.

The dynamic column generation handles different pricing models appropriately and uses proper render functions. The conditional logic for showing ratios and different pricing types is clean.

Minor suggestion for consistency:

   columns.push({
     title: t('计费类型'),
     dataIndex: 'billingType',
     render: (text) => (
-      <Tag color={text === t('按量计费') ? 'violet' : 'teal'} size="small" shape="circle">
+      <Tag color={text.includes('按量') ? 'violet' : 'teal'} size="small" shape="circle">
         {text}
       </Tag>
     ),
   });

This makes the color logic more resilient to translation changes.


174-187: Clean and well-structured component render.

The main render uses appropriate UI components with good visual hierarchy. The card layout and header provide clear context for the pricing table.

web/src/components/table/model-pricing/filter/PricingDisplaySettings.jsx (3)

25-71: Well-structured component interface with helpful tooltips.

The component interface clearly separates different types of settings, and the tooltip explanation for the ratio setting is particularly helpful for user understanding.


73-97: Clean state management logic.

The single change handler effectively manages multiple toggle types, and the active values calculation correctly maps the current state to the expected format.


99-124: Clean component render with appropriate conditional logic.

The component render effectively uses the reusable SelectableButtonGroup and properly conditionally renders the currency selection based on the recharge display setting.

web/src/components/table/model-pricing/layout/PricingSidebar.jsx (3)

30-69: Well-structured component coordination with effective hook usage.

The component effectively orchestrates multiple filter states and uses the usePricingFilterCounts hook appropriately to provide filtered data to each filter component. The extensive prop interface is appropriate for a coordinating component.


71-85: Proper delegation of reset logic to utility function.

The reset handler correctly delegates to the resetPricingFilters utility function with all necessary state setters, promoting code reusability and maintainability.


87-153: Clean and logical component render structure.

The sidebar layout is well-organized with appropriate visual hierarchy. Filter components are rendered in logical order and receive properly filtered data and state handlers.

web/src/components/table/model-pricing/view/card/PricingCardSkeleton.jsx (13)

23-27: Props structure looks good!

The component props are well-defined with sensible defaults. The destructuring pattern and default values follow React best practices.


87-100: Tag skeleton generation is well-implemented.

The dynamic tag count (2-4 tags per card) and consistent sizing create a realistic loading appearance.


132-134: Skeleton wrapper is correctly configured.

The Skeleton component is properly configured with loading={true} and active props to display the skeleton UI.


1-21: License header and imports look good.

The GPL license header is comprehensive and the imports are clean with no unused dependencies.


23-27: Component signature is well-structured.

The props are clearly named with appropriate default values that make sense for the skeleton loading use case.


28-76: Grid layout and card structure are well-implemented.

The responsive grid layout and dynamic width calculations for skeleton elements create a realistic loading experience. The card structure effectively mimics the actual content layout.


78-100: Description and tags sections are properly implemented.

The variable tag count (2 + index % 3) creates realistic variety in the skeleton layout, and the description paragraph skeleton is appropriately sized.


102-120: Ratio section conditional rendering is well-structured.

The ratio section properly uses conditional rendering and includes appropriate layout for title and 3-column ratio display when needed.


124-135: Pagination and wrapper implementation are correct.

The pagination skeleton is appropriately positioned and the wrapper Skeleton component uses the correct props for active loading state.


23-27: LGTM: Well-structured component props

The component props are properly typed with sensible defaults. The naming is clear and the default values are appropriate for typical usage scenarios.


50-64: Good use of dynamic widths for realistic skeleton effect

The varying widths based on card index (120 + (index % 3) * 30 and 160 + (index % 4) * 20) create a more realistic loading appearance by simulating different content lengths.


89-100: Excellent tag count variation

The dynamic tag count (2 + (index % 3)) effectively simulates realistic variation in the number of tags per model, enhancing the skeleton's authenticity.


132-134: Ignore unnecessary wrapper warning
The outer <Skeleton loading active placeholder={…}> is required to provide the loading and active context for all nested Skeleton.Avatar, Skeleton.Title, Skeleton.Paragraph, and Skeleton.Button components in your placeholder. If you simply return placeholder on its own, those inner skeletons won’t receive the context props and won’t render or animate correctly.

No changes needed.

Likely an incorrect or invalid review comment.

web/src/components/table/model-pricing/view/table/PricingTableColumns.js (8)

25-61: Helper functions are well-implemented.

Both renderQuotaType and renderSupportedEndpoints handle edge cases properly and use appropriate UI components with consistent styling.


63-86: Function structure and column assembly look good.

The getPricingTableColumns function is well-organized with clear parameter destructuring and logical column assembly. The conditional inclusion of the ratio column is handled correctly.


1-23: Imports and license header are appropriate.

Clean imports with necessary UI components and helper functions for table column functionality.


25-61: Helper functions are well-implemented.

Both renderQuotaType and renderSupportedEndpoints handle edge cases properly and use appropriate UI components with consistent styling.


63-107: Basic column definitions are comprehensive.

The function signature accommodates necessary customization parameters, and the basic columns (endpoint, model name, quota) are well-structured with appropriate handlers and features.


109-143: Ratio column implementation is comprehensive.

The column properly handles different ratio types and quota conditions, with helpful tooltip and modal integration for user guidance.


145-186: Price column and final assembly are well-structured.

The price column properly handles different pricing models, and the conditional column assembly logic ensures the correct columns are included based on configuration.


179-185: Good modular column construction

The conditional addition of the ratio column and the systematic building of the columns array demonstrates good modularity and maintainability.

web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx (7)

41-81: Memoization and state management are well-implemented.

The use of useMemo for SearchAndActions is appropriate to prevent unnecessary re-renders. The dependency array correctly includes all used props, and the local state for modal visibility is properly managed.


47-54: Excellent search input implementation with IME support.

The search input correctly handles composition events for better support of international input methods (IME), while also providing a clear button and search icon for enhanced UX.


86-106: Component integration is well-designed.

The integration with PricingCategoryIntroWithSkeleton and conditional rendering of PricingFilterModal for mobile devices follows good responsive design patterns. Props are correctly passed through to child components.


83-108: Component render structure is well-organized.

The JSX structure is clean with proper separation of concerns between category introduction, search/actions, and mobile filter modal.


111-111: Standard component export.


43-81: Excellent use of useMemo for performance optimization

The memoization of the SearchAndActions component with proper dependencies prevents unnecessary re-renders while maintaining reactivity to the required props.


47-54: Good composition event handling for search input

The proper handling of composition events (onCompositionStart and onCompositionEnd) ensures correct behavior for CJK (Chinese, Japanese, Korean) input methods.

web/src/components/table/model-pricing/layout/header/PricingCategoryIntro.jsx (9)

37-48: Carousel logic is well-implemented.

The carousel effect is properly controlled with appropriate conditions, interval management, and cleanup. The logic only activates for the "all" category with sufficient items, and properly cleans up the interval to prevent memory leaks.


87-168: Avatar rendering logic is comprehensive and well-implemented.

The carousel rotation logic, fallback handling, and AvatarGroup configuration are all well-designed. The use of key={currentOffset} ensures proper re-rendering for the carousel animation.


50-230: Component render structure is well-organized.

The conditional rendering logic, early returns for invalid states, and consistent Card styling create a robust and maintainable component structure. The responsive design and internationalization are properly implemented.


20-34: Component setup and initial state management are appropriate.

The component properly filters valid categories and initializes carousel state for animation purposes.


36-53: Animation useEffect is properly implemented.

The carousel animation logic correctly handles conditions, timing, and cleanup with an appropriate dependency array.


86-161: Complex avatar rendering logic is well-implemented.

The function handles multiple scenarios including array rotation for carousel effects, fallback cases, and proper overflow handling with AvatarGroup. The logic is complex but appears correct.


163-196: Single category avatar and all models rendering are well-structured.

The render functions use appropriate responsive design and maintain consistent layout patterns throughout the component.


199-232: Specific category rendering and export are appropriate.

The component properly handles specific category cases with null checking and maintains layout consistency. Standard export pattern is used.


37-48: Good interval cleanup and conditional logic

The useEffect properly manages the carousel interval with cleanup and conditional logic to prevent unnecessary intervals when not needed.

web/src/helpers/utils.js (2)

620-639: formatPriceInfo function is well-implemented.

The function properly handles both pricing types, uses theme-consistent colors, and integrates well with the translation system. The JSX structure is clean and semantic.


691-702: DEFAULT_PRICING_FILTERS constant is well-defined.

The default filter values are sensible and comprehensive, covering all the filter dimensions with appropriate initial states.

web/src/components/common/ui/SelectableButtonGroup.jsx (3)

40-51: LGTM: Well-structured props with good defaults.

The component props are well-defined with appropriate defaults and TypeScript-style JSDoc comments. The prop structure supports flexible usage patterns.


227-250: Verify collapsible behavior edge cases.

The collapsible logic has complex conditional rendering that could lead to layout shifts. Test with edge cases like exactly perRow * maxVisibleRows items.

The collapse/expand logic should be tested with boundary conditions to ensure smooth transitions and proper state management.


136-139: Disabled logic is intentional and correct.

All usages of SelectableButtonGroup that supply a numeric tagCount (e.g. quota types, endpoint types) deliberately disable items with zero associated models or counts, and components that shouldn’t ever be disabled (categories, display settings, currencies) omit tagCount and disabled so they remain selectable. No changes are required unless you specifically need to allow a zero-count item to be selectable—in that case you’d need to omit its tagCount or adjust the component logic to let an explicit disabled=false override the zero-count check.

web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1)

320-321: Verify pagination options are appropriate.

The page size options include 100, which might be too large for card view and could impact performance.

Consider whether 100 cards per page provides a good user experience, especially on mobile devices.

web/src/hooks/model-pricing/useModelPricingData.js (3)

28-42: LGTM: Well-structured state management.

The state variables are clearly named and properly initialized. The addition of filter states and modal management enhances the hook's functionality.


239-241: LGTM: Proper pagination reset on filter changes.

The effect properly resets pagination when any filter changes, preventing users from being stuck on empty pages.


212-222: Review automatic filter synchronization behavior

It’s clear from the inline comment (“同时将分组过滤设置为该分组”) that handleGroupClick in web/src/hooks/model-pricing/useModelPricingData.js (lines 212-222) intentionally does two things at once:

  • setSelectedGroup(group)
  • setFilterGroup(group)

Please verify whether this coupling aligns with the desired UX:

  • If selecting a group should only change pricing context, leave as-is.
  • If filtering the table should remain an independent control, remove the setFilterGroup call from handleGroupClick and implement filtering via a separate UI action.

Comment on lines +150 to +163
<Button
onClick={() => { /* disabled */ }}
theme={isActive ? 'light' : 'outline'}
type={isActive ? 'primary' : 'tertiary'}
disabled={isDisabled}
icon={
<Checkbox
checked={isActive}
onChange={() => onChange(item.value)}
disabled={isDisabled}
style={{ pointerEvents: 'auto' }}
/>
}
style={{ width: '100%', cursor: 'default' }}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix accessibility issue with disabled button click handler.

The button with checkbox has an empty onClick handler, which creates an accessibility issue. Users might expect the button to be clickable or it should be properly disabled.

- onClick={() => { /* disabled */ }}
+ onClick={(e) => {
+   e.preventDefault();
+   e.stopPropagation();
+ }}

Or better yet, remove the onClick entirely if the button shouldn't be clickable:

- onClick={() => { /* disabled */ }}
🤖 Prompt for AI Agents
In web/src/components/common/ui/SelectableButtonGroup.jsx around lines 150 to
163, the Button component has an empty onClick handler which causes
accessibility issues. Remove the onClick prop entirely if the button is disabled
or not meant to be clickable, and ensure the disabled prop is set correctly to
prevent user interaction and improve accessibility.

Comment on lines +58 to +84
const getCategoryDescription = (categoryKey) => {
const descriptions = {
all: t('查看所有可用的AI模型,包括文本生成、图像处理、音频转换等多种类型的模型。'),
openai: t('令牌分发介绍:SSVIP 为纯OpenAI官方。SVIP 为纯Azure。Default 为Azure 消费。VIP为近似的复数。VVIP为近似的书发。'),
anthropic: t('Anthropic Claude系列模型,以安全性和可靠性著称,擅长对话、分析和创作任务。'),
gemini: t('Google Gemini系列模型,具备强大的多模态能力,支持文本、图像和代码理解。'),
moonshot: t('月之暗面Moonshot系列模型,专注于长文本处理和深度理解能力。'),
zhipu: t('智谱AI ChatGLM系列模型,在中文理解和生成方面表现优秀。'),
qwen: t('阿里云通义千问系列模型,覆盖多个领域的智能问答和内容生成。'),
deepseek: t('DeepSeek系列模型,在代码生成和数学推理方面具有出色表现。'),
minimax: t('MiniMax ABAB系列模型,专注于对话和内容创作的AI助手。'),
baidu: t('百度文心一言系列模型,在中文自然语言处理方面具有强大能力。'),
xunfei: t('科大讯飞星火系列模型,在语音识别和自然语言理解方面领先。'),
midjourney: t('Midjourney图像生成模型,专业的AI艺术创作和图像生成服务。'),
tencent: t('腾讯混元系列模型,提供全面的AI能力和企业级服务。'),
cohere: t('Cohere Command系列模型,专注于企业级自然语言处理应用。'),
cloudflare: t('Cloudflare Workers AI模型,提供边缘计算和高性能AI服务。'),
ai360: t('360智脑系列模型,在安全和智能助手方面具有独特优势。'),
yi: t('零一万物Yi系列模型,提供高质量的多语言理解和生成能力。'),
jina: t('Jina AI模型,专注于嵌入和向量搜索的AI解决方案。'),
mistral: t('Mistral AI系列模型,欧洲领先的开源大语言模型。'),
xai: t('xAI Grok系列模型,具有独特的幽默感和实时信息处理能力。'),
llama: t('Meta Llama系列模型,开源的大语言模型,在各种任务中表现优秀。'),
doubao: t('字节跳动豆包系列模型,在内容创作和智能对话方面表现出色。'),
};
return descriptions[categoryKey] || t('该分类包含多种AI模型,适用于不同的应用场景。');
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting category descriptions to external configuration

The hardcoded category descriptions make the component less maintainable and harder to internationalize. Consider moving these to a separate configuration file or using a more structured approach.

Extract descriptions to a separate configuration:

+// Create a new file: categoryDescriptions.js
+export const getCategoryDescriptions = (t) => ({
+  all: t('查看所有可用的AI模型,包括文本生成、图像处理、音频转换等多种类型的模型。'),
+  openai: t('令牌分发介绍:SSVIP 为纯OpenAI官方。SVIP 为纯Azure。Default 为Azure 消费。VIP为近似的复数。VVIP为近似的书发。'),
+  // ... other descriptions
+});

-  // 获取分类描述信息
-  const getCategoryDescription = (categoryKey) => {
-    const descriptions = {
-      all: t('查看所有可用的AI模型...'),
-      // ... all hardcoded descriptions
-    };
-    return descriptions[categoryKey] || t('该分类包含多种AI模型,适用于不同的应用场景。');
-  };
+  const getCategoryDescription = (categoryKey) => {
+    const descriptions = getCategoryDescriptions(t);
+    return descriptions[categoryKey] || t('该分类包含多种AI模型,适用于不同的应用场景。');
+  };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In web/src/components/table/model-pricing/layout/header/PricingCategoryIntro.jsx
around lines 58 to 84, the category descriptions are hardcoded inside the
component, which reduces maintainability and complicates internationalization.
To fix this, extract the descriptions object into a separate configuration file
or module that exports these mappings. Then import this configuration into the
component and use it to retrieve descriptions, enabling easier updates and
better support for localization.

Comment on lines +126 to +140
<AvatarGroup
maxCount={4}
size="default"
overlapFrom='end'
key={currentOffset}
renderMore={(restNumber) => (
<Avatar
size="default"
style={{ backgroundColor: 'transparent', color: 'var(--semi-color-text-0)' }}
alt={`${restNumber} more categories`}
>
{`+${restNumber}`}
</Avatar>
)}
>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Accessibility improvement needed for carousel

The carousel functionality lacks accessibility features such as pause on hover, keyboard navigation, or screen reader announcements of changes.

Add accessibility features:

     return (
       <div className="min-w-16 h-16 rounded-2xl bg-white shadow-md flex items-center justify-center px-2">
         <AvatarGroup
           maxCount={4}
           size="default"
           overlapFrom='end'
           key={currentOffset}
+          role="img"
+          aria-label={t('模型分类头像轮播')}
+          aria-live="polite"
           renderMore={(restNumber) => (
             <Avatar
               size="default"
               style={{ backgroundColor: 'transparent', color: 'var(--semi-color-text-0)' }}
               alt={`${restNumber} more categories`}
+              aria-label={t('还有{{count}}个分类', { count: restNumber })}
             >
               {`+${restNumber}`}
             </Avatar>
           )}
         >

Also consider adding pause on hover:

   useEffect(() => {
     if (activeKey !== 'all' || validCategories.length <= 3) {
       setCurrentOffset(0);
       return;
     }

+    let interval;
+    const startInterval = () => {
+      interval = setInterval(() => {
+        setCurrentOffset(prev => (prev + 1) % validCategories.length);
+      }, 2000);
+    };
+
+    startInterval();
-    const interval = setInterval(() => {
-      setCurrentOffset(prev => (prev + 1) % validCategories.length);
-    }, 2000);

     return () => clearInterval(interval);
   }, [activeKey, validCategories.length]);
🤖 Prompt for AI Agents
In web/src/components/table/model-pricing/layout/header/PricingCategoryIntro.jsx
around lines 126 to 140, the AvatarGroup carousel lacks accessibility features
like pause on hover, keyboard navigation, and screen reader announcements. To
fix this, implement keyboard event handlers to allow navigation with arrow keys,
add aria-live regions or similar for screen reader updates when the carousel
changes, and add event handlers to pause the carousel animation or auto-scroll
when the user hovers over it. Ensure all interactive elements have appropriate
ARIA roles and labels.

Comment on lines +43 to +81
const SearchAndActions = useMemo(() => (
<div className="flex items-center gap-4 w-full">
{/* 搜索框 */}
<div className="flex-1">
<Input
prefix={<IconSearch />}
placeholder={t('模糊搜索模型名称')}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onChange={handleChange}
showClear
/>
</div>

{/* 操作按钮 */}
<Button
theme='outline'
type='primary'
icon={<IconCopy />}
onClick={() => copyText(selectedRowKeys)}
disabled={selectedRowKeys.length === 0}
className="!bg-blue-500 hover:!bg-blue-600 text-white"
>
{t('复制')}
</Button>

{/* 移动端筛选按钮 */}
{isMobile && (
<Button
theme="outline"
type='tertiary'
icon={<IconFilter />}
onClick={() => setShowFilterModal(true)}
>
{t('筛选')}
</Button>
)}
</div>
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Optimize useMemo implementation and fix button styling conflict.

The useMemo optimization is well-implemented with correct dependencies. However, there's a styling conflict in the copy button:

      <Button
-        theme='outline'
-        type='primary'
+        theme='solid'
+        type='primary'
        icon={<IconCopy />}
        onClick={() => copyText(selectedRowKeys)}
        disabled={selectedRowKeys.length === 0}
-        className="!bg-blue-500 hover:!bg-blue-600 text-white"
      >

The theme='outline' conflicts with the forced background colors. Use theme='solid' instead or remove the custom className and rely on the theme styling.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const SearchAndActions = useMemo(() => (
<div className="flex items-center gap-4 w-full">
{/* 搜索框 */}
<div className="flex-1">
<Input
prefix={<IconSearch />}
placeholder={t('模糊搜索模型名称')}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onChange={handleChange}
showClear
/>
</div>
{/* 操作按钮 */}
<Button
theme='outline'
type='primary'
icon={<IconCopy />}
onClick={() => copyText(selectedRowKeys)}
disabled={selectedRowKeys.length === 0}
className="!bg-blue-500 hover:!bg-blue-600 text-white"
>
{t('复制')}
</Button>
{/* 移动端筛选按钮 */}
{isMobile && (
<Button
theme="outline"
type='tertiary'
icon={<IconFilter />}
onClick={() => setShowFilterModal(true)}
>
{t('筛选')}
</Button>
)}
</div>
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile]);
const SearchAndActions = useMemo(() => (
<div className="flex items-center gap-4 w-full">
{/* 搜索框 */}
<div className="flex-1">
<Input
prefix={<IconSearch />}
placeholder={t('模糊搜索模型名称')}
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
onChange={handleChange}
showClear
/>
</div>
{/* 操作按钮 */}
<Button
theme='solid'
type='primary'
icon={<IconCopy />}
onClick={() => copyText(selectedRowKeys)}
disabled={selectedRowKeys.length === 0}
>
{t('复制')}
</Button>
{/* 移动端筛选按钮 */}
{isMobile && (
<Button
theme="outline"
type='tertiary'
icon={<IconFilter />}
onClick={() => setShowFilterModal(true)}
>
{t('筛选')}
</Button>
)}
</div>
), [selectedRowKeys, t, handleCompositionStart, handleCompositionEnd, handleChange, copyText, isMobile]);
🤖 Prompt for AI Agents
In web/src/components/table/model-pricing/layout/header/PricingTopSection.jsx
around lines 43 to 81, the copy button uses theme='outline' but also applies
forced background colors via className, causing a styling conflict. To fix this,
change the button's theme from 'outline' to 'solid' to align with the custom
background colors, or alternatively remove the custom className and rely solely
on the theme styling for consistent appearance.

Comment thread web/src/components/table/model-pricing/view/card/PricingCardView.jsx Outdated
Comment on lines +104 to +107
onRow={(record) => ({
onClick: () => openModelDetail && openModelDetail(record),
style: { cursor: 'pointer' }
})}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify click handler doesn't interfere with row selection.

The row click handler for opening model details might conflict with row selection functionality if both are active simultaneously.


🏁 Script executed:

#!/bin/bash
# Check if this table is used with row selection and click handlers simultaneously
ast-grep --pattern 'rowSelection={$_}' | head -10
rg -A 5 -B 5 "PricingTable.*rowSelection"

Length of output: 95


I wasn’t able to find any occurrences of rowSelection or its use with PricingTable. Let’s search the entire repo for both the prop and component usages to be sure:


🏁 Script executed:

#!/bin/bash
# 1) Find every usage of rowSelection prop
rg --color=auto -n "rowSelection" 

# 2) Find all imports and JSX usages of PricingTable
rg --color=auto -n "<PricingTable" 
rg --color=auto -n "import .*PricingTable" 

Length of output: 3636


🏁 Script executed:

#!/bin/bash
# Show context around rowSelection in PricingTable
rg -n -A 5 -B 5 "rowSelection" web/src/components/table/model-pricing/view/table/PricingTable.jsx

# Show context around onRow in PricingTable
rg -n -A 5 -B 5 "onRow" web/src/components/table/model-pricing/view/table/PricingTable.jsx

Length of output: 1697


Prevent detail view opening when selecting a row

Because you’re passing both rowSelection and a blanket onRow.onClick, clicking the selection checkbox will also trigger openModelDetail. To avoid this, scope the click handler so it only fires when clicking non‐selection areas:

• File: web/src/components/table/model-pricing/view/table/PricingTable.jsx
Lines: 104–107

Proposed change:

- onRow={(record) => ({
-   onClick: () => openModelDetail && openModelDetail(record),
-   style: { cursor: 'pointer' }
- })}
+ onRow={(record) => ({
+   onClick: (e) => {
+     // ignore clicks on the selection checkbox column
+     if (e.target.closest('.ant-checkbox-wrapper, .ant-table-selection-column')) {
+       return;
+     }
+     openModelDetail?.(record);
+   },
+   style: { cursor: 'pointer' }
+ })}

Verify in the browser that selecting rows still works without opening the detail pane.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onRow={(record) => ({
onClick: () => openModelDetail && openModelDetail(record),
style: { cursor: 'pointer' }
})}
onRow={(record) => ({
onClick: (e) => {
// ignore clicks on the selection checkbox column
if (e.target.closest('.ant-checkbox-wrapper, .ant-table-selection-column')) {
return;
}
openModelDetail?.(record);
},
style: { cursor: 'pointer' }
})}
🤖 Prompt for AI Agents
In web/src/components/table/model-pricing/view/table/PricingTable.jsx lines 104
to 107, the onRow onClick handler triggers openModelDetail even when clicking
the row selection checkbox. Modify the onClick handler to check the event target
and only call openModelDetail if the click is outside the selection checkbox
area. This prevents the detail view from opening when selecting rows. After the
change, verify in the browser that row selection still works correctly without
opening the detail pane.

Comment on lines +109 to +143
const ratioColumn = {
title: () => (
<div className="flex items-center space-x-1">
<span>{t('倍率')}</span>
<Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
<IconHelpCircle
className="text-blue-500 cursor-pointer"
onClick={() => {
setModalImageUrl('/ratio.png');
setIsModalOpenurl(true);
}}
/>
</Tooltip>
</div>
),
dataIndex: 'model_ratio',
render: (text, record, index) => {
const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
const content = (
<div className="space-y-1">
<div className="text-gray-700">
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
</div>
<div className="text-gray-700">
{t('补全倍率')}:
{record.quota_type === 0 ? completionRatio : t('无')}
</div>
<div className="text-gray-700">
{t('分组倍率')}:{groupRatio[selectedGroup]}
</div>
</div>
);
return content;
},
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null safety for completion_ratio access.

The ratio column accesses record.completion_ratio.toFixed(3) without checking if completion_ratio exists, which could cause runtime errors.

 const ratioColumn = {
   // ... title definition
   render: (text, record, index) => {
-    const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
+    const completionRatio = record.completion_ratio 
+      ? parseFloat(record.completion_ratio.toFixed(3)) 
+      : 0;
     const content = (
       <div className="space-y-1">
         <div className="text-gray-700">
           {t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
         </div>
         <div className="text-gray-700">
           {t('补全倍率')}:
-          {record.quota_type === 0 ? completionRatio : t('无')}
+          {record.quota_type === 0 && record.completion_ratio ? completionRatio : t('无')}
         </div>
         <div className="text-gray-700">
           {t('分组倍率')}:{groupRatio[selectedGroup]}
         </div>
       </div>
     );
     return content;
   },
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ratioColumn = {
title: () => (
<div className="flex items-center space-x-1">
<span>{t('倍率')}</span>
<Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
<IconHelpCircle
className="text-blue-500 cursor-pointer"
onClick={() => {
setModalImageUrl('/ratio.png');
setIsModalOpenurl(true);
}}
/>
</Tooltip>
</div>
),
dataIndex: 'model_ratio',
render: (text, record, index) => {
const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
const content = (
<div className="space-y-1">
<div className="text-gray-700">
{t('模型倍率')}{record.quota_type === 0 ? text : t('无')}
</div>
<div className="text-gray-700">
{t('补全倍率')}
{record.quota_type === 0 ? completionRatio : t('无')}
</div>
<div className="text-gray-700">
{t('分组倍率')}{groupRatio[selectedGroup]}
</div>
</div>
);
return content;
},
};
const ratioColumn = {
title: () => (
<div className="flex items-center space-x-1">
<span>{t('倍率')}</span>
<Tooltip content={t('倍率是为了方便换算不同价格的模型')}>
<IconHelpCircle
className="text-blue-500 cursor-pointer"
onClick={() => {
setModalImageUrl('/ratio.png');
setIsModalOpenurl(true);
}}
/>
</Tooltip>
</div>
),
dataIndex: 'model_ratio',
render: (text, record, index) => {
- const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
+ const completionRatio = record.completion_ratio
+ ? parseFloat(record.completion_ratio.toFixed(3))
+ : 0;
const content = (
<div className="space-y-1">
<div className="text-gray-700">
{t('模型倍率')}{record.quota_type === 0 ? text : t('无')}
</div>
<div className="text-gray-700">
{t('补全倍率')}
- {record.quota_type === 0 ? completionRatio : t('无')}
+ {record.quota_type === 0 && record.completion_ratio ? completionRatio : t('无')}
</div>
<div className="text-gray-700">
{t('分组倍率')}{groupRatio[selectedGroup]}
</div>
</div>
);
return content;
},
};
🤖 Prompt for AI Agents
In web/src/components/table/model-pricing/view/table/PricingTableColumns.js
between lines 109 and 143, the code accesses record.completion_ratio.toFixed(3)
without verifying if completion_ratio is defined, risking runtime errors. Add a
null check to ensure completion_ratio exists and is a number before calling
toFixed; if not, provide a safe fallback value like 0 or an empty string to
prevent errors.

Comment on lines +125 to +143
render: (text, record, index) => {
const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
const content = (
<div className="space-y-1">
<div className="text-gray-700">
{t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
</div>
<div className="text-gray-700">
{t('补全倍率')}:
{record.quota_type === 0 ? completionRatio : t('无')}
</div>
<div className="text-gray-700">
{t('分组倍率')}:{groupRatio[selectedGroup]}
</div>
</div>
);
return content;
},
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential null reference error in ratio calculation

The code accesses record.completion_ratio.toFixed(3) without null checking, which could cause runtime errors if the property is undefined.

Add null safety checks:

     render: (text, record, index) => {
-      const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
+      const completionRatio = record.completion_ratio 
+        ? parseFloat(record.completion_ratio.toFixed(3)) 
+        : 0;
       const content = (
         <div className="space-y-1">
           <div className="text-gray-700">
             {t('模型倍率')}:{record.quota_type === 0 ? text : t('无')}
           </div>
           <div className="text-gray-700">
             {t('补全倍率')}:
-            {record.quota_type === 0 ? completionRatio : t('无')}
+            {record.quota_type === 0 && record.completion_ratio ? completionRatio : t('无')}
           </div>
           <div className="text-gray-700">
-            {t('分组倍率')}:{groupRatio[selectedGroup]}
+            {t('分组倍率')}:{groupRatio?.[selectedGroup] || t('无')}
           </div>
         </div>
       );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
render: (text, record, index) => {
const completionRatio = parseFloat(record.completion_ratio.toFixed(3));
const content = (
<div className="space-y-1">
<div className="text-gray-700">
{t('模型倍率')}{record.quota_type === 0 ? text : t('无')}
</div>
<div className="text-gray-700">
{t('补全倍率')}
{record.quota_type === 0 ? completionRatio : t('无')}
</div>
<div className="text-gray-700">
{t('分组倍率')}{groupRatio[selectedGroup]}
</div>
</div>
);
return content;
},
};
render: (text, record, index) => {
const completionRatio = record.completion_ratio
? parseFloat(record.completion_ratio.toFixed(3))
: 0;
const content = (
<div className="space-y-1">
<div className="text-gray-700">
{t('模型倍率')}{record.quota_type === 0 ? text : t('无')}
</div>
<div className="text-gray-700">
{t('补全倍率')}
{record.quota_type === 0 && record.completion_ratio
? completionRatio
: t('无')}
</div>
<div className="text-gray-700">
{t('分组倍率')}{groupRatio?.[selectedGroup] || t('无')}
</div>
</div>
);
return content;
},
};
🤖 Prompt for AI Agents
In web/src/components/table/model-pricing/view/table/PricingTableColumns.js
around lines 125 to 143, the code calls toFixed(3) on record.completion_ratio
without checking if completion_ratio is null or undefined, risking runtime
errors. Fix this by adding a null safety check before calling toFixed, for
example, verify that record.completion_ratio is a valid number before formatting
it, and provide a fallback value if it is null or undefined.

Comment thread web/src/helpers/utils.js
Comment on lines +577 to +617
export const calculateModelPrice = ({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
precision = 4
}) => {
if (record.quota_type === 0) {
// 按量计费
const inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];

const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';

const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);

const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;

return {
inputPrice: `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(precision)}`,
completionPrice: `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(precision)}`,
unitLabel,
isPerToken: true
};
} else {
// 按次计费
const priceUSD = parseFloat(record.model_price) * groupRatio[selectedGroup];
const displayVal = displayPrice(priceUSD);

return {
price: displayVal,
isPerToken: false
};
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve numeric value extraction robustness.

The numeric extraction using /[^0-9.]/g regex might not handle all currency formats correctly (e.g., numbers with commas, different decimal separators).

-  const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
-  const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
+  const extractNumericValue = (priceString) => {
+    // Remove currency symbols and spaces, handle both comma and dot as decimal separators
+    const cleaned = priceString.replace(/[^\d.,]/g, '').replace(/,/g, '.');
+    return parseFloat(cleaned) || 0;
+  };
+  
+  const numInput = extractNumericValue(rawDisplayInput) / unitDivisor;
+  const numCompletion = extractNumericValue(rawDisplayCompletion) / unitDivisor;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const calculateModelPrice = ({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
precision = 4
}) => {
if (record.quota_type === 0) {
// 按量计费
const inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor;
const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor;
return {
inputPrice: `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(precision)}`,
completionPrice: `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(precision)}`,
unitLabel,
isPerToken: true
};
} else {
// 按次计费
const priceUSD = parseFloat(record.model_price) * groupRatio[selectedGroup];
const displayVal = displayPrice(priceUSD);
return {
price: displayVal,
isPerToken: false
};
}
};
export const calculateModelPrice = ({
record,
selectedGroup,
groupRatio,
tokenUnit,
displayPrice,
currency,
precision = 4
}) => {
if (record.quota_type === 0) {
// 按量计费
const inputRatioPriceUSD = record.model_ratio * 2 * groupRatio[selectedGroup];
const completionRatioPriceUSD =
record.model_ratio * record.completion_ratio * 2 * groupRatio[selectedGroup];
const unitDivisor = tokenUnit === 'K' ? 1000 : 1;
const unitLabel = tokenUnit === 'K' ? 'K' : 'M';
const rawDisplayInput = displayPrice(inputRatioPriceUSD);
const rawDisplayCompletion = displayPrice(completionRatioPriceUSD);
const extractNumericValue = (priceString) => {
// Remove currency symbols and spaces, handle both comma and dot as decimal separators
const cleaned = priceString.replace(/[^\d.,]/g, '').replace(/,/g, '.');
return parseFloat(cleaned) || 0;
};
const numInput = extractNumericValue(rawDisplayInput) / unitDivisor;
const numCompletion = extractNumericValue(rawDisplayCompletion) / unitDivisor;
return {
inputPrice: `${currency === 'CNY' ? '¥' : '$'}${numInput.toFixed(precision)}`,
completionPrice: `${currency === 'CNY' ? '¥' : '$'}${numCompletion.toFixed(precision)}`,
unitLabel,
isPerToken: true
};
} else {
// 按次计费
const priceUSD = parseFloat(record.model_price) * groupRatio[selectedGroup];
const displayVal = displayPrice(priceUSD);
return {
price: displayVal,
isPerToken: false
};
}
};
🤖 Prompt for AI Agents
In web/src/helpers/utils.js around lines 577 to 617, the numeric extraction from
the displayPrice output uses a regex that removes all non-digit and non-dot
characters, which may fail for numbers with commas or different decimal
separators. To fix this, update the extraction logic to handle commas by
removing them before parsing and ensure the decimal separator is correctly
interpreted, possibly by normalizing the string to a standard format before
parsing with parseFloat.

t0ng7u added 3 commits July 27, 2025 09:51
…ntend) and UI refinements

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.
@Calcium-Ion Calcium-Ion self-assigned this Aug 6, 2025
@t0ng7u t0ng7u changed the title ♻️ refactor(model-pricing): refactor the model pricing page (to be implemented in conjunction with the new model logic on the backend) 🤓feat: the model management module Aug 6, 2025
t0ng7u added 4 commits August 7, 2025 10:54
Add visual distinction for enabled/disabled models by applying different
background colors to table rows based on model status. This implementation
follows the same pattern used in ChannelsTable for consistent user experience.

Changes:
- Modified handleRow function in useModelsData.js to include row styling
- Disabled models (status !== 1) now display with gray background using
  --semi-color-disabled-border CSS variable
- Enabled models (status === 1) maintain normal background color
- Preserved existing row click selection functionality

This enhancement improves the visual feedback for users to quickly identify
which models are active vs inactive in the models management interface.
… and overhaul visual JSON editor

Backend (Go)
- Include custom endpoints in each model’s SupportedEndpointTypes by parsing Model.Endpoints (JSON) and appending keys alongside native endpoint types.
- Build a global supportedEndpointMap map[string]EndpointInfo{path, method} by:
  - Seeding with native defaults.
  - Overriding/adding from models.endpoints (accepts string path → default POST, or {path, method}).
- Expose supported_endpoint at the top level of /api/pricing (vendors-like), removing per-model duplication.
- Fix default path for EndpointTypeOpenAIResponse to /v1/responses.
- Keep concurrency/caching for pricing retrieval intact.

Frontend (React)
- Fetch supported_endpoint in useModelPricingData and propagate to PricingPage → ModelDetailSideSheet → ModelEndpoints.
- ModelEndpoints
  - Resolve path+method via endpointMap; replace {model} with actual model name.
  - Fix mobile visibility; always show path and HTTP method.
- JSONEditor
  - Wrap with Form.Slot to inherit form layout; simplify visual styles.
  - Use Tabs for “Visual” / “Manual” modes.
  - Unify editors: key-value editor now supports nested JSON:
    - “+” to convert a primitive into an object and add nested fields.
    - Add “Convert to value” for two‑way toggle back from object.
    - Stable key rename without reordering rows; new rows append at bottom.
    - Use Row/Col grid for clean alignment; region editor uses Form.Slot + grid.
- Editing flows
  - EditModelModal / EditPrefillGroupModal use JSONEditor (editorType='object') for endpoint mappings.
  - PrefillGroupManagement renders endpoint group items by JSON keys.

Data expectations / compatibility
- models.endpoints should be a JSON object mapping endpoint type → string path or {path, method}. Strings default to POST.
- No schema changes; existing TEXT field continues to store JSON.

QA
- /api/pricing now returns custom endpoint types and global supported_endpoint.
- UI shows both native and custom endpoints; paths/methods render on mobile; nested editing works and preserves order.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🔭 Outside diff range comments (1)
web/src/hooks/model-pricing/useModelPricingData.js (1)

159-182: Missing error handling in loadPricing function.

The API call lacks comprehensive error handling for network failures or malformed responses.

 const loadPricing = async () => {
   setLoading(true);
-  let url = '/api/pricing';
-  const res = await API.get(url);
-  const { success, message, data, vendors, group_ratio, usable_group, supported_endpoint } = res.data;
-  if (success) {
-    setGroupRatio(group_ratio);
-    setUsableGroup(usable_group);
-    setSelectedGroup(userState.user ? userState.user.group : 'default');
-    // 构建供应商 Map 方便查找
-    const vendorMap = {};
-    if (Array.isArray(vendors)) {
-      vendors.forEach(v => {
-        vendorMap[v.id] = v;
-      });
-    }
-    setVendorsMap(vendorMap);
-    setEndpointMap(supported_endpoint || {});
-    setModelsFormat(data, group_ratio, vendorMap);
-  } else {
-    showError(message);
-  }
-  setLoading(false);
+  try {
+    let url = '/api/pricing';
+    const res = await API.get(url);
+    const { success, message, data, vendors, group_ratio, usable_group, supported_endpoint } = res.data;
+    if (success) {
+      setGroupRatio(group_ratio);
+      setUsableGroup(usable_group);
+      setSelectedGroup(userState.user ? userState.user.group : 'default');
+      // 构建供应商 Map 方便查找
+      const vendorMap = {};
+      if (Array.isArray(vendors)) {
+        vendors.forEach(v => {
+          vendorMap[v.id] = v;
+        });
+      }
+      setVendorsMap(vendorMap);
+      setEndpointMap(supported_endpoint || {});
+      setModelsFormat(data || [], group_ratio, vendorMap);
+    } else {
+      showError(message || t('加载定价数据失败'));
+    }
+  } catch (error) {
+    console.error('Failed to load pricing:', error);
+    showError(t('网络错误,请重试'));
+  } finally {
+    setLoading(false);
+  }
 };
♻️ Duplicate comments (2)
model/pricing.go (2)

98-99: Database error handling still needs improvement.

The database operations continue to ignore errors using _ = DB.Find(&...).Error pattern. This can lead to silent failures and data inconsistency issues.

-_ = DB.Find(&allMeta).Error
+if err := DB.Find(&allMeta).Error; err != nil {
+    common.SysError(fmt.Sprintf("Failed to load model metadata: %v", err))
+    return
+}

150-151: Database error handling still needs improvement.

Similar to the previous issue, vendor loading also ignores database errors.

-_ = DB.Find(&vendors).Error
+if err := DB.Find(&vendors).Error; err != nil {
+    common.SysError(fmt.Sprintf("Failed to load vendor data: %v", err))
+    return
+}
🧹 Nitpick comments (5)
web/src/hooks/model-pricing/useModelPricingData.js (2)

22-22: Import includes unused functions.

The import statement includes showError, showInfo, and showSuccess from helpers, but only showError and showSuccess appear to be used in the code. Consider removing showInfo to keep imports clean.

-import { API, copy, showError, showInfo, showSuccess } from '../../helpers';
+import { API, copy, showError, showSuccess } from '../../helpers';

60-102: Optimize filtering logic with early returns.

The filtering logic correctly implements multi-dimensional filtering. However, it can be optimized with early returns to avoid unnecessary iterations when the result set is empty.

 const filteredModels = useMemo(() => {
   let result = models;
+  
+  // Early return if no models
+  if (result.length === 0) {
+    return result;
+  }

   // 分组筛选
   if (filterGroup !== 'all') {
     result = result.filter(model => model.enable_groups.includes(filterGroup));
+    if (result.length === 0) return result;
   }

   // 计费类型筛选
   if (filterQuotaType !== 'all') {
     result = result.filter(model => model.quota_type === filterQuotaType);
+    if (result.length === 0) return result;
   }

   // 端点类型筛选
   if (filterEndpointType !== 'all') {
     result = result.filter(model =>
       model.supported_endpoint_types &&
       model.supported_endpoint_types.includes(filterEndpointType)
     );
+    if (result.length === 0) return result;
   }

   // 供应商筛选
   if (filterVendor !== 'all') {
     if (filterVendor === 'unknown') {
       result = result.filter(model => !model.vendor_name);
     } else {
       result = result.filter(model => model.vendor_name === filterVendor);
     }
+    if (result.length === 0) return result;
   }

   // 搜索筛选
   if (searchValue.length > 0) {
     const searchTerm = searchValue.toLowerCase();
     result = result.filter(model =>
       (model.model_name && model.model_name.toLowerCase().includes(searchTerm)) ||
       (model.description && model.description.toLowerCase().includes(searchTerm)) ||
       (model.tags && model.tags.toLowerCase().includes(searchTerm)) ||
       (model.vendor_name && model.vendor_name.toLowerCase().includes(searchTerm))
     );
   }

   return result;
 }, [models, searchValue, filterGroup, filterQuotaType, filterEndpointType, filterVendor]);
web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (1)

37-40: Consider fallback for model name extraction.

The model name extraction has good fallback logic but could benefit from an additional fallback to avoid empty replacements.

 if (path.includes('{model}')) {
-  const modelName = modelData.model_name || modelData.modelName || '';
+  const modelName = modelData.model_name || modelData.modelName || 'unknown-model';
   path = path.replaceAll('{model}', modelName);
 }
web/src/components/common/ui/JSONEditor.js (2)

27-41: Consider adding PropTypes for type safety

The component accepts many props but lacks type definitions. This could lead to runtime errors and makes the component harder to use correctly.

Add PropTypes after the component definition:

import PropTypes from 'prop-types';

// After the component definition
JSONEditor.propTypes = {
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
  onChange: PropTypes.func,
  field: PropTypes.string,
  label: PropTypes.string,
  placeholder: PropTypes.string,
  extraText: PropTypes.node,
  showClear: PropTypes.bool,
  template: PropTypes.object,
  templateLabel: PropTypes.string,
  editorType: PropTypes.oneOf(['keyValue', 'region', 'object']),
  rules: PropTypes.array,
  formApi: PropTypes.object,
};

217-217: Translate comment to English

-  // 渲染键值对编辑器
+  // Render key-value editor
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cfeb6e and 26f44b8.

📒 Files selected for processing (11)
  • common/endpoint_defaults.go (1 hunks)
  • controller/pricing.go (1 hunks)
  • model/pricing.go (5 hunks)
  • web/src/components/common/ui/JSONEditor.js (1 hunks)
  • web/src/components/table/model-pricing/layout/PricingPage.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/ModelDetailSideSheet.jsx (1 hunks)
  • web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (1 hunks)
  • web/src/components/table/models/modals/EditModelModal.jsx (1 hunks)
  • web/src/components/table/models/modals/EditPrefillGroupModal.jsx (1 hunks)
  • web/src/components/table/models/modals/PrefillGroupManagement.jsx (1 hunks)
  • web/src/hooks/model-pricing/useModelPricingData.js (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • controller/pricing.go
  • web/src/components/table/models/modals/EditPrefillGroupModal.jsx
  • web/src/components/table/model-pricing/layout/PricingPage.jsx
  • web/src/components/table/model-pricing/modal/ModelDetailSideSheet.jsx
  • web/src/components/table/models/modals/PrefillGroupManagement.jsx
  • web/src/components/table/models/modals/EditModelModal.jsx
🔇 Additional comments (10)
web/src/hooks/model-pricing/useModelPricingData.js (2)

126-157: LGTM! Well-implemented vendor enrichment logic.

The setModelsFormat function correctly enriches model data with vendor information and implements proper sorting. The dual sorting approach (quota type first, then model name with GPT priority) provides a good user experience.


242-244: LGTM! Proper pagination reset on filter changes.

The effect correctly resets pagination to page 1 when any filter changes, ensuring consistent user experience.

common/endpoint_defaults.go (3)

13-16: LGTM! Well-defined struct with appropriate JSON tags.

The EndpointInfo struct is well-designed with clear field names and proper JSON tags for API serialization.


29-32: LGTM! Clean and efficient getter function.

The GetDefaultEndpointInfo function follows Go best practices with the comma ok idiom for safe map access.


19-26: Endpoint mapping coverage confirmed — verify path accuracy against API specs

All EndpointType constants in constant/endpoint_type.go are present in the defaultEndpointInfoMap (common/endpoint_defaults.go) and used consistently. Please double-check that each path matches the official API documentation:

  • common/endpoint_defaults.go
    • Gemini: /v1beta/models/{model}:generateContent
    – Ensure {model} placeholder aligns with the spec and substitution logic.
    • Anthropic: /v1/messages
    • Jina Rerank: /rerank
    • Image Generation: /v1/images/generations
web/src/components/table/model-pricing/modal/components/ModelEndpoints.jsx (1)

27-65: LGTM! Significant improvement over previous implementation.

The renderAPIEndpoints function has been substantially improved from the previous version:

  1. Eliminated hardcoded values: Now uses dynamic endpointMap instead of hardcoded URLs and methods
  2. Removed inefficient processing: Direct map operation instead of forEach + push pattern
  3. Smart placeholder replacement: Properly handles {model} placeholders in paths
  4. Better conditional rendering: Only shows path and method when available
  5. Improved responsive design: Better mobile layout with text wrapping

The implementation effectively addresses all the concerns raised in the previous review.

model/pricing.go (4)

104-147: LGTM! Well-implemented model matching logic.

The model metadata matching logic correctly handles different name rules (exact, prefix, suffix, contains) with proper precedence. The use of separate slices for non-exact rules and the existence check prevents overwrites, ensuring exact matches take priority.


221-258: Robust endpoint mapping construction.

The endpoint mapping construction logic effectively combines default and custom endpoints. The JSON parsing with type assertions and fallback values (defaulting to "POST" method) makes the system resilient to malformed data.


270-277: Good security practice with status filtering.

The status check (meta.Status != 1) before including models in the pricing response is a good security practice that prevents disabled models from being exposed to the frontend.


292-299: Proper concurrent access protection.

The cache refresh logic correctly uses write locks to protect the shared maps (modelEnableGroups and modelQuotaTypeMap) during updates, ensuring thread safety.

Comment on lines +44 to +75
// 初始化JSON数据
const [jsonData, setJsonData] = useState(() => {
// 初始化时解析JSON数据
if (typeof value === 'string' && value.trim()) {
try {
const parsed = JSON.parse(value);
return parsed;
} catch (error) {
return {};
}
}
if (typeof value === 'object' && value !== null) {
return value;
}
return {};
});

// 根据键数量决定默认编辑模式
const [editMode, setEditMode] = useState(() => {
// 如果初始JSON数据的键数量大于10个,则默认使用手动模式
if (typeof value === 'string' && value.trim()) {
try {
const parsed = JSON.parse(value);
const keyCount = Object.keys(parsed).length;
return keyCount > 10 ? 'manual' : 'visual';
} catch (error) {
// JSON无效时默认显示手动编辑模式
return 'manual';
}
}
return 'visual';
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use English comments for consistency and optimize duplicate JSON parsing

The code contains Chinese comments and parses the same JSON twice during initialization.

-  // 初始化JSON数据
+  // Initialize JSON data
   const [jsonData, setJsonData] = useState(() => {
-    // 初始化时解析JSON数据
+    // Parse JSON data on initialization
     if (typeof value === 'string' && value.trim()) {
       try {
         const parsed = JSON.parse(value);
         return parsed;
       } catch (error) {
         return {};
       }
     }
     if (typeof value === 'object' && value !== null) {
       return value;
     }
     return {};
   });

-  // 根据键数量决定默认编辑模式
+  // Determine default edit mode based on key count
   const [editMode, setEditMode] = useState(() => {
-    // 如果初始JSON数据的键数量大于10个,则默认使用手动模式
-    if (typeof value === 'string' && value.trim()) {
-      try {
-        const parsed = JSON.parse(value);
-        const keyCount = Object.keys(parsed).length;
-        return keyCount > 10 ? 'manual' : 'visual';
-      } catch (error) {
-        // JSON无效时默认显示手动编辑模式
-        return 'manual';
-      }
-    }
-    return 'visual';
+    // Reuse the parsed jsonData to avoid duplicate parsing
+    const keyCount = Object.keys(jsonData).length;
+    return keyCount > 10 ? 'manual' : 'visual';
   });

Note: You'll need to restructure the initialization to avoid the circular dependency. Consider using a single initialization function that returns both values.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 初始化JSON数据
const [jsonData, setJsonData] = useState(() => {
// 初始化时解析JSON数据
if (typeof value === 'string' && value.trim()) {
try {
const parsed = JSON.parse(value);
return parsed;
} catch (error) {
return {};
}
}
if (typeof value === 'object' && value !== null) {
return value;
}
return {};
});
// 根据键数量决定默认编辑模式
const [editMode, setEditMode] = useState(() => {
// 如果初始JSON数据的键数量大于10个,则默认使用手动模式
if (typeof value === 'string' && value.trim()) {
try {
const parsed = JSON.parse(value);
const keyCount = Object.keys(parsed).length;
return keyCount > 10 ? 'manual' : 'visual';
} catch (error) {
// JSON无效时默认显示手动编辑模式
return 'manual';
}
}
return 'visual';
});
// Initialize JSON data
const [jsonData, setJsonData] = useState(() => {
// Parse JSON data on initialization
if (typeof value === 'string' && value.trim()) {
try {
const parsed = JSON.parse(value);
return parsed;
} catch (error) {
return {};
}
}
if (typeof value === 'object' && value !== null) {
return value;
}
return {};
});
// Determine default edit mode based on key count
const [editMode, setEditMode] = useState(() => {
// Reuse the parsed jsonData to avoid duplicate parsing
const keyCount = Object.keys(jsonData).length;
return keyCount > 10 ? 'manual' : 'visual';
});
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js around lines 44 to 75, the code
has duplicate JSON parsing and uses Chinese comments. Refactor by creating a
single initialization function that parses the JSON once and returns both the
initial jsonData and editMode based on the key count. Replace all Chinese
comments with English ones for consistency.

Comment on lines +78 to +94
// 数据同步 - 当value变化时总是更新jsonData(如果JSON有效)
useEffect(() => {
try {
let parsed = {};
if (typeof value === 'string' && value.trim()) {
parsed = JSON.parse(value);
} else if (typeof value === 'object' && value !== null) {
parsed = value;
}
setJsonData(parsed);
setJsonError('');
} catch (error) {
console.log('JSON解析失败:', error.message);
setJsonError(error.message);
// JSON格式错误时不更新jsonData
}
}, [value]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove console.log and use English comments

Production code should not contain console.log statements, and comments should be in English.

-  // 数据同步 - 当value变化时总是更新jsonData(如果JSON有效)
+  // Data sync - always update jsonData when value changes (if JSON is valid)
   useEffect(() => {
     try {
       let parsed = {};
       if (typeof value === 'string' && value.trim()) {
         parsed = JSON.parse(value);
       } else if (typeof value === 'object' && value !== null) {
         parsed = value;
       }
       setJsonData(parsed);
       setJsonError('');
     } catch (error) {
-      console.log('JSON解析失败:', error.message);
+      // JSON parsing failed - handled by setting error state
       setJsonError(error.message);
-      // JSON格式错误时不更新jsonData
+      // Don't update jsonData when JSON format is invalid
     }
   }, [value]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 数据同步 - 当value变化时总是更新jsonData(如果JSON有效)
useEffect(() => {
try {
let parsed = {};
if (typeof value === 'string' && value.trim()) {
parsed = JSON.parse(value);
} else if (typeof value === 'object' && value !== null) {
parsed = value;
}
setJsonData(parsed);
setJsonError('');
} catch (error) {
console.log('JSON解析失败:', error.message);
setJsonError(error.message);
// JSON格式错误时不更新jsonData
}
}, [value]);
// Data sync - always update jsonData when value changes (if JSON is valid)
useEffect(() => {
try {
let parsed = {};
if (typeof value === 'string' && value.trim()) {
parsed = JSON.parse(value);
} else if (typeof value === 'object' && value !== null) {
parsed = value;
}
setJsonData(parsed);
setJsonError('');
} catch (error) {
// JSON parsing failed - handled by setting error state
setJsonError(error.message);
// Don't update jsonData when JSON format is invalid
}
}, [value]);
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js around lines 78 to 94, remove the
console.log statement used for logging JSON parse errors and replace all
comments with English text. Instead of console.log, rely on setting the
jsonError state to handle error reporting. Ensure comments clearly describe the
logic in English for maintainability.

Comment on lines +96 to +216
// 处理可视化编辑的数据变化
const handleVisualChange = useCallback((newData) => {
setJsonData(newData);
setJsonError('');
const jsonString = Object.keys(newData).length === 0 ? '' : JSON.stringify(newData, null, 2);

// 通过formApi设置值(如果提供的话)
if (formApi && field) {
formApi.setValue(field, jsonString);
}

onChange?.(jsonString);
}, [onChange, formApi, field]);

// 处理手动编辑的数据变化
const handleManualChange = useCallback((newValue) => {
onChange?.(newValue);
// 验证JSON格式
if (newValue && newValue.trim()) {
try {
const parsed = JSON.parse(newValue);
setJsonError('');
// 预先准备可视化数据,但不立即应用
// 这样切换到可视化模式时数据已经准备好了
} catch (error) {
setJsonError(error.message);
}
} else {
setJsonError('');
}
}, [onChange]);

// 切换编辑模式
const toggleEditMode = useCallback(() => {
if (editMode === 'visual') {
// 从可视化模式切换到手动模式
setEditMode('manual');
} else {
// 从手动模式切换到可视化模式,需要验证JSON
try {
let parsed = {};
if (typeof value === 'string' && value.trim()) {
parsed = JSON.parse(value);
} else if (typeof value === 'object' && value !== null) {
parsed = value;
}
setJsonData(parsed);
setJsonError('');
setEditMode('visual');
} catch (error) {
setJsonError(error.message);
// JSON格式错误时不切换模式
return;
}
}
}, [editMode, value]);

// 添加键值对
const addKeyValue = useCallback(() => {
const newData = { ...jsonData };
const keys = Object.keys(newData);
let counter = 1;
let newKey = `field_${counter}`;
while (newData.hasOwnProperty(newKey)) {
counter += 1;
newKey = `field_${counter}`;
}
newData[newKey] = '';
handleVisualChange(newData);
}, [jsonData, handleVisualChange]);

// 删除键值对
const removeKeyValue = useCallback((keyToRemove) => {
const newData = { ...jsonData };
delete newData[keyToRemove];
handleVisualChange(newData);
}, [jsonData, handleVisualChange]);

// 更新键名
const updateKey = useCallback((oldKey, newKey) => {
if (oldKey === newKey || !newKey) return;
const newData = {};
Object.entries(jsonData).forEach(([k, v]) => {
if (k === oldKey) {
newData[newKey] = v;
} else {
newData[k] = v;
}
});
handleVisualChange(newData);
}, [jsonData, handleVisualChange]);

// 更新值
const updateValue = useCallback((key, newValue) => {
const newData = { ...jsonData };
newData[key] = newValue;
handleVisualChange(newData);
}, [jsonData, handleVisualChange]);

// 填入模板
const fillTemplate = useCallback(() => {
if (template) {
const templateString = JSON.stringify(template, null, 2);

// 通过formApi设置值(如果提供的话)
if (formApi && field) {
formApi.setValue(field, templateString);
}

// 无论哪种模式都要更新值
onChange?.(templateString);

// 如果是可视化模式,同时更新jsonData
if (editMode === 'visual') {
setJsonData(template);
}

// 清除错误状态
setJsonError('');
}
}, [template, onChange, editMode, formApi, field]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Translate Chinese comments to English

Multiple handler functions contain Chinese comments that should be in English for consistency.

Replace all Chinese comments with English translations throughout these handler functions. For example:

  • Line 96: // 处理可视化编辑的数据变化// Handle visual editing data changes
  • Line 110: // 处理手动编辑的数据变化// Handle manual editing data changes
  • Line 113: // 验证JSON格式// Validate JSON format
  • Line 128: // 切换编辑模式// Toggle edit mode
  • Line 153: // 添加键值对// Add key-value pair
  • Line 167: // 删除键值对// Remove key-value pair
  • Line 174: // 更新键名// Update key name
  • Line 188: // 更新值// Update value
  • Line 195: // 填入模板// Fill template
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js from lines 96 to 216, replace all
Chinese comments with their English translations to maintain consistency. For
example, change comments like "// 处理可视化编辑的数据变化" to "// Handle visual editing
data changes" and similarly translate all other Chinese comments in the handler
functions as indicated in the review comment.

const keys = Object.keys(newData);
let counter = 1;
let newKey = `field_${counter}`;
while (newData.hasOwnProperty(newKey)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Object.prototype.hasOwnProperty for safety

Direct use of hasOwnProperty can fail if the object has a property with the same name.

-    while (newData.hasOwnProperty(newKey)) {
+    while (Object.prototype.hasOwnProperty.call(newData, newKey)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while (newData.hasOwnProperty(newKey)) {
while (Object.prototype.hasOwnProperty.call(newData, newKey)) {
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js at line 159, replace the direct
call to newData.hasOwnProperty(newKey) with
Object.prototype.hasOwnProperty.call(newData, newKey) to safely check property
existence and avoid issues if newData has a hasOwnProperty property.

Comment on lines +282 to +542
// 添加嵌套对象
const flattenObject = useCallback((parentKey) => {
const newData = { ...jsonData };
let primitive = '';
const obj = newData[parentKey];
if (obj && typeof obj === 'object') {
const firstKey = Object.keys(obj)[0];
if (firstKey !== undefined) {
const firstVal = obj[firstKey];
if (typeof firstVal !== 'object') primitive = firstVal;
}
}
newData[parentKey] = primitive;
handleVisualChange(newData);
}, [jsonData, handleVisualChange]);

const addNestedObject = useCallback((parentKey) => {
const newData = { ...jsonData };
if (typeof newData[parentKey] !== 'object' || newData[parentKey] === null) {
newData[parentKey] = {};
}
const existingKeys = Object.keys(newData[parentKey]);
let counter = 1;
let newKey = `field_${counter}`;
while (newData[parentKey].hasOwnProperty(newKey)) {
counter += 1;
newKey = `field_${counter}`;
}
newData[parentKey][newKey] = '';
handleVisualChange(newData);
}, [jsonData, handleVisualChange]);

// 渲染参数值输入控件(支持嵌套)
const renderValueInput = (key, value) => {
const valueType = typeof value;

if (valueType === 'boolean') {
return (
<div className="flex items-center">
<Switch
checked={value}
onChange={(newValue) => updateValue(key, newValue)}
/>
<Text type="tertiary" className="ml-2">
{value ? t('true') : t('false')}
</Text>
</div>
);
}

if (valueType === 'number') {
return (
<InputNumber
value={value}
onChange={(newValue) => updateValue(key, newValue)}
style={{ width: '100%' }}
step={key === 'temperature' ? 0.1 : 1}
precision={key === 'temperature' ? 2 : 0}
placeholder={t('输入数字')}
/>
);
}

if (valueType === 'object' && value !== null) {
// 渲染嵌套对象
const entries = Object.entries(value);
return (
<Card className="!rounded-2xl">
{entries.length === 0 && (
<Text type="tertiary" className="text-gray-500 text-xs">
{t('空对象,点击下方加号添加字段')}
</Text>
)}

{entries.map(([nestedKey, nestedValue], index) => (
<Row key={index} gutter={4} align="middle" className="mb-1">
<Col span={8}>
<Input
size="small"
placeholder={t('键名')}
value={nestedKey}
onChange={(newKey) => {
const newData = { ...jsonData };
const oldValue = newData[key][nestedKey];
delete newData[key][nestedKey];
newData[key][newKey] = oldValue;
handleVisualChange(newData);
}}
/>
</Col>
<Col span={14}>
{typeof nestedValue === 'object' && nestedValue !== null ? (
<TextArea
size="small"
rows={2}
value={JSON.stringify(nestedValue, null, 2)}
onChange={(txt) => {
try {
const obj = txt.trim() ? JSON.parse(txt) : {};
const newData = { ...jsonData };
newData[key][nestedKey] = obj;
handleVisualChange(newData);
} catch {
// ignore parse error
}
}}
/>
) : (
<Input
size="small"
placeholder={t('值')}
value={String(nestedValue)}
onChange={(newValue) => {
const newData = { ...jsonData };
let convertedValue = newValue;
if (newValue === 'true') convertedValue = true;
else if (newValue === 'false') convertedValue = false;
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
convertedValue = Number(newValue);
}
newData[key][nestedKey] = convertedValue;
handleVisualChange(newData);
}}
/>
)}
</Col>
<Col span={2}>
<Button
size="small"
icon={<IconDelete />}
type="danger"
theme="borderless"
onClick={() => {
const newData = { ...jsonData };
delete newData[key][nestedKey];
handleVisualChange(newData);
}}
style={{ width: '100%' }}
/>
</Col>
</Row>
))}

<div className="flex justify-center mt-1 gap-2">
<Button
size="small"
icon={<IconPlus />}
type="tertiary"
onClick={() => addNestedObject(key)}
>
{t('添加字段')}
</Button>
<Button
size="small"
icon={<IconRefresh />}
type="tertiary"
onClick={() => flattenObject(key)}
>
{t('转换为值')}
</Button>
</div>
</Card>
);
}

// 字符串或其他原始类型
return (
<div className="flex items-center gap-1">
<Input
placeholder={t('参数值')}
value={String(value)}
onChange={(newValue) => {
let convertedValue = newValue;
if (newValue === 'true') convertedValue = true;
else if (newValue === 'false') convertedValue = false;
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
convertedValue = Number(newValue);
}
updateValue(key, convertedValue);
}}
/>
<Button
icon={<IconPlus />}
type="tertiary"
onClick={() => {
// 将当前值转换为对象
const newData = { ...jsonData };
newData[key] = { '1': value };
handleVisualChange(newData);
}}
title={t('转换为对象')}
/>
</div>
);
};

// 渲染区域编辑器(特殊格式)
const renderRegionEditor = () => {
const entries = Object.entries(jsonData);
const defaultEntry = entries.find(([key]) => key === 'default');
const modelEntries = entries.filter(([key]) => key !== 'default');

return (
<div className="space-y-2">
{/* 默认区域 */}
<Form.Slot label={t('默认区域')}>
<Input
placeholder={t('默认区域,如: us-central1')}
value={defaultEntry ? defaultEntry[1] : ''}
onChange={(value) => updateValue('default', value)}
/>
</Form.Slot>

{/* 模型专用区域 */}
<Form.Slot label={t('模型专用区域')}>
<div>
{modelEntries.map(([modelName, region], index) => (
<Row key={index} gutter={8} align="middle" className="mb-2">
<Col span={10}>
<Input
placeholder={t('模型名称')}
value={modelName}
onChange={(newKey) => updateKey(modelName, newKey)}
/>
</Col>
<Col span={12}>
<Input
placeholder={t('区域')}
value={region}
onChange={(newValue) => updateValue(modelName, newValue)}
/>
</Col>
<Col span={2}>
<Button
icon={<IconDelete />}
type="danger"
theme="borderless"
onClick={() => removeKeyValue(modelName)}
style={{ width: '100%' }}
/>
</Col>
</Row>
))}

<div className="mt-2 flex justify-center">
<Button
icon={<IconPlus />}
onClick={addKeyValue}
type="primary"
theme="outline"
>
{t('添加模型区域')}
</Button>
</div>
</div>
</Form.Slot>
</div>
);
};

// 渲染可视化编辑器

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Translate remaining Chinese comments to English

Replace Chinese comments with English:

  • Line 282: // 添加嵌套对象// Add nested object
  • Line 298: // 渲染参数值输入控件(支持嵌套)// Render parameter value input control (supports nesting)
  • Line 314: // 渲染参数值输入控件(支持嵌套)// Render value input control (supports nesting)
  • Line 346: // 渲染嵌套对象// Render nested object
  • Line 447: // 字符串或其他原始类型// String or other primitive types
  • Line 467: // 将当前值转换为对象// Convert current value to object
  • Line 478: // 渲染区域编辑器(特殊格式)// Render region editor (special format)
  • Line 486: /* 默认区域 *//* Default region */
  • Line 495: /* 模型专用区域 *//* Model-specific regions */
  • Line 542: // 渲染可视化编辑器// Render visual editor
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js from lines 282 to 542, replace all
Chinese comments with their English equivalents as specified: line 282 to "Add
nested object", line 298 and 314 to "Render parameter value input control
(supports nesting)" and "Render value input control (supports nesting)"
respectively, line 346 to "Render nested object", line 447 to "String or other
primitive types", line 467 to "Convert current value to object", line 478 to
"Render region editor (special format)", line 486 to "Default region", line 495
to "Model-specific regions", and line 542 to "Render visual editor".

const existingKeys = Object.keys(newData[parentKey]);
let counter = 1;
let newKey = `field_${counter}`;
while (newData[parentKey].hasOwnProperty(newKey)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Object.prototype.hasOwnProperty for safety

Same issue as before - direct use of hasOwnProperty can fail.

-    while (newData[parentKey].hasOwnProperty(newKey)) {
+    while (Object.prototype.hasOwnProperty.call(newData[parentKey], newKey)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while (newData[parentKey].hasOwnProperty(newKey)) {
while (Object.prototype.hasOwnProperty.call(newData[parentKey], newKey)) {
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js at line 306, replace the direct
call to hasOwnProperty on newData[parentKey] with
Object.prototype.hasOwnProperty.call(newData[parentKey], newKey) to ensure safe
property checking and avoid potential issues with objects that may override
hasOwnProperty.

Comment thread web/src/components/common/ui/JSONEditor.js
let convertedValue = newValue;
if (newValue === 'true') convertedValue = true;
else if (newValue === 'false') convertedValue = false;
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect numeric conversion logic

The condition newValue !== '0' will prevent the string '0' from being converted to the number 0, which is likely unintended.

-                      else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
+                      else if (!isNaN(newValue) && newValue !== '') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
else if (!isNaN(newValue) && newValue !== '') {
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js at line 399, the condition
`newValue !== '0'` incorrectly blocks the string '0' from being converted to the
number 0. Remove the `newValue !== '0'` check from the condition to allow '0' to
be properly converted to a numeric value.

let convertedValue = newValue;
if (newValue === 'true') convertedValue = true;
else if (newValue === 'false') convertedValue = false;
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the same numeric conversion issue

Same problem with preventing '0' from being converted to number.

-            else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
+            else if (!isNaN(newValue) && newValue !== '') {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
else if (!isNaN(newValue) && newValue !== '') {
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js at line 457, the condition
incorrectly prevents the string '0' from being converted to a number. Modify the
condition to allow '0' to be converted by removing the check that excludes '0',
ensuring that numeric strings including '0' are properly converted.

Comment on lines +591 to +635
{/* JSON错误提示 */}
{hasJsonError && (
<Banner
type="danger"
description={`JSON 格式错误: ${jsonError}`}
className="mb-3"
/>
)}

{/* 编辑器内容 */}
{editMode === 'visual' ? (
<div>
{renderVisualEditor()}
{/* 隐藏的Form字段用于验证和数据绑定 */}
<Form.Input
field={field}
value={value}
rules={rules}
style={{ display: 'none' }}
noLabel={true}
{...props}
/>
</div>
) : (
<div>
<TextArea
placeholder={placeholder}
value={value}
onChange={handleManualChange}
showClear={showClear}
rows={Math.max(8, value ? value.split('\n').length : 8)}
/>
{/* 隐藏的Form字段用于验证和数据绑定 */}
<Form.Input
field={field}
value={value}
rules={rules}
style={{ display: 'none' }}
noLabel={true}
{...props}
/>
</div>
)}

{/* 额外文本显示在卡片底部 */}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Translate final Chinese comments to English

Replace Chinese comments with English:

  • Line 591: {/* JSON错误提示 */}{/* JSON error message */}
  • Line 600: {/* 编辑器内容 */}{/* Editor content */}
  • Line 604: {/* 隐藏的Form字段用于验证和数据绑定 */}{/* Hidden Form field for validation and data binding */}
  • Line 623: {/* 隐藏的Form字段用于验证和数据绑定 */}{/* Hidden Form field for validation and data binding */}
  • Line 635: {/* 额外文本显示在卡片底部 */}{/* Extra text displayed at the bottom of the card */}
🤖 Prompt for AI Agents
In web/src/components/common/ui/JSONEditor.js around lines 591 to 635, replace
all Chinese comments with their English equivalents as specified: change
"JSON错误提示" to "JSON error message", "编辑器内容" to "Editor content", both instances
of "隐藏的Form字段用于验证和数据绑定" to "Hidden Form field for validation and data binding",
and "额外文本显示在卡片底部" to "Extra text displayed at the bottom of the card".

t0ng7u added 2 commits August 8, 2025 02:59
… fix endpoints rendering, and clean up deps

- Why
  - Needed to separate help text from action buttons in JSONEditor for better layout and UX.
  - Models table should robustly render both new object-based endpoint mappings and legacy arrays.
  - Columns should re-render when vendor map changes.
  - Minor import cleanups for consistency.

- What
  - JSONEditor.js
    - Added optional prop extraFooter to render content below the extraText divider.
    - Kept extraText rendered via Divider; extraFooter appears on the next line for clear separation.
  - EditModelModal.jsx
    - Moved endpoint group buttons from extraText into extraFooter to display under the helper text.
    - Kept merge-logic: group items are merged into current endpoints JSON with key override semantics.
    - Consolidated lucide-react imports into a single line.
  - ModelsColumnDefs.js
    - Made endpoint renderer resilient:
      - Supports object-based JSON (keys as endpoint types) and legacy array format.
      - Displays keys/items as tags and limits the number shown; uses stringToColor for visual consistency.
    - Consolidated Semi UI imports into a single line.
  - ModelsTable.jsx
    - Fixed columns memoization dependency to include vendorMap, ensuring re-render when vendor data changes.

- Notes
  - Backward-compatible: extraFooter is additive; existing JSONEditor usage remains unchanged.
  - No API changes to backend.
  - No linter errors introduced.

- Files touched
  - web/src/components/common/ui/JSONEditor.js
  - web/src/components/table/models/modals/EditModelModal.jsx
  - web/src/components/table/models/ModelsColumnDefs.js
  - web/src/components/table/models/ModelsTable.jsx

- Impact
  - Clearer UI for endpoint editing (buttons now below helper text).
  - Correct endpoints display for object-based mappings in models list.
  - More reliable reactivity when vendor data updates.
…” card in EditPrefillGroupModal

- Move `items` field (`JSONEditor` for endpoint type, `Form.TagInput` otherwise) into the first “Basic Information” card
- Remove the second “Content Configuration” card and its header; consolidate to a single-card layout
- Preserve form initialization, validation, and submit logic; API payload structure remains unchanged
- Improves clarity and reduces visual clutter without altering behavior
- Lint passes

Affected file:
- `web/src/components/table/models/modals/EditPrefillGroupModal.jsx`

No breaking changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (14)
web/src/components/table/models/modals/EditModelModal.jsx (4)

79-81: Add error notifications for failed API calls

The error is silently caught without notifying the user when vendor loading fails.

     } catch (error) {
-      // ignore
+      showError(t('Failed to load vendors'));
     }

97-99: Add error notifications for failed API calls

The error is silently caught without notifying the user when prefill groups loading fails.

     } catch (error) {
-      // ignore
+      showError(t('Failed to load prefill groups'));
     }

143-147: Validate vendor_id exists in vendors list

When loading a model for editing, the vendor_id might reference a non-existent vendor if the vendor was deleted or hasn't loaded yet.

         // 处理status,将数字转为布尔值
         data.status = data.status === 1;
+        // Validate vendor_id exists
+        if (data.vendor_id && !vendors.find(v => v.id === data.vendor_id)) {
+          console.warn(`Vendor ID ${data.vendor_id} not found in vendors list`);
+          data.vendor_id = undefined;
+        }
         if (formApiRef.current) {
           formApiRef.current.setValues({ ...getInitValues(), ...data });
         }

218-220: Move form reset to success path only

The form reset happens regardless of submission success or failure. This might clear user input even when submission fails, forcing users to re-enter data.

           showSuccess(t('模型创建成功!'));
           props.refresh();
           props.handleClose();
+          formApiRef.current?.setValues(getInitValues());
         } else {
           showError(t(message));
         }
       }
     } catch (error) {
       showError(error.response?.data?.message || t('操作失败'));
     }
     setLoading(false);
-    formApiRef.current?.setValues(getInitValues());
web/src/components/common/ui/JSONEditor.js (10)

386-388: Handle or document the ignored parse error

Empty catch blocks can hide errors and make debugging difficult.

                       } catch {
-                        // ignore parse error
+                        // Invalid JSON input - keep current value unchanged
+                        // User will see the invalid JSON and can correct it
                       }

46-77: Use English comments for consistency and optimize duplicate JSON parsing

The code contains Chinese comments and parses the same JSON twice during initialization.

-  // 初始化JSON数据
+  // Initialize JSON data
   const [jsonData, setJsonData] = useState(() => {
-    // 初始化时解析JSON数据
+    // Parse JSON data on initialization
     if (typeof value === 'string' && value.trim()) {
       try {
         const parsed = JSON.parse(value);
         return parsed;
       } catch (error) {
         return {};
       }
     }
     if (typeof value === 'object' && value !== null) {
       return value;
     }
     return {};
   });

-  // 根据键数量决定默认编辑模式
+  // Determine default edit mode based on key count
   const [editMode, setEditMode] = useState(() => {
-    // 如果初始JSON数据的键数量大于10个,则默认使用手动模式
-    if (typeof value === 'string' && value.trim()) {
-      try {
-        const parsed = JSON.parse(value);
-        const keyCount = Object.keys(parsed).length;
-        return keyCount > 10 ? 'manual' : 'visual';
-      } catch (error) {
-        // JSON无效时默认显示手动编辑模式
-        return 'manual';
-      }
-    }
-    return 'visual';
+    // Reuse the parsed jsonData to avoid duplicate parsing
+    const keyCount = Object.keys(jsonData).length;
+    return keyCount > 10 ? 'manual' : 'visual';
   });

Note: You'll need to restructure the initialization to avoid the circular dependency.


80-96: Remove console.log and use English comments

Production code should not contain console.log statements, and comments should be in English.

-  // 数据同步 - 当value变化时总是更新jsonData(如果JSON有效)
+  // Data sync - always update jsonData when value changes (if JSON is valid)
   useEffect(() => {
     try {
       let parsed = {};
       if (typeof value === 'string' && value.trim()) {
         parsed = JSON.parse(value);
       } else if (typeof value === 'object' && value !== null) {
         parsed = value;
       }
       setJsonData(parsed);
       setJsonError('');
     } catch (error) {
-      console.log('JSON解析失败:', error.message);
+      // JSON parsing failed - handled by setting error state
       setJsonError(error.message);
-      // JSON格式错误时不更新jsonData
+      // Don't update jsonData when JSON format is invalid
     }
   }, [value]);

98-218: Translate Chinese comments to English

Multiple handler functions contain Chinese comments that should be in English for consistency.

Replace all Chinese comments with English translations throughout these handler functions:

  • Line 98: // 处理可视化编辑的数据变化// Handle visual editing data changes
  • Line 112: // 处理手动编辑的数据变化// Handle manual editing data changes
  • Line 115: // 验证JSON格式// Validate JSON format
  • Line 130: // 切换编辑模式// Toggle edit mode
  • Line 155: // 添加键值对// Add key-value pair
  • Line 169: // 删除键值对// Remove key-value pair
  • Line 176: // 更新键名// Update key name
  • Line 190: // 更新值// Update value
  • Line 197: // 填入模板// Fill template

161-161: Use Object.prototype.hasOwnProperty for safety

Direct use of hasOwnProperty can fail if the object has a property with the same name.

-    while (newData.hasOwnProperty(newKey)) {
+    while (Object.prototype.hasOwnProperty.call(newData, newKey)) {

284-544: Translate remaining Chinese comments to English

Replace Chinese comments with English:

  • Line 284: // 添加嵌套对象// Add nested object
  • Line 316: // 渲染参数值输入控件(支持嵌套)// Render parameter value input control (supports nesting)
  • Line 348: // 渲染嵌套对象// Render nested object
  • Line 449: // 字符串或其他原始类型// String or other primitive types
  • Line 469: // 将当前值转换为对象// Convert current value to object
  • Line 480: // 渲染区域编辑器(特殊格式)// Render region editor (special format)
  • Line 488: /* 默认区域 *//* Default region */
  • Line 497: /* 模型专用区域 *//* Model-specific regions */
  • Line 544: // 渲染可视化编辑器// Render visual editor

308-308: Use Object.prototype.hasOwnProperty for safety

Same issue as before - direct use of hasOwnProperty can fail.

-    while (newData[parentKey].hasOwnProperty(newKey)) {
+    while (Object.prototype.hasOwnProperty.call(newData[parentKey], newKey)) {

401-401: Fix incorrect numeric conversion logic

The condition newValue !== '0' will prevent the string '0' from being converted to the number 0, which is likely unintended.

-                      else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
+                      else if (!isNaN(newValue) && newValue !== '') {

459-459: Fix the same numeric conversion issue

Same problem with preventing '0' from being converted to number.

-            else if (!isNaN(newValue) && newValue !== '' && newValue !== '0') {
+            else if (!isNaN(newValue) && newValue !== '') {

593-647: Translate final Chinese comments to English

Replace Chinese comments with English:

  • Line 593: {/* JSON错误提示 */}{/* JSON error message */}
  • Line 602: {/* 编辑器内容 */}{/* Editor content */}
  • Line 606: {/* 隐藏的Form字段用于验证和数据绑定 */}{/* Hidden Form field for validation and data binding */}
  • Line 625: {/* 隐藏的Form字段用于验证和数据绑定 */}{/* Hidden Form field for validation and data binding */}
  • Line 637: {/* 额外文本显示在卡片底部 */}{/* Extra text displayed at the bottom of the card */}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26f44b8 and 4e75a9b.

📒 Files selected for processing (5)
  • web/src/components/common/ui/JSONEditor.js (1 hunks)
  • web/src/components/table/models/ModelsColumnDefs.js (1 hunks)
  • web/src/components/table/models/ModelsTable.jsx (1 hunks)
  • web/src/components/table/models/modals/EditModelModal.jsx (1 hunks)
  • web/src/components/table/models/modals/EditPrefillGroupModal.jsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • web/src/components/table/models/ModelsTable.jsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/components/table/models/modals/EditPrefillGroupModal.jsx
  • web/src/components/table/models/ModelsColumnDefs.js

Comment on lines +49 to +54
const nameRuleOptions = [
{ label: '精确名称匹配', value: 0 },
{ label: '前缀名称匹配', value: 1 },
{ label: '包含名称匹配', value: 2 },
{ label: '后缀名称匹配', value: 3 },
];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Wrap Chinese text with translation function

The label values in nameRuleOptions contain hardcoded Chinese text. These should be translated in the component where they're used, not hardcoded here.

 const nameRuleOptions = [
-  { label: '精确名称匹配', value: 0 },
-  { label: '前缀名称匹配', value: 1 },
-  { label: '包含名称匹配', value: 2 },
-  { label: '后缀名称匹配', value: 3 },
+  { label: 'exact_match', value: 0 },
+  { label: 'prefix_match', value: 1 },
+  { label: 'contains_match', value: 2 },
+  { label: 'suffix_match', value: 3 },
 ];

Then translate these keys when rendering in line 307:

-optionList={nameRuleOptions.map(o => ({ label: t(o.label), value: o.value }))}
+optionList={nameRuleOptions.map(o => ({ label: t(`name_rule.${o.label}`), value: o.value }))}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const nameRuleOptions = [
{ label: '精确名称匹配', value: 0 },
{ label: '前缀名称匹配', value: 1 },
{ label: '包含名称匹配', value: 2 },
{ label: '后缀名称匹配', value: 3 },
];
const nameRuleOptions = [
{ label: 'exact_match', value: 0 },
{ label: 'prefix_match', value: 1 },
{ label: 'contains_match', value: 2 },
{ label: 'suffix_match', value: 3 },
];
🤖 Prompt for AI Agents
In web/src/components/table/models/modals/EditModelModal.jsx lines 49 to 54, the
label values in the nameRuleOptions array are hardcoded Chinese text. Remove the
Chinese text from this array and replace them with translation keys or
identifiers instead. Then, in the component rendering at line 307, wrap the
labels with the translation function to display the correct localized text
dynamically.

Comment on lines +401 to +414
onClick={() => {
try {
const current = formApiRef.current?.getValue('endpoints') || '';
let base = {};
if (current && current.trim()) base = JSON.parse(current);
const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
const merged = { ...base, ...groupObj };
formApiRef.current?.setValue('endpoints', JSON.stringify(merged, null, 2));
} catch (e) {
try {
const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
formApiRef.current?.setValue('endpoints', JSON.stringify(groupObj, null, 2));
} catch { }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify error handling and provide user feedback

The nested try-catch blocks make error handling complex and difficult to understand. Consider simplifying and providing user feedback when JSON parsing fails.

 onClick={() => {
-  try {
-    const current = formApiRef.current?.getValue('endpoints') || '';
-    let base = {};
-    if (current && current.trim()) base = JSON.parse(current);
-    const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
-    const merged = { ...base, ...groupObj };
-    formApiRef.current?.setValue('endpoints', JSON.stringify(merged, null, 2));
-  } catch (e) {
-    try {
-      const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
-      formApiRef.current?.setValue('endpoints', JSON.stringify(groupObj, null, 2));
-    } catch { }
-  }
+  const current = formApiRef.current?.getValue('endpoints') || '';
+  let base = {};
+  let groupObj = {};
+  
+  // Parse current value
+  if (current && current.trim()) {
+    try {
+      base = JSON.parse(current);
+    } catch (e) {
+      showError(t('Current endpoints JSON is invalid'));
+      return;
+    }
+  }
+  
+  // Parse group items
+  try {
+    groupObj = typeof group.items === 'string' 
+      ? JSON.parse(group.items || '{}') 
+      : (group.items || {});
+  } catch (e) {
+    showError(t('Prefill group JSON is invalid'));
+    return;
+  }
+  
+  const merged = { ...base, ...groupObj };
+  formApiRef.current?.setValue('endpoints', JSON.stringify(merged, null, 2));
 }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={() => {
try {
const current = formApiRef.current?.getValue('endpoints') || '';
let base = {};
if (current && current.trim()) base = JSON.parse(current);
const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
const merged = { ...base, ...groupObj };
formApiRef.current?.setValue('endpoints', JSON.stringify(merged, null, 2));
} catch (e) {
try {
const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
formApiRef.current?.setValue('endpoints', JSON.stringify(groupObj, null, 2));
} catch { }
}
onClick={() => {
const current = formApiRef.current?.getValue('endpoints') || '';
let base = {};
let groupObj = {};
// Parse current value
if (current && current.trim()) {
try {
base = JSON.parse(current);
} catch (e) {
showError(t('Current endpoints JSON is invalid'));
return;
}
}
// Parse group items
try {
groupObj = typeof group.items === 'string'
? JSON.parse(group.items || '{}')
: (group.items || {});
} catch (e) {
showError(t('Prefill group JSON is invalid'));
return;
}
const merged = { ...base, ...groupObj };
formApiRef.current?.setValue(
'endpoints',
JSON.stringify(merged, null, 2)
);
}}
🤖 Prompt for AI Agents
In web/src/components/table/models/modals/EditModelModal.jsx around lines 401 to
414, the nested try-catch blocks for JSON parsing complicate error handling and
lack user feedback. Refactor by consolidating the parsing logic into a single
try-catch block, and in the catch block, provide clear user feedback (e.g., an
alert or error message) indicating the JSON parsing failure instead of silently
failing. This will simplify the code and improve user experience.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
web/src/components/table/models/modals/EditPrefillGroupModal.jsx (1)

86-87: Fix inconsistent edit condition check.

The condition for checking edit mode is inconsistent. Line 58 uses editingGroup && editingGroup.id !== undefined for isEdit, but line 86 directly checks editingGroup.id which could cause issues if editingGroup is null or undefined.

Apply this diff to fix the inconsistency:

-      if (editingGroup.id) {
+      if (isEdit) {
🧹 Nitpick comments (2)
web/src/components/table/models/modals/EditPrefillGroupModal.jsx (2)

227-238: Simplify JSONEditor value prop for better readability.

The inline ternary expression for the JSONEditor value prop is complex and could cause unnecessary re-renders. Consider extracting this logic to a useMemo hook or computed value.

+  const jsonEditorValue = useMemo(() => {
+    if (selectedType !== 'endpoint') return '';
+    const currentValue = formRef.current?.getValue('items');
+    if (currentValue !== undefined) return currentValue;
+    return typeof editingGroup?.items === 'string' 
+      ? editingGroup.items 
+      : JSON.stringify(editingGroup?.items || {}, null, 2);
+  }, [selectedType, editingGroup?.items]);

   {selectedType === 'endpoint' ? (
     <JSONEditor
       field="items"
       label={t('端点映射')}
-      value={formRef.current?.getValue('items') ?? (typeof editingGroup?.items === 'string' ? editingGroup.items : JSON.stringify(editingGroup.items || {}, null, 2))}
+      value={jsonEditorValue}
       onChange={(val) => formRef.current?.setValue('items', val)}
       editorType='object'
       placeholder={'{\n  "openai": {"path": "/v1/chat/completions", "method": "POST"}\n}'}
       template={ENDPOINT_TEMPLATE}
       templateLabel={t('填入模板')}
       extraText={t('键为端点类型,值为路径和方法对象')}
     />
   ) : (

166-180: Extract complex initialization logic to improve maintainability.

The IIFE for items initialization is complex and difficult to test. Consider extracting this to a separate function or using useMemo for better readability and testability.

+  const getInitialItems = useCallback(() => {
+    try {
+      if (editingGroup?.type === 'endpoint') {
+        return typeof editingGroup?.items === 'string'
+          ? editingGroup.items
+          : JSON.stringify(editingGroup.items || {}, null, 2);
+      }
+      return Array.isArray(editingGroup?.items) ? editingGroup.items : [];
+    } catch {
+      return editingGroup?.type === 'endpoint' ? '' : [];
+    }
+  }, [editingGroup?.type, editingGroup?.items]);

   initValues={{
     name: editingGroup?.name || '',
     type: editingGroup?.type || 'tag',
     description: editingGroup?.description || '',
-    items: (() => {
-      try {
-        if (editingGroup?.type === 'endpoint') {
-          // 保持原始字符串
-          return typeof editingGroup?.items === 'string'
-            ? editingGroup.items
-            : JSON.stringify(editingGroup.items || {}, null, 2);
-        }
-        return Array.isArray(editingGroup?.items)
-          ? editingGroup.items
-          : [];
-      } catch {
-        return editingGroup?.type === 'endpoint' ? '' : [];
-      }
-    })(),
+    items: getInitialItems(),
   }}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e75a9b and 6341847.

📒 Files selected for processing (1)
  • web/src/components/table/models/modals/EditPrefillGroupModal.jsx (1 hunks)
🔇 Additional comments (2)
web/src/components/table/models/modals/EditPrefillGroupModal.jsx (2)

20-52: LGTM! Well-structured imports and helpful template.

The imports are comprehensive and the ENDPOINT_TEMPLATE provides useful defaults for different API endpoint types, enhancing user experience.


53-71: LGTM! Proper state management and edit condition handling.

The component properly manages state with appropriate hooks and the isEdit condition correctly checks both editingGroup existence and id presence, which is consistent and safe.

t0ng7u added 5 commits August 8, 2025 03:22
…r PrefillGroup.Items

- Why: Avoid adding `gorm.io/datatypes` for a single field; the rest of the codebase does not use it, and using the standard library keeps dependencies lean.
- What:
  - Switched `PrefillGroup.Items` from `datatypes.JSON` to `json.RawMessage`.
  - Updated imports in `model/prefill_group.go` to use `encoding/json` and removed the unused `gorm.io/datatypes`.
  - Preserved `gorm:"type:json"` so DB column behavior remains the same.
- Impact:
  - API response/request shape for `items` remains unchanged (still JSON).
  - DB schema behavior is unchanged; GORM migration continues to handle the field as JSON.
  - No other references to `datatypes` exist; no `go.mod` changes needed.
  - Lints pass for the modified file.

Files changed:
- model/prefill_group.go

No breaking changes.
…illGroup.Items; fix JSON scan across drivers

- Why:
  - Avoid introducing `gorm.io/datatypes` for a single field.
  - Align with existing pattern (`ChannelInfo`, `Properties`) using `Scanner`/`Valuer`.
  - Fix runtime error when drivers return JSON as string.

- What:
  - Introduced `JSONValue` (based on `json.RawMessage`) implementing `sql.Scanner` and `driver.Valuer`, with `MarshalJSON`/`UnmarshalJSON` to preserve raw JSON in API.
  - Updated `PrefillGroup.Items` to use `JSONValue` with `gorm:"type:json"`.
  - Localized comments in `model/prefill_group.go` to Chinese.

- Impact:
  - Resolves “unsupported Scan, storing driver.Value type string into type *json.RawMessage”.
  - Works with MySQL/Postgres/SQLite whether JSON is returned as `[]byte` or `string`.
  - API and DB schema remain unchanged; no `go.mod` changes; lints pass.

Files changed:
- model/prefill_group.go
…ize JSONEditor manual mode

- Why:
  - Eliminate `gorm.io/datatypes` for a single field and fix scan errors when drivers return JSON as string.
  - Prevent JSONEditor manual mode from locking on invalid JSON and from appending stray characters after “Fill Template”.

- What:
  - Backend (`model/prefill_group.go`):
    - Replaced `datatypes.JSON` with `JSONValue` (based on `json.RawMessage`) for `PrefillGroup.Items`.
    - Implemented `sql.Scanner` and `driver.Valuer` to accept both `[]byte` and `string`.
    - Implemented `MarshalJSON`/`UnmarshalJSON` to preserve raw JSON in API without base64.
    - Converted comments to Chinese.
  - Frontend (`web/src/components/common/ui/JSONEditor.js`):
    - Added `manualText` buffer for manual mode to avoid input being overridden by external value.
    - Only propagate `onChange` when manual text is valid JSON; otherwise show error but do not block typing.
    - Safe manual-mode rendering: derive rows from `manualText` and avoid calling `split` on non-strings.
    - Improved mode toggle: populate `manualText` from visual data; validate before switching back to visual.
    - Fixed “Fill Template” to sync `manualText`, `jsonData`, and `onChange` to avoid stray trailing characters.

- Impact:
  - Resolves: “unsupported Scan, storing driver.Value type string into type *json.RawMessage”.
  - Resolves: `value.split is not a function` in manual mode.
  - Resolves: extra `s` appended after inserting template.
  - API shape and DB column type remain the same (`gorm:"type:json"`); no `go.mod` changes.
  - Lints pass for modified files.

Files changed:
- model/prefill_group.go
- web/src/components/common/ui/JSONEditor.js
@t0ng7u
t0ng7u merged commit ac158e2 into alpha Aug 7, 2025
2 checks passed
@Calcium-Ion
Calcium-Ion deleted the refactor/model-pricing branch August 9, 2025 10:27
@coderabbitai coderabbitai Bot mentioned this pull request Oct 28, 2025
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
Merge pull request QuantumNous#1452 from QuantumNous/refactor/model-pricing
@coderabbitai coderabbitai Bot mentioned this pull request May 1, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants