• Removed the custom `renderArrow` helper and its `Dropdown`-based arrow navigation, simplifying the component logic.
• Switched the `<Tabs>` component to rely on Semi UI’s built-in behaviour (no more `renderArrow` override).
• Kept `type="card"` and `collapsible` props for consistent visual appearance while still using the default style.
• Eliminated the now-unused `Dropdown` import.
This cleanup reduces bespoke UI code, makes future maintenance easier, and keeps the interface consistent with the rest of the application.
• 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.
• Removed the dropdown menu previously used for tag-level operations.
• Added a standalone “Edit” button directly after the “Disable All” button, reducing the number of clicks required to edit a tag group.
• Deleted the now-unused `IconEdit` import and its icon reference.
This streamlines the tag management flow and keeps the UI cleaner and more accessible.
All operation-related UI controls in `ChannelsTable` (buttons, dropdowns,
switches, inputs, tags, etc.) now explicitly use `size="small"`.
Reasons & benefits:
- Creates a more compact and consistent look across the table and modals.
- Improves visual coherence between desktop and mobile views.
- Purely presentational; no functional logic is affected.
No database changes or API interactions are involved.
* ChannelsTable
- Added row-level checkboxes to the model-testing table for multi-selection
- Implemented cross-page “Select All / Deselect All” via rowSelection.onSelectAll
- Introduced allSelectingRef to ignore redundant onChange after onSelectAll
- Added “Copy Selected” button to copy chosen model names (comma-separated) using helpers.copy
- Added “Select Successful” button to auto-tick all models that passed testing
- Moved search bar and new action buttons into the modal title for better UX
- Centralised page size constant MODEL_TABLE_PAGE_SIZE in channel.constants.js
- Fixed pagination slicing and auto-page-switch logic during batch testing
* channel.constants
- Exported MODEL_TABLE_PAGE_SIZE (default 10) for unified pagination control
This commit enables users to conveniently copy or filter successful models, fully supports cross-page bulk operations, and resolves previous selection inconsistencies.
Refs: #1288
Frontend (`ChannelsTable.js`)
1. Initialize `loading` state to `true` so the spinner is visible while the first data request is in-flight.
2. Set `<Table>` prop `loading={loading || searching}` — the spinner now appears for both the initial load and any subsequent search requests.
Result
Users immediately see a loading indicator on page entry and whenever a search is running, improving perceived responsiveness.
1. Backend
• `controller/channel.go`
– Added pagination (`p`, `page_size`) support to `SearchChannels`.
– Added independent `type` filter (keeps `type_counts` unaffected).
– Returned `total`, `type_counts` to match `/api/channel/` response.
2. Frontend
• `ChannelsTable.js`
– `loadChannels` / `searchChannels` now pass `p`, `page_size`, `id_sort`, `type`, `status` correctly.
– Pagination, page-size selector and type tabs work for both normal list and search mode.
– Switch for “ID sort” calls proper API and keeps UI state in sync.
– Removed unnecessary `normalize` helper; `getFormValues` back to concise form.
Result
• Search mode and normal listing now share identical pagination and filtering behavior.
• Type tabs show correct counts even after searching.
• “ID Sort” toggle no longer inverses actual behaviour.
Refactors `ChannelsTable.js` to ensure that the selected group filter is **never lost** when:
1. Cycling between channel-type tabs.
2. Changing the status dropdown (all / enabled / disabled).
Key points:
• `loadChannels` now detects active search filters (keyword / group / model) and transparently delegates to `searchChannels`, guaranteeing all parameters are sent in every request.
• `searchChannels` accepts optional `typeKey` and `statusF` arguments, enabling reuse without code duplication.
• Loading state handling is unified; no extra renders or side effects were introduced, keeping UI performance intact.
• Duplicate logic removed and responsibilities clearly separated for easier future maintenance.
Replaced the verbose placeholder “Search channel ID, name, key and API address ...”
with a concise version “Channel ID, name, key, API address” in
`ChannelsTable.js` and synchronized the corresponding i18n entries.
This improves readability and keeps UI text consistent across languages.
Ensure that the selected "group" filter (and other form search values) persist across
type tab changes, status filter updates, pagination, and page-size changes.
Changes include:
• loadChannels: added `searchParams` argument and now appends keyword, group and model
query strings to API calls.
• refresh / page handlers / type tabs / status Select: now pass current form values
to loadChannels, keeping filters intact.
• searchChannels: maintains active type and status filters when issuing search requests.
• Form.Select (searchGroup): triggers loadChannels when only group filter is active,
preventing parameter loss.
• Minor cleanup and comment adjustments.
WHAT’S NEW
• Backend
– Introduced `parseStatusFilter` helper to normalize `status` query across handlers.
– `GET /api/channel` & `GET /api/channel/search` now accept `status=enabled|disabled` to return only enabled or disabled channels.
– Tag-mode branch respects both `statusFilter` and `typeFilter`; SQL paths trimmed to one query + one lightweight `GROUP BY` for `type_counts`.
• Frontend (`ChannelsTable.js`)
– Added “Status Filter” `<Select>` (All / Enabled / Disabled) with localStorage persistence.
– All data-loading and search requests now always append `type` (when not “all”) and `status` params, so filtering & pagination are handled entirely server-side.
– Removed client-side post-filtering for type, preventing short pages and reducing CPU work.
– Tabs’ type counts stay in sync via backend-provided `type_counts`.
IMPROVEMENTS
• Eliminated duplicated status-parsing logic; single source of truth eases future extension.
• Reduced redundant queries, improved consistency of counts in UI.
• Secured key leakage with `Omit("key")` unchanged; no perf regressions observed.
Closes#1289
Summary
• Added per-table “Compact / Adaptive” view toggle to all major table components (Tokens, Channels, Logs, MjLogs, TaskLogs, Redemptions, Users).
• Persist user preference in a single localStorage entry (`table_compact_modes`) instead of scattered keys.
Details
1. utils.js
• Re-implemented `getTableCompactMode` / `setTableCompactMode` to read & write a shared JSON object.
• Imported storage-key constant from `constants`.
2. hooks/useTableCompactMode.js
• Hook now consumes the unified helpers and listens to `storage` events via the shared key constant.
3. constants
• Added `TABLE_COMPACT_MODES_KEY` to `common.constant.js` and re-exported via `constants/index.js`.
4. Table components
• Integrated `useTableCompactMode('<tableName>')`.
• Dynamically remove `fixed: 'right'` column and horizontal `scroll` when in compact mode.
• UI: toggle button placed at card title’s right; responsive layout on small screens.
5. UI polish
• Replaced all lucide-react `List`/`ListIcon` usages with Semi UI `IconDescend` for consistency.
• Restored correct icons where `Hash` was intended (TaskLogsTable).
Benefits
• Consistent UX for switching list density across the app.
• Cleaner localStorage footprint with easier future maintenance.
This commit enhances the “Copy Selected Tokens to Clipboard” feature in `TokensTable.js` by introducing a user-friendly modal that lets users choose how they want to copy tokens.
Changes made
• Replaced direct copy logic with a `Modal.info` dialog.
• Modal displays the prompt “Please choose your copy mode”.
• Added two buttons in a custom footer:
– **Name + Secret**: copies `tokenName sk-tokenKey`.
– **Secret Only**: copies `sk-tokenKey`.
• Each button triggers the copy operation and closes the dialog.
• Maintains existing validations (e.g., selection check, clipboard feedback).
Benefits
• Gives users clear control over copy format, reducing manual editing.
• Aligns UI with Semi UI’s best practices via custom modal footer.
No backend/API changes are involved; all updates are limited to the front-end UI logic.
Backend
- controller/ratio_sync.go
• Parse /api/pricing response and convert to ratio / price maps.
• Introduce confidence heuristic (model_ratio = 37.5 && completion_ratio = 1) to flag unreliable data.
• Include confidence map when building differences and filter “same”/empty entries.
- dto/ratio_sync.go
• Add `ID` to UpstreamDTO, `upstreams` to UpstreamRequest, and `Confidence` to DifferenceItem.
Frontend
- ChannelSelectorModal.js
• Re-implement with table layout, pagination, search, endpoint-type selector and mobile support.
- UpstreamRatioSync.js
• Send full upstream objects, add ratio-type filter, confidence badges/tooltips, retain endpoints.
• Leverage ChannelSelectorModal’s pagination reset.
- ChannelsTable.js – fix tag color for disabled status.
- en.json – add translations for new UI labels.
Motivation
These changes let users sync model ratios / prices from different upstream endpoints and visually identify potentially unreliable data, improving operational safety and flexibility.
frontend(ChannelsTable):
• Do not render type-filter Tabs when `enableTagMode` is true, preventing UI/logic conflicts in tag aggregation view.
• Adjust API query construction:
– Append `type=` param only when NOT in tag mode and selected tab ≠ 'all'.
– Applies to both `loadChannels` and `searchChannels`.
• Result: UI stays clean in tag view, and backend receives correct parameters across modes.
No other functionality affected.
feat(api):
• Add optional `type` query param to `/api/channel` endpoint for type-specific pagination
• Return `type_counts` map with counts for each channel type
• Implement `GetChannelsByType`, `CountChannelsByType`, `CountChannelsGroupByType` in `model/channel.go`
feat(frontend):
• Introduce type Tabs in `ChannelsTable` to switch between channel types
• Tabs show dynamic counts using backend `type_counts`; “All” is computed from sum
• Persist active type, reload data on tab change (with proper query params)
perf(frontend):
• Use a request counter (`useRef`) to discard stale responses when tabs switch quickly
• Move all `useMemo` hooks to top level to satisfy React Hook rules
• Remove redundant local type counting fallback when backend data present
ui:
• Remove icons from response-time tags for cleaner look
• Use Semi-UI native arrow controls for Tabs; custom arrow code deleted
chore:
• Minor refactor & comments for clarity
• Ensure ESLint Hook rules pass
Result: Channel list now supports fast, accurate type filtering with correct counts, improved concurrency safety, and cleaner UI.
* backend
- model: add `Remark` field (varchar 255, `json:"remark,omitempty"`); AutoMigrate handles schema change automatically
- controller:
* accept `remark` on user create/update endpoints
* hide remark from regular users (`GetSelf`) by zero-ing the field before JSON marshalling
* clarify inline comment explaining the omitempty behaviour
* frontend (React / Semi UI)
- AddUser.js & EditUser.js: add “Remark” input for admins
- UsersTable.js:
* remove standalone “Remark” column
* show remark as a truncated Tag next to username with Tooltip for full text
* import Tooltip component
- i18n: reuse existing translations where applicable
This commit enables administrators to label users with private notes while ensuring those notes are never exposed to the users themselves.
Backend
• Introduced `validateExpiredTime` helper in `controller/redemption.go`; reused in both Add & Update endpoints to enforce “expiry time must not be earlier than now”, eliminating duplicated checks
• Removed `RedemptionCodeStatusExpired` constant and all related references – expiry is now determined exclusively by the `expired_time` field for simpler, safer state management
• Simplified `DeleteInvalidRedemptions`: deletes codes that are `used` / `disabled` or `enabled` but already expired, without relying on extra status codes
• Controller no longer mutates `status` when listing or fetching redemption codes; clients derive expiry status from timestamp
Frontend
• Added reusable `isExpired` helper in `RedemptionsTable.js`; leveraged for:
– status rendering (orange “Expired” tag)
– action-menu enable/disable logic
– row styling
• Removed duplicated inline expiry logic, improving readability and performance
• Adjusted toolbar layout: on small screens the “Clear invalid codes” button now wraps onto its own line, while “Add” & “Copy” remain grouped
Result
The codebase is now more maintainable, secure, and performant with no redundant constants, centralized validation, and cleaner UI behaviour across devices.
- Add informative header section to TokensTable with Key icon and description
- Replace generic eye icon with semantic Ticket icon in RedemptionsTable header
- Import additional UI components (Divider, Typography) for better layout structure
- Improve user experience with contextual information about token and redemption functionality
- Maintain consistent styling and layout between both table components
The changes provide users with clear understanding of each table's purpose:
- Tokens: "令牌用于API访问认证,可以设置额度限制和模型权限" with Key icon
- Redemptions: "兑换码可以批量生成和分发,适合用于推广活动或批量充值" with Ticket icon
- Add IP field to Log model with database index and default empty value
- Implement conditional IP recording based on user setting in RecordConsumeLog and RecordErrorLog
- Add UserSettingRecordIpLog constant and update user settings API to handle record_ip_log field
- Create dedicated "IP记录" tab in personal settings under "其他设置" section
- Add IP column to logs table with help tooltip explaining recording conditions
- Make IP column visible to all users (not admin-only) with proper filtering for consume/error log types
- Restrict display of use_time and retry columns to consume and error log types only
- Update personal settings UI structure: rename "通知设置" to "其他设置" to accommodate new functionality
- Add proper translation support and maintain consistent styling across components
The IP logging feature is disabled by default and only records client IP addresses
for consume (type 2) and error (type 5) logs when explicitly enabled by users
in their personal settings.
Fix the search button loading state to be consistent with other table components.
The search button now properly shows loading animation when the table data is
being fetched.
Changes:
- Update search button loading prop from `loading={searching}` to
`loading={loading || searching}` in TokensTable.js
- This ensures loading state is shown both when searching with keywords
(searching=true) and when loading default data (loading=true)
- Aligns with the behavior of other table components like ChannelsTable,
UsersTable, and RedemptionsTable
Before: Search button only showed loading when searching with keywords
After: Search button shows loading for all table data fetch operations
* Removed `size="middle"` and `centered` props from the column-selector
`Modal` in `ChannelsTable.js` to match the visual style used in
`LogsTable`.
* Re-added `size="middle"` to the main `Table` component to preserve the
original table sizing.
* Ensures consistent UI/UX across both channel and log column settings
modals.
- Remove IconForward import from @douyinfe/semi-icons
- Add Route icon import from lucide-react
- Update model redirection indicator in LogsTable component
The Route icon better represents the concept of model redirection
compared to the generic forward arrow, providing clearer visual
context for users when models are mapped to different upstream models.
- Replace local isDarkMode state with global useTheme hook in TopUp component
- Replace local isDarkMode state with global useTheme hook in PersonalSetting component
- Remove redundant theme detection useEffect hooks that caused state inconsistency
- Update theme condition checks from isDarkMode to theme === 'dark'
- Fix issue where components showed dark gradients in light mode due to theme state mismatch
- Clean up trailing commas in import statements
This ensures all components stay synchronized with the global theme system managed by HeaderBar's theme toggle button.
- Add min-w-[120px] class to Form.Select component for log type filtering
- Remove redundant min-width constraint from parent div container
- Ensure consistent dropdown width across different screen sizes
- Improve UI consistency and readability for log type selection
- Move Form.Select (log type selector) from grid layout to action button row
- Position log type selector on the left side of the action button area
- Keep action buttons (Query, Reset, Column Settings) aligned to the right
- Implement responsive design with sm: breakpoint (640px)
- Mobile: vertical stacking with full-width elements
- Desktop: horizontal layout with proper spacing
- Add min-width constraint (140px) for log type selector
- Remove extra padding-top from button area for cleaner spacing
- Maintain accessibility and usability across all screen sizes
This change improves the UI layout by better utilizing horizontal space
and providing a more intuitive grouping of form controls and actions.
Add consistent empty state handling across all table components to improve
user experience when search/filter results are empty.
Changes:
- Import Empty component and IllustrationNoResult/IllustrationNoResultDark from @douyinfe/semi-ui
- Add empty prop to Table components with "搜索无结果" message
- Support both light and dark theme illustrations
- Apply internationalization support for empty state text
Affected files:
- web/src/components/table/MjLogsTable.js
- web/src/components/table/LogsTable.js
- web/src/components/table/ChannelsTable.js
- web/src/components/table/RedemptionsTable.js
- web/src/components/table/TaskLogsTable.js
- web/src/components/table/TokensTable.js
- web/src/components/table/UsersTable.js
- web/src/components/table/ModelPricing.js
This ensures consistent UX across all table components when no data
matches the current search or filter criteria.
- Replace IconList with Tags icon from lucide-react for better semantic representation
- Update renderTagType function to use Tags icon instead of list icon
- Remove unused IconList import from semi-icons
- Improve visual clarity for tag aggregation feature in channels table
The Tags icon better represents the concept of multiple tags being aggregated
together, providing more intuitive user experience in the channels management
interface.
- Add semantic icons to ChannelsTable.js for channel status, response time, and quota display
- Add status and quota icons to TokensTable.js for better visual distinction
- Add status and quota icons to RedemptionsTable.js for redemption code management
- Add role, status, and statistics icons to UsersTable.js for user management
- Import appropriate lucide-react icons for each table component
- Enhance UI consistency and user experience across all table interfaces
Icons added include:
- Status indicators: CheckCircle, XCircle, AlertCircle, HelpCircle
- Performance metrics: Zap, Timer, Clock, AlertTriangle, TestTube
- Financial data: Coins, DollarSign
- User roles: User, Shield, Crown
- Activity tracking: Activity, Users, UserPlus
This improves visual clarity and makes table data more intuitive to understand.
Fix inconsistent loading state behavior where search buttons in ChannelsTable,
RedemptionsTable, and UsersTable didn't display loading animation when tables
were loading data, unlike LogsTable which handled this correctly.
Changes:
- Fix ChannelsTable searchChannels function to properly manage loading state
- Move setSearching(true) to function start and use try-finally pattern
- Ensure loading state is set for both search and load operations
- Update search button loading prop in ChannelsTable: loading={searching} → loading={loading || searching}
- Update search button loading prop in RedemptionsTable: loading={searching} → loading={loading || searching}
- Update search button loading prop in UsersTable: loading={searching} → loading={loading || searching}
This ensures search buttons show loading state consistently when:
- Table is loading data (initial load, pagination, operations)
- Search operation is in progress
All table components now provide unified UX behavior matching LogsTable,
preventing duplicate clicks and clearly indicating system state to users.
- Refactor LogsTable, MjLogsTable, TokensTable, UsersTable, and ChannelsTable to use Semi-UI Form components
- Replace individual input state management with centralized Form API
- Add form validation and consistent form handling across all tables
- Implement auto-search functionality with proper state update timing
- Add reset functionality to clear all search filters
- Improve responsive layout design for better mobile experience
- Remove duplicate form initial values and consolidate form logic
- Remove column visibility feature from ChannelsTable to simplify UI
- Standardize search form structure and styling across all table components
- Fix state update timing issues in search functionality
- Add proper form submission handling with loading states
BREAKING CHANGE: Form state management has been completely rewritten.
All table components now use Form API instead of individual useState hooks.
Column visibility settings for ChannelsTable have been removed.
- Refactor Form component to use Semi Design best practices
- Remove duplicate initValues configuration for DatePicker
- Add real-time value change monitoring with onValueChange
- Implement auto-search functionality for log type selector changes
- Fix state synchronization issues causing stale values in search requests
- Optimize form layout with proper vertical layout configuration
- Enhance user experience with placeholders, clear buttons, and search icons
- Remove logType parameter passing to prevent async state update conflicts
- Ensure all form controls use latest values from formApi instead of stale state
- Add proper validation triggers and error handling configuration
- Improve reset button logic with proper timing for form state updates
The changes resolve the issue where users needed to select log type twice
for the search request to use the correct value, and ensure all form
interactions provide immediate and accurate results.