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.
Backend (controller/ratio_sync.go):
- Add built‑in official upstream to GetSyncableChannels (ID: -100, BaseURL: https://basellm.github.io)
- Support absolute endpoint URLs; otherwise join BaseURL + endpoint (defaults to /api/ratio_config)
- Harden HTTP client:
- IPv4‑first with IPv6 fallback for github.io
- Add ResponseHeaderTimeout
- 3 attempts with exponential backoff (200/400/800ms)
- Validate Content-Type and limit response body to 10MB (safe decode via io.LimitReader)
- Robust parsing: support type1 ratio_config map and type2 pricing list
- Use net.SplitHostPort for host parsing
- Use float tolerance in differences comparison to avoid false mismatches
- Remove unused code (tryDirect) and improve warnings
Frontend:
- UpstreamRatioSync.jsx: auto-assign official endpoint to /llm-metadata/api/newapi/ratio_config-v1-base.json
- ChannelSelectorModal.jsx:
- Pin the official source at the top of the list
- Show a green “官方” tag next to the status
- Refactor status renderer to accept the full record
Notes:
- Backward compatible; no API surface changes
- Official ratio_config reference: https://basellm.github.io/llm-metadata/api/newapi/ratio_config-v1-base.json
- Ran: bun run eslint:fix && bun run lint:fix
- Inserted AGPL license header via eslint-plugin-header
- Enforced no-multiple-empty-lines and other lint rules
- Formatted code using Prettier v3 (@so1ve/prettier-config)
- No functional changes; formatting-only baseline across JS/JSX files
- 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`.
Eliminated the manual `formatPageText` function that previously rendered
pagination text (e.g. “第 {{start}} - {{end}} 条,共 {{total}} 条”) in each
Table. Pagination now relies on the default Semi UI text or the global
i18n configuration, reducing duplication and making future language
updates centralized.
Why
---
* Keeps table components cleaner and more maintainable.
* Ensures pagination text automatically respects the app-wide i18n
settings without per-component overrides.
Summary
Implement a dedicated, reusable scrolling mechanism for all console-table pages while keeping header and sidebar fixed, plus related layout improvements.
Key Changes
• Added `.table-scroll-card` utility class
– Provides flex column layout and internal vertical scrolling
– Desktop height: `calc(100vh - 110px)`; Mobile (<768 px) height: `calc(100vh - 77px)`
– Hides scrollbars cross-browser (`-ms-overflow-style`, `scrollbar-width`, `::-webkit-scrollbar`)
• Replaced global `.semi-card` scrolling rules with `.table-scroll-card` to avoid affecting non-table cards
• Updated table components (Channels, Tokens, Users, Logs, MjLogs, TaskLogs, Redemptions) to use the new class
• PageLayout
– Footer is now suppressed for all `/console` routes
– Confirmed only central content area scrolls; header & sidebar remain fixed
• Restored hidden scrollbar rules for `.semi-layout-content` and removed unnecessary global overrides
• Minor CSS cleanup & comment improvements for readability
Result
Console table pages now fill the viewport with smooth, internal scrolling and no visible scrollbars, while other cards and pages remain unaffected.
Backend
- setting/payment.go: introduce default `USDExchangeRate` (7.3)
- model/option.go:
• inject `USDExchangeRate` into `InitOptionMap`
• persist & sync value in `updateOptionMap`
- controller/misc.go: expose `usd_exchange_rate` via `/api/status`
Frontend
- OperationSetting.js & SettingsGeneral.js:
• extend state/inputs with `USDExchangeRate`
• add form field “美元汇率 (non-top-up rate, pricing only)”
- ModelPricing.js already consumes `status.usd_exchange_rate`; no change needed
API
- Administrators can update the rate via `PUT /api/option` (key: `USDExchangeRate`)
- All clients receive the latest rate through `GET /api/status`
This closes the end-to-end flow for displaying model prices in both USD and CNY based on a configurable exchange rate.
Replaced every instance of
<div className="mt-[64px]">
with
<div className="mt-[64px] px-2">
to provide uniform horizontal padding across pages. No functional changes—visual layout improvement only.
BREAKING CHANGE:
helpers/utils.js no longer exports `isMobile()`.
Any external code that relied on this function must switch to the `useIsMobile` React hook.
Summary
-------
1. Deleted the obsolete `isMobile()` function from helpers/utils.js.
2. Introduced `MOBILE_BREAKPOINT` constant and `matchMedia`-based detection for non-React contexts.
3. Reworked toast positioning logic in utils.js to rely on `matchMedia`.
4. Updated render.js:
• Removed isMobile import.
• Added MOBILE_BREAKPOINT detection in `truncateText`.
5. Migrated every page/component to the `useIsMobile` hook:
• Layout: HeaderBar, PageLayout, SiderBar
• Pages: Home, Detail, Playground, User (Add/Edit), Token, Channel, Redemption, Ratio Sync
• Components: ChannelsTable, ChannelSelectorModal, ConflictConfirmModal
6. Purged all remaining `isMobile()` calls and legacy imports.
7. Added missing `const isMobile = useIsMobile()` declarations where required.
Benefits
--------
• Unifies mobile detection with a React-friendly hook.
• Eliminates duplicated logic and improves maintainability.
• Keeps non-React helpers lightweight by using `matchMedia` directly.
This commit brings System Settings in line with Payment Settings and
prepares the whole component for multi-language deployments.
Key points
----------
• Introduced a “General Settings” card in `SystemSetting.js`
- Adds a `ServerAddress` field (copied from Payment Settings).
- Explains that this URL is used for payment callback & default homepage.
- Implements `submitServerAddress` to persist the option.
• Internationalisation
- Imported `useTranslation` and wrapped **all** Chinese UI strings in `t()`.
- Localised section titles, labels, placeholders, banners, buttons,
modal texts and toast messages.
- Dynamic banner/tool-tip descriptions now combine `t()` fragments with
template literals for runtime URLs.
• UX improvements
- Added contextual `extraText` under the ServerAddress input.
- Ensured placeholders like “sensitive info…” are translatable.
With these changes, administrators can configure the server URL from the
System Settings tab, and the entire page is ready for seamless language
switching via the existing i18n framework.
• Added read-only Base URL input that shows `status.server_address` (fallback `window.location.origin`) and copies value on click.
• Embedded `ScrollList` as input `suffix`; auto-cycles common endpoints every 3 s and allows manual selection.
• Introduced `API_ENDPOINTS` array in `web/src/constants/common.constant.js` for centralized endpoint management.
• Implemented custom CSS to hide ScrollList wheel indicators / scrollbars for a cleaner look.
• Created two blurred colour spheres behind the banner (`blur-ball-indigo`, `blur-ball-teal`) with light-/dark-mode opacity tweaks and lower vertical placement.
• Increased letter-spacing for Chinese heading via conditional `tracking-wide` / `md:tracking-wider` classes to improve readability.
• Misc: updated imports, helper functions, and responsive sizes to keep UI consistent across devices.
Add a `useEffect` hook in `UpstreamRatioSync.js` to automatically set
`currentPage` to `1` whenever `ratioTypeFilter` or `searchKeyword`
updates.
This prevents the table from appearing empty when users switch to the
“model_price” (fixed price) filter or perform a new search while on a
later page.
Additional changes:
- Import `useEffect` from React.
This enhancement delivers a smoother UX by ensuring the first page of
results is always shown after any filtering action.
Introduce the ability to quickly locate models with conflicting billing configurations.
Key points
• Added `hasConflict` flag to detect models that define both a fixed price (`ModelPrice`) and any ratio (`ModelRatio` or `CompletionRatio`).
• Added “Show Only Conflict Rates” `Checkbox` to toolbar; filtering logic now supports keyword + conflict filtering.
• Display a red `Tag` beside the model name when a conflict is detected for immediate visual feedback.
• Kept `hasConflict` state in sync during add, update and delete operations.
• Imported `Checkbox` and `Tag` from **@douyinfe/semi-ui**.
• Minor UI tweaks (circle tag style, margin) for consistency.
This enhancement helps administrators swiftly identify and resolve incompatible pricing rules, addressing the need discussed in issue #1286.