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.
Backend
1. controller/channel.go
• Always hydrate `ChannelInfo` from DB in `UpdateChannel`, keeping `IsMultiKey` true so `MultiKeySize` is recalculated.
2. model/channel.go
• getKeys(): accept both newline-separated keys and JSON array (`[ {...}, {...} ]`).
• Update(): reuse new parser-logic to recalc `MultiKeySize`; prune stale indices in `MultiKeyStatusList`.
Frontend
1. pages/Channel/EditChannel.js
• `handleVertexUploadChange`
– Reset `vertexErroredNames` on every change so the “ignored files” prompt always re-appears.
– In single-key mode keep only the last file; in batch mode keep all valid files.
– Parse files, display “以下文件解析失败,已忽略:…”.
• Batch-toggle checkbox
– When switching from batch→single while multiple files are present, show a confirm dialog and retain only the first file (synchronises state, form and local caches).
• On opening the “new channel” side-sheet, clear `vertexErroredNames` to restore error prompts.
Result
• “已启用 x/x” count updates immediately after editing multi-key channels.
• Vertex-AI key upload works intuitively: proper error feedback, no duplicated files, and safe down-switch from batch to single mode.
Backend
• `model/channel.go`
– On multi-key channel updates, recalculate `MultiKeySize` from the current key list.
– If the request omits `key`, fetch existing keys from DB to avoid resetting the count to `0/0`.
– Remove out-of-range entries from `MultiKeyStatusList` to keep data consistent.
Frontend
• `web/src/pages/Channel/EditChannel.js`
– Vertex AI channels no longer require re-uploading a key file in edit mode.
– When no new key is provided during editing, exclude the `key` field from the payload to prevent overwriting the stored value.
These changes ensure that editing a channel no longer zeroes out the enabled key counter and that Vertex AI channels can be modified without mandatory key re-submission.
1. Recalculate `ChannelInfo.MultiKeySize` based on the current key list when a multi-key channel is updated.
2. Purge any indexes in `MultiKeyStatusList` that exceed the new key count to avoid stale or out-of-range data.
This ensures the front-end display (`enabled x/x`) always reflects the real number of keys after users add or remove keys in edit mode.
This commit refactors the application's error handling mechanism by introducing a new standardized error type, `types.NewAPIError`. It also renames common JSON utility functions for better clarity.
Previously, internal error handling was tightly coupled to the `dto.OpenAIError` format. This change decouples the internal logic from the external API representation.
Key changes:
- A new `types.NewAPIError` struct is introduced to serve as a canonical internal representation for all API errors.
- All relay adapters (OpenAI, Claude, Gemini, etc.) are updated to return `*types.NewAPIError`.
- Controllers now convert the internal `NewAPIError` to the client-facing `OpenAIError` format at the API boundary, ensuring backward compatibility.
- Channel auto-disable/enable logic is updated to use the new standardized error type.
- JSON utility functions are renamed to align with Go's standard library conventions (e.g., `UnmarshalJson` -> `Unmarshal`, `EncodeJson` -> `Marshal`).
Summary
1. **model/channel.go**
• Replaced the pointer-only `Value()` with a value-receiver implementation
• GORM can now marshal both `ChannelInfo` and `*ChannelInfo`, eliminating
`unsupported type model.ChannelInfo` runtime error.
2. **controller/channel.go**
• Refactored `getVertexArrayKeys()` – every element is now
- passed through `json.Marshal` when not already a string
- trimmed & validated before insertion
• Guarantees each service-account key is persisted as a **pure JSON string**
instead of the previous `map[...]` dump.
Result
• Channel creation / update succeeds without SQL driver errors.
• Vertex-AI batch & multi-key uploads are stored in canonical JSON, ready for
downstream SDKs to consume.
Summary
• Migrated all ratio-related sources into `setting/ratio_setting/`
– `model_ratio.go` (renamed from model-ratio.go)
– `cache_ratio.go`
– `group_ratio.go`
• Changed package name to `ratio_setting` and relocated initialization (`ratio_setting.InitRatioSettings()` in main).
• Updated every import & call site:
– Model / cache / completion / image ratio helpers
– Group ratio helpers (`GetGroupRatio*`, `ContainsGroupRatio`, `CheckGroupRatio`, etc.)
– JSON-serialization & update helpers (`*Ratio2JSONString`, `Update*RatioByJSONString`)
• Adjusted controllers, middleware, relay helpers, services and models to reference the new package.
• Removed obsolete `setting` / `operation_setting` imports; added missing `ratio_setting` imports.
• Adopted idiomatic map iteration (`for key := range m`) where value is unused.
• Ran static checks to ensure clean build.
This commit centralises all ratio configuration (model, cache and group) in one cohesive module, simplifying future maintenance and improving code clarity.
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
- Introduce `setting/console_setting` package that defines `ConsoleSetting` struct with JSON tags and validation rules.
- Register the new module with `config.GlobalConfig` to enable automatic injection/export of configuration values.
- Remove legacy `setting/console.go` and the manual `OptionMap` hooks; clean up `model/option.go`.
- Add `controller/console_migrate.go` providing `/api/option/migrate_console_setting` endpoint for one-off data migration.
- Update controllers (`misc`, `option`, `uptime_kuma`) and router to consume namespaced keys `console_setting.*`.
Frontend
- Refactor dashboard pages (`SettingsAPIInfo`, `SettingsAnnouncements`, `SettingsFAQ`, `SettingsUptimeKuma`) and detail page to read/write the new keys.
- Simplify `DashboardSetting.js` state to only include namespaced options.
BREAKING CHANGE: All console-related option keys are now stored under `console_setting.*`. Run the migration endpoint once after deployment to preserve existing data.
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.
Front-end enhancements around “Add custom models”:
• EditChannel.js / EditTagModal.js
– Skip models that already exist instead of blocking the action.
– Collect actually inserted items and display:
• Success toast: “Added N models: model1, model2 …”
• Info toast when no new model detected.
– Keeps UX smooth while preserving deduplication logic.
• i18n
– en.json: added keys
• "已新增 {{count}} 个模型:{{list}}"
• "未发现新增模型"
– Fixed a broken JSON string containing smart quotes to maintain valid syntax.
Result:
Users can bulk-paste model names; duplicates are silently ignored and the UI clearly lists what was incrementally appended. All messages are fully internationalised.
Closes#1218
The previous patch lower-cased `group` and `model` when building the
temporary `abilitySet` used to prevent duplicate inserts.
This merged models that differ only by letter case, e.g.
`GPT-3.5-turbo` vs `gpt-3.5-turbo`, causing them to disappear from the
user’s available-models list and pricing page.
Change:
• ability.go – removed all `strings.ToLower` calls when composing the
deduplication key (`group|model`), so duplicates are checked in a
case-sensitive manner while preserving the original data.
Result:
• `GPT-3.5-turbo` and `gpt-3.5-turbo` are now treated as distinct
models throughout the system.
When importing large model lists (≈700+) an attempt to save a channel
could fail with:
Error 1062 (23000): Duplicate entry 'default-DeepSeek-1' for key 'abilities.PRIMARY'
Root cause: AddAbilities / UpdateAbilities inserted the same
(group, model) pair multiple times if the input list contained
duplicates or case-variants (e.g. `default` vs `Default`).
Changes:
• ability.go
– AddAbilities: introduced `abilitySet` to deduplicate by
lower-cased `group|model` key before batch-inserting.
– UpdateAbilities: applied the same deduplication logic when
rebuilding abilities inside a transaction.
Notes:
• The lower-casing is only for set comparison; the original
`group` and `model` values are preserved when persisting to DB,
so case sensitivity of stored data is unchanged.
• Batch chunking logic (lo.Chunk) and performance characteristics
remain unaffected.
Fixes#1215