Implement comprehensive topup billing system with user history viewing and admin management capabilities.
## Features Added
### Frontend
- Add topup history modal with paginated billing records
- Display order details: trade number, payment method, amount, money, status, create time
- Implement empty state with proper illustrations
- Add payment method column with localized display (Stripe, Alipay, WeChat)
- Add admin manual completion feature for pending orders
- Add Coins icon for recharge amount display
- Integrate "Bills" button in RechargeCard header
- Optimize code quality by using shared utility functions (isAdmin)
- Extract constants for status and payment method mappings
- Use React.useMemo for performance optimization
### Backend
- Create GET `/api/user/topup/self` endpoint for user topup history with pagination
- Create POST `/api/user/topup/complete` endpoint for admin manual order completion
- Add `payment_method` field to TopUp model for tracking payment types
- Implement `GetUserTopUps` method with proper pagination and ordering
- Implement `ManualCompleteTopUp` with transaction safety and row-level locking
- Add application-level mutex locks to prevent concurrent order processing
- Record payment method in Epay and Stripe payment flows
- Ensure idempotency and data consistency with proper error handling
### Internationalization
- Add i18n keys for Chinese (zh), English (en), and French (fr)
- Support for billing-related UI text and status messages
## Technical Improvements
- Use database transactions with FOR UPDATE row-level locking
- Implement sync.Map-based mutex for order-level concurrency control
- Proper error handling and user-friendly toast notifications
- Follow existing codebase patterns for empty states and modals
- Maintain code quality with extracted render functions and constants
## Files Changed
- Backend: controller/topup.go, controller/topup_stripe.go, model/topup.go, router/api-router.go
- Frontend: web/src/components/topup/modals/TopupHistoryModal.jsx (new), web/src/components/topup/RechargeCard.jsx, web/src/components/topup/index.jsx
- i18n: web/src/i18n/locales/{zh,en,fr}.json
- Replace blanket console route footer hiding with specific page targeting
- Only hide footer on pages that use CardPro component:
* /console/channel (channels management)
* /console/log (usage logs)
* /console/redemption (redemption codes)
* /console/user (user management)
* /console/token (token management)
* /console/midjourney (midjourney logs)
* /console/task (task logs)
* /console/models (model management)
* /pricing (pricing page)
- Footer now displays on other console pages (dashboard, settings, topup, etc.)
- Improves UI consistency by showing footer where CardPro's internal pagination isn't used
This change ensures footer is only hidden when CardPro component provides its own
pagination/footer functionality, while preserving footer visibility on other pages
that benefit from the global footer navigation.
Replace the legacy boolean “DisplayInCurrencyEnabled” with an injected, type-safe
configuration `general_setting.quota_display_type`, and wire it through the
backend and frontend.
Backend
- Add `QuotaDisplayType` to `operation_setting.GeneralSetting` with injected
registration via `config.GlobalConfig.Register("general_setting", ...)`.
Helpers: `IsCurrencyDisplay()`, `IsCNYDisplay()`, `GetQuotaDisplayType()`.
- Expose `quota_display_type` in `/api/status` and keep legacy
`display_in_currency` for backward compatibility.
- Logger: update `LogQuota` and `FormatQuota` to support USD/CNY/TOKENS. When
CNY is selected, convert using `operation_setting.USDExchangeRate`.
- Controllers:
- `billing`: compute subscription/usage amounts based on the selected type
(USD: divide by `QuotaPerUnit`; CNY: USD→CNY; TOKENS: keep raw tokens).
- `topup` / `topup_stripe`: treat inputs as “amount” for USD/CNY and as
token-count for TOKENS; adjust min topup and pay money accordingly.
- `misc`: include `quota_display_type` in status payload.
- Compatibility: in `model/option.UpdateOption`, map updates to
`DisplayInCurrencyEnabled` → `general_setting.quota_display_type`
(true→USD, false→TOKENS). Keep exporting the legacy key in `OptionMap`.
Frontend
- Settings: replace the “display in currency” switch with a Select
(`general_setting.quota_display_type`) offering USD / CNY / Tokens.
Provide fallback mapping from legacy `DisplayInCurrencyEnabled`.
- Persist `quota_display_type` to localStorage (keep `display_in_currency`
for legacy components).
- Rendering helpers: base all quota/price rendering on `quota_display_type`;
use `usd_exchange_rate` for CNY symbol/values.
- Pricing page: default view currency follows site display type (USD/CNY),
while TOKENS mode still allows per-view currency toggling when needed.
Notes
- No database migrations required.
- Legacy clients remain functional via compatibility fields.
- Ran: bun run eslint:fix && bun run lint:fix
- Inserted AGPL license header via eslint-plugin-header
- Enforced no-multiple-empty-lines and other lint rules
- Formatted code using Prettier v3 (@so1ve/prettier-config)
- No functional changes; formatting-only baseline across JS/JSX files
- Rename React components/pages/utilities that contain JSX to `.jsx` across `web/src`
- Update import paths and re-exports to match new `.jsx` extensions
- Fix Vite entry by switching `web/index.html` from `/src/index.js` to `/src/index.jsx`
- Verified remaining `.js` files are plain JS (hooks/helpers/constants) and do not require JSX
- No runtime behavior changes; extension and reference alignment only
Context: Resolves the Vite pre-transform error caused by the stale `/src/index.js` entry after migrating to `.jsx`.
- Added unique keys for JSONEditor components to ensure proper re-rendering based on channelId.
- Implemented input reset to clear previous JSON field values when the modal is opened.
- 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.
- 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
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.
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.
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.