Commit Graph
100 Commits
Author SHA1 Message Date
t0ng7u 067f3ce9ed 🔧 chore(db): drop legacy single-column UNIQUE indexes to prevent duplicate-key errors after soft-delete
Why
Previous versions created single-column UNIQUE constraints (`models.model_name`, `vendors.name`).
After introducing composite indexes on `(model_name, deleted_at)` and `(name, deleted_at)` for soft-delete support, those legacy constraints could still exist in user databases.
When a record was soft-deleted and re-inserted with the same name, MySQL raised `Error 1062 … for key 'models.model_name'`.

What
• In `migrateDB` and `migrateDBFast` paths of `model/main.go`, proactively drop:
  – `models.uk_model_name` and fallback `models.model_name`
  – `vendors.uk_vendor_name` and fallback `vendors.name`
• Keeps existing helper `dropIndexIfExists` to ensure the operation is MySQL-only and error-free when indexes are already absent.

Result
Startup migration now removes every possible legacy UNIQUE index, ensuring composite index strategy works correctly.
Users can soft-delete and recreate models/vendors with identical names without hitting duplicate-entry errors.
2025-08-11 01:25:13 +08:00
t0ng7u 649fa84231 Merge remote-tracking branch 'origin/alpha' into alpha 2025-08-10 23:18:16 +08:00
t0ng7u 8d54f86261 🤓 chore: format code file 2025-08-10 23:17:04 +08:00
t0ng7u 0bcd7388f4 🏎️ perf: optimize aggregated model look-ups by batching bound-channel queries
Summary
-------
1. **Backend**
   • `model/model_meta.go`
     – Add `GetBoundChannelsForModels([]string)` to retrieve channels for multiple models in a single SQL (`IN (?)`) and deduplicate with `GROUP BY`.

   • `controller/model_meta.go`
     – In non-exact `fillModelExtra`:
       – Remove per-model `GetBoundChannels` calls.
       – Collect matched model names, then call `GetBoundChannelsForModels` once and merge results into `channelSet`.
       – Minor cleanup on loop logic; channel aggregation now happens after quota/group/endpoint processing.

Impact
------
• Eliminates N+1 query pattern for prefix/suffix/contains rules.
• Reduces DB round-trips from *N + 1* to **1**, markedly speeding up the model-management list load.
• Keeps existing `GetBoundChannels` API intact for single-model scenarios; no breaking changes.
2025-08-10 23:11:35 +08:00
t0ng7u 51f7f89190 🔍 feat: Show matched model names & counts for non-exact model rules
Summary
-------
1. **Backend**
   • `model/model_meta.go`
     – Add `MatchedModels []string` and `MatchedCount int` (ignored by GORM) to expose matching details in API responses.
   • `controller/model_meta.go`
     – When processing prefix/suffix/contains rules in `fillModelExtra`, collect every matched model name, fill `MatchedModels`, and calculate `MatchedCount`.

2. **Frontend**
   • `web/src/components/table/models/ModelsColumnDefs.js`
     – Import `Tooltip`.
     – Enhance `renderNameRule` to:
       – Display tag text like “前缀 5个模型” for non-exact rules.
       – Show a tooltip listing all matched model names on hover.

Impact
------
Users now see the total number of concrete models aggregated under each prefix/suffix/contains rule and can inspect the exact list via tooltip, improving transparency in model management.
2025-08-10 21:32:18 +08:00
t0ng7u e1d51fd169 Merge remote-tracking branch 'origin/alpha' into alpha 2025-08-10 21:14:47 +08:00
t0ng7u 85ecfd7062 feat: enhance model billing aggregation & UI display for unknown quota type
Summary
-------
1. **Backend**
   • `controller/model_meta.go`
     – For prefix/suffix/contains rules, aggregate endpoints, bound channels, enable groups, and quota types across all matched models.
     – When mixed billing types are detected, return `quota_type = -1` (unknown) instead of defaulting to volume-based.

2. **Frontend**
   • `web/src/helpers/utils.js`
     – `calculateModelPrice` now handles `quota_type = -1`, returning placeholder `'-'`.

   • `web/src/components/table/model-pricing/view/card/PricingCardView.jsx`
     – Billing tag logic updated: displays “按次计费” (times), “按量计费” (volume), or `'-'` for unknown.

   • `web/src/components/table/model-pricing/view/table/PricingTableColumns.js`
     – `renderQuotaType` shows “未知” for unknown billing type.

   • `web/src/components/table/models/ModelsColumnDefs.js`
     – Unified `renderQuotaType` to return `'-'` when type is unknown.

   • `web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx`
     – Group price table honors unknown billing type; pricing columns show `'-'` and neutral tag color.

3. **Utilities**
   • Added safe fallback colours/tags for unknown billing type across affected components.

Impact
------
• Ensures correct data aggregation for non-exact model matches.
• Prevents UI from implying volume billing when actual type is ambiguous.
• Provides consistent placeholder display (`'-'` or “未知”) across cards, tables and modals.

No breaking API changes; frontend gracefully handles legacy values.
2025-08-10 21:09:49 +08:00
t0ng7u 2cabe4f6ac 🔠 refactor: refine group label formatting in price info
Summary:
• Updated `helpers/utils.js` to display the “group” label without a colon, ensuring consistent typography with other price elements.

Details:
1. `formatPriceInfo`
   – Changed `{t('分组')}:` to `{t('分组')}` for a cleaner look.
   – Keeps spacing intact between label and selected group name.
2. No functional impact; purely visual polish.
2025-08-10 17:17:49 +08:00
t0ng7u ca85d224f1 📱 style: Hide vendor introduction on mobile devices
Summary:
• Updated `PricingTopSection.jsx` to conditionally render `PricingVendorIntroWithSkeleton` only when `isMobile` is false.

Details:
1. Wrapped vendor-intro block in `!isMobile` check, preventing unnecessary content on small screens.
2. Kept desktop experience unchanged; no impact on other features.
3. Lint check passed with no new issues.

Result:
Cleaner mobile UI with improved performance and visual focus.
2025-08-10 14:10:50 +08:00
t0ng7u b6dd701ce8 feat: Add tag-based filtering & refactor filter counts logic
Overview:
• Introduced a new “Model Tag” filter across pricing screens
• Refactored `usePricingFilterCounts` to eliminate duplicated logic
• Improved tag handling to be case-insensitive and deduplicated
• Extended utilities to reset & persist the new filter

Details:
1. Added `filterTag` state to `useModelPricingData` and integrated it into all filtering paths.
2. Created reusable `PricingTags` component using `SelectableButtonGroup`.
3. Incorporated tag filter into `PricingSidebar` and mobile `PricingFilterModal`, including reset support.
4. Enhanced `resetPricingFilters` (helpers/utils) to restore tag filter defaults.
5. Refactored `usePricingFilterCounts.js`:
   • Centralized predicate `matchesFilters` to remove redundancy
   • Normalized tag parsing via `normalizeTags` helper
   • Memoized model subsets with concise filter calls
6. Updated lints – zero errors after refactor.

Result:
Users can now filter models by custom tags with consistent UX, and internal logic is cleaner, faster, and easier to extend.
2025-08-10 14:05:25 +08:00
t0ng7u 7130ec8e32 🐛 fix(PricingCardView): hide placeholder dash when no custom tags
Previously, the card view displayed a “-” whenever a model had no custom tags,
because `renderLimitedItems` returned a dash for an empty array.
Now the function is only invoked when `customTags.length > 0`, removing the
unwanted placeholder and keeping the UI clean.

File affected:
- web/src/components/table/model-pricing/view/card/PricingCardView.jsx
2025-08-10 13:45:16 +08:00
t0ng7u c79b6cea32 fix: prevent model name flicker when closing SideSheet
- Delay clearing selectedModel until SideSheet close animation completes
- Prevents brief display of 'AI/Unknown Model' text during closing transition
- Improves user experience by eliminating visual flicker
- Uses 300ms timeout matching Semi UI default animation duration
2025-08-10 13:41:19 +08:00
t0ng7u 1ea54eb2ed 🖼️ chore: format code file 2025-08-10 12:11:31 +08:00
t0ng7u 750dea9de7 🐛 fix(model): allow zero-value updates so tags/description can be cleared 2025-08-10 11:03:39 +08:00
t0ng7u abee65c1db feat: Add model icon support across backend and UI; prefer model icon over vendor; add icon column in Models table
Backend:
- Model: Add `icon` field to `model.Model` (gorm: varchar(128)); auto-migrated via GORM.
- Pricing API: Extend `model.Pricing` with `icon` and populate from model meta in `GetPricing()`.

Frontend:
- EditModelModal: Add `icon` input (with @lobehub/icons helper link); wire into init/load/submit flows.
- ModelHeader / PricingCardView: Prefer rendering `model.icon`; fallback to `vendor_icon`; final fallback to initials avatar.
- Models table: Add leading “Icon” column, rendering `model.icon` or `vendor` icon via `getLobeHubIcon`.

Notes:
- Backward-compatible. Existing data without `icon` remain unaffected.
- No manual SQL needed; column is added by AutoMigrate.

Affected files:
- model/model_meta.go
- model/pricing.go
- web/src/components/table/models/modals/EditModelModal.jsx
- web/src/components/table/model-pricing/modal/components/ModelHeader.jsx
- web/src/components/table/model-pricing/view/card/PricingCardView.jsx
- web/src/components/table/models/ModelsColumnDefs.js
2025-08-10 01:38:59 +08:00
t0ng7u 734ef7ddac feat: Support dot‑chained props for LobeHub icons
- render.js: Enhance getLobeHubIcon to parse dot‑chained props, e.g.:
  - OpenAI.Avatar.type={'platform'}
  - OpenRouter.Avatar.shape={'square'}
  - Parses booleans/numbers/strings and {…} wrappers; keeps the 2nd arg `size` unless overridden by chain props. Backward compatible.
- EditVendorModal.jsx: Update UI copy — simplify placeholder; document chain‑parameter examples in extra text with doc link.
- en.json: Fix invalid escape sequences in the new i18n string to satisfy linter.

No behavioral changes outside icon rendering; lints pass.
2025-08-10 01:18:36 +08:00
t0ng7u 58af8ed688 🍎 chore: modify pagination pageSizeOpts 2025-08-10 00:58:17 +08:00
t0ng7u 8a3107448b 🎨 refactor: MultiKeyManageModal: cleaner stats UI, remove chart, integrate toolbar/pagination, and improve UX
- Replace custom dots with Semi Badge types (success/danger/warning); add compact Progress bars
- Remove pie chart and related deps/config; move total key count and mode tags into the modal title
- Rework header using Row/Col; three equal stat cards (enabled/manual-disabled/auto-disabled)
- Integrate toolbar into Table title; wrap content with Card; use Table’s native empty state
- Make “Enable All” conditional (hidden when all keys are enabled), mirroring “Disable All”
- Unify numeric typography (current/total same size) for better readability
- Default page size set to 10; fallback to 10 when backend page_size is absent; page-size options: 10/20/50/100
- Cleanup imports and dead code (remove VChart and pie-spec logic)
- Minor spacing polish (extra bottom margin before table), no footer buttons
2025-08-10 00:55:18 +08:00
t0ng7u a0f48f5a30 Merge remote-tracking branch 'origin/alpha' into alpha 2025-08-09 21:41:33 +08:00
t0ng7u 2177620e68 💄 style(ui): replace inline gradients with reusable pastel blur balls; improve dark mode
- Introduce a global CSS utility `with-pastel-balls` in `web/src/index.css`, rendering pastel “blur balls” via ::before with CSS variables (`--pb1..--pb4`, `--pb-opacity`, `--pb-blur`) for easy theming.
- Apply the utility to pricing header cards and skeletons:
  - `web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx`
  - `web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx`
- Remove per-component inline `linear-gradient(...)` backgrounds and redundant absolute-positioned decoration nodes to reduce duplication.
- Dark mode:
  - Keep the same pastel palette (pink/lavender/mint/peach).
  - Increase visibility with `--pb-opacity: 0.36`, `--pb-blur: 65px`, and `mix-blend-mode: screen`.
- No functional logic changes; UI-only. Lint passes.

Affected files:
- web/src/index.css
- web/src/components/table/model-pricing/layout/header/PricingVendorIntro.jsx
- web/src/components/table/model-pricing/layout/header/PricingVendorIntroSkeleton.jsx
2025-08-09 21:40:32 +08:00
t0ng7u 2da040f74c 🧹 Refactor: remove redundant code and simplify renderers
- Users table (UsersColumnDefs.js):
  - Remove unused quota calculations from the status renderer
  - Keep status Tag minimal; tooltip shows request count only
  - No functional changes

- Tokens table (TokensColumnDefs.js):
  - Simplify chats menu parsing from localStorage; remove redundant flags/loops
  - Remove unused variables and console statements
  - Keep error handling via showError; preserve existing operations behavior

- General:
  - Codebase tidying only; no UI/logic changes beyond cleanup
  - Lint passes successfully
2025-08-09 16:51:09 +08:00
t0ng7u 2757d555c7 style(ui): Replace switches with buttons; add quota column with Popover; cleanup
- Tokens/Users tables:
  - Replaced status Switch with explicit Enable/Disable buttons in the operation column
  - Unified button styles with Channels/Models (Disable: danger + small; Enable: default + small)
  - Status column now shows a small Tag only; standardized labels (Enabled/Disabled/etc.); removed usage info

- New "Remaining/Total Quota" column:
  - Wrapped in a white Tag; shows Remaining/Total with a progress bar
  - Replaced Tooltip with Popover; contents use Typography.Paragraph with copyable values
  - Copyable content excludes percentages (only numeric quota values are copied)
  - Added padding to Popover content for better readability

- Tokens specifics:
  - For unlimited quota, show a white Tag "Unlimited quota" with a Popover that displays copyable "Used quota"

- Cleanup:
  - Removed Switch imports/handlers and unused code paths
  - Eliminated console logs and redundant flags; simplified chats parsing
  - Removed quota calculations from status renderers

Files:
- web/src/components/table/tokens/TokensColumnDefs.js
- web/src/components/table/users/UsersColumnDefs.js
2025-08-09 16:47:14 +08:00
t0ng7u 1dd1ef862f 🐛 fix(db): allow re-adding models & vendors after soft delete; add safe index cleanup
Replace legacy single-column unique indexes with composite unique indexes on
(name, deleted_at) and introduce a safe index drop utility to eliminate
duplicate-key errors and noisy MySQL 1091 warnings.

WHAT
• model/model_meta.go
  - Model.ModelName  → `uniqueIndex:uk_model_name,priority:1`
  - Model.DeletedAt  → `index; uniqueIndex:uk_model_name,priority:2`
• model/vendor_meta.go
  - Vendor.Name      → `uniqueIndex:uk_vendor_name,priority:1`
  - Vendor.DeletedAt → `index; uniqueIndex:uk_vendor_name,priority:2`
• model/main.go
  - Add `dropIndexIfExists(table, index)`:
    • Checks `information_schema.statistics`
    • Drops index only when present (avoids Error 1091)
  - Invoke helper in `migrateDB` & `migrateDBFast`
  - Remove direct `ALTER TABLE … DROP INDEX …` calls

WHY
• Users received `Error 1062 (23000)` when re-creating a soft-deleted
  model/vendor because the old unique index enforced uniqueness on name alone.
• Directly dropping nonexistent indexes caused MySQL `Error 1091` noise.

HOW
• Composite unique indexes `(model_name, deleted_at)` / `(name, deleted_at)`
  respect GORM soft deletes.
• Safe helper ensures idempotent migrations across environments.

RESULT
• Users can now delete and re-add the same model or vendor without manual SQL.
• Startup migration runs quietly across MySQL, PostgreSQL, and SQLite.
• No behavior changes for existing data beyond index updates.

TEST
1. Add model “deepseek-chat” → delete (soft) → re-add → success.
2. Add vendor “DeepSeek”     → delete (soft) → re-add → success.
3. Restart service twice → no duplicate key or 1091 errors.
2025-08-09 15:44:08 +08:00
t0ng7u 4886f86cc8 style(JSONEditor): add AGPL-3.0 license header, clean imports & refine Banner UI
* Added full GNU Affero General Public License v3 header at the top of `JSONEditor.js`.
* Removed unused `IconCode` and `IconRefresh` imports to eliminate dead code.
* Set `closeIcon={null}` and applied `!rounded-md` class for `Banner`, improving visual consistency and preventing unintended dismissal.
* Normalized whitespace and line-breaks for better readability and lint compliance.
2025-08-09 14:08:28 +08:00
t0ng7u 92a979751e Merge remote-tracking branch 'origin/alpha' into alpha 2025-08-09 13:10:20 +08:00
t0ng7u e1796b92cd 🐛 fix(db): allow re-adding models/vendors after soft delete via composite unique indexes
Ensure models and vendors can be re-created after soft deletion by switching to composite unique indexes on (name, deleted_at) and cleaning up legacy single-column unique indexes on MySQL.

Why
- MySQL raised 1062 duplicate key errors when re-adding a soft-deleted model/vendor because the legacy unique index enforced uniqueness on the name column alone (uk_model_name / uk_vendor_name), despite soft deletes.
- Users encountered errors such as:
  - Error 1062 (23000): Duplicate entry 'deepseek-chat' for key 'models.uk_model_name'
  - Error 1062 (23000): Duplicate entry 'DeepSeek' for key 'vendors.uk_vendor_name'

How
- Model indices:
  - model/model_meta.go:
    - Model.ModelName → gorm: uniqueIndex:uk_model_name,priority:1
    - Model.DeletedAt → gorm: index; uniqueIndex:uk_model_name,priority:2
- Vendor indices:
  - model/vendor_meta.go:
    - Vendor.Name → gorm: uniqueIndex:uk_vendor_name,priority:1
    - Vendor.DeletedAt → gorm: index; uniqueIndex:uk_vendor_name,priority:2
- Migration (automatic, idempotent):
  - model/main.go (migrateDB, migrateDBFast):
    - On MySQL, drop legacy single-column unique indexes if present:
      - ALTER TABLE models DROP INDEX uk_model_name;
      - ALTER TABLE vendors DROP INDEX uk_vendor_name;
    - Then run AutoMigrate to create composite unique indexes.
  - Missing-index errors are ignored to keep the migration safe to run multiple times.

Result
- Users can delete and re-add the same model/vendor name without manual SQL.
- Migration runs automatically at startup; no user action required.
- PostgreSQL and SQLite remain unaffected.

Files changed
- model/model_meta.go
- model/vendor_meta.go
- model/main.go (migrateDB, migrateDBFast)

Testing
- Create model "deepseek-chat" → delete (soft) → re-create → succeeds.
- Create vendor "DeepSeek" → delete (soft) → re-create → succeeds.

Backward compatibility
- Data remains intact; only index definitions are updated.
- Behavior is unchanged except for fixing the uniqueness constraint with soft deletes.
2025-08-09 13:07:57 +08:00
t0ng7u 8d7ef18fc1 📱 feat(ui): enhance mobile pagination in PricingCardView
* Integrate `useIsMobile` hook to detect mobile devices.
* Pagination now automatically:
  * sets `size="small"` on mobile screens
  * enables `showQuickJumper` for quicker navigation on small screens
* Desktop behaviour remains unchanged.
2025-08-09 10:14:35 +08:00
t0ng7u 81a24e9a87 feat: Extend endpoint templates to cover all native endpoints
This commit updates the quick-fill endpoint templates used in the model and pre-fill group editors:

• `EditModelModal.jsx`
• `EditPrefillGroupModal.jsx`

Key changes
1. Added missing default endpoints defined in `common/endpoint_defaults.go`.
   - `openai-response`
   - `gemini`
   - `jina-rerank`
2. Ensured each template entry includes both `path` and `method` for clarity.

Benefits
• Provides one-click access to every built-in upstream endpoint, reducing manual input.
• Keeps the UI definitions in sync with backend defaults, preventing mismatch errors.
2025-08-09 09:33:13 +08:00
t0ng7u 13da9eb20a 🔧 fix(pricing): synchronize group ratio in Table & Card views with sidebar selection
Problem
Choosing a different token-group in the pricing sidebar only updated the filter but did **not** refresh the displayed group ratio in both the Table (`@table/`) and Card (`@card/`) views. The callback used by the sidebar changed `filterGroup` yet left `selectedGroup` untouched, so ratio columns/cards kept showing the previous value.

Solution
• `PricingSidebar.jsx`
  – Accept new prop `handleGroupClick` (from `useModelPricingData`).
  – Forward this callback to `PricingGroups` (`setFilterGroup={handleGroupClick}`) while retaining `setFilterGroup` for reset logic.
  – Keeps both `filterGroup` filtering and `selectedGroup` state in sync via the single unified handler.

Result
Switching groups in the sidebar now simultaneously updates:
1. the model list filtering, and
2. the ratio information shown in both pricing Table and Card views.

No UI/UX regression; linter passes.
2025-08-09 08:58:36 +08:00
t0ng7u 53ac48aeca 🎨 chore(sidebar): swap “Channel Management” and “Model Management” positions in admin menu
The sidebar’s admin section now displays “Channel Management” before “Model Management” to better reflect common user workflows and improve navigation clarity.

Details:
• Updated `web/src/components/layout/SiderBar.js`
  – Re-ordered items in `adminItems` array so `channel` precedes `models`.
• No logic or route changes; this is purely a UI ordering adjustment.

This change enhances usability for administrators by presenting frequently accessed channel settings first.
2025-08-09 08:50:39 +08:00
t0ng7u 9f452e4781 Merge remote-tracking branch 'origin/alpha' into alpha 2025-08-08 15:17:23 +08:00
t0ng7u b3b9fb9f93 🔧 refactor(pricing): render “auto” routing chain only when relevant & remove unused prop
Changes
1. ModelPricingTable.jsx
   • Compute `autoChain` as the intersection of `autoGroups` and the model’s `enable_groups` (order preserved).
   • Display the chain banner only when `autoChain.length > 0`; banner shows the reduced path (e.g. `a → c → e`).
   • Dropped obsolete `selectedGroup` prop; all callers updated.

2. ModelDetailSideSheet.jsx / PricingPage.jsx
   • Removed forwarding of deleted `selectedGroup` prop.

Outcome
– “Auto group routing” appears only for models that actually participate in the chain, avoiding empty or irrelevant banners.
– Codebase simplified by eliminating an unused prop.
2025-08-08 15:11:31 +08:00
t0ng7u bd421f471b Merge remote-tracking branch 'origin/alpha' into alpha 2025-08-08 14:50:08 +08:00
t0ng7u 55469a90dc 🚀 feat(pricing): clarify “auto” group routing chain and exclude it from price table
Detailed changes
Backend
• `controller/pricing.go` now includes `auto_groups` in `/api/pricing` response, sourced from `setting.AutoGroups`.

Frontend
• `useModelPricingData.js`
  – Parses `auto_groups` and exposes `autoGroups` state.
• `PricingPage.jsx` → `ModelDetailSideSheet.jsx` → `ModelPricingTable.jsx`
  – Thread `autoGroups` through component tree.
• `ModelPricingTable.jsx`
  – Removes deprecated `getGroupDescription` / `Tooltip`.
  – Filters out `auto` when building price table rows.
  – Renders a descriptive banner: “auto 分组调用链路 → auto → group1 → …”, clarifying fallback order without showing prices.
• Minor i18n tweak: adds `auto分组调用链路` key for the banner text.

Why
Users were confused by the “auto” tag appearing alongside regular groups with no price.
This change:
1. Makes the routing chain explicit.
2. Keeps the pricing table focused on billable groups.

No breaking API changes; existing clients can ignore the new `auto_groups` field.
2025-08-08 14:49:55 +08:00
t0ng7u 85032b3eb8 feat(web): add model prefill group quick-add buttons to channel models selector
- Added support to fetch and render “model prefill groups” in `EditChannelModal.jsx`
- Users can now click a group button to instantly merge that group’s models into the models Select
- Mirrors the prefill-group UX used for tags/endpoints in `EditModelModal.jsx`

Details
- UI/UX:
  - Renders one button per model group inside the models field’s extra actions
  - Clicking a button merges its items into the selected models (trimmed, deduplicated), updating immediately
  - Non-destructive and works alongside existing actions (fill related/all models, fetch upstream, clear, copy)
- API:
  - GET `/api/prefill_group?type=model`
  - Handles `items` as either an array or a JSON string array for robustness
  - If request fails or returns no groups, buttons are simply not shown
- i18n:
  - Reuses existing i18n; group names come from backend and are displayed as-is
- Performance:
  - Simple set merge; negligible overhead
- Backward compatibility:
  - No changes required on the backend or elsewhere; feature is additive
- Testing (manual):
  1) Open channel modal (new or edit) and navigate to the Models section
  2) Confirm model group buttons render when groups are configured
  3) Click a group button → models Select updates with merged models (no duplicates)
  4) Verify other actions (fill related/all, fetch upstream, clear, copy) still work
  5) Close/reopen modal → state resets as expected

Implementation
- `web/src/components/table/channels/modals/EditChannelModal.jsx`
  - Added `modelGroups` state and `fetchModelGroups()` (GET `/api/prefill_group?type=model`)
  - Invoked `fetchModelGroups()` when the modal opens
  - Rendered group buttons in the models Select `extraText`, merging group items into current selection

Chore
- Verified no new linter errors were introduced.
2025-08-08 04:48:18 +08:00
t0ng7u f080bef7b8 💄 ui: adjust column layout in pricing table
- Move model price column to fixed right position
- Convert endpoint types column from fixed to regular column
- Reorder columns: endpoint types now appears before ratio column
- Improve table layout and user experience for pricing data viewing

Changes made to web/src/components/table/model-pricing/view/table/PricingTableColumns.js:
* Removed `fixed: 'right'` from endpointColumn
* Added `fixed: 'right'` to priceColumn
* Updated column order in the columns array
2025-08-08 04:41:46 +08:00
t0ng7u 70719d4a43 🐛 fix(model, web): robust JSON handling; remove datatypes dep; stabilize 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
2025-08-08 04:21:50 +08:00
t0ng7u 2da0e1a542 ♻️ refactor(model): replace gorm.io/datatypes with JSONValue for PrefillGroup.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
2025-08-08 04:09:53 +08:00
t0ng7u 3fc7f7e8f8 ♻️ refactor(model): replace gorm.io/datatypes with json.RawMessage for 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.
2025-08-08 03:52:07 +08:00
t0ng7u ec402abba3 🤔 revert: revert go.sum & go.mod version 2025-08-08 03:35:31 +08:00
t0ng7u 41c93f250e 🤔 revert: revert go.sum & go.mod version 2025-08-08 03:22:25 +08:00
t0ng7u f46acac691 🎨 refactor(ui): merge “Content Configuration” into “Basic Information” 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.
2025-08-08 03:04:51 +08:00
t0ng7u 908714b73c feat: Improve models UX and robustness: add JSONEditor extraFooter, 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.
2025-08-08 02:59:45 +08:00
t0ng7u 68f1a2c329 Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing 2025-08-08 02:34:44 +08:00
t0ng7u 5b268d855e feat(pricing+endpoints+ui): wire custom endpoint mapping end‑to‑end 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.
2025-08-08 02:34:15 +08:00
t0ng7u 05cf55ac15 Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing 2025-08-07 11:09:28 +08:00
t0ng7u 1d28e4ddcb 🎨 feat(models): add row styling for disabled models in ModelsTable
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.
2025-08-07 10:54:05 +08:00
t0ng7u 029768e868 feat(models): Revamp EditModelModal UI and UX
This commit significantly refactors the `EditModelModal` component to streamline the user interface and enhance usability, aligning it with the interaction patterns found elsewhere in the application.

- **Consolidated Layout:** Merged the "Vendor Information" and "Feature Configuration" sections into a single "Basic Information" card. This simplifies the form, reduces clutter, and makes all settings accessible in one view.

- **Improved Prefill Groups:** Replaced the separate `Select` dropdowns for tag and endpoint groups with a more intuitive button-based system within the `extraText` of the `TagInput` components.

- **Additive Button Logic:** The prefill group buttons now operate in an additive mode. Users can click multiple group buttons to incrementally add tags or endpoints, with duplicates being automatically handled.

- **Clear Functionality:** Added "Clear" buttons for both tags and endpoints, allowing users to easily reset the fields.

- **Code Cleanup:** Removed the unused `endpointOptions` constant and unnecessary icon imports (`Building`, `Settings`) to keep the codebase clean.
2025-08-06 03:29:45 +08:00
t0ng7u ac0544614b 🚀 refactor: migrate vendor-count aggregation to model layer & align frontend logic
Summary
• Backend
  – Moved duplicate-name validation and total vendor-count aggregation from controllers (`controller/model_meta.go`, `controller/vendor_meta.go`, `controller/prefill_group.go`) to model layer (`model/model_meta.go`, `model/vendor_meta.go`, `model/prefill_group.go`).
  – Added `GetVendorModelCounts()` and `Is*NameDuplicated()` helpers; controllers now call these instead of duplicating queries.
  – API response for `/api/models` now returns `vendor_counts` with per-vendor totals across all pages, plus `all` summary.
  – Removed redundant checks and unused imports, eliminating `go vet` warnings.

• Frontend
  – `useModelsData.js` updated to consume backend-supplied `vendor_counts`, calculate the `all` total once, and drop legacy client-side counting logic.
  – Simplified initial data flow: first render now triggers only one models request.
  – Deleted obsolete `updateVendorCounts` helper and related comments.
  – Ensured search flow also sets `vendorCounts`, keeping tab badges accurate.

Why
This refactor enforces single-responsibility (aggregation in model layer), delivers consistent totals irrespective of pagination, and removes redundant client queries, leading to cleaner code and better performance.
2025-08-06 01:40:08 +08:00
t0ng7u ef8675c070 Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing 2025-08-05 23:19:24 +08:00
t0ng7u cfb5b6024c 🚀 refactor: refine pricing refresh logic & hide disabled models
Summary
-------
1. Pricing generation
   • `model/pricing.go`: skip any model whose `status != 1` when building
     `pricingMap`, ensuring disabled models are never returned to the
     front-end.

2. Cache refresh placement
   • `controller/model_meta.go`
     – Removed `model.RefreshPricing()` from pure read handlers
       (`GetAllModelsMeta`, `SearchModelsMeta`).
     – Kept refresh only in mutating handlers
       (`Create`, `Update`, `Delete`), guaranteeing data is updated
       immediately after an admin change while avoiding redundant work
       on every read.

Result
------
Front-end no longer receives information about disabled models, and
pricing cache refreshes occur exactly when model data is modified,
improving efficiency and consistency.
2025-08-05 23:18:12 +08:00
t0ng7u d7428227f6 feat: enhance soft-delete handling & boost pricing cache performance
Summary
-------
This commit unifies soft-delete behaviour across meta tables and
introduces an in-memory cache for model pricing look-ups to improve
throughput under high concurrency.

Details
-------
Soft-delete consistency
• PrefillGroup / Vendor / Model
  – Added `gorm.DeletedAt` field with `json:"-" gorm:"index"`.
  – Replaced plain `uniqueIndex` with partial unique indexes
    `uniqueIndex:<name>,where:deleted_at IS NULL`
    allowing duplicate keys after logical deletion while preserving
    uniqueness for active rows.
• Imports updated to include `gorm.io/gorm`.
• JSON output now hides `deleted_at`, matching existing tables.

High-throughput pricing cache
• model/pricing.go
  – Added thread-safe maps `modelEnableGroups` & `modelQuotaTypeMap`
    plus RW-mutex for O(1) access.
  – `updatePricing()` now refreshes these maps alongside `pricingMap`.
• model/model_extra.go
  – Rewrote `GetModelEnableGroups` & `GetModelQuotaType` to read from
    the new maps, falling back to automatic refresh via `GetPricing()`.

Misc
• Retained `RefreshPricing()` helper for immediate cache invalidation
  after admin actions.
• All modified files pass linter; no breaking DB migrations required
  (handled by AutoMigrate).

Result
------
– Soft-delete logic is transparent, safe, and allows record “revival”.
– Pricing-related queries are now constant-time, reducing CPU usage and
  latency under load.
2025-08-05 22:26:19 +08:00
t0ng7u 607b6d4c1d 🐛 fix(models): eliminate vendor column flicker by loading vendors before models
Why:
• The vendor list API is separate from the models API, causing the “Vendor” column in `ModelsTable` to flash (rendering `'-'` first, then updating) after the table finishes loading.
• This visual jump degrades the user experience.

What:
• Updated `web/src/hooks/models/useModelsData.js`
  – In the initial `useEffect`, vendors are fetched first with `loadVendors()` and awaited.
  – Only after vendors are ready do we call `loadModels()`, ensuring `vendorMap` is populated before the table renders.

Outcome:
• The table now renders with complete vendor data on first paint, removing the flicker and providing a smoother UI.
2025-08-04 22:11:13 +08:00
t0ng7u a436f81e1c 💄 fix(pricing-card): align skeleton responsive grid with actual card layout
- Update PricingCardSkeleton grid classes from 'sm:grid-cols-2 lg:grid-cols-3'
  to 'xl:grid-cols-2 2xl:grid-cols-3' to match PricingCardView layout
- Ensures consistent column count between skeleton and actual content
  at same screen sizes
- Improves loading state visual consistency across different breakpoints
2025-08-04 22:03:12 +08:00
t0ng7u 5b55a53b07 🍎 chore: modify the JSONEditor component import path 2025-08-04 21:58:10 +08:00
t0ng7u e64a7520cb Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing 2025-08-04 21:37:38 +08:00
t0ng7u f6b49dce15 🏗️ refactor: Replace model categories with vendor-based filtering and optimize data structure
- **Backend Changes:**
  - Refactor pricing API to return separate vendors array with ID-based model references
  - Remove redundant vendor_name/vendor_icon fields from pricing records, use vendor_id only
  - Add vendor_description to pricing response for frontend display
  - Maintain 1-minute cache protection for pricing endpoint security

- **Frontend Data Flow:**
  - Update useModelPricingData hook to build vendorsMap from API response
  - Enhance model records with vendor info during data processing
  - Pass vendorsMap through component hierarchy for consistent vendor data access

- **UI Component Replacements:**
  - Replace PricingCategories with PricingVendors component for vendor-based filtering
  - Replace PricingCategoryIntro with PricingVendorIntro in header section
  - Remove all model category related components and logic

- **Header Improvements:**
  - Implement vendor intro with real backend data (name, icon, description)
  - Add text collapsible feature (2-line limit with expand/collapse functionality)
  - Support carousel animation for "All Vendors" view with vendor icon rotation

- **Model Detail Modal Enhancements:**
  - Update ModelHeader to use real vendor icons via getLobeHubIcon()
  - Move tags from header to ModelBasicInfo content area to avoid SideSheet title width constraints
  - Display only custom tags from backend with stringToColor() for consistent styling
  - Use Space component with wrap property for proper tag layout

- **Table View Optimizations:**
  - Integrate RenderUtils for description and tags columns
  - Implement renderLimitedItems for tags (max 3 visible, +x popover for overflow)
  - Use renderDescription for text truncation with tooltip support

- **Filter Logic Updates:**
  - Vendor filter shows disabled options instead of hiding when no models match
  - Include "Unknown Vendor" category for models without vendor information
  - Remove all hardcoded vendor descriptions, use real backend data

- **Code Quality:**
  - Fix import paths after component relocation
  - Remove unused model category utilities and hardcoded mappings
  - Ensure consistent vendor data usage across all pricing views
  - Maintain backward compatibility with existing pricing calculation logic

This refactor provides a more scalable vendor-based architecture while eliminating
data redundancy and improving user experience with real-time backend data integration.
2025-08-04 21:36:31 +08:00
t0ng7u 12b1893287 feat: add model name matching rules with priority-based lookup
Add flexible model name matching system to support different matching patterns:

Backend changes:
- Add `name_rule` field to Model struct with 4 matching types:
  * 0: Exact match (default)
  * 1: Prefix match
  * 2: Contains match
  * 3: Suffix match
- Implement `FindModelByNameWithRule` function with priority order:
  exact > prefix > suffix > contains
- Add database migration for new `name_rule` column

Frontend changes:
- Add "Match Type" column in models table with colored tags
- Add name rule selector in create/edit modal with validation
- Auto-set exact match and disable selection for preconfigured models
- Add explanatory text showing priority order
- Support i18n for all new UI elements

This enables users to define model patterns once and reuse configurations
across similar models, reducing repetitive setup while maintaining exact
match priority for specific overrides.

Closes: #[issue-number]
2025-08-04 16:01:56 +08:00
t0ng7u c504c9af5d 💰 feat: Add model billing type (quota_type) support across backend & frontend
Summary
• Backend
  1. model/model_meta.go
     – Added `QuotaType` field to `Model` struct (JSON only, gorm `-`).
  2. model/model_groups.go
     – Implemented `GetModelQuotaType(modelName)` leveraging cached pricing map.
  3. controller/model_meta.go
     – Enhanced `fillModelExtra` to populate `QuotaType` using new helper.

• Frontend
  1. web/src/components/table/models/ModelsColumnDefs.js
     – Introduced `renderQuotaType` helper that visualises billing mode with coloured tags (`teal = per-call`, `violet = per-token`).
     – Added “计费类型” column (`quota_type`) to models table.

Why
Providing the billing mode alongside existing pricing/group information gives administrators instant visibility into whether each model is priced per call or per token, aligning UI with new backend metadata.

Notes
No database migration required – `quota_type` is transient, delivered via API. Frontend labels/colours can be adjusted via i18n or theme tokens if necessary.
2025-08-04 15:38:01 +08:00
t0ng7u 8816896166 Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing 2025-08-04 10:00:27 +08:00
t0ng7u 663e25b311 feat: Add prefill group management system for models
- Add new PrefillGroup model with CRUD operations
  * Support for model, tag, and endpoint group types
  * JSON storage for group items with GORM datatypes
  * Automatic database migration support

- Implement backend API endpoints
  * GET /api/prefill_group - List groups by type with admin auth
  * POST /api/prefill_group - Create new groups
  * PUT /api/prefill_group - Update existing groups
  * DELETE /api/prefill_group/:id - Delete groups

- Add comprehensive frontend management interface
  * PrefillGroupManagement component for group listing
  * EditPrefillGroupModal for group creation/editing
  * Integration with EditModelModal for auto-filling
  * Responsive design with CardTable and SideSheet

- Enhance model editing workflow
  * Tag group selection with auto-fill functionality
  * Endpoint group selection with auto-fill functionality
  * Seamless integration with existing model forms

- Create reusable UI components
  * Extract common rendering utilities to models/ui/
  * Shared renderLimitedItems and renderDescription functions
  * Consistent styling across all model-related components

- Improve user experience
  * Empty state illustrations matching existing patterns
  * Fixed column positioning for operation buttons
  * Item content display with +x indicators for overflow
  * Tooltip support for long descriptions
2025-08-04 02:54:37 +08:00
t0ng7u d70e9a48f1 🚀 feat: expose “Enabled Groups” for models with real-time refresh
Backend
• model/model_meta.go
  – Added `EnableGroups []string` to Model struct
  – fillModelExtra now populates EnableGroups

• model/model_groups.go
  – New helper `GetModelEnableGroups` (reuses Pricing cache)

• model/pricing_refresh.go
  – Added `RefreshPricing()` to force immediate cache rebuild

• controller/model_meta.go
  – `GetAllModelsMeta` & `SearchModelsMeta` call `model.RefreshPricing()` before querying, ensuring groups / endpoints are up-to-date

Frontend
• ModelsColumnDefs.js
  – Added `renderGroups` util and “可用分组” table column displaying color-coded tags

Result
Admins can now see which user groups can access each model, and any ability/group changes are reflected instantly without the previous 1-minute delay.
2025-08-04 00:00:51 +08:00
t0ng7u 2e201a4355 feat: polish “Missing Models” UX & mobile actions layout
Overview
• Re-designed `MissingModelsModal` to align with `ModelTestModal` and deliver a cleaner, paginated experience.
• Improved mobile responsiveness for action buttons in `ModelsActions`.

Details
1. MissingModelsModal.jsx
   • Switched from `List` to `Table` for a more structured view.
   • Added search bar with live keyword filtering and clear icon.
   • Implemented pagination via `MODEL_TABLE_PAGE_SIZE`; auto-resets on search.
   • Dynamic rendering: when no data, show unified Empty state without column header.
   • Enhanced header layout with total-count subtitle and modal corner rounding.
   • Removed unused `Typography.Text` import.

2. ModelsActions.jsx
   • Set “Delete Selected Models” and “Missing Models” buttons to `flex-1 md:flex-initial`, placing them on the same row as “Add Model” on small screens.

Result
The “Missing Models” workflow now offers quicker discovery, a familiar table interface, and full mobile friendliness—without altering API behavior.
2025-08-03 22:51:24 +08:00
t0ng7u 1bd8e2a157 feat(edit-vendor-modal): add icon-library reference link & tidy status switch
Highlights
• Introduced `Typography.Text` link with `IconLink` in `extraText` for the **icon** field, pointing to LobeHub’s full icon catalogue; only “请点击我” is clickable for clarity.
• Added required imports for `Typography` and `IconLink`.
• Removed unnecessary `size="large"` prop from the status `Form.Switch` to align with default form styling.

These tweaks improve user guidance when selecting vendor icons and refine the modal’s visual consistency.
2025-08-03 19:45:58 +08:00
t0ng7u 4a9610e200 ♻️ refactor(models-table): extract reusable renderLimitedItems for list popovers
Introduce a generic `renderLimitedItems` helper within `ModelsColumnDefs.js` to eliminate duplicated logic for list-style columns.

Key changes
• Added `renderLimitedItems` to handle item limiting, “+N” indicator, and popover display.
• Migrated `renderTags`, `renderEndpoints`, and `renderBoundChannels` to use the new helper.
• Removed redundant inline implementations, reducing complexity and improving readability.
• Preserved previous UX: first 3 items shown, overflow accessible via popover.

This refactor streamlines code maintenance and ensures consistent behavior across related columns.
2025-08-03 19:31:29 +08:00
t0ng7u 652506f1b3 Merge branch 'alpha' into refactor/model-pricing 2025-08-02 22:26:40 +08:00
t0ng7u 8ffa48ab34 feat(ui): enhance tag input
1. EditModelModal quality-of-life
   • Added comma parsing to `Form.TagInput`; users can now paste
     `tag1, tag2 , tag3` to bulk-create tags.
   • Updated placeholder copy to reflect the new capability.

All files pass linting; no runtime changes outside the intended UI updates.
2025-08-01 03:00:12 +08:00
t0ng7u e3d98a3f5b 🎨 style(sidebar): unify highlight color & assign unique icon for Models
• Removed obsolete `sidebarIconColors` map and `getItemColor` util from
  SiderBar/render; all selected states now use the single CSS variable
  `--semi-color-primary` for both text and icons.
• Simplified `getLucideIcon`:
  – Added `Package` to Lucide imports.
  – Switched “models” case to `<Package />`, avoiding duplication with
    the Layers glyph.
  – Replaced per-key color logic with `iconColor` derived from the new
    uniform highlight color.
• Stripped any unused imports / dead code paths after the refactor.
• Lint passes; sidebar hover/focus behavior unchanged while visual
  consistency is improved.
2025-08-01 02:50:06 +08:00
t0ng7u 1f35f66820 ♻️ refactor(hooks/models): deduplicate useModelsData and optimize vendor-tab counts
Highlights
──────────
1. Removed code duplication
   • Introduced `extractItems` helper to safely unwrap API payloads.
   • Simplified `getFormValues` to a single-line fallback expression.
   • Replaced repeated list-extraction code in `loadModels`, `searchModels`,
     and `refreshVendorCounts` with the new helper.

2. Vendor tab accuracy & performance
   • Added `refreshVendorCounts` to recalc counts via a single lightweight
     request; invoked only when必要 (current tab ≠ "all“) to avoid redundancy.
   • `loadModels` still updates counts instantly when viewing "all", ensuring
     accurate numbers on initial load and page changes.

3. Misc clean-ups
   • Streamlined conditional URL building and state updates.
   • Confirmed all async branches include error handling with i18n messages.
   • Ran linter → zero issues.

Result: leaner, easier-to-maintain hook with correct, real-time vendor counts
and no repeated logic.
2025-08-01 02:39:12 +08:00
t0ng7u b0fe72910f 🐛 fix(model): preserve created_time on Model update and streamline field maintenance
The update operation for Model previously overwrote `created_time` with zero
because GORM included every struct field in the UPDATE statement.
This commit adjusts `Model.Update()` to:

* Call `Omit("created_time")` so the creation timestamp is never modified.
* Refresh `UpdatedTime` with `common.GetTimestamp()` before persisting.
* Delegate the remainder of the struct to GORM, eliminating the need to
  maintain an explicit allow-list whenever new fields are introduced.

No API contract is changed; existing CRUD endpoints continue to work
normally while data integrity for historical records is now guaranteed.
2025-08-01 02:21:14 +08:00
t0ng7u ad4de7aaef 🔄 fix: improve vendor-tab filtering & counts, resolve SQL ambiguity, and reload data correctly
Backend
• model/model_meta.go
  – Import strconv
  – SearchModels: support numeric vendor ID filter vs. fuzzy name search
  – Explicitly order by `models.id` to avoid “ambiguous column name: id” error

Frontend
• hooks/useModelsData.js
  – Change vendor-filter API to pass vendor ID
  – Automatically reload models when `activeVendorKey` changes
  – Update vendor counts only when viewing “All” to preserve other tab totals
• Add missing effect in EditModelModal to refresh vendor list only when modal visible
• Other minor updates to keep lints clean

Result
Tabs now:
1. Trigger API requests on click
2. Show accurate per-vendor totals
3. Filter models without resetting other counts
Backend search handles both vendor IDs and names without SQL errors.
2025-07-31 23:30:45 +08:00
t0ng7u 8ec0f0a2bc Merge branch 'alpha' into refactor/model-pricing 2025-07-31 22:28:59 +08:00
t0ng7u 1baad070d7 🚀 feat: Introduce full Model & Vendor Management suite (backend + frontend) 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.
2025-07-31 22:28:09 +08:00
t0ng7u b243fce7ec Merge branch 'alpha' into refactor/model-pricing 2025-07-31 00:41:01 +08:00
t0ng7u e0d9a419be Merge remote-tracking branch 'origin/alpha' into alpha 2025-07-28 01:33:36 +08:00
t0ng7u e85bf7b1b0 feat: Enhance Channel Model Management UI
Summary
• Introduced standalone `ModelSelectModal.jsx` for selecting channel models
• Fetch-list now opens modal instead of in-place select, keeping EditChannelModal lean

Modal Features
1. Search bar with `IconSearch`, keyboard clear & mobile full-screen support
2. Tab layout (“New Models” / “Existing Models”) displayed next to title, responsive wrapping
3. Models grouped by vendor via `getModelCategories` and rendered inside always-expanded `Collapse` panels
4. Per-category checkbox in panel extra area for bulk select / deselect
5. Footer checkbox for bulk select of all models in current tab, with real-time counter
6. Empty state uses `IllustrationNoResult` / `IllustrationNoResultDark` for visual consistency
7. Accessible header/footer paddings aligned with Semi UI defaults

Fixes & Improvements
• All indeterminate and full-select states handled correctly
• Consistent “selected X / Y” stats synced with active tab, not global list
• All panels now controlled via `activeKey`, ensuring they remain expanded
• Search, vendor grouping, and responsive layout tested across mobile & desktop

These changes modernise the channel model management workflow and prepare the codebase for upcoming upstream-ratio integration.
2025-07-28 01:33:23 +08:00
t0ng7u 8d38cb8663 Merge branch 'alpha' into refactor/model-pricing 2025-07-27 09:51:35 +08:00
t0ng7u 84fb6eea1f 🗂️ refactor(table): isolate column preferences per role
Summary
• Added role-specific localStorage keys for column visibility in three hooks:
  - `useUsageLogsData.js` → `logs-table-columns-admin` / `logs-table-columns-user`
  - `useMjLogsData.js`   → `mj-logs-table-columns-admin` / `mj-logs-table-columns-user`
  - `useTaskLogsData.js` → `task-logs-table-columns-admin` / `task-logs-table-columns-user`

Details
1. Each hook now derives a `STORAGE_KEY` based on `isAdminUser`, preventing admin and non-admin sessions from overwriting one another’s column settings.
2. Removed the previous “save but strip admin columns” workaround—settings are persisted unmodified to each role’s key.
3. Kept runtime behaviour: non-admin users still see admin-only columns forcibly hidden.
4. Replaced newly added Chinese comments with clear English equivalents for consistency.

Result
Switching between admin and non-admin accounts no longer corrupts column visibility preferences, and codebase comments are fully English-localized.
2025-07-27 09:49:57 +08:00
t0ng7u 1fb829ddaa Merge branch 'alpha' into refactor/model-pricing 2025-07-27 00:03:18 +08:00
t0ng7u 90cf9efd6b 🔍 fix: select search filter
Summary
• Introduced a unified `selectFilter` helper that matches both `option.value` and `option.label`, ensuring all `<Select>` components support intuitive search (fixes channel “type” dropdown not filtering).
• Replaced all usages of the old `modelSelectFilter` with `selectFilter` in:
  • `EditChannelModal.jsx`
  • `SettingsPanel.js`
  • `EditTokenModal.jsx`
  • `EditTagModal.jsx`
• Removed the deprecated `modelSelectFilter` export from `utils.js` (no backward-compat alias).
• Updated documentation comments accordingly.

Why
The old filter only inspected `option.value`, causing searches to fail when `label` carried the meaningful text (e.g., numeric IDs for channel types). The new helper searches both fields, covering all scenarios and unifying the API across the codebase.

Notes
No functional regressions expected; all components have been migrated.
2025-07-27 00:01:12 +08:00
t0ng7u 017f32b978 refactor: pricing filters for dynamic counting & cleaner logic
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.
2025-07-26 18:38:18 +08:00
t0ng7u 2dd99898ca Merge branch 'alpha' into refactor/model-pricing 2025-07-26 17:21:47 +08:00
t0ng7u bbc3ae6e7b 💄 style(ui): show "Force Format" toggle only for OpenAI channels
Previously, the "Force Format" switch was displayed for every channel type
although it only applies to OpenAI (type === 1).
This change wraps the switch in a conditional so it renders exclusively when
the selected channel type is OpenAI.

Why:
- Prevents user confusion when configuring non-OpenAI channels
- Keeps the UI clean and context-relevant

Scope:
- web/src/components/table/channels/modals/EditChannelModal.jsx

No backend logic affected.
2025-07-26 17:18:47 +08:00
t0ng7u 2356c3fe66 🎨 refactor(ui): redesign channel extra settings section in EditChannelModal
- Extract channel extra settings into a dedicated Card component for better visual hierarchy
- Replace custom gray background container with consistent Form component styling
- Simplify layout structure by removing complex Row/Col grid layout in favor of native Form component layout
- Unify help text styling by using extraText prop consistently across all form fields
- Move "Settings Documentation" link to card header subtitle for better accessibility
- Improve visual consistency with other setting cards by using matching design patterns

The channel extra settings (force format, thinking content conversion, pass-through body, proxy address, and system prompt) now follow the same design language as other configuration sections, providing a more cohesive user experience.

Affected settings:
- Force Format (OpenAI channels only)
- Thinking Content Conversion
- Pass-through Body
- Proxy Address
- System Prompt
2025-07-26 13:33:10 +08:00
t0ng7u 1327404e35 refactor: Restructure model pricing components and improve UX consistency
- **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.
2025-07-26 04:24:22 +08:00
t0ng7u b04fd36c53 Merge branch 'alpha' into refactor/model-pricing 2025-07-25 20:33:14 +08:00
t0ng7u 3ea058e0db 🔒 fix: Enforce admin-only column visibility in logs tables
Ensure non-admin users cannot enable columns reserved for administrators
across the following hooks:

* web/src/hooks/usage-logs/useUsageLogsData.js
  - Force-hide CHANNEL, USERNAME and RETRY columns for non-admins.

* web/src/hooks/mj-logs/useMjLogsData.js
  - Force-hide CHANNEL and SUBMIT_RESULT columns for non-admins.

* web/src/hooks/task-logs/useTaskLogsData.js
  - Force-hide CHANNEL column for non-admins.

The checks run when loading column preferences from localStorage, overriding
any tampered settings to keep sensitive information hidden from
unauthorized users.
2025-07-25 20:31:20 +08:00
t0ng7u d351c61606 🍭 style(ui): Optimize style layout and improve responsive layout 2025-07-24 23:28:55 +08:00
t0ng7u 27fffe0c5e Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing 2025-07-24 17:47:08 +08:00
t0ng7u b935068b1f 📱 fix(ui): adjust responsive breakpoints for pricing card grid layout
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
2025-07-24 17:44:48 +08:00
t0ng7u 4116111b57 ♻️ refactor(utils): optimize resetPricingFilters function for better 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.
2025-07-24 17:22:20 +08:00
t0ng7u 16b2d0f1bf ♻️ refactor(utils): optimize resetPricingFilters function for better 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.
2025-07-24 17:15:28 +08:00
t0ng7u 0ff0027aa6 ♻️ Refactor: Move token unit toggle from table header to filter settings
- 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)
2025-07-24 17:10:08 +08:00
t0ng7u b3a05d2bbb 🖼️ style(ui): change skeleton button size to 16*16 2025-07-24 03:29:48 +08:00
t0ng7u 8462931253 feat: Add endpoint type filter to model pricing system
- 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.
2025-07-24 03:25:57 +08:00
t0ng7u f19f1aecc0 💄 style(pricing): enhance card view UI and skeleton loading experience (#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.
2025-07-24 03:19:32 +08:00
t0ng7u 7b04596b7f Merge branch 'alpha' into refactor/model-pricing 2025-07-24 01:35:59 +08:00
t0ng7u 68e61b407d ♻️ refactor(model-pricing): improve table UI and optimize code structure (#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
2025-07-23 11:20:55 +08:00
t0ng7u 024dcda92a 🔧 fix: filter out empty string group from pricing groups selector (#1365)
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
2025-07-23 10:04:32 +08:00
t0ng7u 3ae8c74160 feat: Add skeleton loading animation to SelectableButtonGroup component (#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
2025-07-23 04:31:27 +08:00