Compare commits

...

1256 Commits

Author SHA1 Message Date
Calcium-Ion 423796e790 Merge pull request #1247 from RedwindA/feat/25lite-thinking
feat: improve gemini thinking budget adaption
2025-06-18 01:00:08 +08:00
RedwindA a004db93c9 feat(Gemini): enhance budget clamping logic for Gemini models 2025-06-18 00:49:35 +08:00
CaIon 36a0d4d7ae fix(relay): ensure consistent setting of web search context size in TextHelper function 2025-06-18 00:37:22 +08:00
CaIon d4e20df7a6 fix(relay): refine error message for unsupported MIME types and enhance error handling in OpenAI wrapper 2025-06-17 22:44:57 +08:00
CaIon 8d0b54bf6e fix(relay): improve error handling for unsupported MIME types by sanitizing URLs 2025-06-17 22:40:41 +08:00
CaIon 5506e4feed feat(file_decoder): expand MIME type detection to include additional file extensions 2025-06-17 22:20:19 +08:00
CaIon 16bffe05d4 feat(file_decoder): add debug logging for MIME type detection when handling application/octet-stream 2025-06-17 22:18:51 +08:00
CaIon 680ff8e8eb feat(file_decoder): enhance MIME type detection based on URL and Content-Disposition header 2025-06-17 21:49:13 +08:00
Calcium-Ion 129d1b081f Merge pull request #1239 from QuantumNous/auto_group
feat: auto分组
2025-06-17 21:14:09 +08:00
CaIon 4ec7012974 feat: enhance group ratio handling in pricing calculations 2025-06-17 21:05:35 +08:00
CaIon 26c6087d80 feat(GroupRatioSettings): enhance JSON validation for group ratios 2025-06-17 21:05:24 +08:00
CaIon 4238068c65 feat(channel): enhance Claude response handling with new Done flag and improved usage tracking 2025-06-17 20:08:25 +08:00
CaIon 822ed681de feat(channel): add handling for pre_consume_token_quota_failed error type 2025-06-17 16:46:52 +08:00
CaIon 2a1ed60738 Merge branch 'alpha' 2025-06-17 14:49:13 +08:00
Calcium-Ion a13a63549b Merge pull request #1243 from cjm0810151/main
fix(audio): :bugs: fix webm audio strconv.ParseFloat: parsing "N/A"
2025-06-17 14:48:17 +08:00
Calcium-Ion 24ae1fa55e Merge pull request #1240 from RedwindA/fix/redis
Fix: optimize Redis expiration handling and refactor cache duration retrieval
2025-06-17 14:47:00 +08:00
chenjm ab5dfd8d97 fix(audio): :bugs: fix webm audio strconv.ParseFloat: parsing "N/A" 2025-06-17 10:04:36 +08:00
chenjm 99dacb1d4b fix(audio): :bugs: fix webm audio strconv.ParseFloat: parsing "N/A" 2025-06-17 09:21:56 +08:00
RedwindA 09ffc364f2 🔧 refactor(redis): replace direct constant usage with RedisKeyCacheSeconds function for cache duration 2025-06-17 03:24:39 +08:00
RedwindA e34d3c2160 🔧 fix(redis): only set expiration if greater than 0 in RedisHSetObj 2025-06-17 02:37:19 +08:00
CaIon 28a0eb2a6d Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-17 00:09:38 +08:00
CaIon af103dcc7f 🧹 chore(relay): remove unused import in relay-palm.go 2025-06-17 00:09:26 +08:00
Calcium-Ion 71fe637d2a Merge pull request #1231 from RedwindA/feat/gemini-budget-in-name
feat(Gemini): implement thinking budget control in model name
2025-06-17 00:03:53 +08:00
Calcium-Ion 293decb96e Merge pull request #1208 from feitianbubu/pr/fix-hot-reload-sync-options
fix: enabled hot reload SyncOptions
2025-06-16 23:03:29 +08:00
creamlike1024 c6a9df67b1 feat: auto分组 2025-06-16 22:15:12 +08:00
CaIon 288f279379 🔧 refactor(relay): replace UUID generation with helper function for response IDs 2025-06-16 21:02:27 +08:00
Apple\Apple abceadcc6d Merge branch 'main' into alpha 2025-06-16 20:06:40 +08:00
Apple\Apple 35a08da791 🐛 fix(console-setting): ensure announcements are returned in newest-first order
Summary
• Added stable, descending sort to `GetAnnouncements()` so that the API always returns the latest announcements first.
• Introduced helper `getPublishTime()` to safely parse `publishDate` (RFC 3339) and fall back to zero value on failure.
• Switched to `sort.SliceStable` for deterministic ordering when timestamps are identical.
• Imported the standard `sort` package and removed redundant, duplicate date parsing.

Impact
Front-end no longer needs to perform client-side sorting; the latest announcement is guaranteed to appear at the top on all platforms and clients.
2025-06-16 20:05:54 +08:00
RedwindA 9209355f7d refactor: replace inline closure with a helper function 2025-06-16 19:41:42 +08:00
Calcium-Ion bd82e7c341 Merge pull request #1205 from a37836323/fix-azure-responses-api
修复Azure渠道对responses API的兼容性支持 - 为Azure渠道添加对responses API的特殊处理 - 兼容微软新…
2025-06-16 19:17:21 +08:00
Calcium-Ion e0e548eb94 Merge pull request #1232 from RedwindA/fix/playground-group
fix: include group in payload for playground
2025-06-16 18:35:27 +08:00
CaIon c4e652461e 🔧 refactor(dto): update BudgetTokens handling in Thinking struct 2025-06-16 18:29:49 +08:00
Apple\Apple 1f6ec0174a feat: add admin-only "remark" support for users
* 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.
2025-06-16 03:20:54 +08:00
RedwindA fe0ff4726d 🐛 fix(api): include group in payload for playground 2025-06-16 01:12:18 +08:00
RedwindA 45e9607995 update i18n 2025-06-15 23:40:58 +08:00
RedwindA 1e1a6aae55 feat: implement thinking budget control in model name 2025-06-15 23:20:41 +08:00
Apple\Apple 1c4819b21f 💄 style: hide announcement modal scrollbar
Improve UX by hiding the vertical scrollbar inside the announcement (NoticeModal)
while keeping the content scrollable.

Changes
• NoticeModal.js
  - Introduce `notice-content-scroll` class to the content wrapper.
  - Remove inline custom scrollbar styling for cleaner code.

• index.css
  - Add `.notice-content-scroll` to the global hidden-scrollbar rules, ensuring
    scrollbars are hidden across browsers.

Result
Users can still scroll through long announcements, but no scrollbar is shown,
giving the modal a cleaner and more consistent appearance.
2025-06-15 03:28:06 +08:00
Apple\Apple c10f0dd021 🎨 style(dashboard): Standardize Empty component visuals in Detail page
Summary:
Refactored the `Detail` page to deliver a more consistent and compact visual experience when displaying empty states.

Key changes:
1. Introduced a reusable `ILLUSTRATION_SIZE` constant (96 × 96) to ensure all `IllustrationConstruction` / `IllustrationConstructionDark` icons render at a uniform, reduced size.
2. Applied the new size to every `Empty` component instance within the file.
3. Ensured Empty‐state content (title, description, icon) is centrally aligned for better readability.
4. Updated the Uptime panel’s empty description text for greater clarity.

These adjustments improve UI cohesion, reduce visual noise, and make empty messages easier to scan.
2025-06-15 03:22:23 +08:00
Apple\Apple c92ba89173 ♻️ refactor(console_migrate): migrate legacy UptimeKumaUrl/Slug to new uptime_kuma_groups format
* Added migration logic in `controller/console_migrate.go`
  * Detects both `UptimeKumaUrl` and `UptimeKumaSlug`
  * Creates a single-group JSON array under `console_setting.uptime_kuma_groups`
    - Uses `categoryName: "old"` to mark migrated data
    - Preserves original `url` and `slug` values
  * Clears and removes obsolete `UptimeKumaUrl` and `UptimeKumaSlug` keys
* Removes outdated code paths that wrote to `console_setting.uptime_kuma_url` and `console_setting.uptime_kuma_slug`
* Keeps frontend `DashboardSetting.js` compatible — no additional changes required
* Aligns migration behavior with previous `ApiInfo` refactor for consistent console settings management
2025-06-15 03:12:34 +08:00
Apple\Apple 0305ed051e 🚦 feat(uptime-kuma): support custom category names & monitor subgroup rendering
Backend
• controller/uptime_kuma.go
  - Added Group field to Monitor struct to carry publicGroupList.name.
  - Extended status page parsing to capture group Name and inject it into each monitor.
  - Re-worked fetchGroupData loop: aggregate all sub-groups, drop unnecessary pre-allocation/breaks.

Frontend
• web/src/pages/Detail/index.js
  - renderMonitorList now buckets monitors by the new group field and renders a lightweight header per subgroup.
  - Fallback gracefully when group is empty to preserve previous single-list behaviour.

Other
• Expanded anonymous struct definition for statusData.PublicGroupList to include ID/Name, enabling JSON unmarshalling of group names.

Result
Custom CategoryName continues to work while each uptime group’s internal sub-groups are now clearly displayed in the UI, providing finer-grained visibility without impacting performance or existing validation logic.
2025-06-15 02:54:54 +08:00
Calcium-Ion d1431b0c96 Merge pull request #1226 from RedwindA/fix-gemini/empty-system
💬 fix(Gemini): clean up empty system instructions in request
2025-06-14 20:09:22 +08:00
CaIon c6226f3678 feat(database): implement database migration logic for PostgreSQL and add fast migration fallback 2025-06-14 19:47:44 +08:00
RedwindA 66ffccf164 💬 fix(GeminiHelper): clean up empty system instructions in request 2025-06-14 19:36:58 +08:00
Calcium-Ion eaf3b6ac55 Merge pull request #1225 from QuantumNous/fix_mixing_sql_conflicts
Fix mixing databases conflicts
2025-06-14 18:24:53 +08:00
CaIon 49cb5f3fa3 🐛 fix(log): optimize channel ID collection by using a map to prevent duplicates 2025-06-14 18:23:25 +08:00
CaIon a9ec62eea8 🐛 fix(ability, channel): standardize boolean value handling across database queries 2025-06-14 18:15:45 +08:00
CaIon 4371717c78 fix: Resolving conflicts caused by mixing multiple databases 2025-06-14 17:51:05 +08:00
Apple\Apple 882cdbd34a 🎛️ feat(dashboard): add per-panel enable switches & conditional backend payload
Backend:
• ConsoleSetting
  - Introduce `ApiInfoEnabled`, `UptimeKumaEnabled`, `AnnouncementsEnabled`, `FAQEnabled` (default true).
• misc.GetStatus
  - Refactor to build response map dynamically.
  - Return the four *_enabled flags.
  - Only append `api_info`, `announcements`, `faq` when their respective flags are true.

Frontend:
• Detail page
  - Remove all `self_use_mode_enabled` checks.
  - Render API, Announcement, FAQ and Uptime panels based on the new *_enabled flags.
• Dashboard → Settings
  - Added `Switch` controls in:
    · SettingsAPIInfo.js
    · SettingsAnnouncements.js
    · SettingsFAQ.js
    · SettingsUptimeKuma.js
  - Each switch persists its state via `/api/option` to the corresponding
    `console_setting.<panel>_enabled` key and reflects current status on load.
  - DashboardSetting.js now initialises and refreshes the four *_enabled keys so
    child components receive accurate panel states.

Fixes:
• Switches previously defaulted to “on” because *_enabled keys were missing.
  They are now included, ensuring correct visual state when panels are disabled.

No breaking changes; existing functionality remains untouched aside from the
new per-panel visibility control.
2025-06-14 01:39:23 +08:00
Apple\Apple bccb87fef3 feat: Add controller/console_migrate.go providing /api/option/migrate_console_setting endpoint for one-off data migration. 2025-06-14 01:05:09 +08:00
Apple\Apple ce0ff67978 🧹 refactor: drop obsolete ValidateApiInfo API & update callers
Backend
• Removed the exported function `ValidateApiInfo` from `setting/console_setting/validation.go`; it was only a legacy wrapper and is no longer required.
• Updated `controller/option.go` to call `ValidateConsoleSettings(value, "ApiInfo")` directly when validating `console_setting.api_info`.
• Confirmed there are no remaining references to `ValidateApiInfo` in the codebase.

This commit eliminates the last piece of compatibility code related to the old validation interface, keeping the API surface clean and consistent.
2025-06-14 00:59:38 +08:00
Apple\Apple 9d384f9778 refactor(console_setting): migrate console settings to model_setting auto-injection
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.
2025-06-14 00:40:29 +08:00
Apple\Apple edd44b7e52 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-13 20:52:12 +08:00
Apple\Apple f5e3617cf7 🎫 feat: Enhance redemption code expiry handling & improve UI responsiveness
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.
2025-06-13 20:51:20 +08:00
CaIon d26ae9aa77 feat(dto): add VlHighResolutionImages parameter to GeneralOpenAIRequest 2025-06-13 17:29:26 +08:00
Apple\Apple 8c5f46cea6 🎨 feat(ui): enhance table headers with descriptive titles and semantic icons
- 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
2025-06-13 14:11:39 +08:00
Apple\Apple f4874c2c1d feat(ui): add incremental-add feedback & translations for model lists (#1218)
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
2025-06-13 13:49:15 +08:00
Apple\Apple dd98db59f1 Merge branch 'main' into alpha 2025-06-13 13:03:28 +08:00
Apple\Apple 0c34eb4351 🐛 fix(ability): keep case-sensitive (group, model) keys during deduplication
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.
2025-06-13 13:03:06 +08:00
Apple\Apple 215238332d 🐛 fix(ability): prevent duplicate (group, model) pairs when saving channels
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
2025-06-13 12:39:54 +08:00
Apple\Apple bbeaa6a2e3 🐛 fix(setup): enforce username length ≤ 12 during initial system setup
The User model applies `validate:"max=12"` to the `Username` field, but the
initial setup flow did not validate this constraint. This allowed creation
of a root user with an overly long username (e.g. "Uselessly1344"), which
later caused every update request to fail with:

  Field validation for 'Username' failed on the 'max' tag

This patch adds an explicit length check in `controller/setup.go` to reject
usernames longer than 12 characters during setup, keeping validation rules
consistent across the entire application.

Refs: #1214
2025-06-13 12:28:26 +08:00
Calcium-Ion 671dcea96e Update README.md 2025-06-13 01:59:34 +08:00
Apple\Apple ee2679b9cc 💬 fix(PersonalSetting): improve notification message accuracy for settings save operation
- Change success message from "通知设置已更新" to "设置保存成功"
- Change error message from "更新通知设置失败" to "设置保存失败"
- Makes messages more generic since the function saves multiple types of settings (notification, pricing, IP logging) not just notification settings
2025-06-13 01:43:43 +08:00
Apple\Apple 3d3384fa69 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-13 01:34:26 +08:00
Apple\Apple 28c3aef76f 🔒 feat: Add user-configurable IP logging for consume and error logs
- 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.
2025-06-13 01:34:01 +08:00
Calcium-Ion 158d621fd7 Merge pull request #1207 from QuantumNous/user_group_ratio
feat: 分组特殊倍率
2025-06-13 01:25:46 +08:00
CaIon 0e97441843 🔧 refactor(LogsTable, render): remove undefined parameters for improved clarity and consistency in function signatures 2025-06-13 01:25:26 +08:00
CaIon 1358a36028 feat(render): introduce getEffectiveRatio helper for improved group ratio handling 2025-06-13 01:16:16 +08:00
CaIon fa7d18ab7f 🔒 feat(setting): add mutex for GroupGroupRatio to ensure thread safety 2025-06-13 01:08:38 +08:00
Apple\Apple b1176d0258 🔧 fix(token hooks): adapt token key fetcher to new paginated API
Changes
1. web/src/helpers/token.js
   • `fetchTokenKeys` now calls `/api/token/?p=1&size=10` (1-based paging).
   • Supports new response shape `{ items, total, page, page_size }`; falls back gracefully if array is returned.
   • Filters active tokens from `tokenItems`, not `data` directly.

`useTokenKeys` remains unchanged—its consumer code receives the same list of active keys.
2025-06-12 23:53:34 +08:00
Apple\Apple 3f67ef2d9c 🐛 fix: correct loading state for search button in TokensTable
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
2025-06-12 17:48:20 +08:00
Apple\Apple f7b870c16d 🎨style: Standardize pagination text format in Dashboard components
- Replace `showTotal` with `formatPageText` in Dashboard table components
- Unify pagination text format to match table components pattern
- Update SettingsAnnouncements.js, SettingsAPIInfo.js, and SettingsFAQ.js
- Change from "共 X 条记录,显示第 Y-Z 条" to "第 Y - Z 条,共 X 条" format
- Ensure consistent user experience across all table components

This change improves UI consistency by standardizing the pagination
text format across Dashboard and table components.
2025-06-12 17:40:32 +08:00
Apple\Apple a50601295e 🚀 feat(pagination): unify backend-driven pagination & improve channel tag aggregation
SUMMARY
• Migrated Token, Task, Midjourney, Channel, Redemption tables to true server-side pagination.
• Added total / page / page_size metadata in API responses; switched all affected React tables to consume new structure.
• Implemented counting helpers:
  – model/token.go CountUserTokens
  – model/task.go TaskCountAllTasks / TaskCountAllUserTask
  – model/midjourney.go CountAllTasks / CountAllUserTask
  – model/channel.go CountAllChannels / CountAllTags
• Refactored controllers (token, task, midjourney, channel) for 1-based paging & aggregated returns.
• Redesigned `ChannelsTable.js`:
  – `loadChannels`, `syncPageData`, `enrichChannels` for tag-mode grouping without recursion.
  – Fixed runtime white-screen (maximum call-stack) by removing child duplication.
  – Pagination, search, tag-mode, idSort all hot-reload correctly.
• Removed unused `log` import in controller/midjourney.go.

BREAKING CHANGES
Front-end consumers must now expect data.items / total / page / page_size from list endpoints (`/api/channel`, `/api/task`, `/api/mj`, `/api/token`, etc.).
2025-06-12 17:25:25 +08:00
skynono 8c20aef5d1 fix: enabled hot reload SyncOptions 2025-06-12 17:17:07 +08:00
creamlike1024 2a0bf7f991 feat: 分组特殊倍率 2025-06-11 23:46:59 +08:00
a37836323 a126015940 修复Azure渠道对responses API的兼容性支持 - 为Azure渠道添加对responses API的特殊处理 - 兼容微软新的API格式,使用preview版本的api-version - 修复了Azure渠道无法正确处理responses请求的问题 2025-06-11 22:11:47 +08:00
Apple\Apple dede9e117a 🎨 style(dashboard): Optimize the layout of the Uptime card legend on the dashboard to resolve the issue where the last monitoring item is obscured 2025-06-11 15:07:01 +08:00
Apple\Apple 477608865a 🧶chore: remove useless web files 2025-06-11 14:45:12 +08:00
Apple\Apple 86205d73e9 🧶chore: remove redundant semantic-related dependencies and configurations 2025-06-11 12:38:51 +08:00
Apple\Apple f2fb28cfd2 feat(controller): gracefully handle missing Uptime Kuma configuration
Previously, the uptime status endpoint returned HTTP 400 with
“未配置 Uptime Kuma URL/Slug” when either option was not set, resulting in
frontend error states.

Changes:
• Treat absence of `UptimeKumaUrl` or `UptimeKumaSlug` as a valid scenario.
• Immediately respond with HTTP 200, `success: true`, and an empty `data` array.
• Preserve existing behavior when both options are provided.

This prevents unnecessary error notifications on the dashboard when
Uptime Kuma integration is not configured and improves overall UX.
2025-06-11 03:41:05 +08:00
Apple\Apple b6272de078 💄style: Align ChannelsTable column selector modal style with LogsTable
* 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.
2025-06-11 03:26:16 +08:00
Apple\Apple d66ed068c4 Merge remote-tracking branch 'origin/main' into alpha 2025-06-11 03:19:53 +08:00
同語 d502ac2023 Merge pull request #1199 from feitianbubu/revert-column-visiblity-settting-channel
feat: add column visibility settings for channels
2025-06-11 03:19:20 +08:00
Apple\Apple 8b2fa26250 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-11 03:16:27 +08:00
同語 f51f6fb8ef Merge pull request #1200 from RedwindA/fix/playground-sse
fix playground-sse
2025-06-11 03:15:51 +08:00
Apple\Apple 16e0c94ed0 🥳 feat(detail): unify uptime status handling & enhance availability card UI
Summary
• Centralized uptime status definition via `uptimeStatusMap`, containing color / label / text for each status.
• Generated `uptimeLegendData`, `getUptimeStatusColor`, `getUptimeStatusText` directly from the map, removing multiple switch-case blocks.

UI Improvements
1. Added statuses 2 (High Latency) & 3 (Maintenance) with dedicated colors.
2. Relocated status legend to a styled footer wrapped in a borderless sub-Card; header now only shows title + refresh button.
3. Footer (and its negative margin) renders only when `uptimeData` is present, preventing empty legend display.
4. Applied rounded, blurred badge style and always-on shadow to legend container for clearer separation.

Maintenance
• Simplified code paths, reduced duplication, and improved readability without breaking existing functionality.
2025-06-11 03:12:34 +08:00
Apple\Apple 67c9757969 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-11 02:29:08 +08:00
Apple\Apple 5f4d6648f7 ⏱️ feat: implement uptime monitoring
Introduce application uptime monitoring to improve observability and reliability.

• Add UptimeService to track process start time and expose uptime in seconds
• Create /health/uptime endpoint returning the current uptime in JSON format
• Integrate uptime metric into existing health-check middleware
• Update README with instructions for consuming the new endpoint
• Add unit tests covering UptimeService and new health route

This change enables operations teams and dashboards to programmatically
determine how long the service has been running, facilitating automated
alerts and trend analysis.
2025-06-11 02:28:36 +08:00
RedwindA b9a026ed23 🔧 fix(useApiRequest): improve playground SSE error handling and stream completion tracking 2025-06-11 02:05:16 +08:00
skynono f49a797fe7 feat: add column visibility settings for channels 2025-06-11 01:36:23 +08:00
CaIon d62769376b Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-11 00:18:26 +08:00
CaIon 5511ba3670 🔧 fix(stream_scanner): improve resource management and error handling in StreamScannerHandler 2025-06-11 00:18:16 +08:00
Apple\Apple 1d28968193 🔧 fix: Update payment callback return URL path from /log to /console/log
- Modified returnUrl configuration in RequestEpay function
- Changed payment success redirect path to match updated frontend routing
- Updated controller/topup.go line 116 to use correct callback path
2025-06-10 20:41:43 +08:00
Apple\Apple 85077d6f63 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-10 20:10:35 +08:00
Apple\Apple 0f3696eef3 feat: Add console announcements and FAQ management system
- Add SettingsAnnouncements component with full CRUD operations for system announcements
  * Support multiple announcement types (default, ongoing, success, warning, error)
  * Include publish date, content, type classification and additional notes
  * Implement batch operations and pagination for better data management
  * Add real-time preview with relative time display and date formatting

- Add SettingsFAQ component for comprehensive FAQ management
  * Support question-answer pairs with rich text content
  * Include full editing, deletion and creation capabilities
  * Implement batch delete operations and paginated display
  * Add validation for complete Q&A information

- Integrate announcement and FAQ modules into DashboardSetting
  * Add unified configuration interface in admin console
  * Implement auto-refresh functionality for real-time updates
  * Add loading states and error handling for better UX

- Enhance backend API support in controller and setting modules
  * Add validation functions for console settings
  * Include time and sorting utilities for announcement management
  * Extend API endpoints for announcement and FAQ data persistence

- Improve frontend infrastructure
  * Add new translation keys for internationalization support
  * Update utility functions for date/time formatting
  * Enhance CSS styles for better component presentation
  * Add icons and visual improvements for announcements and FAQ sections

This implementation provides administrators with comprehensive tools to manage
system-wide announcements and user FAQ content through an intuitive console interface.
2025-06-10 20:10:07 +08:00
CaIon ad4d3efd2e feat(middleware): add HTTP statistics middleware 2025-06-10 19:29:32 +08:00
CaIon 7dc7805c6a 🔧 fix(token_counter): enhance token encoder caching and concurrency handling 2025-06-10 18:55:21 +08:00
CaIon 1abd77c17c 🔧 fix(token_counter): refactor token encoder initialization and retrieval logic 2025-06-10 18:51:26 +08:00
CaIon 88a801af2d 🔧 fix(token_counter): update token encoder implementation and dependencies 2025-06-10 18:04:49 +08:00
Apple\Apple 26521f42d9 🎨 refactor(ui): replace IconSearch with semantic lucide icons
- Replace IconSearch with Server icon for API info card title to better represent server/API related content
- Add Server imports from lucide-react

This change improves the semantic meaning of icons and provides better visual representation of their respective functionalities.
2025-06-10 12:43:14 +08:00
Apple\Apple 6880cca9b2 🔧 refactor(console): enhance URL validation and restructure settings module
- Refactor api_info.go to console.go for broader console settings support
- Update URL regex pattern to accept both domain names and IP addresses
- Add support for IPv4 addresses with optional port numbers
- Improve validation to handle formats like http://192.168.1.1:8080
- Add ValidateConsoleSettings function for extensible settings validation
- Maintain backward compatibility with existing ValidateApiInfo function
- Add comprehensive comments explaining supported URL formats

Fixes issue where IP-based URLs were incorrectly rejected as invalid format.
Prepares infrastructure for additional console settings validation.
2025-06-10 12:20:26 +08:00
Apple\Apple 154eaed83f 🔧 fix(api): enhance URL validation to support IP addresses and ports
- Update URL regex pattern to accept both domain names and IP addresses
- Add support for IPv4 addresses with optional port numbers
- Improve validation to handle formats like http://192.168.1.1:8080
- Add comprehensive comments explaining supported URL formats
- Maintain backward compatibility with existing domain-based URLs

Fixes issue where IP-based URLs were incorrectly rejected as invalid format.
2025-06-10 12:12:55 +08:00
CaIon b23416bbb4 🔧 fix(channel-test): ensure proper state reset to prevent deadlocks 2025-06-10 03:54:18 +08:00
CaIon 92de4d0d04 🔧 fix(api_request): enhance ping keep-alive mechanism with error handling and timeout controls 2025-06-10 03:42:23 +08:00
Apple\Apple 624fdecc6e feat: implement GET request deduplication in API layer
Add request deduplication mechanism to prevent duplicate GET requests
to the same endpoint within the same timeframe, significantly reducing
unnecessary network overhead.

**Changes:**
- Add `patchAPIInstance()` function to intercept and deduplicate GET requests
- Implement in-flight request tracking using Map with URL+params as unique keys
- Apply deduplication patch to both initial API instance and `updateAPI()` recreated instances
- Add `disableDuplicate: true` config option to bypass deduplication when needed

**Benefits:**
- Eliminates redundant API calls caused by component re-renders or rapid user interactions
- Reduces server load and improves application performance
- Provides automatic protection against accidental duplicate requests
- Maintains backward compatibility with existing code

**Technical Details:**
- Uses Promise sharing for identical concurrent requests
- Automatically cleans up completed requests from tracking map
- Preserves original axios functionality with minimal overhead
- Zero breaking changes to existing API usage

Addresses the issue observed in EditChannel.js where multiple calls
were made to the same endpoints during component lifecycle.
2025-06-10 02:32:50 +08:00
Apple\Apple df00c5c6b0 🎨 style(LogsTable): replace IconForward with Route icon for model redirection
- 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.
2025-06-10 02:12:52 +08:00
Apple\Apple a0f89a36a3 🎨 style: change headerbar px-3 to px-2 2025-06-10 01:53:12 +08:00
Apple\Apple 5881bc7658 🎨 style: change headerbar px-4 to px-3 2025-06-10 01:51:49 +08:00
Apple\Apple 000c7c7b95 🎨 style: disable y-axis scrolling for semi-layout components
- Hide scrollbars for .semi-layout, .semi-layout-content, and .semi-sider
- Set scrollbar width and height to 0 for webkit browsers
- Add cross-browser scrollbar hiding support (webkit, firefox, IE/Edge)
- Change Content container overflow from 'auto' to 'hidden' on desktop
- Remove redundant scrollbar styling (thumb, hover, track styles)

This ensures that all semi-layout related components have no visible
scrollbars and prevents vertical scrolling functionality entirely.

Files modified:
- web/src/index.css
- web/src/components/layout/PageLayout.js
2025-06-10 01:42:38 +08:00
Apple\Apple 94df6885ae 🎨 feat(home): redesign homepage hero section with improved layout and multilingual support
- Remove system name display from homepage title
- Replace with unified gateway branding: "统一的大模型接口网关"
- Add subtitle highlighting key benefits: price, stability, no subscription
- Implement language-specific title rendering:
  - English: Two-line layout ("The Unified" / "LLMs API Gateway")
  - Chinese: Single-line layout for better readability
- Increase title font sizes for better visual hierarchy
- Adjust vertical padding for improved centering
- Enhance overall visual appeal and user experience

This update modernizes the homepage presentation and provides better
localization support for different language preferences.
2025-06-10 01:01:03 +08:00
Apple\Apple 64fe8a7f89 📱 feat(TopUp): enhance mobile UX with responsive layout and bottom fixed payment panel
- Convert copy button to Input suffix for cleaner UI design
- Add responsive grid layout for balance cards and preset amounts
  - Mobile (< md): single column layout for better readability
  - Desktop (>= md): multi-column layout for space efficiency
- Implement bottom fixed payment panel on mobile devices
  - Fixed positioning for easy access to payment options
  - Includes custom amount input and payment method buttons
  - Auto-hide on desktop to maintain original layout
- Improve mobile payment flow with sticky bottom controls
- Add proper spacing to prevent content overlap with fixed elements
- Maintain consistent functionality across all breakpoints

This update significantly improves the mobile user experience by making
payment controls easily accessible without scrolling, while preserving
the desktop layout and functionality.
2025-06-10 00:40:47 +08:00
Apple\Apple d5222743ac Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-09 22:28:08 +08:00
Apple\Apple 3fd1667813 🐛 fix(theme): sync theme state between global context and local components
- 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.
2025-06-09 22:27:39 +08:00
IcedTangerine 6d90b2f3dc Merge pull request #1145 from RedwindA/feature/gemini_snake_case_support
feat: 支持Gemini inline_data 的蛇形命名法
2025-06-09 22:06:58 +08:00
Calcium-Ion f95feae571 Merge pull request #1180 from RedwindA/fix/gemini-tool
🐛 fix(Gemini): improve JSON parsing for tool content handling
2025-06-09 20:51:28 +08:00
CaIon b21e378fee feat(ChannelsTable): add renderQuotaWithAmount function and clean up imports 2025-06-09 20:50:37 +08:00
Calcium-Ion 5f5333ebda Merge pull request #1181 from feitianbubu/fix-balance-unit-sync
fix: balance unit sync
2025-06-09 20:48:58 +08:00
Calcium-Ion c821b0edcc Merge branch 'alpha' into fix-balance-unit-sync 2025-06-09 20:48:50 +08:00
Calcium-Ion be71bbb64f Merge pull request #1182 from RedwindA/fix/mistral-tool-content
fix(mistral): adjust condition for assistant content with tool call
2025-06-09 20:47:19 +08:00
CaIon 78b0a82cb8 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-09 19:24:45 +08:00
CaIon 158c18fe22 feat: add dark mode detection and styling enhancements to PersonalSetting and TopUp components 2025-06-09 19:24:21 +08:00
Apple\Apple e3b1d6915e ♻️ refactor(setting): move API info functions to dedicated module
Move validateApiInfo and getApiInfo functions from controller layer to
setting/api_info.go to improve code organization and separation of concerns.

Changes:
- Create setting/api_info.go with ValidateApiInfo() and GetApiInfo() functions
- Remove validateApiInfo function from controller/option.go
- Remove getApiInfo function from controller/misc.go
- Update function calls to use setting package
- Clean up unused imports (net/url, regexp, fmt) in controller/option.go

This refactoring aligns the API info configuration management with the
existing pattern used by other setting modules (chat.go, group_ratio.go,
rate_limit.go, etc.) and improves code reusability and maintainability.
2025-06-09 19:14:34 +08:00
Apple\Apple 52a665d6a4 feat: Add speed test functionality to API info display
- Add speed test tag with gauge icon for each API route
- Integrate tcptest.cn service for API endpoint performance testing
- Implement handleSpeedTest callback to open speed test in new tab
- Add Tag component import from @douyinfe/semi-ui
- Use Gauge icon with white circular tag styling
- Position speed test tag before API route for better visibility
- URL encoding handles special characters for proper test URL generation
- Remove unused IconTestScoreStroked import and clean up comments

The speed test feature allows users to quickly test API endpoint
performance by clicking a small circular tag that opens the
tcptest.cn speed testing service with the encoded API URL.
2025-06-09 19:03:04 +08:00
CaIon a5baba6b9f 💄 style(LogsTable): remove prefix icons from tags for cleaner UI 2025-06-09 19:00:28 +08:00
Apple\Apple b40f4b99fb 💄 style(ui): improve API info card layout with separate columns for avatar and text
- Restructure API info card layout to use two-column design
- Move avatar to separate left column with fixed width
- Align route name, URL, and description text to same starting position
- Remove unnecessary indentation and improve visual hierarchy
- Enhance readability and consistency of API information display
2025-06-09 18:31:49 +08:00
Apple\Apple c29ac9bb47 💄 style(LogsTable): set minimum width for log type selector
- 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
2025-06-09 18:27:01 +08:00
Apple\Apple efa807abe2 🎨 refactor: reorganize log type selector layout with responsive design
- 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.
2025-06-09 18:22:18 +08:00
Apple\Apple 07961bccca Merge remote-tracking branch 'origin/main' into alpha 2025-06-09 17:46:00 +08:00
Apple\Apple 49c3b9ebcc Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-09 17:45:49 +08:00
Apple\Apple 4c673d2628 feat: major refactor and enhancement of Detail dashboard component & add api url display
- **Code Organization & Architecture:**
  - Restructured component with clear sections (Hooks, Constants, Helper Functions, etc.)
  - Added comprehensive code organization comments for better maintainability
  - Extracted reusable helper functions and constants for better separation of concerns

- **Performance Optimizations:**
  - Implemented extensive use of useCallback and useMemo hooks for expensive operations
  - Optimized data processing pipeline with dedicated processing functions
  - Memoized chart configurations, performance metrics, and grouped stats data
  - Cached helper functions like getTrendSpec, handleCopyUrl, and modal handlers

- **UI/UX Enhancements:**
  - Added Empty state component with construction illustrations for better UX
  - Implemented responsive grid layout with conditional API info section visibility
  - Enhanced button styling with consistent rounded design and hover effects
  - Added mini trend charts to statistics cards for visual data representation
  - Improved form field consistency with reusable createFormField helper

- **Feature Improvements:**
  - Added self-use mode detection to conditionally hide/show API information section
  - Enhanced chart configurations with centralized CHART_CONFIG constant
  - Improved time handling with dedicated helper functions (getTimeInterval, getInitialTimestamp)
  - Added comprehensive performance metrics calculation (RPM/TPM trends)
  - Implemented advanced data aggregation and processing workflows

- **Code Quality & Maintainability:**
  - Extracted complex data processing logic into dedicated functions
  - Added proper prop destructuring and state organization
  - Implemented consistent naming conventions and helper utilities
  - Enhanced error handling and loading states management
  - Added comprehensive JSDoc-style comments for better code documentation

- **Technical Debt Reduction:**
  - Replaced repetitive form field definitions with reusable components
  - Consolidated chart update logic into centralized updateChartSpec function
  - Improved data flow with better state management patterns
  - Reduced code duplication through strategic use of helper functions

This refactor significantly improves component performance, maintainability, and user experience while maintaining backward compatibility and existing functionality.
2025-06-09 17:44:23 +08:00
Calcium-Ion bc71259c2c Merge pull request #1186 from tylinux/main
feat: use bun when develop locally
2025-06-09 15:52:17 +08:00
Calcium-Ion a8b24bed00 Merge pull request #1184 from QuantumNous/refactor/message
fix: message 转 any 后,ImageUrl判断 panic
2025-06-09 15:51:53 +08:00
tylinux 8c9dfd3bb4 feat: use bun when develop locally 2025-06-09 14:57:01 +08:00
Apple\Apple 03645264c3 refactor(home): redesign homepage layout with centered content and improved responsiveness
- Remove example image and right-side image section for cleaner layout
- Center all content vertically and horizontally on the page
- Implement comprehensive responsive design using Tailwind CSS breakpoints
  - Typography scales from text-3xl to xl:text-6xl across screen sizes
  - Spacing and padding adjust dynamically (py-12 to lg:py-20)
  - Icon grid adapts from gap-3 to lg:gap-8
- Keep action buttons horizontally aligned on all screen sizes
- Add play icon to "Get Started" button for better UX
- Refactor version display logic:
  - Show version tag only in demo site mode
  - Replace GitHub button text with version number in demo mode
  - Add docs button with same logic as HeaderBar when not in demo mode
- Optimize icon layout with consistent 40px size and responsive containers
- Improve overall mobile-first responsive design from 320px to 1280px+ screens
2025-06-09 13:43:50 +08:00
Xyfacai be38aaa80d fix: message 转 any 后,ImageUrl判断 panic 2025-06-09 11:27:24 +08:00
RedwindA 07a671ec27 🐛 fix: update condition to check for empty content in assistant role messages 2025-06-09 02:15:39 +08:00
skynono d30ae8204e fix: balance unit sync 2025-06-09 01:31:39 +08:00
Apple\Apple af0f59cbc9 🎨 style: remove overly vibrant colors and simplify UI design
- Remove colorful gradient backgrounds from dashboard panel headers in Detail page
- Replace custom header styling with default Semi-UI card title styling
- Remove background images and gradient overlays from all authentication pages
- Simplify authentication page layouts with clean gray backgrounds
- Update title text colors from white to dark gray for better contrast
- Remove unnecessary z-index layering and complex positioning
- Clean up unused background image imports

This change creates a more professional and consistent visual appearance
across the application by removing distracting visual elements.
2025-06-09 00:14:35 +08:00
Apple\Apple 4c4d33318f feat(tables): add "No Results" empty state for all table components
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.
2025-06-08 23:42:39 +08:00
Apple\Apple 2fc3b39236 feat(ui): add lucide-react icons to dashboard sections
Add visual icons to improve user experience and section identification:

- Import lucide-react icons: Wallet, Activity, Zap, Gauge, PieChart
- Add Wallet icon to "Account Data" section
- Add Activity icon to "Usage Statistics" section
- Add Zap icon to "Resource Consumption" section
- Add Gauge icon to "Performance Metrics" section
- Add PieChart icon to "Model Data Analysis" card

All icons are styled with 16px size and proper flex layout with consistent spacing. Icons inherit parent text color for seamless integration with existing gradient headers.
2025-06-08 23:22:19 +08:00
Apple\Apple 2df42cea24 🎨 feat(ui): replace list icon with tags icon for channel tag aggregation
- 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.
2025-06-08 23:16:34 +08:00
Apple\Apple 48599d709d feat: Add lucide-react icons to all table Tag components
- 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.
2025-06-08 23:13:45 +08:00
Apple\Apple 26d8c3aea9 🔄 fix(tables): ensure search buttons show loading state consistently across all tables
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.
2025-06-08 22:01:54 +08:00
CaIon 1de4af1c74 fix(main.go): correct comment formatting for embed directives 2025-06-08 20:26:14 +08:00
Apple\Apple a508eb2669 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-08 18:41:18 +08:00
Apple\Apple a62a84418a ♻️ refactor(components): migrate all table components to use Form API
- 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.
2025-06-08 18:41:04 +08:00
Apple\Apple d1fb841510 refactor(LogsTable): enhance Form component with auto-search and state synchronization
- 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.
2025-06-08 17:28:28 +08:00
CaIon 2fb222b9a4 refactor(dto): change function and encoding fields to use json.RawMessage for improved flexibility 2025-06-08 16:28:47 +08:00
CaIon 2b3e64e8b6 fix(relay-channel): correct condition for mediaMessages initialization in requestOpenAI2Mistral function 2025-06-08 16:25:00 +08:00
CaIon fcb9647aeb fix(relay-gemini): remove outdated unsupported models from CovertGemini2OpenAI function 2025-06-08 16:22:39 +08:00
Calcium-Ion 5d7e688bfe Merge pull request #1174 from QuantumNous/refactor/message
refactor: message content 改成 any
2025-06-08 16:22:20 +08:00
CaIon 7469446a20 fix(relay-gemini): add unsupported models to CovertGemini2OpenAI function 2025-06-08 16:04:31 +08:00
Apple\Apple ef721bb055 Merge remote-tracking branch 'origin/main' into alpha 2025-06-08 15:08:03 +08:00
RedwindA da41545139 🐛 fix(Gemini): improve JSON parsing for tool content handling 2025-06-08 14:35:56 +08:00
Calcium-Ion 762d2c81f5 Merge pull request #1179 from QuantumNous/alpha
merge alpha to main
2025-06-08 14:34:24 +08:00
Calcium-Ion f115342784 Merge pull request #1162 from RedwindA/fix-redis-hdel
fix: Rename and refactor RedisHDelObj to RedisDelKey
2025-06-08 14:33:48 +08:00
Apple\Apple 4d9ecdb8d5 💄 style(channel): unify text link styles in EditTagModal with EditChannel
Update text link styling in EditTagModal.js to match the consistent design
pattern used in EditChannel.js. Changed className from 'text-blue-500 cursor-pointer'
to '!text-semi-color-primary cursor-pointer' for template-related action links
("填入模板", "清空重定向", "不更改").

This change ensures:
- Visual consistency across channel editing components
- Better theme adaptability using Semi Design color variables
- Adherence to established design patterns in the codebase

Files modified:
- web/src/pages/Channel/EditTagModal.js
2025-06-08 14:16:57 +08:00
Calcium-Ion 58f235af5e Merge pull request #1176 from RedwindA/feat/tagMode-channelModelList
feat: 标签聚合模式编辑渠道时复用渠道模型列表
2025-06-08 13:52:36 +08:00
RedwindA b197dfb95d 🐛 fix(EditTagModal): add info banner to clarify modelList behavior 2025-06-08 13:23:59 +08:00
Apple\Apple e0ff3b6f28 feat(token): auto-generate default token names when user input is empty
When creating tokens, if the user doesn't provide a token name (empty or whitespace-only),
the system will now automatically generate a name using the format "default-xxxxxx" where
"xxxxxx" is a 6-character random alphanumeric string.

This enhancement ensures that all created tokens have meaningful names and improves the
user experience by removing the requirement to manually input token names for quick token
creation scenarios.

Changes:
- Modified token creation logic to detect empty token names
- Added automatic fallback to "default" base name when user input is missing
- Maintained existing behavior for multiple token creation with random suffixes
- Ensured consistent naming pattern across single and batch token creation
2025-06-08 12:38:03 +08:00
Apple\Apple 5040235919 💄 style(Logs): Add rounded corners to image view button in MjLogsTable
- Add rounded-full class to "查看图片" (View Image) button for consistent UI styling
- All other buttons in both MjLogsTable.js and TaskLogsTable.js already have rounded corners applied
- Ensures uniform button styling across the log tables interface
2025-06-08 12:23:54 +08:00
Apple\Apple 07f141e8c8 🎨 fix: Import Semi UI CSS explicitly to resolve missing component styles
- Add explicit import of '@douyinfe/semi-ui/dist/css/semi.css' in index.js
- Ensures Semi Design components render with proper styling
- Resolves issue where Semi components appeared unstyled after dependency updates

This change addresses the style loading issue that occurred after adding antd
dependency and updating the build configuration. The explicit import ensures
consistent style loading regardless of plugin behavior changes.
2025-06-08 12:14:49 +08:00
CaIon e1039c67f7 chore: update CI workflows 2025-06-08 03:37:17 +08:00
CaIon 594636bace fix: update import statement for vite-plugin-semi to use default import 2025-06-08 03:35:04 +08:00
CaIon e647163969 chore: update CI workflow to use latest Bun version and adjust build environment variables 2025-06-08 03:28:36 +08:00
CaIon c3a205e792 chore: update Bun version in CI workflow to 1.2.8 2025-06-08 02:57:44 +08:00
CaIon e7289297e2 chore: update package.json to replace sse dependency and add trustedDependencies 2025-06-08 02:56:09 +08:00
CaIon 2e04d28483 chore: update CI workflows to use Bun for package management across all platforms 2025-06-08 02:45:18 +08:00
CaIon 4069b3f789 chore: update CI workflows to support manual triggers and rename Docker image workflow 2025-06-08 02:42:27 +08:00
CaIon 2cb067f485 chore: remove deprecated Docker image workflow for amd64 2025-06-08 02:39:53 +08:00
CaIon 550c068994 Merge branch 'alpha' 2025-06-08 02:38:49 +08:00
CaIon 8b55c9b917 Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-08 02:34:22 +08:00
CaIon e09931558e chore: update CI workflows to use Bun for package management and build process 2025-06-08 02:34:06 +08:00
Calcium-Ion 7607da2387 Merge pull request #1177 from QuantumNous/alpha
merge alpha to main
2025-06-08 02:26:06 +08:00
Apple\Apple 09c6c2c576 🔧 fix(auth): add copy button to disabled password input in reset confirmation
- Import IconCopy from semi-icons for copy functionality
- Replace onClick handler with suffix copy button to fix disabled input issue
- Use borderless tertiary button as input suffix for better alignment
- Update notification messages formatting (colon spacing)
- Ensure password copying works even when input field is disabled
2025-06-08 02:23:47 +08:00
Apple\Apple 9f4a32d6c0 chore(PasswordResetConfirm): Improve password reset confirm UI and fix form data binding
- Replace error message div with Semi UI Banner component for better UX
- Add rounded corners to Banner component with !rounded-lg class
- Fix Form.Input not displaying values by implementing proper formApi usage
- Use getFormApi callback to obtain form API instance
- Replace manual value props with formApi.setValues() for dynamic updates
- Set proper initValues for form initialization
- Remove unused Input import and console.log statements
- Clean up debugging code and optimize form state management

This change enhances the visual consistency with Semi Design system
and resolves the issue where email field was not showing URL parameter values.
2025-06-08 01:44:38 +08:00
RedwindA 18f82f120c 🐛 fix(EditTagModal): add fetchTagModels function to retrieve models based on tag 2025-06-08 01:16:39 +08:00
RedwindA c4f664f376 🎨 feat(channel): add endpoint to retrieve models by tag 2025-06-08 01:16:27 +08:00
Apple\Apple b6dd9f96a1 🐛 fix(auth): resolve password reset confirmation display and functionality issues
- Fix input field display issues in password reset confirmation page
  * Replace `readOnly` with `disabled={true}` for proper field state
  * Improve URL parameter parsing and state management
  * Add proper null checks and fallback values

- Enhance user experience and error handling
  * Add validation for invalid reset links
  * Display appropriate error messages and placeholders
  * Add debug logging for troubleshooting
  * Improve button states and loading indicators

- Improve password reset form validation
  * Add proper email input validation with error messages
  * Enhance user feedback for empty email submissions

- Add missing English translations
  * Add i18n support for new UI text strings
  * Ensure proper internationalization coverage

The password reset confirmation page now correctly displays email addresses
from URL parameters and prevents user input as intended. Error handling
has been improved to provide better user guidance when reset links are
invalid or malformed.

Fixes: Password reset input fields showing empty and allowing user input
when they should display email/password and be read-only.
2025-06-08 01:08:03 +08:00
Apple\Apple 0377cf37c5 🔖chore(ui): Improve Loading prompt 2025-06-08 00:33:26 +08:00
Calcium-Ion 8f938d5b1a Merge pull request #1171 from RedwindA/feat/ali-rerank
feat: ali rerank
2025-06-08 00:15:21 +08:00
Calcium-Ion 258bad0a17 Merge pull request #1173 from RedwindA/fix/ali-embedding
🐛 fix(ali): Remove hardcoding of embedding model names.
2025-06-08 00:14:55 +08:00
Calcium-Ion ff6e12861a Merge pull request #1175 from RedwindA/fix/mistral-tool-id
🐛 fix: 适应Mistral的tool call格式要求
2025-06-08 00:14:23 +08:00
CaIon 28a2387dff Merge remote-tracking branch 'origin/alpha' into alpha 2025-06-08 00:11:28 +08:00
CaIon 5ace100f63 chore: update bun.lockb file 2025-06-08 00:10:54 +08:00
Apple\Apple 685cf70016 ♻️ refactor(auth): replace custom loading UI with shared Loading component and add i18n support
- Replace inline loading UI in OAuth2Callback with shared Loading component
- Add internationalization support using useTranslation hook
- Translate all hardcoded Chinese strings to support multiple languages
- Remove unused processing state variable
- Maintain consistent loading experience across the application
- Support dynamic text content for retry attempts with parameter interpolation
2025-06-08 00:07:37 +08:00
RedwindA 546d0140d3 🐛 fix(mistral): validate and generate new IDs for tool calls and tool call IDs; Correctly handle null content for assistant messages with tool_calls. 2025-06-08 00:06:56 +08:00
CaIon e7ebfd498d fix: CI 2025-06-08 00:06:53 +08:00
Xyfacai 8343fe0740 refactor: message content 改成 any
refactor: message content 改成 any
2025-06-07 23:47:22 +08:00
Apple\Apple 226ef003e5 🎨feat(ui): Improve Chat page UI and add i18n support
- Replace Banner with full-screen Spin component for better loading UX
- Add English translation for "正在跳转..." ("Redirecting...")
- Integrate i18next translation hook in Chat page component
- Remove unused useEffect import for cleaner code

The Chat page now shows a centered full-screen loading spinner instead of
a banner when redirecting, providing a more consistent and professional
user experience. The loading text is now properly internationalized and
will display "Redirecting..." in English and "正在跳转..." in Chinese.
2025-06-07 23:22:25 +08:00
Apple\Apple c46e410a0d 🔗feat(ui): Standardize link colors and update documentation URL in EditChannel component
**Changes:**
- Unify link color styling across EditChannel.js by replacing `text-blue-500` with consistent primary color scheme
- Apply `!text-semi-color-primary hover:!text-semi-color-primary-hover transition-colors` to all template fill and documentation links
- Update documentation URL from Calcium-Ion repository to QuantumNous repository
- Add smooth hover transitions and consistent visual feedback for all clickable links

**Affected Elements:**
- Model mapping template fill link
- Deployment region template fill link
- Channel settings template fill link
- Channel settings documentation link
- Status code mapping template fill link

**Benefits:**
- Consistent visual design language across the entire application
- Improved user experience with unified link styling
- Better accessibility with clear hover states and transitions
- Correct documentation references pointing to the current project repository

**Technical Details:**
- Maintains existing functionality while improving visual consistency
- Links now match the color scheme used in About page and Footer components
- Smooth color transitions enhance user interaction feedback
2025-06-07 23:15:25 +08:00
Apple\Apple 242cf8e976 🔗feat(ui): Enhance About page with interactive project links and improve external link handling 2025-06-07 22:55:12 +08:00
Apple\Apple 8122a30c8a 🔗feat(ui): Enhance About page with interactive project links and improve external link handling
**Changes:**
- Replace React Router `Link` components with native `<a>` tags for external links in About and Footer components
- Add clickable links for "NewAPI", "QuantumNous", and "One API v0.5.4" in the About page
- Link "NewAPI" to the main project repository (https://github.com/QuantumNous/new-api)
- Link "QuantumNous" to the organization page (https://github.com/QuantumNous)
- Link "One API v0.5.4" to the specific release page (https://github.com/songquanpeng/one-api/releases/tag/v0.5.4)
- Apply consistent styling with primary color theme and hover effects across all links
- Add proper security attributes (`rel="noopener noreferrer"`) to all external links

**i18n Updates:**
- Refactor i18n translation keys to support the new link structure
- Split the original copyright string into smaller, reusable translation keys
- Add new translation keys: `"© {{currentYear}}"` and `"| 基于"`
- Maintain backward compatibility for existing translations

**Benefits:**
- Improved user experience with direct access to relevant project resources
- Better SEO and link accessibility
- Consistent visual styling across all external links
- Enhanced security for external link navigation
- Proper separation of concerns between internal routing and external navigation
2025-06-07 22:50:31 +08:00
RedwindA 894f9b98d9 Merge remote-tracking branch 'upstream/main' into fix/ali-embedding 2025-06-07 22:32:02 +08:00
RedwindA 7671f82ab6 🐛 fix(ali): Remove hardcoding of embedding model names. 2025-06-07 22:28:32 +08:00
Calcium-Ion e2df80118f Merge pull request #1172 from QuantumNous/alpha
feat: 0.8 version
2025-06-07 21:36:07 +08:00
RedwindA b8cfb64a45 feat: ali rerank 2025-06-07 21:29:46 +08:00
Apple\Apple 91fd8b3762 🎨 feat(UI): add minimum width to progress bars in log tables
Set a minimum width of 200px for progress components in both MjLogsTable.js and TaskLogsTable.js to ensure consistent display and prevent them from becoming too narrow when table resizes.
2025-06-07 21:03:16 +08:00
Apple\Apple e9416e620c 🐛 fix(table): add rowExpandable to prevent empty rows from expanding
The Table component in LogsTable.js was previously showing expand icons for all rows, even those without any expandable content. This led to a confusing UX where users could click to expand rows but would see empty content.

This commit adds the `rowExpandable` property to the Table configuration to ensure that only rows with actual expandable content show the expand icon and can be expanded. The function checks if each record has corresponding data in the expandData object before allowing it to be expanded.
2025-06-07 16:00:55 +08:00
Apple\Apple 51ea2bac0d 💄 style(UI): standardize scrollbar styling for semi-sidesheet-body
Standardize the scrollbar appearance of semi-sidesheet-body to match the existing semi-table-body style. This change:
- Sets scrollbar width to 6px
- Applies light gray color (rgba(var(--semi-grey-2), 0.3))
- Adds subtle hover effect
- Uses 2px border radius for consistency
- Improves overall UI cohesion across components
2025-06-07 13:18:50 +08:00
creamlike1024 46e6d593a6 Merge branch 'gemini-audio-billing' into alpha 2025-06-07 12:32:20 +08:00
creamlike1024 0e9a193ed8 feat: gemini audio input billing 2025-06-07 12:26:23 +08:00
Apple\Apple 6b965995ca 🐛 fix(home): prevent empty notice modal from auto-showing
Previously, the notice modal would automatically show every day even when
the notice content was empty, causing unnecessary user interruption.

This commit modifies the logic to:
- Check notice content before showing the modal
- Only display the modal when notice content exists and is not empty
- Add proper error handling to prevent modal showing on API failures
- Improve user experience by avoiding empty notice interruptions

Changes:
- Modified useEffect in Home component to fetch notice content first
- Added API call to /api/notice before setting noticeVisible state
- Added try-catch block for graceful error handling
- Only show modal when notice data is truthy and non-empty after trimming
2025-06-07 11:34:50 +08:00
Apple\Apple f4b6ec4f50 🎨 feat(table): add conditional row expansion for LogsTable
Implement rowExpandable property to control which rows can be expanded
in the logs table. Rows are now only expandable when they have actual
expand data content, preventing empty expansion sections from being
displayed to users.

- Add rowExpandable function to check if expandData exists and has content
- Improve user experience by hiding expand functionality for rows without details
- Maintain existing expand behavior for rows with valid expansion data
2025-06-07 03:09:44 +08:00
Apple\Apple da535b9587 feat: add support for OpenAI o1/o3/o4 series models in model categorization
- Extended OpenAI model filter to include o1, o3, and o4 series models
- Updated model categorization logic to properly classify reasoning models
- Ensures all OpenAI model variants (o1-mini, o1-preview, o3, o4, etc.) are correctly grouped under OpenAI category
- Maintains backward compatibility with existing GPT and other OpenAI model series
2025-06-07 02:57:16 +08:00
Apple\Apple 85ade00e95 🎨 style(table): customize table scrollbar appearance
Enhance table scrollbar visual design with lighter and thinner styling for better user experience.

Changes:
- Add custom scrollbar styling for .semi-table-body
- Set scrollbar dimensions to 6px width/height
- Apply lighter color using rgba(var(--semi-grey-2), 0.3) with 30% opacity
- Add hover effect with 50% opacity for better interaction feedback
- Use 2px border radius for smoother appearance
- Keep scrollbar track transparent for clean look
- Utilize Semi Design color variables for theme consistency

The new scrollbar design provides a more elegant and less intrusive horizontal scrolling experience across all data tables.
2025-06-07 02:51:38 +08:00
Apple\Apple ed2677c7f5 📌 feat(table): add fixed right column to all data tables
Fix the last column (operation/detail columns) to the right side across all table components to improve user experience and ensure important actions remain visible during horizontal scrolling.

Changes:
- ChannelsTable.js: Fix operation column to right
- UsersTable.js: Fix operation column to right
- TokensTable.js: Fix operation column to right
- RedemptionsTable.js: Fix operation column to right
- LogsTable.js: Fix details column to right
- MjLogsTable.js: Fix fail reason column to right
- TaskLogsTable.js: Fix fail reason column to right

All tables now have their rightmost column fixed using Semi Design's `fixed: 'right'` property, ensuring critical information and actions are always accessible regardless of table scroll position.
2025-06-07 02:45:07 +08:00
Apple\Apple 4c92025d55 🎨 style(footer): improve theme compatibility and color consistency
Update Footer component to use semantic color variables for better theme integration:

- Replace hardcoded background color with semi-color-bg-2 for theme consistency
- Update text colors to use semantic variables (semi-color-text-0, semi-color-text-1)
- Replace hardcoded link colors with semi-color-primary for brand consistency
- Add hover effects with smooth transitions for better user experience
- Keep logo container background as gray-800 for visual stability

This ensures the footer adapts properly to different theme modes while maintaining
good readability and visual consistency across the application.
2025-06-07 01:50:53 +08:00
Apple\Apple 5a2272553c feat(home): add default system name fallback to "New API"
Add fallback display value when system_name is not available or empty.
This ensures the homepage title always shows meaningful content instead
of being blank when the system name hasn't been configured or fails to load.

- Update Home component to display "New API" as default when statusState?.status?.system_name is falsy
- Improves user experience by preventing empty title display
2025-06-07 01:14:58 +08:00
Apple\Apple 6439ff4b95 🐛 fix(ui): ensure consistent dark mode background color for footer
Fix dark mode background color rendering issue in the footer component
where the custom dark background color (#1C1F23) was not being applied
consistently across different devices due to missing !important declaration.

Changes:
- Add !important to dark mode background color class in footer
- Change `dark:bg-[#1C1F23]` to `dark:!bg-[#1C1F23]`
- Ensure footer dark mode styling is not overridden by other CSS rules

This resolves visual inconsistencies where the footer would not display
the intended dark background color in dark theme mode on certain devices
or screen configurations.
2025-06-07 01:11:04 +08:00
Apple\Apple 0546725ac3 🐛 fix(ui): ensure consistent background colors for header action buttons
Fix background color rendering issues for notification bell, theme toggle,
and language switcher buttons in the header bar. These buttons were missing
!important declarations in their CSS classes, causing inconsistent styling
across different devices where other styles could override the intended
background colors.

Changes:
- Add !important to background color classes for notification button
- Add !important to background color classes for theme toggle button
- Add !important to background color classes for language switcher button
- Ensure all header action buttons now have consistent styling matching
  the user avatar dropdown button

This resolves visual inconsistencies where these buttons would appear
without proper background colors on certain devices or screen configurations.
2025-06-07 01:07:37 +08:00
Apple\Apple 6731a2c202 🎨 refactor: Refactor dashboard statistics cards and charts layout
- Consolidate 8 individual stat cards into 4 grouped cards:
  * Account Data (Current Balance, Historical Consumption)
  * Usage Statistics (Request Count, Statistics Count)
  * Resource Consumption (Statistics Quota, Statistics Tokens)
  * Performance Metrics (Average RPM, Average TPM)

- Add gradient header backgrounds with white text for card titles:
  * Blue gradient for Account Data
  * Green gradient for Usage Statistics
  * Yellow gradient for Resource Consumption
  * Pink gradient for Performance Metrics

- Implement mini trend charts using real API data:
  * Replace mock data with actual time-series data from API
  * Hide x and y axes to show pure trend lines
  * Display trends only for metrics with available historical data
  * Remove trend charts for Current Balance, Historical Consumption, and Request Count

- Merge model analysis charts into single card:
  * Combine "Model Consumption Distribution" and "Model Call Count Ratio"
  * Use responsive grid layout (vertical on mobile, horizontal on desktop)
  * Update card title to "Model Data Analysis"

- Optimize chart configurations:
  * Hide axes, legends, and tooltips for mini trend charts
  * Maintain color consistency between metrics and trend lines
  * Improve performance by processing all trend data in single API call
2025-06-07 00:53:29 +08:00
Apple\Apple 1ed8a1e0bd 🎨style: remove header borderbottom style 2025-06-06 21:17:08 +08:00
Apple\Apple 6282eccf16 🎨 refactor: migrate sidebar inline styles to CSS classes and improve code organization
This commit improves the codebase structure by:
- Moving all inline styles from SiderBar.js to CSS classes in index.css
- Organizing CSS with clear section comments for better maintainability
- Removing unused imports and components
- Improving sidebar design with cleaner styling and consistent color management
- Restructuring CSS to group related styles together
- Adjusting sidebar width from 200px to 180px
- Replacing Text components with semantic divs for group labels
- Creating a color management function for sidebar icons
2025-06-06 20:55:52 +08:00
Apple\Apple f31144545e 🎨 feat(ui): enhance model display with category tabs and loading states
Improve the model list section in PersonalSetting component with the following enhancements:
- Add category-based tabs for filtering models by provider (OpenAI, Anthropic, etc.)
- Implement skeleton loading states using Semi UI components
- Add empty state illustrations when no models are available
- Use Semi UI design tokens for consistent styling
- Optimize display for both expanded and collapsed model lists
- Simplify some button text labels for better UI consistency
2025-06-06 04:20:57 +08:00
Calcium-Ion d8d0a6fb64 Merge pull request #1168 from RedwindA/fix/2.5-pro-128
Fix: 更新2.5 pro的思考预算范围
2025-06-06 01:59:58 +08:00
RedwindA eb23dffa04 修复2.5-pro的预算范围 2025-06-06 01:58:02 +08:00
Calcium-Ion 84a9a54af6 Merge pull request #1166 from RedwindA/fix/gemini-functionResponse
Fix: Correctly relay FunctionResponse content for Gemini API
2025-06-06 01:46:23 +08:00
RedwindA 8add0b1592 解决合并冲突 2025-06-06 01:29:06 +08:00
Calcium-Ion 7601f7f969 Merge pull request #1167 from RedwindA/feat/2.5-pro-thinkingBudget
Feat: 2.5-pro thinkingBudget
2025-06-06 01:25:13 +08:00
RedwindA d84da74a0a 硬编码旧模型,让新2.5pro均支持设置预算 2025-06-06 01:22:50 +08:00
RedwindA 2c3464c2c6 清理注释 2025-06-06 01:09:51 +08:00
RedwindA f24e2b0d40 Fix: Correctly relay FunctionResponse content for Gemini API 2025-06-06 00:56:38 +08:00
同語 508188caa6 Merge pull request #1163 from RedwindA/fix/multi-select-copy-label
fix: 修复复制多选令牌按钮的文本
2025-06-05 22:20:17 +08:00
RedwindA 24abf51b8d 修复复制多选令牌按钮的文本 2025-06-05 21:29:14 +08:00
RedwindA 098c1e8829 refactor: rename RedisHDelObj to RedisDelKey and update references 2025-06-05 21:17:57 +08:00
RedwindA 15607996e6 fix: change RedisHDelObj to use Del instead of HDel 2025-06-05 20:42:56 +08:00
Apple\Apple b72e641d22 Merge remote-tracking branch 'origin/main' into alpha 2025-06-05 18:59:41 +08:00
neotf a813b24184 feat: support claude cache and thinking for upstream [OpenRouter] (#983)
* feat: support claude cache for upstream [OpenRouter]

* feat: support claude thinking for upstream [OpenRouter]

* feat: reasoning is common params for OpenRouter
2025-06-05 17:35:48 +08:00
Apple\Apple 74c00e2296 Merge branch 'main' into alpha 2025-06-05 11:27:59 +08:00
Apple\Apple a1c1a336ac 📕docs: Add DeepWiki Badge in README.en.md 2025-06-05 11:27:00 +08:00
Apple\Apple 8212d391d3 Merge remote-tracking branch 'origin/main' into alpha 2025-06-05 11:19:14 +08:00
Apple\Apple 465cd77b4c 🎨 refactor: move Turnstile component to global scope in auth forms
Move the Turnstile verification component from the renderOAuthOptions method to the main render function in both LoginForm and RegisterForm components. This ensures the Turnstile verification is globally visible and accessible regardless of which authentication method the user chooses (email login/register or third-party OAuth options).

The change improves UI consistency and ensures the verification mechanism works properly across all authentication flows.
2025-06-05 11:19:00 +08:00
Calcium-Ion 28e40c90eb Merge pull request #1156 from RedwindA/deepwiki-badge
Add DeepWiki Badge in README
2025-06-05 02:19:54 +08:00
RedwindA 5bb8f2dfed Add DeepWiki Badge in README 2025-06-05 02:09:21 +08:00
Apple\Apple 9b3c48c7ce 🎨 style: update ModelPricing card with IconLayers and larger icon size
Replace the main card icon in ModelPricing component with IconLayers to better represent the model catalog functionality. Increase icon size from 'large' to 'extra-large' for improved visibility and visual hierarchy.

This change enhances the user interface by using more appropriate iconography and giving the primary navigation elements greater emphasis.
2025-06-04 21:39:04 +08:00
Apple\Apple 085bb658e9 feat: enhance model display with category counts and consistent styling
This commit improves the model display interface with several enhancements:

1. Add model count badges to each category tab and dropdown menu item
2. Highlight active category with red badge and use grey for inactive ones
3. Optimize performance by caching category counts with useMemo
4. Standardize model tag rendering across components:
   - Replace direct Tag component with centralized renderModelTag function
   - Update renderModelTag to use stringToColor for consistent coloring
   - Remove redundant color calculations in LogsTable

These changes improve the UI by providing users with visual cues about model distribution across categories while ensuring consistent styling throughout the application.
2025-06-04 21:33:24 +08:00
Calcium-Ion 7111ec2e10 Merge pull request #1148 from RedwindA/x-goog-api-key
feat: support X-goog-api-key;remove [Done] in stream mode
2025-06-04 20:47:31 +08:00
Apple\Apple 5a7314eea0 🎨 refactor: standardize model tag rendering across components
Refactor model tag rendering to ensure consistency throughout the application:

- Replace direct Tag component in ModelPricing with centralized renderModelTag function
- Update renderModelTag in render.js to use stringToColor for consistent color generation
- Remove redundant stringToColor calls in LogsTable.js renderModelName function

This change improves UI consistency by ensuring all model tags have the same styling, iconography, and color generation logic. Model tags now automatically display appropriate vendor icons based on the model name pattern.
2025-06-04 20:13:02 +08:00
RedwindA 0827ebd22e fix: 移除流式响应结尾的[Done],以适应Gemini API的行为 2025-06-04 15:41:25 +08:00
RedwindA 5e88e76001 feat: 支持从 x-goog-api-key header 中获取授权密钥 2025-06-04 15:41:25 +08:00
Apple\Apple 7c0c6b848c 🐛 fix(channels): resolve type mismatch when copying tested channels
Fix JSON unmarshal error that occurred when copying channels after testing. The JavaScript timestamp (floating point number) in the test_time field was causing type conversion errors in Go backend which expected an int64.

Solution:
- Create deep copy of channel record instead of modifying original
- Remove test_time and response_time fields before sending to backend
- Allow backend to use default values for these fields

Error fixed: "json: cannot unmarshal number into Go struct field Channel.test_time of type int64"
2025-06-04 15:29:10 +08:00
Apple\Apple 04007b5955 🐛 fix(sidebar): fix the sidebar user permission to display navigation 2025-06-04 14:41:18 +08:00
Apple\Apple f2bca12d9a feat(ui): Enhance model tag rendering in logs table with icons
Improve the logs table by implementing brand-specific model icons and better
redirection visualization:

- Replace standard tags with `renderModelTag` to display appropriate brand
  icons for each model (OpenAI, Claude, Gemini, etc.)
- Fix background colors by explicitly passing color parameters
- Restore descriptive text for model redirection in popover
- Replace refresh icon with forward icon for better representation of model
  redirection
- Clean up unused imports

This change provides a more intuitive visual representation of models and
their relationships, making the logs table easier to understand at a glance.
2025-06-04 14:31:54 +08:00
Apple\Apple 56491f4df6 💅 feat(ui): Remove fixed width constraints from model pricing table
This commit removes all fixed width constraints from the model pricing table columns, allowing them to naturally expand and adjust based on content. This improves the table's responsiveness and ensures better space utilization across different screen sizes.
2025-06-04 13:16:45 +08:00
Apple\Apple 3feed08651 feat(ui): add the attribute mode="password" to the password input on the Setup page to enable password visibility toggle 2025-06-04 08:50:38 +08:00
Apple\Apple d5a3b05ba6 🔖chore(text): Optimize the text prompts on the login and registration pages 2025-06-04 08:46:21 +08:00
Apple\Apple 80d43b0082 Refactor: Conditionally render OAuth options in login and registration forms
This commit refactors the login and registration forms to enhance user experience by conditionally displaying OAuth-related UI elements.

- In `LoginForm.js` and `RegisterForm.js`:
    - The "Other login/registration options" button and the "or" divider are now only displayed if at least one OAuth provider is enabled in the system settings.
    - This change ensures a cleaner interface when no OAuth options are configured, preventing user confusion.
- In `RegisterForm.js`:
    - Changed `div` class from `relative` to `min-h-screen relative` to ensure the registration form an take up the entire screen height.
2025-06-04 08:34:52 +08:00
Apple\Apple 618ed976f1 🎨 refactor(ui): modernize setup page
- Refactored system initialization page using TailwindCSS 3 and SemiUI components
- Changed layout from step navigation to single page display for all configurations
- Modified top area from vertical to more compact horizontal layout
- Updated gradient color scheme from blue/purple to orange/pink
- Fixed form field name duplication issues and optimized Form implementation
- Changed usage mode selection from three-column grid to vertical layout
- Replaced usage mode card icons from settings to more appropriate Layers icon
- Added specific prompts for different database types (SQLite/MySQL/PostgreSQL)
- Removed configuration summary section while keeping the initialization button
- Fixed useSetupCheck issue by using SetupCheck as a direct component for proper redirection
2025-06-04 08:15:48 +08:00
RedwindA 9390cbeea8 支持Gemini inline_data的蛇形命名法 2025-06-04 02:18:54 +08:00
Apple\Apple 3b1d1de912 Merge branch 'ui/refactor' into alpha 2025-06-04 01:43:08 +08:00
Apple\Apple f44e7258b9 Merge remote-tracking branch 'origin/alpha' into ui/refactor 2025-06-04 01:35:51 +08:00
Apple\Apple 0ea194e7d7 Merge branch 'main' into ui/refactor 2025-06-04 01:31:41 +08:00
CaIon 21613f8f73 Merge remote-tracking branch 'origin/main' into alpha 2025-06-04 01:25:09 +08:00
CaIon eaaee594ef chore: add Docker Buildx setup to workflow
- Integrated Docker Buildx setup step in docker-image-alpha.yml to enhance multi-platform build capabilities.
2025-06-04 01:16:42 +08:00
CaIon c8ab726655 chore: update docker image workflows
- Added support for multiple platforms (linux/amd64, linux/arm64) in docker-image-alpha.yml.
- Removed outdated docker-image-amd64.yml and docker-image-arm64.yml workflows.
- Deleted linux-release.yml, macos-release.yml, and windows-release.yml as part of workflow cleanup.
2025-06-04 01:12:49 +08:00
Apple\Apple 6a8971dd98 💄 style: Remove min-h-screen style 2025-06-04 01:03:32 +08:00
Apple\Apple 912c11ab2c ♻️ refactor: refactor the logic of fetchTokenKeys and SetupCheck 2025-06-04 01:00:48 +08:00
Apple\Apple 60e6fc67aa ♻️ refactor(components): refactor the components folder structure and related imports 2025-06-04 00:42:06 +08:00
Apple\Apple 0cd5e5cf48 ♻️ refactor(auth): move the auth component to the auth.js file in the helpers folder 2025-06-04 00:21:03 +08:00
Apple\Apple 1c0c24e0ac ♻️ refactor(components): refactor the components/utils.js to helpers/api.js and related imports 2025-06-04 00:14:15 +08:00
Apple\Apple 3daa5f70bd ♻️ refactor(utils.js): refactor the components/utils.js to helpers/api.js and related imports 2025-06-04 00:11:52 +08:00
Apple\Apple d98fb70328 ♻️ refactor(helpers): refactor the components/utils.js to helpers/api.js and related imports 2025-06-04 00:11:06 +08:00
Apple\Apple 4794a7cadc 🐛 fix(api): resolve missing imports in buildApiPayload function
Fix ReferenceError caused by undefined `isValidMessage` and `MESSAGE_ROLES`
in the buildApiPayload function within api.js.

Changes:
- Add missing `isValidMessage` import from utils.js
- Add missing `MESSAGE_ROLES` import from playground constants
- Consolidate duplicate `formatMessageForAPI` import
- Clean up import statements organization

Resolves: ReferenceError: isValidMessage is not defined at buildApiPayload (api.js:39:13)
2025-06-04 00:01:41 +08:00
Apple\Apple 61d1add156 ♻️ refactor(helpers): refactor the helpers folder and related imports 2025-06-03 23:56:39 +08:00
Apple\Apple 1a8888211f ♻️ refactor(helpers): standardize file naming conventions and improve code organization
- Rename files to follow camelCase naming convention:
  • auth-header.js → authUtils.js
  • other.js → logUtils.js
  • rehypeSplitWordsIntoSpans.js → textAnimationUtils.js

- Update import paths in affected components:
  • Update exports in helpers/index.js
  • Fix import in LogsTable.js for logUtils
  • Fix import in MarkdownRenderer.js for textAnimationUtils

- Remove old files after successful migration

- Improve file naming clarity:
  • authUtils.js better describes authentication utilities
  • logUtils.js clearly indicates log processing functions
  • textAnimationUtils.js concisely describes text animation functionality

This refactoring enhances code maintainability and follows consistent naming patterns throughout the helpers directory.
2025-06-03 16:13:50 +08:00
creamlike1024 0e642a58e0 update docker-image-alpha.yml 2025-06-03 11:10:09 +08:00
creamlike1024 a5de6f91d6 feat: alpha docker image 2025-06-03 11:07:26 +08:00
IcedTangerine b90aa227ef Merge pull request #1107 from QuantumNous/gemini-relay
Gemini 格式
2025-06-03 10:50:50 +08:00
Apple\Apple 42598febd9 🔧 refactor(preview): remove default placeholder message from empty conversation preview
- Remove automatic insertion of "你好" placeholder message in preview payload
- Keep messages array empty when no user messages exist in conversation
- Only process image handling logic when user messages are present
- Ensure preview request body accurately reflects current conversation state

Previously, the preview panel would automatically inject a default "你好"
user message when the conversation was empty, which could be misleading.
This change ensures the preview payload shows exactly what would be sent
based on the current conversation state, improving accuracy and user
understanding of the actual API request structure.
2025-06-03 01:25:21 +08:00
Apple\Apple 3f142ba4e9 feat(ui): add clear conversation button to input area with symmetric layout
- Add clear context button positioned on the left side of input area
- Create symmetric layout with clear button (left) and send button (right)
- Standardize both buttons to 32x32px size for consistent appearance
- Apply distinct styling: gray background for clear (red on hover), purple for send
- Add smooth transition animations for better user experience
- Align buttons vertically centered for improved visual balance

The clear conversation button provides quick access to context clearing
functionality directly from the input area, matching the design patterns
shown in Semi Design documentation and improving overall user workflow.
2025-06-03 01:18:08 +08:00
Apple\Apple b924cab0e6 🐛 fix(message): ensure retry uses current selected model instead of stale one
- Add onMessageReset reference comparison to OptimizedMessageActions memo
- Force component re-render when model selection changes
- Prevent stale closure issue in retry functionality
- Ensure first retry attempt uses newly selected model

Previously, when changing the model selection, the retry button would
still use the previous model due to React memo optimization preventing
re-renders. By comparing the onMessageReset callback reference, the
component now properly updates when the model changes, ensuring the
retry functionality immediately uses the currently selected model.
2025-06-03 00:45:28 +08:00
Apple\Apple 6df7017d2d 🐛 fix(message): prevent history loss when editing messages with duplicate IDs
- Store editing message object reference using useRef to avoid ID conflicts
- Use object reference comparison first before falling back to ID matching
- Apply fix to both save and cancel operations in message editing
- Clear reference after edit completion to prevent stale object issues

Previously, when editing imported messages that contained duplicate IDs,
the findIndex operation would match the first occurrence rather than the
intended message, causing conversation history truncation when saving
edits. This change stores and uses object references for accurate message
identification during the editing process.
2025-06-03 00:37:10 +08:00
Apple\Apple a18e90fe88 🐛 fix(message): prevent history loss when retrying imported messages with duplicate IDs
- Use object reference comparison first before falling back to ID matching
- Prevent incorrect message index lookup when duplicate IDs exist
- Apply fix to both handleMessageReset and handleMessageDelete functions
- Maintain backward compatibility with ID-based message identification

Previously, when importing messages that contained duplicate IDs, the
findIndex operation would match the first occurrence rather than the
intended message, causing history truncation on retry. This change uses
object reference comparison as the primary method, ensuring accurate
message identification and preserving conversation history.
2025-06-03 00:32:42 +08:00
Apple\Apple 59d1f76db5 🐛 fix(message): enable retry functionality for system role messages
- Extend handleMessageReset condition to include 'system' role messages
- Allow system messages to trigger regeneration like assistant messages
- Fix disabled retry button issue when message role is switched to system
- Maintain consistent user experience across different message roles

Previously, when an assistant message was switched to system role,
the retry button became non-functional. This change ensures that
system messages can be regenerated by finding the previous user
message and resending it, maintaining feature parity with assistant
messages.
2025-06-03 00:22:11 +08:00
Apple\Apple 2db2bbc6b2 feat(ui): enhance fade-in animation effect for streaming text rendering
- Add multi-dimensional animation with translateY, scale, and blur transforms
- Introduce 60% keyframe for smoother animation progression
- Extend animation duration from 0.4s to 0.6s for better visual impact
- Apply cubic-bezier(0.22, 1, 0.36, 1) easing for more natural motion
- Add will-change property to optimize rendering performance
- Improve perceived responsiveness during AI response streaming

The enhanced animation now provides a more polished and engaging user
experience when AI responses are being streamed, with text appearing
smoothly from bottom-up with subtle scaling and blur effects.
2025-06-03 00:12:50 +08:00
Apple\Apple 704098603d 🔧 fix(playground): resolve message state issues after page refresh and config reset
**Problem 1: Chat interface not refreshing when resetting imported messages**
- The "reset messages simultaneously" option during config import failed to
  update the chat interface properly
- Normal conversation resets worked correctly

**Problem 2: Messages stuck in loading state after page refresh**
- When AI was generating a response and user refreshed the page, the message
  remained in loading state indefinitely
- Stop button had no effect on these orphaned loading messages

**Changes Made:**

1. **Fixed config reset message refresh** (`usePlaygroundState.js`):
   ```javascript
   // Clear messages first, then set defaults to force component re-render
   setMessage([]);
   setTimeout(() => {
     setMessage(DEFAULT_MESSAGES);
   }, 0);
   ```

2. **Enhanced stop generator functionality** (`useApiRequest.js`):
   ```javascript
   // Handle orphaned loading messages even without active SSE connection
   const onStopGenerator = useCallback(() => {
     // Close active SSE if exists
     if (sseSourceRef.current) {
       sseSourceRef.current.close();
       sseSourceRef.current = null;
     }

     // Always attempt to complete any loading/incomplete messages
     // ... processing logic
   }, [setMessage, applyAutoCollapseLogic, saveMessages]);
   ```

3. **Added automatic message state recovery** (`usePlaygroundState.js`):
   ```javascript
   // Auto-fix loading/incomplete messages on page load
   useEffect(() => {
     const lastMsg = message[message.length - 1];
     if (lastMsg.status === MESSAGE_STATUS.LOADING ||
         lastMsg.status === MESSAGE_STATUS.INCOMPLETE) {
       // Process incomplete content and mark as complete
       // Save corrected message state
     }
   }, []);
   ```

**Root Cause:**
- Config reset: Direct state assignment didn't trigger component refresh
- Loading state: No recovery mechanism for interrupted SSE connections after refresh

**Impact:**
-  Config reset now properly refreshes chat interface
-  Stop button works on orphaned loading messages
-  Page refresh automatically recovers incomplete messages
-  No more permanently stuck loading states
2025-06-02 23:56:58 +08:00
Apple\Apple 269a1e1204 Merge remote-tracking branch 'origin/main' into ui/refactor 2025-06-02 23:25:29 +08:00
Apple\Apple 36a9781d03 🐛 fix(playground): ensure chat interface refreshes when resetting imported messages
When using the "reset messages simultaneously" option during config import,
the conversation messages in the chat interface were not being properly reset,
while normal conversation resets worked correctly.

**Changes:**
- Modified `handleConfigReset` in `usePlaygroundState.js` to clear messages
  before setting default examples
- Added asynchronous message update to force Chat component re-render
- Ensures immediate UI refresh when resetting imported conversation data

**Root Cause:**
The direct assignment to DEFAULT_MESSAGES didn't trigger a complete
component refresh, causing the chat interface to display stale data.

**Solution:**
```javascript
// Before
setMessage(DEFAULT_MESSAGES);

// After
setMessage([]);
setTimeout(() => {
  setMessage(DEFAULT_MESSAGES);
}, 0);
```

This two-step approach forces the Chat component to unmount and remount
with fresh data, resolving the display inconsistency.
2025-06-02 23:24:50 +08:00
Calcium-Ion c668362f9d Merge pull request #1139 from RedwindA/gemini-fix
feat: 增加对Gemini MimeType类型的验证
2025-06-02 22:33:01 +08:00
Calcium-Ion 326bd4d977 Merge pull request #1140 from RedwindA/gemini-tool-fix
fix: 完善Gemini渠道对tools中additionalProperties的清理
2025-06-02 22:32:43 +08:00
Calcium-Ion 16f49cfe6f Merge pull request #1141 from xqx121/patch-1
Fix: The edit interface is not billed (usage-based pricing).
2025-06-02 22:32:18 +08:00
xqx121 f234b3f16c Fix: The edit interface is not billed (usage-based pricing). 2025-06-02 22:11:11 +08:00
Apple\Apple e9b74651c2 🐛fix: Fix message saving missing the last conversation
- Modify saveMessagesImmediately to accept messages parameter
- Pass updated message list to all save calls instead of relying on closure
- Ensure complete message history is saved including the last message
- Fix timing issue where old message state was being saved

This fixes the issue where the last conversation was not being persisted to localStorage.
2025-06-02 21:39:51 +08:00
Apple\Apple 27ad46cdd5 🐛fix: Fix React hooks order violation causing page crash on API errors
- Move useEffect hooks before conditional returns in MessageContent and ThinkingContent
- Ensure hooks are called in the same order every render
- Fix "Rendered fewer hooks than expected" error when API returns non-200 status
- Follow React hooks rules: only call hooks at the top level

This prevents the entire page from crashing when API requests fail.
2025-06-02 21:26:56 +08:00
Apple\Apple f6fc8e9f7e perf: Optimize message persistence and reduce localStorage operations
- Refactor message saving strategy from automatic to manual saving
  - Save messages only on key operations: send, complete, edit, delete, role toggle, clear
  - Prevent frequent localStorage writes during streaming responses

- Remove excessive console logging
  - Remove all console.log statements from save/load operations
  - Clean up debug logs to reduce console noise

- Optimize initial state loading with lazy initialization
  - Replace useRef with useState lazy initialization for config and messages
  - Ensure loadConfig and loadMessages are called only once on mount
  - Prevent redundant localStorage reads during re-renders

- Update hooks to support new save strategy
  - Pass saveMessages callback through component hierarchy
  - Add saveMessagesImmediately to relevant hooks (useApiRequest, useMessageActions, useMessageEdit)
  - Trigger saves at appropriate lifecycle points

This significantly improves performance by reducing localStorage I/O operations
from continuous writes during streaming to discrete saves at meaningful points.
2025-06-02 21:21:46 +08:00
Apple\Apple 3ea13caab9 💄 feat(playground): Enhance the fade-in animation for the chat 2025-06-02 20:15:00 +08:00
Apple\Apple b7b5b6cf5b 💄 feat(playground): chat streaming animation 2025-06-02 19:58:10 +08:00
RedwindA 088abcdedc feat: 增加对GeminiMIME类型的验证 2025-06-02 19:00:55 +08:00
Apple\Apple 7ad0082cd5 💄 style(SettingsPanel): Select componet style adjustments 2025-06-02 06:28:24 +08:00
Apple\Apple 890c953f31 🐛 fix(playground): ensure proper streaming updates & safeguard message handling
Summary
This commit addresses two critical issues affecting the real-time chat experience in the Playground:

1. Optimized re-rendering of reasoning content
   • Added `reasoningContent` to the comparison function of `OptimizedMessageContent` (`web/src/components/playground/OptimizedComponents.js`).
   • Ensures the component re-renders while reasoning text streams, resolving the bug where only the first characters (“好,”) were shown until the stream finished.

2. Defensive checks for SSE message updates
   • Added early-return guards in `streamMessageUpdate` (`web/src/hooks/useApiRequest.js`).
   • Skips updates when `lastMessage` is undefined or the last message isn’t from the assistant, preventing `TypeError: Cannot read properties of undefined (reading 'status')` during rapid SSE responses.

Impact
• Real-time reasoning content now appears progressively, enhancing user feedback.
• Eliminates runtime crashes caused by undefined message references, improving overall stability.
2025-06-02 06:21:05 +08:00
Apple\Apple 0d9c6eb3a8 💄 style(thinkingcontent): delete thinkcontent minHeight style 2025-06-02 04:48:00 +08:00
Apple\Apple ea632c6ff8 ♻️ refactor(playground): eliminate code duplication in thinking content rendering
- Remove duplicate thinking content rendering logic from MessageContent component
- Import and utilize ThinkingContent component for consistent thinking display
- Clean up unused icon imports (ChevronRight, ChevronUp, Brain)
- Consolidate "思考中..." header text logic into single component
- Reduce code duplication by ~70 lines while maintaining all functionality
- Improve component separation of concerns and maintainability

The MessageContent component now delegates thinking content rendering to the
dedicated ThinkingContent component, eliminating the previously duplicated
UI logic and state management for thinking processes.
2025-06-02 04:45:38 +08:00
Apple\Apple e30eca5cca 💄 feat(playground): unify SettingsPanel header design with DebugPanel
- Add consistent title section with gradient icon and heading
- Include close button in mobile view for better UX consistency
- Standardize mobile and desktop ConfigManager styling
- Adjust layout structure and padding for visual alignment
- Use Settings icon with purple-to-pink gradient to match design system

This change ensures both SettingsPanel and DebugPanel have identical
header layouts and interaction patterns across all screen sizes.
2025-06-02 04:35:04 +08:00
Apple\Apple a1918aa65e ♻️ refactor(StyleContext): modernize context architecture and eliminate route transition flicker
## Breaking Changes
- Remove backward compatibility layer for old action types
- StyleContext is no longer exported, use useStyle hook instead

## Major Improvements
- **Architecture**: Replace useState with useReducer for complex state management
- **Performance**: Add debounced resize handling and batch updates via BATCH_UPDATE action
- **DX**: Export useStyle hook and styleActions for type-safe usage
- **Memory**: Use useMemo to cache context value and prevent unnecessary re-renders

## Bug Fixes
- **UI**: Eliminate padding flicker when navigating to /console/chat* and /console/playground routes
- **Logic**: Remove redundant localStorage operations and state synchronization

## Implementation Details
- Define ACTION_TYPES and ROUTE_PATTERNS constants for better maintainability
- Add comprehensive JSDoc documentation for all functions
- Extract custom hooks: useWindowResize, useRouteChange, useMobileSiderAutoHide
- Calculate shouldInnerPadding directly in PageLayout based on pathname to prevent async updates
- Integrate localStorage saving logic into SET_SIDER_COLLAPSED reducer case
- Remove SET_INNER_PADDING action as it's no longer needed

## Updated Components
- PageLayout.js: Direct padding calculation based on route
- HeaderBar.js: Use new useStyle hook and styleActions
- SiderBar.js: Remove redundant localStorage calls
- LogsTable.js: Remove unused StyleContext import
- Playground/index.js: Migrate to new API

## Performance Impact
- Reduced component re-renders through optimized context structure
- Eliminated unnecessary effect dependencies and state updates
- Improved route transition smoothness with synchronous padding calculation
2025-06-02 04:16:48 +08:00
Apple\Apple d15e53ed33 💄 style(ui): some style adjustments 2025-06-02 00:15:53 +08:00
Apple\Apple 05396dae39 feat: remove image upload limit and improve scrollbar styling
Remove the 5-image upload restriction in playground and enhance UI consistency

Changes:
- Remove 5-image limit constraint from ImageUrlInput component
- Update hint text to remove "maximum 5 images" references
- Add custom scrollbar styling for image list to match site-wide design
- Apply consistent thin scrollbar (6px width) with Semi Design color variables
- Maintain hover effects and rounded corners for better UX

Breaking Changes: None

Files modified:
- web/src/components/playground/ImageUrlInput.js
- web/src/index.css

This change allows users to upload unlimited images in playground mode while
maintaining visual consistency across the application's scrollable elements.
2025-06-02 00:02:33 +08:00
Apple\Apple 4488693808 🎨 ui(playground): reorganize config manager layout to place reset button with timestamp
- Move reset settings button to the same row as the last modified timestamp
- Use flexbox layout with justify-between to align timestamp left and reset button right
- Keep export and import buttons on the separate row below
- Improve space utilization and visual hierarchy in the settings panel

This change enhances the user interface by creating a more compact and intuitive layout
for the configuration management controls in the playground component.
2025-06-01 18:25:43 +08:00
Apple\Apple d01054e2e4 🐛 fix: resolve duplicate toast notifications when toggling message roles
- Remove duplicate onRoleToggle prop passing to ChatArea component in Playground/index.js
- Move Toast notification outside setMessage callback in useMessageActions hook
- Prevent multiple event bindings that caused repeated role switch notifications
- Add early return validation for role toggle eligibility

This fixes the issue where users would see multiple success toasts when switching
between Assistant and System roles in the chat interface.

Files changed:
- web/src/pages/Playground/index.js
- web/src/hooks/useMessageActions.js
2025-06-01 17:44:36 +08:00
Apple\Apple 4080f2ed7b ♻️ refactor: Refactor the structure of the common component 2025-06-01 17:39:41 +08:00
Apple\Apple 309f3b9fe3 💄 style(ui): hide scrollbars across chat interface and request editor
Hide y-axis scrollbars to provide a cleaner UI experience while maintaining
scroll functionality through mouse wheel and keyboard navigation.

Changes include:
- Hide scrollbars in CustomRequestEditor TextArea component
- Hide scrollbars in chat container and all related chat components
- Hide scrollbars in thinking content areas
- Add cross-browser compatibility for scrollbar hiding
- Maintain scroll functionality while improving visual aesthetics

Components affected:
- CustomRequestEditor.js: Added custom-request-textarea class
- index.css: Updated scrollbar styles for chat, thinking, and editor areas

The interface now provides a more streamlined appearance consistent with
modern UI design patterns while preserving all interactive capabilities.
2025-06-01 17:31:13 +08:00
Apple\Apple ec4dbcd7d8 🐛 fix: Role switching not updating UI immediately in playground
Fix role toggle functionality where switching message roles (assistant/system)
did not update the UI immediately and required page refresh to see changes.

Changes:
- Add message.role comparison in OptimizedMessageContent memo function
- Add message.role comparison in OptimizedMessageActions memo function

The issue was caused by React.memo optimization that wasn't tracking role
changes, preventing re-renders when only the message role property changed.
Now role switches are reflected immediately in both message content display
and action button states.

Fixes: Role switching requires page refresh to display correctly
2025-06-01 17:19:45 +08:00
Apple\Apple 77157da8fc feat: Add custom request body editor with persistent message storage
- Add CustomRequestEditor component with JSON validation and real-time formatting
- Implement bidirectional sync between chat messages and custom request body
- Add persistent local storage for chat messages (separate from config)
- Remove redundant System Prompt field in custom mode
- Refactor configuration storage to separate messages and settings

New Features:
• Custom request body mode with JSON editor and syntax highlighting
• Real-time bidirectional synchronization between chat UI and custom request body
• Persistent message storage that survives page refresh
• Enhanced configuration export/import including message data
• Improved parameter organization with collapsible sections

Technical Changes:
• Add loadMessages/saveMessages functions in configStorage
• Update usePlaygroundState hook to handle message persistence
• Refactor SettingsPanel to remove System Prompt in custom mode
• Add STORAGE_KEYS constants for better storage key management
• Implement debounced auto-save for both config and messages
• Add hash-based change detection to prevent unnecessary updates

UI/UX Improvements:
• Disabled state styling for parameters in custom mode
• Warning banners and visual feedback for mode switching
• Mobile-responsive design for custom request editor
• Consistent styling with existing design system
2025-06-01 17:07:36 +08:00
Apple\Apple aa044d829b 🌏i18n: Siderbar Playground 2025-06-01 13:13:32 +08:00
Apple\Apple 03675397f5 Merge branch 'main' into ui/refactor 2025-06-01 02:40:44 +08:00
RedwindA 7aad5b0479 refactor: enhance cleanFunctionParameters for improved handling of JSON schema, including support for $defs and conditional keywords 2025-06-01 02:08:13 +08:00
CaIon 46f95fddcd feat: add ToMap method and enhance OpenAI request handling 2025-06-01 01:10:10 +08:00
Apple\Apple dec73f75ec Merge remote-tracking branch 'origin/main' into ui/refactor 2025-05-31 22:18:51 +08:00
Calcium-Ion a3d65d23f4 Merge pull request #1134 from QuantumNous/fix_ping_keepalive
fix: 流式请求ping
2025-05-31 22:16:16 +08:00
creamlike1024 2da94e62d5 fix: 流式请求ping 2025-05-31 22:13:17 +08:00
Apple\Apple f1d69fb42a Merge remote-tracking branch 'origin/main' into ui/refactor 2025-05-31 21:34:57 +08:00
Calcium-Ion 4febdff3ac Merge pull request #1122 from akkuman/feat/stream-tts
feat: streaming response for tts
2025-05-31 18:44:48 +08:00
Calcium-Ion 641d30afc2 Merge pull request #1123 from RedwindA/patch-3
Add `ERROR_LOG_ENABLED` description in README
2025-05-31 18:44:24 +08:00
Calcium-Ion 730f76a45a Merge pull request #1130 from xqx121/main
Fix: Gemini2.5pro ThinkingConfig
2025-05-31 18:43:57 +08:00
xqx121 efd4a46a97 Fix: Gemini2.5pro ThinkingConfig 2025-05-31 17:50:00 +08:00
Apple\Apple 1fbd107be5 🎨 feat(ui): Update thinking section design to match EditChannel header card style
- Replace gradient background with purple theme matching EditChannel cards
- Add decorative circle elements for visual consistency
- Update all icons and text to white color for better contrast
- Apply inline styles to ensure proper color rendering
- Maintain hover effects with adjusted opacity values

This change creates visual consistency across the application by adopting
the same modern gradient design pattern used in EditChannel header cards.
2025-05-31 03:30:28 +08:00
Apple\Apple f8b6526090 🎨 style(MessageContent): unify thinking section color scheme with PersonalSetting
- Replace pink-purple gradient with indigo-purple gradient to match PersonalSetting.js color scheme
- Update icon container gradient from purple-indigo to indigo-purple for consistency
- Enhance shadow effects from shadow-sm to shadow-lg to align with card design standards
- Improve hover effects with refined opacity and smoother transitions (duration-300)
- Increase content background opacity from 70% to 80% for better readability
- Update scrollbar color to purple theme (rgba(139, 92, 246, 0.3))
- Standardize border radius to rounded-xl for unified styling
- Apply consistent styling to "thinking in progress" state with matching gradient background

This change ensures visual consistency across the application by adopting the same
purple-blue color palette used in PersonalSetting component, creating a more
cohesive user experience.
2025-05-31 03:25:29 +08:00
Apple\Apple 1a9be006c7 🐛 fix(Playground): Fix thinking section scroll issue and clean up unused CSS
- Fix thinking content area scroll functionality in MessageContent.js
  * Replace max-h-50 with explicit maxHeight style (200px)
  * Add thinking-content-scroll CSS class for proper scrollbar styling
  * Ensure scrollbars are visible when content exceeds container height
- Add thinking-content-scroll CSS class in index.css
  * Define webkit scrollbar styles with proper dimensions (6px)
  * Add hover effects and cross-browser compatibility
  * Support both webkit and Firefox scrollbar styling
- Remove unused CSS classes to improve code cleanliness
  * Remove .hide-on-mobile class (no usage found in codebase)
  * Remove .debug-code class (no longer needed)
- Improve user experience for viewing lengthy thinking content
  * Users can now properly scroll through AI reasoning content
  * Maintains content visibility with appropriate height constraints

Resolves issue where thinking section had max height but no scrolling capability.
2025-05-31 03:19:18 +08:00
Apple\Apple ed291026eb perf: optimize CodeViewer performance for large content rendering
- Add intelligent content truncation for payloads over 50K characters
- Implement tiered performance handling based on content size
- Use useMemo and useCallback for memory optimization and caching
- Add progressive loading with async processing for very large content
- Introduce performance warning indicators and user feedback
- Create expand/collapse functionality with smooth animations
- Skip syntax highlighting for extremely large content (>100K)
- Add loading states and debounce handling for better UX
- Display remaining content size indicators (e.g., +15K)
- Implement chunk-based processing to prevent UI blocking

This optimization ensures that even multi-megabyte JSON responses
won't cause page freezing or performance degradation in the debug
panel, while maintaining full functionality for regular-sized content.
2025-05-31 03:06:12 +08:00
Apple\Apple a84f402a2b 🐛 fix: correct JSON syntax highlighting for API responses in debug panel
- Change response content language from 'javascript' to 'json' for proper highlighting
- Improve automatic JSON detection to handle both objects and arrays
- Add intelligent content type detection based on string patterns
- Include development environment debug logging for troubleshooting
- Ensure all API responses display with correct JSON syntax coloring

This fix resolves the issue where API response data was not properly
syntax highlighted, ensuring consistent JSON formatting across all
debug panel tabs (preview, request, and response).
2025-05-31 02:50:36 +08:00
Apple\Apple 744f78b241 feat: enhance debug panel with VS Code dark theme and syntax highlighting
- Create new CodeViewer component with VS Code dark theme styling
- Implement custom JSON syntax highlighting with proper color coding
- Add improved copy functionality with hover effects and user feedback
- Refactor DebugPanel to use the new CodeViewer component
- Apply dark background (#1e1e1e) with syntax colors matching VS Code
- Enhance UX with floating copy button and responsive design
- Support automatic JSON formatting and parsing
- Maintain compatibility with existing Semi Design components

The debug panel now displays preview requests, actual requests, and
responses in a professional code editor style, improving readability
and developer experience in the playground interface.
2025-05-31 02:47:31 +08:00
Apple\Apple 2c37aba585 feat(markdown): replace Semi UI MarkdownRender with react-markdown for enhanced rendering
- Replace Semi UI's MarkdownRender with react-markdown library for better performance and features
- Add comprehensive markdown rendering support including:
  * Math formulas with KaTeX
  * Code syntax highlighting with rehype-highlight
  * Mermaid diagrams support
  * GitHub Flavored Markdown (tables, task lists, etc.)
  * HTML preview for code blocks
  * Media file support (audio/video)
- Create new MarkdownRenderer component with enhanced features:
  * Copy code button with hover effects
  * Code folding for long code blocks
  * Responsive design for mobile devices
- Add white text styling for user messages to improve readability on blue backgrounds
- Update all components using markdown rendering:
  * MessageContent.js - playground chat messages
  * About/index.js - about page content
  * Home/index.js - home page content
  * NoticeModal.js - system notice modal
  * OtherSetting.js - settings page
- Install new dependencies: react-markdown, remark-math, remark-breaks, remark-gfm,
  rehype-katex, rehype-highlight, katex, mermaid, use-debounce, clsx
- Add comprehensive CSS styles in markdown.css for better theming and user experience
- Remove unused imports and optimize component imports

Breaking changes: None - maintains backward compatibility with existing markdown content
2025-05-31 02:26:23 +08:00
Apple\Apple d0651f7a88 ♻️ refactor(playground): Refactor auto-collapse logic to eliminate code duplication
- Extract common `applyAutoCollapseLogic` function for reasoning panel collapse behavior
- Consolidate duplicated auto-collapse logic across multiple functions
- Simplify conditional expressions using logical OR operator
- Replace repetitive property assignments with object spread syntax
- Update dependency arrays to include new shared function
- Ensure consistent behavior across stream/non-stream/error scenarios

This refactoring improves code maintainability by following DRY principles
and centralizing the auto-collapse logic in a single reusable function.
All message handling functions now use consistent logic for determining
when to auto-collapse the reasoning panel.

Benefits:
- Reduced code duplication from ~20 lines to 6 lines per function
- Single source of truth for auto-collapse behavior
- Improved readability and maintainability
- Easier to modify collapse logic in the future

Files changed:
- web/src/hooks/useApiRequest.js: Refactored message handling functions
2025-05-31 01:35:13 +08:00
Apple\Apple 1686522fac 🐛 fix(Playground): Fix reasoning panel auto-collapse behavior to allow user control
- Add `hasAutoCollapsed` flag to track auto-collapse state
- Modify reasoning panel to auto-collapse only once after thinking completion
- Allow users to manually toggle reasoning panel after auto-collapse
- Update message creation, streaming updates, and completion handlers
- Ensure consistent behavior across stream/non-stream requests and error cases

Previously, the reasoning/thinking panel would auto-collapse every time
the AI completed its thinking process, preventing users from reopening
it to review the reasoning content. Now it auto-collapses only once
when thinking is complete, then allows full user control.

Files changed:
- web/src/hooks/useApiRequest.js: Updated all message handling functions
- web/src/utils/messageUtils.js: Added hasAutoCollapsed to initial state
2025-05-31 01:29:19 +08:00
Apple\Apple 7979c9a38c feat: improve thinking state management for better UX in reasoning display
Previously, the "thinking" indicator and loading icon would only disappear
after the entire message generation was complete, which created a poor user
experience where users had to wait for the full response to see that the
reasoning phase had finished.

Changes made:
- Add `isThinkingComplete` field to independently track reasoning state
- Update streaming logic to mark thinking complete when content starts flowing
- Detect closed `<think>` tags to mark reasoning completion
- Modify MessageContent component to use independent thinking state
- Update "思考中..." text and loading icon display conditions
- Ensure thinking state is properly set in all completion scenarios
  (non-stream, errors, manual stop)

Now the thinking section immediately shows as complete when reasoning ends,
rather than waiting for the entire message to finish, providing much better
real-time feedback to users.

Files modified:
- web/src/hooks/useApiRequest.js
- web/src/components/playground/MessageContent.js
- web/src/utils/messageUtils.js
2025-05-31 01:12:45 +08:00
Apple\Apple 87c9c51a7e improve: Update disabled action tooltips for better accuracy
Update tooltip messages from "正在生成中,请稍候..." to "操作暂时被禁用"
in MessageActions component to better reflect that actions can be disabled
during both message generation and editing states.

Changes:
- Replace specific "generating" message with generic "temporarily disabled" message
- Applies to retry, edit, role toggle, and delete action tooltips
- Provides more accurate user feedback for disabled states

Files modified:
- web/src/components/playground/MessageActions.js
2025-05-30 22:35:43 +08:00
Apple\Apple 7e019db91b perf(playground): optimize config loading and saving to reduce console spam
- Cache initial config using useRef to prevent repeated loadConfig() calls
- Fix useEffect dependencies to only trigger on actual config changes
- Modify debouncedSaveConfig dependency from function reference to actual config values
- Update handleConfigReset to use DEFAULT_CONFIG directly instead of reloading
- Prevent excessive console logging during chat interactions and frequent re-renders

This resolves the issue where console was flooded with:
"配置已从本地存储加载" and "配置已保存到本地存储" messages,
especially during active chat sessions where logs appeared every second.

Fixes: Frequent config load/save operations causing performance issues
2025-05-30 22:29:02 +08:00
Apple\Apple 3cc232e6b9 ♻️ refactor(playground): major architectural overhaul and code optimization
Completely restructured the Playground component from a 1437-line monolith
into a maintainable, modular architecture with 62.4% code reduction (540 lines).

**Key Improvements:**
- **Modular Architecture**: Extracted business logic into separate utility files
  - `utils/constants.js` - Centralized constant management
  - `utils/messageUtils.js` - Message processing utilities
  - `utils/apiUtils.js` - API-related helper functions
- **Custom Hooks**: Created specialized hooks for better state management
  - `usePlaygroundState.js` - Centralized state management
  - `useMessageActions.js` - Message operation handlers
  - `useApiRequest.js` - API request management
- **Code Quality**: Applied SOLID principles and functional programming patterns
- **Performance**: Optimized re-renders with useCallback and proper dependency arrays
- **Maintainability**: Implemented single responsibility principle and separation of concerns

**Technical Achievements:**
- Eliminated code duplication and redundancy
- Replaced magic strings with typed constants
- Extracted complex inline logic into pure functions
- Improved error handling and API response processing
- Enhanced code readability and testability

**Breaking Changes:** None - All existing functionality preserved

This refactor transforms the codebase into enterprise-grade quality following
React best practices and modern development standards.
2025-05-30 22:14:44 +08:00
Apple\Apple 6ec7b67ab4 feat(playground): add role toggle feature for AI messages
- Add role toggle button in MessageActions component for assistant/system messages
- Implement handleRoleToggle function in Playground component to switch between assistant and system roles
- Add visual distinction for system messages with orange badge indicator
- Update roleInfo configuration to use consistent avatars for assistant and system roles
- Add proper tooltip texts and visual feedback for role switching
- Ensure role toggle is disabled during message generation to prevent conflicts

This feature allows users to easily switch message roles between assistant and system,
providing better control over conversation flow and message categorization in the playground interface.
2025-05-30 21:51:09 +08:00
Apple\Apple 75c448e5ec feat: enhance debug panel with real-time preview and collapsible tabs
- Add real-time request body preview that updates when parameters change
- Implement pre-constructed payload generation for debugging without sending requests
- Add support for image URLs in preview payload construction
- Upgrade debug panel to card-style tabs with custom arrow navigation
- Add collapsible functionality and dropdown menu for tab selection
- Integrate image-enabled messages with proper multimodal content structure
- Refactor tab state management with internal useState and external sync
- Remove redundant status labels and clean up component structure
- Set preview tab as default active tab for better UX
- Maintain backward compatibility with existing debug functionality

This enhancement significantly improves the debugging experience by allowing
developers to see exactly what request will be sent before actually sending it,
with real-time updates as they adjust parameters, models, or image settings.
2025-05-30 21:34:13 +08:00
Apple\Apple b5cc31772c feat: add image upload toggle with auto-disable after sending
Add a toggle switch to enable/disable image uploads in the playground,
with automatic disabling after message sending to prevent accidental
image inclusion in subsequent messages.

Changes:
- Add `imageEnabled` field to default configuration with false as default
- Enhance ImageUrlInput component with enable/disable toggle switch
- Update UI to show disabled state with opacity and color changes
- Modify message sending logic to only include images when enabled
- Implement auto-disable functionality after message is sent
- Update SettingsPanel to pass through new imageEnabled props
- Maintain backward compatibility with existing configurations

User Experience:
- Images are disabled by default for privacy and intentional usage
- Users must explicitly enable image uploads before adding URLs
- After sending a message with images, the feature auto-disables
- Clear visual feedback shows current enabled/disabled state
- Manual control allows users to re-enable when needed

This improves user control over multimodal conversations and prevents
unintentional image sharing in follow-up messages.
2025-05-30 20:05:13 +08:00
Apple\Apple b2b98e1264 🎨 style(ui): Optimize the detail page height 2025-05-30 19:37:12 +08:00
Apple\Apple 8be6c8351f 🐛 fix(playground): improve multimodal content handling and error resilience
Fix TypeError when processing multimodal messages containing both text and images.
The error "textContent.text.trim is not a function" occurred when textContent
was null or textContent.text was not a string type.

Changes:
- Add comprehensive type checking for textContent.text access
- Implement getTextContent() utility function for unified content extraction
- Enhance error handling to support multimodal content display
- Fix message copy functionality to handle array-format content
- Improve message reset functionality to extract text content for retry
- Add user-friendly warnings when copying messages without text content

Technical improvements:
- Validate textContent existence and text property type before calling trim()
- Extract text content from multimodal messages for operations like copy/retry
- Maintain backward compatibility with string-format content
- Preserve all existing functionality while adding robust error handling

Fixes issues with:
- Image + text message processing
- Message copying from multimodal content
- Message retry with image attachments
- Error display for complex message formats

This ensures the playground component handles multimodal content gracefully
without breaking existing text-only message functionality.
2025-05-30 19:32:49 +08:00
Apple\Apple 554ac3195c 🎨 refactor(playground): Refactor the structure of the playground and implement responsive design adaptation 2025-05-30 19:24:17 +08:00
Apple\Apple f3dd960c04 Revert "🔖chore: Remove the handling of the MarkdownRender component and the <think> tag"
This reverts commit 30ef878688.
2025-05-30 01:31:38 +08:00
Apple\Apple d8debe7945 Merge remote-tracking branch 'origin/ui/refactor' into ui/refactor 2025-05-29 16:25:33 +08:00
Apple\Apple 30ef878688 🔖chore: Remove the handling of the MarkdownRender component and the <think> tag 2025-05-29 16:25:13 +08:00
CaIon bb8923e76a Merge remote-tracking branch 'origin/ui/refactor' into ui/refactor 2025-05-29 15:52:16 +08:00
CaIon 8f6b0ede99 refactor: Update TopUp component to utilize StatusContext for dynamic top-up settings 2025-05-29 15:52:11 +08:00
RedwindA aabeec7425 Add ERROR_LOG_ENABLED description 2025-05-29 12:35:13 +08:00
RedwindA 3ad58d61b4 Add ERROR_LOG_ENABLED description 2025-05-29 12:33:27 +08:00
Apple\Apple 66927d055b 🛠️ fix(chat): optimize think tag handling and reasoning panel behavior
BREAKING CHANGE: Refactor message stream processing and think tag handling logic

- Improve automatic collapse behavior of reasoning panel
  - Collapse panel immediately after reasoning content completion
  - Add detection for complete think tag pairs
  - Remove dependency on full content completion

- Enhance think tag processing
  - Unify tag handling logic across stream and stop states
  - Add robust handling for unclosed think tags
  - Prevent markdown rendering errors from malformed tags

- Simplify state management
  - Reduce complexity in collapse condition checks
  - Remove redundant state transitions
  - Improve code readability by removing unnecessary comments

This commit ensures a more stable and predictable behavior in the chat interface, particularly in handling streaming responses and reasoning content display.
2025-05-29 12:02:29 +08:00
Apple\Apple 3e7bff1b9d 🐛 fix(Playground): Prevent markdown error from <think> tags in reasoning display
- Modified the `renderCustomChatContent` function to strip `<think>` and `</think>` tags from `finalExtractedThinkingContent` before passing it to the `MarkdownRender` component. This resolves potential parsing errors caused by unclosed or malformed tags.
- Added a conditional check to ensure that the thinking process block is only rendered if `finalExtractedThinkingContent` has actual content and the section is expanded, improving rendering stability.
2025-05-29 11:25:26 +08:00
Akkuman 2e5943c1d4 feat: streaming response for tts 2025-05-29 10:56:01 +08:00
Apple\Apple d6e0310921 🧠 fix(Playground): Enhance <think> tag processing and remove redundant comments
This commit significantly refactors the handling of <think> tags within the Playground component to improve robustness, prevent Markdown rendering errors, and ensure accurate separation of reasoning and displayable content.

Key changes include:
- Modified `handleNonStreamRequest`, `onStopGenerator`, and `renderCustomChatContent` for more resilient <think> tag parsing.
- Implemented logic to correctly extract content from fully paired `<think>...</think>` tags.
- Added handling for unclosed `<think>` tags, particularly relevant during streaming responses, to capture ongoing thought processes.
- Ensured `reasoningContent` is accurately populated from all sources (API, paired tags, and unclosed streaming tags).
- Thoroughly sanitized the final `currentDisplayableFinalContent` to remove all residual `<think>` and `</think>` string instances, preventing issues with the Markdown renderer.
- Corrected the usage of `thinkTagRegex.lastIndex = 0;` to ensure proper regex state resetting before each use in loops.

Additionally, numerous explanatory comments detailing the "how" of the code (rather than the "why") have been removed to improve code readability and reduce noise.
2025-05-29 04:21:33 +08:00
Apple\Apple f0b496e149 Merge branch 'main' into ui/refactor 2025-05-29 03:57:05 +08:00
Apple\Apple f5e133c100 Merge remote-tracking branch 'origin/ui/refactor' into ui/refactor 2025-05-29 03:56:28 +08:00
Apple\Apple b5069c28d2 feat: enhance chat playground with improved message handling and UI
- Add message operations (copy, retry, delete) with tooltips
- Improve thinking chain processing and display logic
- Enhance error handling and debug information presentation
- Optimize UI layout and responsive design
  - Fix height calculations for better viewport usage
  - Improve mobile view adaptability
- Add comprehensive error feedback for message operations
- Implement fallback clipboard functionality
- Refine debug panel with better error tracking
- Add transition animations for better UX
- Update styling with modern gradient backgrounds
2025-05-29 03:56:08 +08:00
CaIon cdf7c35df2 style: Add styles for semi-table components to improve layout and presentation 2025-05-29 03:35:18 +08:00
creamlike1024 ac9df0c147 fix: pingerCtx 泄漏 2025-05-28 21:34:45 +08:00
CaIon ef21119a29 fix: Change ParallelTooCalls from bool to *bool in GeneralOpenAIRequest for optional handling 2025-05-28 21:12:55 +08:00
CaIon a536e9ca78 feat: Enhance image request validation in relay-image.go: set default model and size, improve error handling for size format, and ensure prompt and N parameters are validated correctly. 2025-05-28 20:18:37 +08:00
Apple\Apple 97cbe9b55e Merge remote-tracking branch 'origin/main' into ui/refactor 2025-05-28 18:42:19 +08:00
IcedTangerine 8a6b4c38ed Merge pull request #1110 from wangr0031/fix_parallel_tool_calls
feat: chat/completion路由透传parallel_tool_calls参数
2025-05-28 14:25:43 +08:00
IcedTangerine 3494accae9 Update openai_request.go 2025-05-28 14:25:24 +08:00
IcedTangerine f19a6d611b Merge pull request #1113 from tbphp/tbphp_vertex_gemini_global_region
fix: Vertex channel global region format
2025-05-28 14:16:47 +08:00
IcedTangerine 6add81d459 Merge pull request #1111 from feitianbubu/fxm-ali-fetch-models-url
fix: ali FetchUpstreamModels url
2025-05-28 14:11:17 +08:00
Apple\Apple 369e05649c 🎨 style(ui): The send button for chat is changed to have rounded corners 2025-05-28 02:01:54 +08:00
tbphp c5dff26d6c fix: Vertex channel global region format 2025-05-27 21:50:53 +08:00
skynono bee6c661f7 fix: ali FetchUpstreamModels url 2025-05-27 11:22:40 +08:00
wang.rong 2f09010d22 chat/completion透传parallel_tool_calls参数 2025-05-27 09:32:20 +08:00
Apple\Apple d6a9dd08bf 🛠️ fix(chat): enhance message generation stop behavior
This commit improves the handling of message generation interruption in the
playground chat interface, ensuring both content and thinking process are
properly preserved.

Key changes:
- Preserve existing content when stopping message generation
- Handle both direct reasoningContent and <think> tag formats
- Extract and merge thinking process from unclosed <think> tags
- Maintain consistent thinking chain format with separators
- Auto-collapse reasoning panel after stopping for cleaner UI

Technical details:
- Modified onStopGenerator to properly handle SSE connection closure
- Added regex pattern to extract thinking content from <think> tags
- Implemented proper state management for incomplete messages
- Ensured all content types are preserved in their respective fields

This fix resolves the issue where thinking chain content would be lost
when stopping message generation mid-stream.
2025-05-27 02:07:42 +08:00
Apple\Apple 158fc72c00 🔗 feat: add navigation to topup page from balance cards
- Add click-to-navigate functionality on balance cards in both Detail and TokensTable components
- Implement navigation to '/console/topup' when clicking on current balance
- Add useNavigate hook and necessary imports
- Keep consistent navigation behavior across components

Components affected:
- web/src/pages/Detail/index.js
- web/src/components/TokensTable.js
2025-05-26 23:30:26 +08:00
Apple\Apple ca8ca37392 🌐 i18n: add internationalization support for Loading component
This commit introduces the following changes:

- Add i18n support to the Loading component
- Import useTranslation hook from react-i18next
- Replace hardcoded Chinese text with translation keys
- Support dynamic content interpolation for loading prompts
- Use {{name}} variable in translation template

Technical details:
- Added: import { useTranslation } from 'react-i18next'
- Modified: Loading text from static Chinese to i18n keys
- Translation keys added:
  - "加载中..."
  - "加载{{name}}中..."

File changed: web/src/components/Loading.js
2025-05-26 23:10:42 +08:00
Apple\Apple 70f43b3523 feat: add notice modal component with empty state support
This commit introduces the following changes:

- Create a reusable NoticeModal component to handle system announcements
- Extract notice functionality from Home and HeaderBar components
- Add loading and empty states using Semi UI illustrations
- Implement "close for today" feature with localStorage
- Support both light and dark mode for empty state illustrations
- Add proper error handling and loading states
- Improve code reusability and maintainability

Breaking changes: None
Related components: HeaderBar.js, Home/index.js, NoticeModal.js
2025-05-26 23:06:55 +08:00
Apple\Apple f4f4cea765 🎨 style(ui): Adjust the size of the icon 2025-05-26 22:30:04 +08:00
Apple\Apple 66bc794768 🎨 style(ui): Optimize icon adaptation 2025-05-26 22:25:38 +08:00
Apple\Apple 324d938da2 🎨 style(ui): Modify the sidebar background color to match the theme color 2025-05-26 22:09:50 +08:00
Apple\Apple 603c4e8fac 🐛 fix(headerbar): Fix the issue where the header disappears on mobile screen sizes. 2025-05-26 22:01:57 +08:00
Apple\Apple 620a4d09d4 Merge branch 'main' into ui/refactor
# Conflicts:
#	web/src/components/ChannelsTable.js
#	web/src/pages/Home/index.js
#	web/src/pages/Playground/Playground.js
2025-05-26 21:53:30 +08:00
Apple\Apple ef32958ffb 🔖chore: Preparation modifications before the merger 2025-05-26 21:52:37 +08:00
Apple\Apple 9c67bf129e 🔧 fix(chat): optimize SSE connection status handling
- Remove unnecessary undefined status check in readystatechange event
- Only show disconnection message for actual non-200 status codes
- Remove redundant else branch for normal status handling
- Prevent false "Connection lost" messages on successful completion
2025-05-26 20:24:04 +08:00
Apple\Apple 4ff1084dd6 Merge remote-tracking branch 'origin/ui/refactor' into ui/refactor 2025-05-26 19:59:51 +08:00
Apple\Apple 1551d196fe 🐛 fix(chat): improve error handling and UI feedback for SSE communication
- Add comprehensive error handling for SSE events
- Implement proper error state UI with Semi Typography
- Prevent white screen issues on non-200 responses
- Add error logging for better debugging
- Enhance message state management for error conditions
- Improve user feedback with i18n error messages
- Ensure UI stability during error states
- Add try-catch blocks for JSON parsing and stream initialization
- Handle connection termination gracefully
- Preserve error states in message stream updates
2025-05-26 19:58:01 +08:00
CaIon bdf2f18bdc 🎨 style(ModelPricing): Update card shadow and add margin for improved layout 2025-05-26 19:41:21 +08:00
IcedTangerine 7c34c01056 Merge pull request #1109 from feitianbubu/fix-qwen-thinking
fix: ali parameter.enable_thinking must be set to false for non-strea…
2025-05-26 19:32:34 +08:00
creamlike1024 79d67d2d02 Merge branch 'main' of github.com:QuantumNous/new-api 2025-05-26 18:56:14 +08:00
creamlike1024 87a407da4b fix: search-preview model web search billing 2025-05-26 18:53:41 +08:00
skynono ea075f75e2 fix: ali parameter.enable_thinking must be set to false for non-streaming calls 2025-05-26 17:41:02 +08:00
IcedTangerine c9c939dab1 Merge pull request #1075 from feitianbubu/fix-default-model-not-exist
fix: if default model is not exist, set the first one as default
2025-05-26 17:21:14 +08:00
IcedTangerine 90fcc56440 Merge pull request #1081 from feitianbubu/fixTypoOidcEnabledField
fix: typo in oidc_enabled field (previously oidc)
2025-05-26 17:20:35 +08:00
IcedTangerine d04f48604c Merge pull request #1099 from feitianbubu/fixTagModeStatusSave
fix: keep BatchDelete and TagMode enabled status
2025-05-26 17:17:34 +08:00
Apple\Apple 9ebdc62113 🖼️style(ui): Optimize the styles in the search modal of the detail page in the dashboard 2025-05-26 15:13:55 +08:00
Apple\Apple 793ac9637e 🎨 style: modify the border-radius style of the paginator 2025-05-26 15:03:03 +08:00
creamlike1024 77d6299557 vertex 2025-05-26 15:02:20 +08:00
creamlike1024 253f487d80 gemini stream 2025-05-26 14:50:50 +08:00
Apple\Apple 3951690757 🌞feat(playground): To enable chain-of-thought (CoT) rendering for inference models in the playground, support reasoningContent and the <think> tag 2025-05-26 14:35:35 +08:00
creamlike1024 75d859dce2 gemini text generation 2025-05-26 13:34:41 +08:00
Apple\Apple 9b5bceae9a 🔖chore: Change the display style of request count, TPM, and RPM in the LogsTable 2025-05-26 00:45:31 +08:00
Apple\Apple b87bcbc296 🎨 style(ui): enhance dashboard statistics cards presentation
- Replace emoji icons with Semi Design Avatar components
- Standardize statistics cards layout across TokensTable and LogsTable
- Remove informational text banners for cleaner interface
- Implement consistent grid layout for metric cards
- Add elevated shadow effect to white Avatar for better contrast

Changes include:
- Convert emoji indicators to Semi Icons (MoneyExchangeStroked, Pulse, Typograph)
- Unify card styling with rounded corners and hover shadows
- Implement responsive grid layout (1 column on mobile, 3 columns on desktop)
- Standardize typography and spacing across all stat cards
- Apply Semi Design's elevated shadow to improve white Avatar visibility
2025-05-26 00:07:14 +08:00
Apple\Apple 7acd318bba 🎨 style: adjust table column widths for better layout
This commit adjusts the column widths in multiple table components to optimize the layout and improve visual consistency:

- TokensTable:
  - Reduce ID width from 80px to 50px
  - Reduce name width from 160px to 80px
  - Reduce group width from 200px to 180px
  - Reduce balance width from 200px to 120px
  - Reduce operation width from 400px to 350px

- RedemptionsTable:
  - Add fixed widths for all columns:
    - ID: 50px
    - Name: 120px
    - Status: 100px
    - Quota: 100px
    - Created time: 180px
    - User ID: 100px
    - Operation: 300px

- TaskLogsTable:
  - Add fixed widths for all columns:
    - Submit time: 180px
    - Finish time: 180px
    - Duration: 120px
    - Channel: 100px
    - Platform: 120px
    - Type: 120px
    - Task ID: 200px
    - Status: 120px
    - Progress: 160px
    - Fail reason: 160px

- UsersTable:
  - Add fixed widths for all columns:
    - ID: 50px
    - Username: 100px
    - Group: 100px
    - Stats info: 280px
    - Invite info: 250px
    - Role: 120px
    - Status: 100px
    - Operation: 150px

These adjustments ensure better space utilization and consistent column sizing across all table components.
2025-05-25 23:35:59 +08:00
Apple\Apple f1c58b391a 🔖chore: remove useless index.css style 2025-05-25 18:26:29 +08:00
Apple\Apple dff8e2d3fe 🎨 refactor(home): enhance notice modal UI and behavior
- Replace showNotice with Semi Modal component for better UI consistency
- Add "Close Today" and "Close" actions for flexible notice control
- Improve markdown rendering with marked.parse
- Add responsive modal size based on device type
- Implement max height with scrollable content (60vh)
- Style scrollbar to match Semi design system
- Remove old notice caching logic for more reliable notifications
2025-05-25 18:03:57 +08:00
Apple\Apple 013372327b 🎨 refactor(ModelPricing): enhance UI/UX with modern design ModelPricing component
This commit implements a comprehensive UI/UX overhaul of the ModelPricing component,
focusing on improved aesthetics and responsiveness while maintaining existing API logic.

Key improvements:
- Redesigned status card with gradient background and floating elements
- Implemented responsive grid layout for pricing metrics
- Enhanced visual hierarchy with Semi UI components
- Added smooth transitions and hover effects
- Optimized spacing and typography for better readability
- Unified design language with PersonalSettings component
- Integrated Tailwind CSS 3.0 utility classes
- Added decorative elements for visual interest
- Improved mobile responsiveness across all breakpoints
- Enhanced accessibility with proper contrast ratios

The redesign follows modern UI/UX best practices while maintaining consistency
with the application's design system.
2025-05-25 17:27:45 +08:00
Apple\Apple d8f63f376b 🔖chore: Temporarily disable links to related open source projects 2025-05-25 15:06:51 +08:00
Apple\Apple 0de3f7b2a4 🎨 UI/UX: Replace emoji icons with Semi UI components and enhance card design
BREAKING CHANGE: Replace all emoji icons with Semi UI icons in statistics cards

- Replace emoji icons with corresponding Semi UI icons:
  - 💰 -> IconMoneyExchangeStroked
  - 📊 -> IconHistogram
  - 🔄 -> IconRotate
  - 💲 -> IconCoinMoneyStroked
  - 🔤 -> IconTextStroked
  - 📈 -> IconPulse
  - ⏱️ -> IconStopwatchStroked
  - 📝 -> IconTypograph

- Add Avatar component as circular background for icons
  - Implement color-coded avatars for each statistic card
  - Set avatar size to 'medium' for better visual balance
  - Add appropriate color mapping for each statistic type

- Adjust layout spacing
  - Reduce top margin from mb-6 to mb-4 for better vertical rhythm
  - Maintain consistent spacing in card layouts

- Import necessary Semi UI components and icons
  - Add Avatar component import
  - Add required icon imports from @douyinfe/semi-icons

This change improves the overall UI consistency and professional appearance by adopting Semi UI's design system components.
2025-05-25 13:30:47 +08:00
Apple\Apple ad9755f474 🖼️feat(ui): unify card header styles across edit pages
This commit standardizes the card header design across multiple edit pages
to create a consistent and modern UI experience.

Changes made:
- Add gradient background colors to card headers
- Implement decorative circular elements for visual appeal
- Update icon colors to white with semi-transparent backgrounds
- Standardize text colors and opacity for better readability
- Add consistent padding and border radius
- Maintain unique color schemes for different functional sections

Modified files:
- EditChannel.js
- EditRedemption.js
- EditToken.js
- EditUser.js
- AddUser.js

The new design features:
- Blue gradient for basic information sections
- Green gradient for quota/permission settings
- Purple gradient for access restrictions
- Orange gradient for binding/group information
- Consistent layout structure across all edit pages

This update improves visual hierarchy and maintains brand consistency
while enhancing the overall user experience.
2025-05-25 13:01:31 +08:00
Apple\Apple e197dc0a98 ♻️refactor(EditChannel & EditTagModal): enhance UI/UX and optimize code structure
BREAKING CHANGE: None

- UI Improvements:
  - Add gradient backgrounds to card headers
  - Enhance visual hierarchy with decorative elements
  - Improve button styles with rounded corners and icons
  - Standardize input field sizes and styles
  - Add consistent border radius to components

- Code Optimizations:
  - Remove unused imports (IconHelpCircle, IconKey, IconPlus)
  - Remove unused showWarning import
  - Remove unused loadChannelModels import
  - Remove unused useRef and useParams hooks
  - Clean up whitespace and formatting

- Style Enhancements:
  - Convert static colors to gradient backgrounds
  - Add floating circle decorations for visual interest
  - Improve text contrast with white text on gradient backgrounds
  - Add semi-transparent white backgrounds to icons
  - Standardize card header layouts

- Accessibility:
  - Improve text contrast ratios
  - Maintain consistent visual hierarchy
  - Add relative positioning for better overlay handling

This refactor focuses on modernizing the UI while maintaining all existing functionality. The changes are purely presentational and do not affect the component's behavior.
2025-05-25 12:53:00 +08:00
Apple\Apple e2fcb73a94 🌞feat(channels): add success notification for single channel test
- Add success notification when testing a single channel
- Only show notification for individual channel test, not for batch model testing
- Include channel name and response time in the notification message
- Keep error notifications for both single and batch testing scenarios

This improves user feedback when manually testing channel connectivity.
2025-05-25 11:43:19 +08:00
Apple\Apple d28dd059e7 🤯feat(channels): enhance channel management UI and model testing
This commit improves the ChannelsTable component with enhanced UI and functionality:

UI Improvements:
- Refactor operation column layout with primary actions exposed
- Move secondary actions (delete, copy) to dropdown menu
- Unify button styles with theme='light' and size="small"
- Add !rounded-full design to all buttons
- Add appropriate icons (IconStop, IconPlay etc.)

Column Settings Modal:
- Replace inline styles with Tailwind CSS
- Add rounded corners design
- Optimize button layout and styling
- Improve responsive design

Batch Operations:
- Unify dropdown button styles with !rounded-full
- Replace inline styles with Tailwind w-full
- Maintain semantic button types (warning/secondary/danger)
- Improve visual hierarchy

Model Testing Enhancement:
- Add comprehensive model testing modal
- Implement batch testing functionality
- Add model search and filtering
- Add real-time test status indicators
- Show response time for successful tests
- Add test queue management system
- Implement graceful test cancellation

Other Improvements:
- Optimize responsive layout for mobile devices
- Add i18n support for all new features
- Improve error handling and user feedback
- Enhance performance with optimized state management
2025-05-25 01:46:45 +08:00
Apple\Apple 485ea96524 Merge remote-tracking branch 'origin/main' into ui/refactor 2025-05-24 17:21:38 +08:00
Calcium-Ion 268be18baa Merge pull request #1100 from daggeryu/patch-4
fix aws claude-sonnet-4-20250514
2025-05-24 15:27:30 +08:00
CaIon e1fbf73afc fix: improve input validation and error handling in ModelSetting and SettingGeminiModel components 2025-05-24 15:26:55 +08:00
Apple\Apple f764a27ecd Merge remote-tracking branch 'origin/main' into ui/refactor 2025-05-24 10:13:30 +08:00
daggeryu 0a7842e897 fix aws claude-sonnet-4-20250514 2025-05-24 01:21:14 +08:00
Apple\Apple 334011d410 🍎refactor(PersonalSettings): Comprehensive UI/UX redesign and improvements
- Unify input field styles with size="large" for consistent appearance
- Replace account binding sections with Semi UI Card components for better visual hierarchy
- Redesign invitation statistics cards with responsive layout and Card components
- Enhance security settings with structured Card layout and improved visual design
- Complete i18n internationalization for all text strings and placeholders
- Optimize main profile card responsive design following TopUp page patterns
- Update avatar component to display first two characters with animated border
- Improve user role display with three-tier system (Super Admin/Admin/User)
- Enhance tag visibility with white background for better contrast on purple gradient
- Implement dynamic colors for model tags using stringToColor function
- Add hover effects and improved accessibility across all components
- Maintain consistent design language throughout the interface

This refactor significantly improves the user experience with modern UI patterns,
better responsiveness, and enhanced visual appeal while maintaining all existing
functionality.
2025-05-23 23:53:10 +08:00
CaIon eea5967e2c feat: add support for new regions in Claude Sonnet 4 and Claude Opus 4 models in AWS constants 2025-05-23 21:11:00 +08:00
skynono 05077594bc fix: keep BatchDelete and TagMode enabled status 2025-05-23 20:17:48 +08:00
Apple\Apple aae61434b7 🍭feat: add loading states to all async operation buttons for better UX
- Add paymentLoading state for Alipay and WeChat payment buttons
- Add confirmLoading state for payment confirmation modal
- Implement proper loading management in preTopUp function with try-catch error handling
- Implement proper loading management in onlineTopUp function with comprehensive error handling
- Add loading={paymentLoading} to both payment method buttons to prevent double-clicks
- Add confirmLoading={confirmLoading} to modal confirmation button
- Enhance error handling with user-friendly error messages for failed operations
- Ensure loading states are properly cleared in finally blocks for consistent UX

This update provides immediate visual feedback when users interact with payment buttons,
prevents accidental double-clicks, and improves overall payment flow reliability
with comprehensive error handling and loading state management.
2025-05-23 19:40:43 +08:00
Apple\Apple ee0e1161d4 ♻️refactor: Completely redesign TopUp page with modern card-based UI and enhanced UX
- Replace simple form layout with sophisticated card-based design system
- Implement bank card-style wallet display with gradient backgrounds and decorative elements
- Integrate real user data from UserContext (username, quota, usage stats, user role, group)
- Add personalized color schemes using stringToColor for unique user identification
- Implement comprehensive responsive design for mobile, tablet, and desktop devices
- Add skeleton loading states for all data-dependent components and API calls
- Replace basic Input with InputNumber component for amount input with built-in validation (min: 1)
- Add official brand icons for payment methods (Alipay, WeChat) using react-icons/si
- Integrate Semi UI Banner component for better warning notifications
- Implement real-time data synchronization between local state and UserContext
- Add sophisticated loading states with proper error handling and user feedback
- Clean up all code comments and remove unused imports, functions, and state variables
- Enhance visual hierarchy with proper spacing, shadows, and modern typography
- Add glass-morphism effects and backdrop filters for premium visual experience
- Improve accessibility with proper text truncation and responsive font sizing

This update transforms the TopUp page from a basic form into a professional,
modern payment interface that provides excellent user experience across all devices
while maintaining full functionality and adding comprehensive data validation.
2025-05-23 19:31:36 +08:00
Apple\Apple 8e802565de ♻️refactor: Modernize edit and add user components with unified design system
- Refactor EditRedemption.js with card-based layout and modern UI components
- Refactor EditUser.js with three-section card layout (basic info, permissions, bindings)
- Refactor AddUser.js with modern card design and improved user experience
- Replace inline styles with Tailwind CSS 3 classes throughout all components
- Add semantic icons (IconUser, IconKey, IconGift, IconCreditCard, etc.) for better UX
- Implement unified header design with colored tags and consistent typography
- Replace deprecated Title imports with destructured Typography components
- Add proper internationalization support with useTranslation hook
- Standardize form layouts with consistent spacing, rounded corners, and shadows
- Improve button styling with rounded design and loading states
- Fix IconTicket import error by replacing with existing IconGift
- Enhance modal designs with modern styling and icon integration
- Ensure responsive design consistency across all edit/add components

This update brings all user management interfaces in line with the modern
design system established in EditToken.js, providing a cohesive and
professional user experience.
2025-05-23 17:33:32 +08:00
Apple\Apple cb21e756d2 ♻️Refactor: Users Page 2025-05-23 17:12:17 +08:00
Apple\Apple 04ba012f12 ♻️Refactor: Redemptions Page 2025-05-23 16:58:19 +08:00
CaIon 3cc1a79449 feat: add new model entries for Claude Sonnet 4 and Claude Opus 4 across multiple components, including constants and cache settings 2025-05-23 15:20:16 +08:00
Apple\Apple 07d87d6074 ♻️Refactor: TaskLogs Page 2025-05-23 13:43:02 +08:00
Apple\Apple 17c42cc02c ♻️Refactor: MJLogs Page 2025-05-23 13:30:40 +08:00
Apple\Apple 63183bd4eb ♻️Refactor: Logs Page 2025-05-23 13:06:53 +08:00
CaIon 4978d63f46 feat: add new model ratios for Claude Sonnet 4 and Claude Opus 4; update ratio retrieval logic for improved handling of model names 2025-05-23 02:02:21 +08:00
CaIon 94455e34e6 fix: update Init method to correctly set RequestMode based on upstream model name prefixes 2025-05-23 01:34:53 +08:00
CaIon adb2c39564 feat: add panic recovery and retry mechanism for InitChannelCache; improve batch deletion of abilities in FixAbility 2025-05-23 01:26:52 +08:00
Apple\Apple 4fde65ce6e 🌏i18n: Tokens Page 2025-05-23 00:26:00 +08:00
Apple\Apple 192049d932 ♻️Refactor: Token Page 2025-05-23 00:24:08 +08:00
Apple\Apple 80765bddcd feat(ui): Add loading states to all authentication buttons
Add loading indicators to improve user experience during authentication processes:
- Implement loading states for all login and registration buttons
- Add try/catch/finally structure to properly handle async operations
- Create wrapper functions for OAuth redirect operations
- Set loading state for verification code submission
- Update modal confirmation buttons with loading state
- Add proper error handling with user feedback
- Split generic loading state into specific state variables

This change enhances user experience by providing clear visual feedback
during authentication actions.
2025-05-22 21:42:21 +08:00
CaIon fa48d43f0f feat: implement search functionality in ChannelsTable for improved channel filtering 2025-05-22 16:54:55 +08:00
CaIon 6436a165af feat: enhance Gemini response handling by adding reasoning content and updating JSON decoding method 2025-05-22 16:11:50 +08:00
CaIon 526b7e6048 feat: add Thought field to GeminiPart and update response handling in streamResponseGeminiChat2OpenAI 2025-05-22 15:52:23 +08:00
skynono 616ba15af1 fix: typo in oidc_enabled field (previously oidc) 2025-05-21 09:33:57 +08:00
Apple\Apple 74267bdfc6 ♻️Refactor: OAuth2Callback Page 2025-05-20 23:47:29 +08:00
Apple\Apple e5990751f7 ♻️Refactor: Logo 2025-05-20 23:21:48 +08:00
CaIon e0fc7a501f feat: add OutputFormat field to ImageRequest for enhanced image processing options 2025-05-20 19:40:29 +08:00
Apple\Apple 6ba7e552f2 ♻️Refactor: Detail Page 2025-05-20 18:01:38 +08:00
Apple\Apple 74d4a54120 🐛fix(sidebar): Ensure sidebar displays correctly on desktop devices
When resizing from medium screens to desktop view in the console section,
the sidebar previously failed to appear automatically. This commit fixes
the issue by simplifying the logic to always show the sidebar when in
desktop mode and in console pages, regardless of previous screen size.
2025-05-20 12:59:47 +08:00
Apple\Apple 4489475bb0 🎨style: Adjust the fixed placeholder width for homepage image loading 2025-05-20 12:25:39 +08:00
Apple\Apple 48835616b3 feat: Redirect to console if logged in and accessing auth pages
This commit introduces a new component `AuthRedirect` which checks
if a user is already logged in.

If the user is logged in and attempts to access the /login or /register
pages, they will be redirected to the /console page. Otherwise, the
respective authentication form (Login or Register) will be rendered.

The `App.js` file has been updated to utilize this new `AuthRedirect`
component for the /login and /register routes.
2025-05-20 11:53:04 +08:00
Apple\Apple 3c0960fd36 ♻️Refactor: About Page 2025-05-20 11:31:03 +08:00
Apple\Apple 2b6c179247 ♻️Refactor: PasswordResetConfirm and PasswordReset 2025-05-20 11:02:20 +08:00
Apple\Apple d7e577cbbf ♻️Refactor: RegisterForm 2025-05-20 10:38:31 +08:00
Apple\Apple 0221400181 ♻️Refactor: Login Page 2025-05-20 04:43:11 +08:00
Apple\Apple 7568490905 ♻️Refactor: NotFound Page 2025-05-20 02:33:38 +08:00
Apple\Apple 6eb1ba958a 🐛fix(HeaderBar): Prevent flicker when unauthenticated users click Console link
Previously, when an unauthenticated user clicked the "Console" navigation
link, the application would first attempt to navigate to '/console'.
This would then trigger a redirect to '/login', causing a brief flicker
between the two pages.

This commit modifies the `renderNavLinks` function in `HeaderBar.js`
to proactively check the user's authentication status. If the user is
not logged in and clicks the "Console" link, the navigation target
is directly set to '/login', avoiding the intermediate redirect and
eliminating the flickering effect.
2025-05-20 02:23:06 +08:00
CaIon 83ea1b2f64 feat: Add country flag icons package to project dependencies 2025-05-20 02:19:05 +08:00
Apple\Apple 0a3186ac5d ♻️Refactor: Decouple sidebar visibility from HeaderBar nav clicks
The `handleNavLinkClick` function in `HeaderBar.js` was previously
forcing the sidebar to be visible for all non-'home' navigation links
on non-mobile devices. This interfered with the intended logic in
`StyleContext` which controls sidebar visibility based on the current
route.

This commit modifies `handleNavLinkClick` to:
- Only apply specific style dispatches (setting inner padding and sider
  to false) for the 'home' link, which may have unique layout requirements.
- Remove the logic that unconditionally set sidebar visibility and inner
  padding for other navigation links.
- Continue to close the mobile menu загрязнения (`setMobileMenuOpen(false)`) on any nav link click.

This change ensures that `StyleContext` is the single source of truth
for determining sidebar visibility based on the route, resolving an
issue where clicking a non-console link Pferde (e.g., 'Pricing', 'About')
would incorrectly display the sidebar, especially when the link was
clicked Pferde a second time while already on that page.
2025-05-20 01:58:44 +08:00
Apple\Apple b13f52e3fe 🎨style: Remove the paddingBottom style in isMobile 2025-05-20 01:25:24 +08:00
Apple\Apple d751fc7373 feat(ui): Add control for the HeaderBar menu in the console routing under the Sidebar 2025-05-20 01:22:01 +08:00
Apple\Apple 9934c978b0 feat: Add the /console/* route 2025-05-20 01:11:37 +08:00
Apple\Apple bf767d6550 feat(ui): Add a frosted glass effect with Gaussian blur to the HeaderBar 2025-05-20 00:56:45 +08:00
Apple\Apple 83407167fc 🌏i18n: Demo Site 2025-05-20 00:52:47 +08:00
Apple\Apple ae4a239f17 🐛fix(session): The localStorage did not clear user information after the login session expired 2025-05-20 00:50:09 +08:00
Apple\Apple 3b0d4c8487 ♻️refactor: Home Page and Footer 2025-05-20 00:23:47 +08:00
CaIon 0bf328939a refactor: update JSON field names in GeminiChatRequest for consistency 2025-05-19 20:26:30 +08:00
skynono bedeb15d3d fix: if default model is not exist, set the first one as default 2025-05-19 14:56:39 +08:00
Apple\Apple 99185dfae4 🔖chore: Remove useless codes in Footer.js 2025-05-18 22:58:21 +08:00
Apple\Apple fc361c1cad 🎨style: Modify the transition shadow effect of the SiderBar 2025-05-18 22:06:40 +08:00
Apple\Apple 79486fab44 feat(ui): Add a skeleton screen placeholder for the Logo and systemName in the HeaderBar during loading 2025-05-18 22:01:50 +08:00
Apple\Apple ee3d6d8e46 feat(ui): Add a dropdown menu item for the user avatar in the HeaderBar 2025-05-18 21:54:10 +08:00
Apple\Apple 63e149b121 🎨style: Modify the transition shadow effect of the headerBar 2025-05-18 21:43:07 +08:00
Apple\Apple 1f78dd5927 ♻️refactor: HeaderBar 2025-05-18 21:33:08 +08:00
IcedTangerine 1667f0895f Merge pull request #1071 from feitianbubu/fixMjImageProxy
fix: proxy settings not applied when request MJ image url
2025-05-18 14:56:47 +08:00
Apple\Apple 3d6493c3ec Merge branch 'main' into ui 2025-05-17 11:22:22 +08:00
skynono dc1d09df84 fix: proxy settings not applied when request MJ image url 2025-05-16 18:07:56 +08:00
Calcium-Ion bb18f64d1f Merge pull request #1067 from QuantumNous/coze
Coze 渠道
2025-05-16 16:11:02 +08:00
creamlike1024 63232607cf coze stream 2025-05-16 10:27:07 +08:00
creamlike1024 e9690c7ad7 add frontend display, more model 2025-05-15 20:00:59 +08:00
CaIon c0d85e2d98 feat: enhance image decoding logic to handle base64 file types and improve error handling 2025-05-15 14:51:33 +08:00
CaIon f88b7cd7f2 fix: update model selection logic for image edits in distributor middleware 2025-05-14 17:01:50 +08:00
creamlike1024 5e8217db0f use channel bot id 2025-05-13 22:23:38 +08:00
creamlike1024 ee7cdae0a8 cozeChatHelper 2025-05-13 22:01:12 +08:00
creamlike1024 7e0b2521a7 DoRequest 2025-05-13 21:13:34 +08:00
IcedTangerine 9bd7e18ae5 Merge pull request #1063 from kingfs/fix/ali-completions-api
fix: ALI completions api path error
2025-05-13 17:51:35 +08:00
王永振 6a92483b01 fix: ALI completions api path error 2025-05-13 13:39:44 +08:00
creamlike1024 cd8a2f85db add coze request 2025-05-13 12:52:22 +08:00
creamlike1024 a9b0c25fb7 Merge branch 'a37836323-add-dalle-fields' 2025-05-11 17:03:56 +08:00
creamlike1024 8aaab38748 Merge branch 'add-dalle-fields' of github.com:a37836323/new-api into a37836323-add-dalle-fields 2025-05-11 17:03:27 +08:00
creamlike1024 5c53f48a86 feat: add support for socks5h 2025-05-11 17:00:33 +08:00
CaIon 5ca8f13ab0 feat: add moderation and background fields to ImageRequest struct in dalle.go #1052 2025-05-10 15:52:41 +08:00
a37836323 f249b7cf24 添加DALL-E图像生成请求中的Background和Moderation字段 2025-05-10 04:33:49 +08:00
CaIon c2f3749cfe feat: enhance OpenAI handler to support forced response formatting and add debug logging for request URLs 2025-05-09 18:57:06 +08:00
Calcium-Ion 97f98a0c08 Merge pull request #1046 from QuantumNous/workerHttpRequest
feat: add option to allow worker HTTP image requests
2025-05-09 18:31:25 +08:00
IcedTangerine 86d1e416ca Merge pull request #1050 from feitianbubu/fixRatio
fix: correct formatting string in PriceData.ToSetting to handle Image…
2025-05-09 18:15:39 +08:00
creamlike1024 92ddbc4dfd fix: GetRequestURL remove unnecessary case 2025-05-09 18:13:19 +08:00
creamlike1024 b843c356c1 feat: change azure default api version to 2025-04-01-preview 2025-05-09 18:11:37 +08:00
skynono e53163f68e fix: correct formatting string in PriceData.ToSetting to handle ImageRatio as float instead of integer 2025-05-09 17:12:35 +08:00
creamlike1024 f8a972da0d Merge branch '9Ninety-fix/sse_ping' 2025-05-09 13:57:26 +08:00
creamlike1024 a074e994b0 feat: send SSE ping before get response 2025-05-09 13:57:00 +08:00
creamlike1024 a8b46d5212 Merge branch 'fix/sse_ping' of github.com:9Ninety/new-api into 9Ninety-fix/sse_ping 2025-05-09 12:28:05 +08:00
IcedTangerine 6b71eb6437 Merge pull request #1045 from tbphp/feat_openrouter_balance
feat: update OpenRouter balance
2025-05-09 12:19:20 +08:00
creamlike1024 285c4a7e4c feat: add option to allow worker HTTP image requests 2025-05-09 02:00:42 +08:00
tbphp 3f3672124d fix: 修改命名规范 2025-05-09 00:20:26 +08:00
tbphp d6942dfe06 feat: update OpenRouter balance 2025-05-09 00:15:44 +08:00
creamlike1024 4d0b438f81 Merge branch 'tbphp-tbphp_model_request_rate_limit_for_group' 2025-05-08 23:20:08 +08:00
CaIon 66cda24386 feat: add AzureNoRemoveDotTime constant and update channel handling #1044
- Introduced a new constant `AzureNoRemoveDotTime` in `constant/azure.go` to manage model name formatting for channels created after May 10, 2025.
- Updated `distributor.go` to set `channel_create_time` in the context.
- Modified `adaptor.go` to conditionally remove dots from model names based on the channel creation time.
- Enhanced `relay_info.go` to include `ChannelCreateTime` in the `RelayInfo` struct.
- Updated English localization files to reflect changes in model name handling for new channels.
2025-05-08 23:19:40 +08:00
CaIon abc9d60fdc fix: update OpenAI request handling to include 'o1-preview' model support #1029 2025-05-08 23:19:38 +08:00
creamlike1024 58cd3d22aa fix: tool quota calculate 2025-05-08 23:19:37 +08:00
liusanp 9265d41b84 fix: xAi response 2025-05-08 23:19:35 +08:00
liusanp 89d40bbbf3 fix: xAi requestUrl 2025-05-08 23:19:34 +08:00
liusanp 2e73714f49 fix: quality, size or style are not supported by xAI API 2025-05-08 23:19:32 +08:00
creamlike1024 c483c7df5f Merge branch 'tbphp_model_request_rate_limit_for_group' of github.com:tbphp/new-api into tbphp-tbphp_model_request_rate_limit_for_group 2025-05-08 23:16:06 +08:00
CaIon 54f69c7d80 feat: add AzureNoRemoveDotTime constant and update channel handling #1044
- Introduced a new constant `AzureNoRemoveDotTime` in `constant/azure.go` to manage model name formatting for channels created after May 10, 2025.
- Updated `distributor.go` to set `channel_create_time` in the context.
- Modified `adaptor.go` to conditionally remove dots from model names based on the channel creation time.
- Enhanced `relay_info.go` to include `ChannelCreateTime` in the `RelayInfo` struct.
- Updated English localization files to reflect changes in model name handling for new channels.
2025-05-08 22:39:55 +08:00
CaIon 9aa7f7c755 fix: update OpenAI request handling to include 'o1-preview' model support #1029 2025-05-08 21:34:31 +08:00
Calcium-Ion ae788c8ac8 Merge pull request #1040 from QuantumNous/responses-quota
fix: tool quota calculate
2025-05-08 01:21:34 +08:00
9 882d1723db fix: ensure SSE ping packets are sent before upstream response
These changes ensures SSE ping packets are sent before receiving a response from the upstream. The previous implementation did not send ping packets until after the upstream response, rendering the feature ineffective.
2025-05-07 23:29:06 +08:00
IcedTangerine a63b2e77eb Merge pull request #1039 from liusanp/main
Fix grok-2-image request error
2025-05-07 22:06:51 +08:00
IcedTangerine fdaed7bd00 Merge pull request #1032 from feitianbubu/upstream
fix: correct error messages for dall-e models size parameters
2025-05-07 20:56:36 +08:00
IcedTangerine eb15a90ecd Merge pull request #1037 from LarchLiu/main
fix: gemini response json schema
2025-05-07 20:53:45 +08:00
creamlike1024 5002f7c402 fix: tool quota calculate 2025-05-07 19:33:32 +08:00
liusanp e514d1f6fa fix: xAi response 2025-05-07 18:59:27 +08:00
liusanp 678c61d208 fix: xAi requestUrl 2025-05-07 18:32:59 +08:00
Alex Liu 7ecd9d053e fix: gemini response json schema 2025-05-07 18:08:56 +08:00
CaIon 465504033f fix: update ResponseChunkData to format data correctly without newline 2025-05-07 17:02:47 +08:00
CaIon 4ad7c26345 feat: add support for BaiduV2 channel in relay info 2025-05-07 16:30:32 +08:00
liusanp ef5480bd31 fix: quality, size or style are not supported by xAI API 2025-05-07 16:17:22 +08:00
CaIon 645b19b937 Merge remote-tracking branch 'origin/main' 2025-05-07 16:16:19 +08:00
joey 2cbedaf6d7 feat: support model mapping chain
#1033
2025-05-07 16:00:35 +08:00
skynono 451133d92d fix: correct error messages for dall-e models size parameters
(cherry picked from commit 149d06850c10cc6cdb3291164e3e46f99ca59abc)
2025-05-07 11:21:19 +08:00
Calcium-Ion 910e058c8e Merge pull request #1025 from QuantumNous/responses_buildin_tools
feat: implement OpenAI responses built-in tool tracking
2025-05-07 02:25:47 +08:00
creamlike1024 6eda56bd45 feat: 添加 built in tools 计费前端显示 2025-05-07 01:08:20 +08:00
creamlike1024 8cebbde4a0 chore: move file search tool price to operation_setting 2025-05-06 23:57:22 +08:00
creamlike1024 1db30c55af chore: move web search tool price to operation_setting 2025-05-06 23:25:16 +08:00
IcedTangerine ccadf0eec2 Merge pull request #1026 from tbphp/tbphp_fix_redis_limit
fix: Redis limit ignoring max eq 0
2025-05-06 22:36:13 +08:00
creamlike1024 b4847f119f Merge branch 'feitianbubu-upstream' 2025-05-06 22:31:39 +08:00
creamlike1024 13afcd4266 fix: 修复未输入新密码时提示修改成功 2025-05-06 22:28:32 +08:00
creamlike1024 a3fe88772f feat: 添加 built in tools 计费
- 增加非流的工具调用次数统计
- 添加 web search 和 file search 计费
2025-05-06 21:58:01 +08:00
CaIon 062dbb109e feat: add support for DeepSeek channel in streamSupportedChannels 2025-05-06 18:41:01 +08:00
skynono bb596ae8e6 feat: add original password verification when changing password 2025-05-06 14:28:27 +08:00
Apple\Apple b2198c6cff 🔖chore: Remove semantic-ui styles
Signed-off-by: Apple\Apple <zeraturing@foxmail.com>
2025-05-06 01:38:44 +08:00
Apple\Apple 2491b154b9 feat: Support TailwindCSS V3
Signed-off-by: Apple\Apple <zeraturing@foxmail.com>
2025-05-06 01:36:23 +08:00
Apple\Apple b432ffd441 Revert "feat: Support TailwindCSS V3"
This reverts commit 77382f828b.
2025-05-06 01:24:36 +08:00
Apple\Apple 77382f828b feat: Support TailwindCSS V3
Signed-off-by: Apple\Apple <zeraturing@foxmail.com>
2025-05-06 00:14:18 +08:00
tbphp 21b0d13bab fix: 样式修复 2025-05-05 23:56:15 +08:00
tbphp b648b3cf98 fix: 缩进修复还原 2025-05-05 23:53:05 +08:00
tbphp 9e2af59840 fix: text 2025-05-05 23:48:15 +08:00
Apple\Apple e8111e2bdb 📕docs: Update the content in README.en.md and the structure of the docs directory 2025-05-05 23:44:30 +08:00
tbphp 914b3d1cfe fix: 请求完成数必须大于等于1 2025-05-05 23:41:43 +08:00
tbphp fbbbe77657 feat: 分组速率前端优化 2025-05-05 22:06:16 +08:00
tbphp 103cca019d feat: Modellimitgroup check 2025-05-05 20:00:06 +08:00
tbphp cd40bdbdc6 refactor: 调整代码,符合项目现有规范 2025-05-05 19:32:22 +08:00
tbphp e24a3f7587 fix: rm debug file 2025-05-05 17:57:02 +08:00
tbphp 2a0e1ab914 fix: Redis limit ignoring max eq 0 2025-05-05 12:55:48 +08:00
tbphp c9ce1210be feat: 优化代码,去除多余注释和修改 2025-05-05 11:34:57 +08:00
tbphp b32ed55624 feat: 增加分组速率功能 2025-05-05 07:31:54 +08:00
CaIon e9812b0fd5 feat: implement OpenAI responses handling and streaming support with built-in tool tracking 2025-05-05 00:40:16 +08:00
Calcium-Ion 52d5d495c0 Merge pull request #1024 from tbphp/fix-edituser-text
fix: EditUser text error
2025-05-04 18:30:32 +08:00
tbphp fb81fe05dd fix: EditUser text error 2025-05-04 18:26:18 +08:00
CaIon 24200df47e refactor: remove unnecessary call to helper.Done and adjust data rendering in ClaudeChunkData 2025-05-04 17:35:45 +08:00
Calcium-Ion 4cc1f2b4e3 Merge pull request #1020 from QuantumNous/v1responses
feat: support /v1/responses API
2025-05-04 17:13:39 +08:00
Calcium-Ion fa89410e41 Merge pull request #1012 from tbphp/vertex_thinking_support
feat: support thinking suffix for vertex gemini channel
2025-05-04 17:11:27 +08:00
CaIon 209265e68c feat: enhance OaiResponsesStreamHandler to handle output text and improve response streaming 2025-05-04 17:09:37 +08:00
creamlike1024 3753e5720a add OaiResponsesStreamHandler 2025-05-03 22:36:27 +08:00
CaIon 4bf0d094c4 feat: add video URL support in MediaContent and update token counting logic 2025-05-03 21:12:07 +08:00
creamlike1024 8ea4c76f2b feat: support /v1/responses API 2025-05-02 13:59:46 +08:00
CaIon f5c2fda22a feat: enable error logging configuration in docker-compose and application 2025-04-29 16:26:55 +08:00
CaIon d309727e99 fix: gemini thinking tokens count #1014 2025-04-29 16:21:54 +08:00
CaIon 7e3e9b1a96 refactor: Reducing the lock duration to the minimum necessary time in CacheGetRandomSatisfiedChannel function 2025-04-29 15:57:21 +08:00
tbphp 5b81e88e41 feat: support thinking suffix for vertex gemini channel 2025-04-29 13:30:03 +08:00
CaIon 8b9ba91184 fix: update audio ratio logic for model names in GetAudioRatio function 2025-04-28 20:55:40 +08:00
IcedTangerine d402f5a5d9 Merge pull request #1008 from JoeyLearnsToCode/feat-search-channel-by-url
feat: support searching channels by base url
2025-04-28 13:15:49 +08:00
creamlike1024 8bea13976f Merge branch 'wzxjohn-feature/wellknown' 2025-04-28 12:55:06 +08:00
JoeyLearnsToCode c56ec3b3ea feat: support searching channels by base url 2025-04-28 11:38:53 +08:00
wzxjohn 9f165b8924 fix: remove custom header in oidc well known request 2025-04-28 11:25:04 +08:00
wzxjohn 93f634d228 feat: support empty well known url 2025-04-28 11:25:04 +08:00
wzxjohn 45073b3b2b feat: improve log delete api 2025-04-28 11:25:04 +08:00
creamlike1024 4f123cf9f4 Merge branch 'error-logs' of github.com:zenghongtu/new-api into zenghongtu-error-logs 2025-04-28 11:06:32 +08:00
CaIon 4759bbdb95 feat: add image preview functionality and update model name instructions in EditChannel 2025-04-27 17:20:49 +08:00
CaIon 5bf1f2f275 refactor: rename InitModelSettings to InitRatioSettings 2025-04-26 17:15:34 +08:00
CaIon e24d9bd8db fix: update cacheRatioMap initialization in InitModelSettings function 2025-04-26 17:09:23 +08:00
CaIon f4dcf8d6b6 feat: initialize cacheRatioMap in InitModelSettings function 2025-04-26 17:06:03 +08:00
CaIon 9246dd2070 fix: handle optional user_group_ratio in LogsTable and render helper 2025-04-26 15:59:49 +08:00
CaIon 1ab9892090 Merge remote-tracking branch 'new-api/main' into gpt-image
# Conflicts:
#	relay/relay-image.go
2025-04-26 15:54:08 +08:00
CaIon 0647872c15 feat: support image edit model mapping
(cherry picked from commit 1a869d8ad77f262ee27675ec2deaf451b1743eb7)
2025-04-26 15:48:59 +08:00
xyfacai 873ebcf0c7 feat: support /images/edit
(cherry picked from commit 1c0a1238787d490f02dd9269b616580a16604180)
2025-04-26 15:44:56 +08:00
IcedTangerine 1727d5664a Merge pull request #950 from datehoer/main
fix: update getAndValidImageRequest function in relay/relay-image.go to support grok-2-image model
2025-04-26 15:34:15 +08:00
IcedTangerine 8160fb642c Merge pull request #843 from IllTamer/pr
fix: the pricing available popover display anyway
2025-04-25 18:27:45 +08:00
IcedTangerine b26b34ec2f Merge branch 'main' into pr 2025-04-25 18:27:11 +08:00
han shi a0017f2235 feat: 增加sendcloud邮件服务器的支持 (#947)
* 增加sendcloud邮件服务器的支持

* 调整代码结构

* Used slince.Contains function

---------

Co-authored-by: shih <shih@knownsec.com>
2025-04-25 18:17:46 +08:00
creamlike1024 b16c05ead4 fix: remove apikey from test channel log, close #1000 2025-04-25 17:08:26 +08:00
CaIon 283d021fa2 refactor: update deepseek beta api 2025-04-25 16:26:16 +08:00
creamlike1024 fea6f17e80 fix: GetMaxUserId use Unscope, close #987 2025-04-25 16:13:11 +08:00
IcedTangerine f77a153e6c Merge pull request #975 from asjfoajs/qn-main
[#969] Refactor: Optimize the request rate limiting for ModelRequestRateLimi…
2025-04-25 11:59:05 +08:00
CaIon c1a0ca0bc7 refactor: update claude media source handling 2025-04-24 15:59:43 +08:00
CaIon 3169bfe362 refactor: update ClaudeMessageSource struct to include optional Url field and adjust media source handling in relay-claude #993 2025-04-24 00:39:09 +08:00
CaIon 2b1470d5f7 f*** gemini 2025-04-19 18:07:51 +08:00
CaIon 91902ce1f4 refactor: enhance SystemSetting submission logic and handle empty WorkerUrl 2025-04-19 00:20:25 +08:00
CaIon fdd349c909 refactor: update GeminiThinkingConfig initialization 2025-04-18 23:13:28 +08:00
CaIon ace677b3e7 refactor: remove unsupported 'exclusiveMinimum' field from cleanFunctionParameters 2025-04-18 22:40:05 +08:00
CaIon 6e2e10adf1 refactor: remove unsupported root-level fields from cleanFunctionParameters 2025-04-18 21:38:12 +08:00
CaIon 1989ae95c1 refactor: streamline value assignment in SettingGeminiModel 2025-04-18 20:08:26 +08:00
CaIon d378543819 feat: add gemini thinking suffix support #981 2025-04-18 19:36:18 +08:00
CaIon 8ead0864b5 refactor: remove reasoning field from GeneralOpenAIRequest struct 2025-04-17 17:11:42 +08:00
CaIon 80e6b9e04e feat: add reasoning field to GeneralOpenAIReques 2025-04-17 17:09:46 +08:00
CaIon b364e9e3b1 refactor: simplify model prefix checks and update message role for o-series models 2025-04-17 16:50:52 +08:00
Apple\Apple 45e8e04d56 🐛fix: Fix the issue where new whitelist email domain names cannot be added in the system settings 2025-04-16 17:11:59 +08:00
霍雨佳 e121e77265 Refactor: Optimize the token bucket algorithm, specifically the New method in common/imiterlimiter.go.
Solution: Remove Redis ping. When printing exceptions, use SysLog to print and add additional logging information.
2025-04-16 16:36:07 +08:00
Apple\Apple c20fbec17b Merge pull request #927 from QuentinHsu/refactor-system-setting
# Conflicts:
#	web/src/App.js
#	web/src/components/ModelSetting.js
#	web/src/components/PersonalSetting.js
#	web/src/components/SystemSetting.js
#	web/src/pages/Channel/EditChannel.js
2025-04-16 16:27:11 +08:00
霍雨佳 5aa2076e18 Refactor: Optimize the request rate limiting for ModelRequestRateLimitCount.
Reason: The original steps 1 and 3 in the redisRateLimitHandler method were not atomic, leading to poor precision under high concurrent requests. For example, with a rate limit set to 60, sending 200 concurrent requests would result in none being blocked, whereas theoretically around 140 should be intercepted.
Solution: I chose not to merge steps 1 and 3 into a single Lua script because a single atomic operation involving read, write, and delete operations could suffer from performance issues under high concurrency. Instead, I implemented a token bucket algorithm to optimize this, reducing the atomic operation to just read and write steps while significantly decreasing the memory footprint.
2025-04-16 10:33:43 +08:00
CaIon 50bf4a6615 refactor: remove unused mutex from RelayInfo struct 2025-04-15 23:06:32 +08:00
CaIon 115c85390f fix: claude parallel function calling 2025-04-15 04:52:33 +08:00
CaIon effa523a54 feat: support gemini output text and inline images. (close #866) 2025-04-15 02:32:51 +08:00
CaIon 44a14ced01 fix: try to fix claude to openai format mcp #966 2025-04-15 01:16:06 +08:00
Calcium-Ion 12ca7c4789 Merge pull request #967 from neotf/fix-01
fix: wrong field for Claude (OpenAI Upstream)
2025-04-15 00:05:41 +08:00
CaIon e3b262da1d feat: 添加流模式下的SSE保活机制 #945 2025-04-14 19:40:23 +08:00
neotf 935cc1c605 fix: wrong systemStr for Claude (OpenAI Upstream) 2025-04-14 01:09:02 +08:00
CaIon da86db0d46 fix: update model name handling in UI and localization 2025-04-12 17:44:29 +08:00
jasonzeng 75f0cb8eb0 feat: add error logging functionality to relay and update logs table for error type display 2025-04-12 00:43:34 +08:00
CaIon f970a03986 fix: xAI usage 2025-04-11 23:31:32 +08:00
CaIon 74d9bb1a12 feat: enhance Claude to OpenAI request conversion with additional relay info support 2025-04-11 19:13:38 +08:00
CaIon 9e4506ebaf feat: 完善openai转claude支持 2025-04-11 18:28:50 +08:00
CaIon c94f662829 chore: update .gitignore and docker-compose.yml to include tiktoken_cache directory 2025-04-11 16:24:27 +08:00
CaIon 577b18a1a1 feat: enhance file handling and logging in the application 2025-04-11 16:23:54 +08:00
CaIon 7e0d4cd055 refactor: move maxFileSize variable inside GetFileBase64FromUrl function 2025-04-11 15:53:23 +08:00
CaIon 95f0ed1821 feat: implement parameter cleaning for Gemini functions 2025-04-10 22:35:03 +08:00
CaIon 984f91d111 feat: support zhipu_4v embeddings path 2025-04-10 20:53:51 +08:00
Calcium-Ion cc219801f8 Merge pull request #959 from Praying/main
fix(relay): 优化数据流处理
2025-04-10 17:21:55 +08:00
CaIon 60a9bd45c6 feat: add xAI handling and response processing 2025-04-10 17:20:59 +08:00
quran 5bcfb8507d fix(relay): 优化数据流处理
- 移除了 bufio 的无效使用
- 在 StreamScannerHandler 中增加了初始和最大缓冲区大小的常量设置
- 调整 StreamScannerHandler 中缓冲区大小,避免出现token too long报错
2025-04-10 16:56:16 +08:00
Calcium-Ion b40bf7c812 Merge pull request #953 from wkxu/main
fix: .env文件配置DEBUG=true等参数不起作用的fix
2025-04-10 16:14:11 +08:00
Calcium-Ion 55ec0e5424 Merge pull request #956 from HynoR/feat/xai
feat: add xAI channel
2025-04-10 16:13:48 +08:00
HynoR cdd58282c2 feat: update adaptor methods and add new image model 2025-04-10 15:08:12 +08:00
HynoR 5c1386af2c feat: add xai grok-3-mini reasoning effort 2025-04-10 13:31:43 +08:00
HynoR 4622c17c83 feat: add xai channel
feat: add xai channel

feat: add xai channel
2025-04-10 13:04:43 +08:00
wkxu 54c5c3118b refactor: 把common/instants.go里的从Getenv获取的参数,放到init.go的LoadEnv函数里获取
把constant/env.go里的从Getenv获取的参数,放到env.go的InitEnv函数里获取。以避免.env文件配置参数不起作用的情况
2025-04-10 09:02:19 +08:00
Calcium-Ion 3f58fcb4f3 Merge pull request #944 from lamcodes/main
Update: Gemini channel fetch_models
2025-04-10 00:09:54 +08:00
CaIon 32db85607e fix: Update model ratios for gemini-2.5-pro 2025-04-10 00:09:11 +08:00
CaIon ed855e78f7 refactor: Remove duplicate model settings initialization in main function 2025-04-10 00:07:34 +08:00
CaIon b4889878d0 refactor: Update localization keys for API address in English translations and adjust related UI labels 2025-04-09 22:22:19 +08:00
datehoer 7566ae9b3e Add support for grok-2-image. Currently, grok-2-image doesn't support the size, quality, or style parameters. Set 'size'='empty' to use grok-2-image 2025-04-09 15:05:00 +08:00
zkp 20c47c12b4 Update: Gemini channel fetch_models 2025-04-08 22:43:13 +08:00
CaIon ac06e9a46f feat: Add CheckSetup function call in main to ensure proper initialization #942 2025-04-08 18:14:36 +08:00
Calcium-Ion 97be874cf5 Merge pull request #930 from Yiffyi/main
fix: save OIDC settings
2025-04-08 17:39:42 +08:00
CaIon c40404a8c1 Update MaxTokens for gemini model to 300 in test request 2025-04-08 17:37:25 +08:00
Calcium-Ion ba9cd9314b Merge pull request #936 from lamcodes/main
fix: gemini test MaxTokens
2025-04-08 17:33:31 +08:00
Calcium-Ion 950572b02e Set MaxTokens to 50 for gemini 2025-04-08 17:33:10 +08:00
CaIon d85e25fc46 feat: Integrate SetupCheck component for improved setup validation in routing 2025-04-08 17:31:46 +08:00
CaIon 9dc851d6ef feat: Initialize model settings and improve concurrency control in operation settings 2025-04-07 22:20:47 +08:00
CaIon 9be721ee29 feat: Add concurrency control to group ratio management with mutexes 2025-04-07 21:55:54 +08:00
zkp 93d9f141fe fix: gemini test MaxTokens 2025-04-06 23:24:47 +08:00
Yiffyi Jia 3d5e93d6a2 fix: cannot save OIDC settings 2025-04-05 04:24:38 +00:00
CaIon 69d790b47a Update model-ratio.go 2025-04-04 23:43:14 +08:00
CaIon 1e95160293 Update model-ratio.go 2025-04-04 23:41:41 +08:00
CaIon eca18ce25c fix: Improve setup check logic and logging for system initialization 2025-04-04 21:27:24 +08:00
QuentinHsu 48fe64ebff refactor(web): systemSetting component to enhance UI structure and add new configuration options
- Wrapped form sections in Card components for better visual separation
- Added new configuration options for payment settings, email domain whitelist, SMTP, OIDC, GitHub OAuth, Linux DO OAuth, WeChat, and Telegram
- Improved layout with responsive design using Row and Col components
- Updated button actions for saving settings in new sections
2025-04-04 17:46:34 +08:00
QuentinHsu 775b1c458b style(web): format code 2025-04-04 17:37:27 +08:00
CaIon ad7a64e585 Update model-ratio.go 2025-04-04 00:31:24 +08:00
CaIon d8b8a44c9c feat: Enhance ModelSettingsVisualEditor with pricing modes and improved model management features 2025-04-03 20:42:08 +08:00
CaIon c2a37d83a7 feat: Add new localization strings for system initialization 2025-04-03 19:27:25 +08:00
CaIon a1f6781c0e fix: Update option key from SelfUseModeEnabled to DemoSiteEnabled in PostSetup function 2025-04-03 19:21:53 +08:00
CaIon 88f7c0670f feat: Add timestamp and version to setup initialization in PostSetup function 2025-04-03 19:16:17 +08:00
CaIon c08edc315d fix: Correct option key for SelfUseModeEnabled in setup controller 2025-04-03 19:15:04 +08:00
CaIon 683accf05b Merge remote-tracking branch 'origin/main' 2025-04-03 19:09:26 +08:00
CaIon d8c10dcb51 Update README.md 2025-04-03 19:09:13 +08:00
Calcium-Ion 3e55cbcda2 Merge pull request #925 from Calcium-Ion/setup
 feat: Implement system setup functionality
2025-04-03 19:01:45 +08:00
CaIon 07d31760da feat: Refine personal mode description in setup page for clarity 2025-04-03 19:01:16 +08:00
CaIon f33ebc8e2c feat: Implement system setup functionality 2025-04-03 18:57:15 +08:00
CaIon 1de0d9123b Merge remote-tracking branch 'origin/main' 2025-04-03 17:33:03 +08:00
CaIon f0925dc105 feat: Enhance user settings and notification options 2025-04-03 17:32:48 +08:00
Calcium-Ion d9223147f2 Merge pull request #909 from jasinliu/feature/fix-dify-thinking
feat: fix dify thinking
2025-04-03 16:23:12 +08:00
Calcium-Ion f502e49c96 Merge pull request #893 from wizcas/replace-linux-do-icon
替换登录界面的 Linux.do OAuth 图标
2025-03-31 22:38:41 +08:00
Calcium-Ion 28fffb30a4 Merge pull request #895 from Feiyuyu0503/main
docs: fix a typo
2025-03-31 22:38:25 +08:00
Calcium-Ion fbc33e9a18 Merge pull request #912 from OrdinarySF/main
fix: fixed bug where target.id was null when clicking 'x' icon
2025-03-31 22:38:08 +08:00
Calcium-Ion fd31803f9b Merge pull request #914 from JoeyLearnsToCode/main
feat: Add Parameters Override
2025-03-31 22:37:26 +08:00
Calcium-Ion 9f34ea059c Merge pull request #916 from xifan2333/fix/systemSettingsUI
 feat: Update option handling in SystemSetting
2025-03-31 22:36:14 +08:00
xifan f5b4316213 feat: Update option handling in SystemSetting
-  Add backend validation for OIDC & Telegram OAuth config
- ♻️ Refactor frontend option updates with batch processing
2025-03-31 00:46:13 +08:00
JoeyLearnsToCode f00ac5f27b feat: Add Parameters Override 2025-03-29 14:39:39 +08:00
Ordinary 9c68911a68 refactor: use handleFieldChange function on change event 2025-03-28 12:44:40 +00:00
Ordinary f8efe264f7 fix: fixed bug where target.id was null when clicking 'x' icon 2025-03-28 12:43:26 +00:00
jasinliu 111cedf795 fix dify thinking 2025-03-28 00:21:27 +08:00
1808837298@qq.com 638950c230 feat: Add new cache ratios for o3-mini and gpt-4.5-preview models 2025-03-27 18:47:50 +08:00
1808837298@qq.com 3b047b18fd update model ratio 2025-03-27 17:02:09 +08:00
1808837298@qq.com a696cf5832 feat: Enhance GetCompletionRatio function 2025-03-27 16:38:29 +08:00
1808837298@qq.com 29ef130794 update model ratio 2025-03-27 16:24:30 +08:00
feiyuyu 93087f3e9e docs: fix a typo 2025-03-22 21:28:25 +08:00
Wizcas Chen 5809b174bd replace the linuxdo icon in the login form 2025-03-22 17:16:07 +08:00
Calcium-Ion cd2c8fd5ab Merge pull request #886 from seefs001/main
fix: claude function calling type
2025-03-20 23:22:20 +08:00
Seefs f501a3e92e fix: claude function calling type 2025-03-19 22:48:49 +08:00
1808837298@qq.com 18b5122a9b fix: Adjust MaxTokens logic for non-Claude models in test request 2025-03-17 23:44:32 +08:00
1808837298@qq.com 6e17d31e92 feat: Add support for cross-region AWS model handling in awsStreamHandler 2025-03-17 23:41:00 +08:00
1808837298@qq.com 5e06085744 refactor: Improve token quota consumption logic 2025-03-17 17:52:54 +08:00
1808837298@qq.com 9b2cc6add7 feat: Enhance ConvertClaudeRequest method to set request model and handle vertex-specific request conversion 2025-03-17 17:13:33 +08:00
1808837298@qq.com 4f6167243f feat: Update RerankerInfo structure and modify GenRelayInfoRerank function to accept RerankRequest 2025-03-17 16:44:53 +08:00
Calcium-Ion eafbfac6a0 Merge pull request #872 from neotf/main
feat: support AWS Model CrossRegion
2025-03-17 16:18:11 +08:00
1808837298@qq.com 9b85cb2371 refactor: Update ClaudeResponse error handling to use pointer for ClaudeError and improve nil checks in response processing 2025-03-16 23:14:45 +08:00
1808837298@qq.com 08db1449d6 Update README 2025-03-16 21:53:00 +08:00
1808837298@qq.com 2db59995ad Update README 2025-03-16 21:47:32 +08:00
1808837298@qq.com aa04c573dc Update README 2025-03-16 21:17:08 +08:00
1808837298@qq.com 8918381c96 feat: support xinference rerank to jina format 2025-03-16 21:06:29 +08:00
1808837298@qq.com 6e8916207e refactor: Enhance Claude response handling 2025-03-16 19:11:58 +08:00
1808837298@qq.com 62dc82638d feat: Introduce JSON decoding utility functions and update error handling in Claude and OpenAI response structures 2025-03-16 18:34:39 +08:00
1808837298@qq.com 2bfd471ff0 Merge remote-tracking branch 'origin/main' 2025-03-16 16:48:15 +08:00
1808837298@qq.com 9b6d92601a refactor: Enhance error handling in AWS and Claude response processing by updating function signatures and improving error propagation 2025-03-16 16:47:16 +08:00
Calcium-Ion 65ba4a6910 Merge pull request #851 from HynoR/main
Fix: 修正DeepSeek缓存倍率
2025-03-16 16:31:48 +08:00
1808837298@qq.com cc406e4fad refactor: Streamline AWS and Claude response handling by consolidating logic and improving error management 2025-03-16 16:07:51 +08:00
Calcium-Ion e951d55247 Merge pull request #874 from HynoR/feat/gemini2
Chore: Sync Cohere Latest Model
2025-03-15 19:44:37 +08:00
1808837298@qq.com 921ad4530e refactor: Replace direct access to ImageUrl with GetImageMedia method across multiple relay channels 2025-03-15 19:43:37 +08:00
1808837298@qq.com 476cb6f20f feat: Add warning modal for base URL input and display warning banner for specific channel type in EditChannel component 2025-03-15 19:38:05 +08:00
1808837298@qq.com b93827c425 feat: support dify upload image file 2025-03-15 19:10:12 +08:00
TAKO adbfdd0150 Sync Cohere Latest Model 2025-03-15 12:12:46 +08:00
TAKO 9ad40f382c Merge branch 'main' into main 2025-03-15 12:08:44 +08:00
neotf ac9bd53098 feat: support AWS Model CrossRegion 2025-03-15 01:42:24 +08:00
1808837298@qq.com b869cec78b refactor: Change ClaudeError field type to non-pointer and enhance response handling with reasoning content 2025-03-14 17:48:26 +08:00
CalciumIon 5021000c5d refactor: Simplify OpenAI handler function signature and remove unused TextResponseWithError struct; introduce common_handler for rerank functionality 2025-03-14 17:31:05 +08:00
CalciumIon d3cdbd2fac feat: Add HasSentThinkingContent field to ThinkingContentInfo struct 2025-03-14 17:09:40 +08:00
Calcium-Ion 6d5abaa404 Merge pull request #867 from Sh1n3zZ/wrong-think-label-fix
fix: wrong thinking labels appear in non-thinking models (#861)
2025-03-14 16:59:56 +08:00
CalciumIon 15c5fbd3c8 refactor: Update token usage calculation in FormatClaudeResponseInfo #865 2025-03-14 17:00:39 +08:00
Sh1n3zZ 7513065760 fix: wrong thinking labels appear in non-thinking models (#861) 2025-03-14 03:13:52 +08:00
1808837298@qq.com 25aaefc6b1 chore: Update GitHub Actions workflows and refactor adaptor logic for Docker image builds 2025-03-13 21:10:39 +08:00
Calcium-Ion b5d644d56c Merge pull request #857 from asjfoajs/main
Refactor: Optimize the ImageHandler under the Alibaba large model to …
2025-03-13 19:51:08 +08:00
1808837298@qq.com 47c297794d feat: 初步兼容流模式下openai渠道类型转为claude格式访问 #862 2025-03-13 19:32:08 +08:00
霍雨佳 63aa548cdf Refactor: Optimize the ImageHandler under the Alibaba large model to retrieve the key from the header.
Reason: The info parameter already includes the key, so there is no need to retrieve it again from the header.
Solution: Delete the code for obtaining the key and directly use info.ApiKey.
2025-03-13 08:54:45 +08:00
Calcium-Ion 5f1bffebae Update README.md 2025-03-12 22:22:21 +08:00
Calcium-Ion d33eccd61f Update README.md 2025-03-12 22:13:35 +08:00
Calcium-Ion af048e44d1 Update README.md 2025-03-12 22:12:09 +08:00
Calcium-Ion 2792061138 Merge pull request #854 from seefs001/main
feat: Support postgresql:// dsn format
2025-03-12 21:36:30 +08:00
Calcium-Ion 0d70355f56 Merge pull request #855 from Calcium-Ion/claude
feat: claude relay
2025-03-12 21:36:11 +08:00
1808837298@qq.com cbc4b3a9e7 fix panic 2025-03-12 21:35:57 +08:00
1808837298@qq.com 78fc3a191c feat: claude relay 2025-03-12 21:31:46 +08:00
Seefs 884e25325f feat: Support postgresql:// dsn format 2025-03-12 21:08:47 +08:00
1808837298@qq.com 8f2412fb79 fix: claude to openai tools use 2025-03-12 19:46:08 +08:00
1808837298@qq.com 6cb9001ff3 fix: claude to openai tools use 2025-03-12 19:29:15 +08:00
1808837298@qq.com d9a6a2db87 fix: claude to openai tools use 2025-03-12 18:53:38 +08:00
1808837298@qq.com 03de7e2ea1 Merge remote-tracking branch 'origin/main' 2025-03-12 17:53:52 +08:00
1808837298@qq.com 1800e0ae9e feat(relay): Add Xinference channel support 2025-03-12 17:53:46 +08:00
TAKO 210d88862d Fix Deepseek Cache Ratio 2025-03-12 10:51:12 +08:00
Calcium-Ion f4cf7c8d43 Merge pull request #848 from wzxjohn/feature/oidc
feat: add oidc support
2025-03-11 23:20:55 +08:00
1808837298@qq.com a280feeae0 fix: Add error logging for OIDC configuration retrieval 2025-03-11 23:20:27 +08:00
1808837298@qq.com 30d9f433f1 refactor: Update OIDC status check to use oidc_enabled flag 2025-03-11 22:36:31 +08:00
1808837298@qq.com 3ede51a9a7 refactor: Remove OIDC configuration from option initialization 2025-03-11 22:03:20 +08:00
1808837298@qq.com 9f3cc03508 refactor: Migrate OIDC configuration to system settings 2025-03-11 22:00:31 +08:00
1808837298@qq.com 215e768caf feat(ui): Improve model testing button layout and styling 2025-03-11 21:22:10 +08:00
1808837298@qq.com 0db072de86 feat(error): Enhance error handling with optional detailed error messages 2025-03-11 17:25:06 +08:00
1808837298@qq.com ba696b33dc feat(relay): Add pass-through request option for global settings 2025-03-11 17:02:35 +08:00
1808837298@qq.com e1130c3e94 Merge remote-tracking branch 'origin/main' 2025-03-11 16:41:18 +08:00
Calcium-Ion 31616c1225 Merge pull request #849 from OrdinarySF/main
feat(setting): add 'Document Link' option i18n support
2025-03-11 16:27:37 +08:00
Ordinary e72ed6a9ba feat(setting): add 'Document Link' option i18n support 2025-03-11 08:22:59 +00:00
wzxjohn bdb1a2fcb9 feat: add oidc support 2025-03-11 15:52:03 +08:00
1808837298@qq.com 78cc82dca7 fix: Improve mobile layout and scrolling behavior 2025-03-11 15:05:23 +08:00
1808837298@qq.com dbcbe3ffd5 Merge remote-tracking branch 'origin/main' 2025-03-11 14:55:56 +08:00
1808837298@qq.com f8bd6da813 feat: Improve route handling and dynamic chat navigation in SiderBar 2025-03-11 14:55:48 +08:00
Calcium-Ion b1eff818ad Merge pull request #845 from Sh1n3zZ/gemini-embedding
feat: gemini Embeddings support
2025-03-10 23:46:53 +08:00
Sh1n3zZ 9a878df8c0 feat: gemini Embeddings support 2025-03-10 23:32:06 +08:00
IllTamer 7f677e85de feat & fix: fix the pricing available sort, set defaultSortOrder descend 2025-03-10 22:39:21 +08:00
IllTamer 56716c16b7 fix: the pricing available popover display anyway 2025-03-10 22:16:02 +08:00
1808837298@qq.com 5c794a362c Merge remote-tracking branch 'origin/main' 2025-03-10 21:05:43 +08:00
1808837298@qq.com 00a16b2d18 refactor: Improve responsive design across multiple setting pages 2025-03-10 21:05:22 +08:00
Calcium-Ion daff23d1d2 Merge pull request #842 from asjfoajs/dev
Fix: Under Ali's large model, the task ID result for image retrieval …
2025-03-10 20:18:53 +08:00
1808837298@qq.com fa1a6cb5a1 refactor: Remove unnecessary transition styles and simplify sidebar state management 2025-03-10 20:14:23 +08:00
1808837298@qq.com e704f0763a refactor: Improve sidebar state management and layout responsiveness 2025-03-10 19:48:17 +08:00
1808837298@qq.com ddd6706c6c feat: Enhance mobile UI responsiveness and layout for ChannelsTable and SiderBar 2025-03-10 19:01:56 +08:00
霍雨佳 6a6e8e22e8 Fix: Under Ali's large model, the task ID result for image retrieval is incorrect.
Reason: The URL is incomplete, missing baseurl.
Solution: Add baseurl. url := fmt.Sprintf("%s/api/v1/tasks/%s", info.BaseUrl, taskID).
2025-03-10 16:22:40 +08:00
1808837298@qq.com e28ad3689c refactor: Improve mobile responsiveness and scrolling behavior in UI layout 2025-03-10 15:49:32 +08:00
1808837298@qq.com 58b8ca90aa refactor: Enhance UI layout and styling with responsive design improvements 2025-03-10 03:25:02 +08:00
1808837298@qq.com f4c5ab7fc2 style: Enhance LogsTable header tags with improved styling and visual hierarchy 2025-03-10 00:34:24 +08:00
1808837298@qq.com abd5de7902 refactor: Make Channel Setting nullable and improve setting handling #836 2025-03-09 23:42:48 +08:00
1808837298@qq.com 8fffa159d0 fix: Correct typo in group_ratio variable name in LogsTable 2025-03-09 21:24:19 +08:00
1808837298@qq.com c0aa19c58a fix: Add optional chaining to prevent potential undefined errors in LogsTable #833 2025-03-09 21:23:33 +08:00
1808837298@qq.com 6f9412ead3 feat: Introduce configurable docs link and remove hardcoded chat links
- Added a new GeneralSetting struct to manage configurable docs link
- Removed hardcoded ChatLink and ChatLink2 variables across multiple files
- Updated frontend components to dynamically render docs link from status
- Simplified chat and link-related logic in various components
- Added a warning modal for quota per unit setting in operation settings
2025-03-09 18:31:16 +08:00
1808837298@qq.com 61da6c3664 fix: Refine embedding model detection in channel test 2025-03-09 15:03:07 +08:00
1808837298@qq.com 98e2443563 refactor: Improve price rendering with clearer token and price calculations 2025-03-08 23:47:02 +08:00
Calcium-Ion 47646487b8 Merge pull request #830 from Calcium-Ion/decimal
feat: Improve decimal precision for quota and payment calculationsDecimal
2025-03-08 22:01:15 +08:00
1808837298@qq.com a92d9b12f8 refactor: Update topup amount type from int to int64 for improved precision 2025-03-08 21:59:18 +08:00
1808837298@qq.com 0876bc3f7f feat: Improve decimal precision for quota and payment calculations
- Added github.com/shopspring/decimal for precise floating-point calculations
- Refactored quota and payment calculations in multiple files to use decimal arithmetic
- Updated go.mod and go.sum to include decimal library
- Improved precision in topup, relay, and quota service calculations
- Added support for more OpenAI model variants in cache ratio settings
2025-03-08 21:55:50 +08:00
Calcium-Ion 1b9246f788 Merge pull request #828 from Calcium-Ion/ui
feat: Add column visibility settings for Channels and Logs tables
2025-03-08 19:55:28 +08:00
1808837298@qq.com f8a8dbec6e feat: Add column visibility settings for Channels and Logs tables
- Implemented dynamic column visibility for ChannelsTable and LogsTable
- Added localStorage persistence for column preferences
- Introduced column selector modal with select all/reset functionality
- Supported role-based default column visibility
- Added column settings button to table interfaces
2025-03-08 19:53:07 +08:00
1808837298@qq.com 47a92439fc refactor: Simplify chat menu items rendering in SiderBar 2025-03-08 19:06:49 +08:00
1808837298@qq.com 69e6d5ce51 feat: update readme and i18n 2025-03-08 18:13:44 +08:00
Calcium-Ion 32c09c26cb Merge pull request #826 from Calcium-Ion/cache
feat: Add prompt cache hit tokens support for DeepSeek channel #406
2025-03-08 16:52:19 +08:00
1808837298@qq.com f939c28b7a fix: Adjust DeepSeek cache ratio to 0.1 2025-03-08 16:51:43 +08:00
1808837298@qq.com a36d6de8d1 feat: Add prompt cache hit tokens support for DeepSeek channel #406 2025-03-08 16:50:53 +08:00
1808837298@qq.com 5bed29e2bd refactor: Improve quota calculation precision using floating-point arithmetic 2025-03-08 16:44:08 +08:00
Calcium-Ion 14959944fb Merge pull request #821 from Calcium-Ion/cache
chore: Update terminology from "cache ratio" to "cache multiplier" in UI and add placeholder for default create cache ratio
2025-03-08 02:49:21 +08:00
1808837298@qq.com 314878ff20 fix: Update default cache ratio from 0.5 to 1 2025-03-08 02:47:41 +08:00
1808837298@qq.com ec818773f5 chore: Update terminology from "cache ratio" to "cache multiplier" in UI and add placeholder for default create cache ratio 2025-03-08 02:44:09 +08:00
Calcium-Ion f9b17f1fa9 Merge pull request #820 from Calcium-Ion/cache
feat: Implement cache token ratio for more precise token pricing
2025-03-08 01:31:44 +08:00
1808837298@qq.com b640118d74 feat: Implement cache token ratio for more precise token pricing 2025-03-08 01:30:50 +08:00
1808837298@qq.com 7e4474e59b refactor: Remove redundant user quota retrieval in audio relay 2025-03-07 19:59:00 +08:00
Calcium-Ion 0787083310 Merge pull request #815 from Sh1n3zZ/openrouter-adapter
fix: adapting return format for openrouter think content (#793)
2025-03-07 19:25:20 +08:00
1808837298@qq.com 5aa67994b1 refactor: Reorganize sidebar navigation and add personal settings route 2025-03-07 17:22:37 +08:00
Sh1n3zZ c1ed9d552e fix: possible incomplete return of the think field and incorrect occurrences of the reasoning field 2025-03-06 19:20:29 +08:00
Sh1n3zZ aa6c894f56 fix: adapting return format for openrouter think content (#793) 2025-03-06 19:16:26 +08:00
1808837298@qq.com 783c60ee4d feat: Enhance channel status update with success tracking and dynamic notification #812 2025-03-06 17:46:03 +08:00
1808837298@qq.com 063618e256 fix: Handle error in NotifyRootUser and log system errors #812 2025-03-06 17:25:39 +08:00
1808837298@qq.com fa20af817f refactor: Improve model request rate limit middleware execution 2025-03-06 16:32:11 +08:00
1808837298@qq.com 7cec111b1f fix: error NotifyRootUser #812 2025-03-06 15:56:42 +08:00
1808837298@qq.com c4ca4af8ce fix: Prevent resource leaks by adding body close in stream handlers 2025-03-05 19:51:22 +08:00
1808837298@qq.com d647214555 refactor: Centralize stream handling and helper functions in relay package 2025-03-05 19:47:41 +08:00
1808837298@qq.com 3f824d7781 Update README.md 2025-03-05 16:55:17 +08:00
1808837298@qq.com 8b52803910 fix: vertex claude 2025-03-05 16:43:40 +08:00
1808837298@qq.com 208824451a fix: #810 2025-03-05 16:39:42 +08:00
1808837298@qq.com 62542b0b84 fix: #810 2025-03-05 16:34:08 +08:00
1808837298@qq.com da79af07d2 refactor: Extract operation-related settings into a separate package 2025-03-04 18:52:08 +08:00
1808837298@qq.com 6f64e6cde9 Update README.md 2025-03-04 18:50:05 +08:00
1808837298@qq.com efc7c4d7f9 feat: Add context-aware goroutine pool for safer concurrent operations 2025-03-04 18:42:34 +08:00
1808837298@qq.com 67e17dcdbd fix: Ignore EOF errors in OpenAI stream scanner 2025-03-04 17:35:41 +08:00
1808837298@qq.com 33d171bc91 Merge remote-tracking branch 'origin/main' 2025-03-04 17:11:07 +08:00
1808837298@qq.com b0ee0f1501 fix: Handle scanner errors in OpenAI relay stream handler 2025-03-04 17:10:56 +08:00
Calcium-Ion 2a0a156fc5 Merge pull request #805 from PaperPlaneDeemo/main
Fix: fix typo in README
2025-03-04 16:27:15 +08:00
1808837298@qq.com 894f950e96 fix: vertex claude 2025-03-03 20:06:08 +08:00
1808837298@qq.com d499b6410c feat: Improve image download and validation in GetImageFromUrl 2025-03-03 16:15:04 +08:00
Nekof 4f82f27218 Merge branch 'Calcium-Ion:main' into main 2025-03-03 11:37:40 +08:00
“Deemo” bead5963af fix: Typo in README 2025-03-03 11:35:04 +08:00
1808837298@qq.com 4208ecb92c fix: channel test model mapped 2025-03-02 23:53:10 +08:00
1808837298@qq.com c450fb5c2d feat: yanjingxia 2025-03-02 23:17:37 +08:00
1808837298@qq.com 53b9bce353 feat: Add model testing modal with search functionality in ChannelsTable
- Implement a new modal for selecting and testing models per channel
- Add search functionality to filter models by keyword
- Replace dropdown with direct button for model testing
- Introduce new state variables for managing model test modal
2025-03-02 19:53:35 +08:00
1808837298@qq.com 35cd4fde3a refactor: Add index to Username column in Log model 2025-03-02 17:57:52 +08:00
1808837298@qq.com 3f1e340391 refactor: Update rate limit configuration to use dynamic expiration duration 2025-03-02 17:34:39 +08:00
1808837298@qq.com 574e28bad8 fix: Use channel group in model testing log record 2025-03-02 15:59:39 +08:00
1808837298@qq.com 1665fcee3c refactor: Improve channel testing and model price handling 2025-03-02 15:47:12 +08:00
1808837298@qq.com 90c9c1b587 feat: Persist models expanded state in PersonalSetting component 2025-03-02 01:35:50 +08:00
1808837298@qq.com 6e07286e53 feat: Enhance update checking and system information display
- Add version and startup time display in OtherSetting component
- Implement robust GitHub release update checking mechanism
- Add error handling for update check process
- Update Modal component for displaying update information
- Add new translations for version and system information
2025-03-02 01:31:27 +08:00
1808837298@qq.com 5e22043040 feat: Add self-use mode and demo site mode indicators to HeaderBar 2025-03-02 00:46:54 +08:00
1808837298@qq.com 8c0219ebe5 fix: Correct option map key for PreConsumedQuota 2025-03-01 22:37:14 +08:00
1808837298@qq.com 56a06df41c feat: Add translations for self-use mode and demo site mode settings 2025-03-01 21:15:59 +08:00
1808837298@qq.com 485face2c0 feat: Add self-use mode for model ratio and price configuration
- Introduce `SelfUseModeEnabled` setting to allow flexible model ratio configuration
- Update error handling to provide more informative messages when model ratios are not set
- Modify pricing and relay logic to support self-use mode
- Add UI toggle for enabling self-use mode in operation settings
- Implement fallback mechanism for model ratios when self-use mode is enabled
2025-03-01 21:13:48 +08:00
1808837298@qq.com 5f8e73e297 fix: Enhance error message for missing model ratio configuration 2025-03-01 17:02:31 +08:00
1808837298@qq.com 3f7d9c4b0f fix: Improve error handling for model ratio and price validation #800 2025-03-01 15:27:32 +08:00
1808837298@qq.com 159f39d119 fix: Improve model ratio and price management
- Update error message for missing model ratio to be more user-friendly
- Modify ModelRatioNotSetEditor to filter models without price or ratio
- Enhance model data initialization with fallback values
2025-02-28 23:28:47 +08:00
1808837298@qq.com 0aa30ed3f6 feat: Add new model management features
- Implement `/api/channel/models_enabled` endpoint to retrieve enabled models
- Add `EnabledListModels` handler in controller
- Create new `ModelRatioNotSetEditor` component for managing unset model ratios
- Update router to include new models_enabled route
- Add internationalization support for new model management UI
- Include GPT-4.5 preview model in OpenAI model list
2025-02-28 21:13:30 +08:00
1808837298@qq.com 1e388d9d68 fix 2025-02-28 20:28:44 +08:00
1808837298@qq.com 3447df85c4 feat: add new GPT-4.5 preview model ratios 2025-02-28 19:17:15 +08:00
1808837298@qq.com 86a88f8203 feat: Enhance Claude default max tokens configuration
- Replace ThinkingAdapterMaxTokens with a more flexible DefaultMaxTokens map
- Add support for model-specific default max tokens configuration
- Update relay and web interface to use the new configuration approach
- Implement a fallback mechanism for default max tokens
2025-02-28 17:53:08 +08:00
1808837298@qq.com 29fc0a6b1d feat: Implement model-specific headers configuration for Claude 2025-02-28 16:47:31 +08:00
1808837298@qq.com 6be0914bb6 fix: Simplify Claude settings value conversion logic 2025-02-27 22:26:21 +08:00
1808837298@qq.com 58a9c63657 fix: Prevent duplicate headers in Claude settings 2025-02-27 22:14:53 +08:00
1808837298@qq.com 9a6d84dbd6 refactor: Reorganize Claude MaxTokens configuration UI layout 2025-02-27 22:12:14 +08:00
1808837298@qq.com 96e73ad8e0 feat: Enhance Claude MaxTokens configuration handling
- Update Claude relay to set default MaxTokens dynamically
- Modify web interface to clarify default MaxTokens input purpose
- Improve token configuration logic for thinking adapter models
2025-02-27 22:10:29 +08:00
1808837298@qq.com 71682f1522 fix: Update Claude thinking adapter token percentage input guidance 2025-02-27 20:59:32 +08:00
1808837298@qq.com ed1a1c9b09 fix: Correct model request configuration in Vertex Claude adaptor 2025-02-27 20:51:10 +08:00
1808837298@qq.com 5371af0b42 feat: Refactor model configuration management with new config system
- Introduce a new configuration management approach for model-specific settings
- Update Gemini settings to use the new config system with more flexible management
- Add support for dynamic configuration updates in option handling
- Modify Claude and Vertex adaptors to use new configuration methods
- Enhance web interface to support namespaced configuration keys
2025-02-27 20:49:34 +08:00
1808837298@qq.com fd6ae3ea78 feat: Add Claude model configuration management #791 2025-02-27 20:49:21 +08:00
1808837298@qq.com fe9a3025d1 fix: Add pagination support to user search functionality 2025-02-27 16:55:02 +08:00
1808837298@qq.com bf9f5e59b5 chore: Update Azure OpenAI API version and embedding model detection
- Enhance channel test to detect more embedding models
- Update Azure OpenAI default API version to 2024-12-01-preview
- Remove redundant default API version setting in channel edit
- Add user cache writing in channel test
2025-02-27 16:49:32 +08:00
1808837298@qq.com 2d77733cd3 fix: Improve AWS Claude adaptor request conversion error handling #796 2025-02-27 14:57:00 +08:00
1808837298@qq.com ac00e9bbb3 init openrouter adaptor 2025-02-27 00:01:21 +08:00
1808837298@qq.com 0646fa1892 fix: gemini&claude tool call format #795 #766 2025-02-26 23:56:10 +08:00
1808837298@qq.com 23de62ec0d fix: claude tool call format #795 #766 2025-02-26 23:40:16 +08:00
1808837298@qq.com c3b0e57ea4 feat: Add Jina reranking support for OpenAI adaptor 2025-02-26 21:46:06 +08:00
1808837298@qq.com ce03e77906 fix: Update Gemini safety settings to use 'OFF' as default 2025-02-26 19:20:17 +08:00
1808837298@qq.com 832f4b2b1a fix: Update Gemini safety settings category 2025-02-26 19:18:00 +08:00
1808837298@qq.com 7100c787d4 fix: Update Gemini safety settings default value 2025-02-26 19:01:45 +08:00
1808837298@qq.com 8a30d64a75 feat: Add Gemini version settings configuration support (close #568) 2025-02-26 18:19:09 +08:00
1808837298@qq.com 0a369cc193 feat: Add Gemini safety settings configuration support (close #703) 2025-02-26 16:54:43 +08:00
1808837298@qq.com 5ba44f5ad5 feat: Update Claude relay temperature setting 2025-02-25 22:01:05 +08:00
1808837298@qq.com d04d78a116 refactor: Enhance user context and quota management
- Add new context keys for user-related information
- Modify user cache and authentication middleware to populate context
- Refactor quota and notification services to use context-based user data
- Remove redundant database queries by leveraging context information
- Update various components to use new context-based user retrieval methods
2025-02-25 20:56:16 +08:00
1808837298@qq.com 8c2323d74d feat: redis poolsize 2025-02-25 19:39:29 +08:00
1808837298@qq.com 583678d9ff fix: Adjust Claude thinking mode request parameters 2025-02-25 16:52:45 +08:00
1808837298@qq.com fd38e59f78 docs: Update README 2025-02-25 16:31:42 +08:00
Calcium-Ion f5cbab77cf Merge pull request #788 from MartialBE/main
feat: Add Claude 3.7 Sonnet thinking mode support
2025-02-25 15:21:39 +08:00
1808837298@qq.com d4706d6b8e Merge branch 'main' into thinking
# Conflicts:
#	relay/channel/claude/dto.go
2025-02-25 15:21:22 +08:00
1808837298@qq.com 6c8016e5f8 feat: Add support for Claude thinking parameter in request 2025-02-25 14:37:03 +08:00
MartialBE 7160012fe2 feat: Add Claude 3.7 Sonnet thinking mode support 2025-02-25 14:10:43 +08:00
1808837298@qq.com c62276fcc4 feat: Add Claude 3.7 Sonnet model to AWS channel mapping 2025-02-25 02:55:23 +08:00
1808837298@qq.com 15a3b44689 feat: Add support for Claude 3.7 Sonnet model 2025-02-25 02:51:31 +08:00
1808837298@qq.com 8f3c7280cf feat: Support max_tokens parameter for Ollama channel #782 2025-02-24 17:35:49 +08:00
Calcium-Ion e5e73a33f0 Merge pull request #781 from zeyugao/main
feat: Pass extra_body in OpenAI request to the backend
2025-02-24 16:29:48 +08:00
Calcium-Ion 2d15f63eaa Merge pull request #783 from Calcium-Ion/rate-limit
feat: Add model request rate limiting functionality
2025-02-24 16:29:23 +08:00
1808837298@qq.com 6f3072895a feat: Add model rate limit settings in system configuration 2025-02-24 16:27:20 +08:00
1808837298@qq.com 1763145fea feat: Add model request rate limiting functionality 2025-02-24 16:20:55 +08:00
1808837298@qq.com 66831a1bde feat: Add support for different Dify bot types and request URLs 2025-02-24 14:18:30 +08:00
1808837298@qq.com fd44ac7c0c feat: Enhance token counting and content parsing for messages 2025-02-24 14:18:15 +08:00
Elsa f5bf67c636 Pass extra_body to the backend 2025-02-24 10:52:55 +08:00
1808837298@qq.com 7becf62a7a fix: Improve 429 error logging with detailed message 2025-02-23 21:26:31 +08:00
1808837298@qq.com 40c0333eaa fix typo 2025-02-23 17:27:33 +08:00
1808837298@qq.com 65021e2e0e feat: Add thinking-to-content option in channel extra settings #780 2025-02-23 17:13:08 +08:00
1808837298@qq.com 4597816a14 feat: Add thinking-to-content conversion for stream responses 2025-02-23 17:05:57 +08:00
1808837298@qq.com 991b6f8bb0 fix: mistral 2025-02-22 16:29:48 +08:00
1808837298@qq.com 0333576bee fix: fix image ratio calculation 2025-02-22 15:50:18 +08:00
Calcium-Ion 1c35a2fd0b Merge pull request #778 from utopeadia/main
美化日志界面刷新图标
2025-02-22 15:21:28 +08:00
1808837298@qq.com 7a13f9c99a fix: Ensure correct quota warning threshold type conversion 2025-02-22 15:19:55 +08:00
1808837298@qq.com 1c20d16c49 chore: update rerank.md 2025-02-22 15:13:26 +08:00
HowieWood 4f2d44187d 进一步美化刷新图标 2025-02-22 14:18:25 +08:00
HowieWood d3b8647019 优化日志刷新图标显示 2025-02-22 14:12:49 +08:00
1808837298@qq.com 3eb186825d fix: ShouldDisableChannel 2025-02-22 02:02:03 +08:00
1808837298@qq.com d71315cfa5 fix: mistral adaptor (close #774) 2025-02-21 22:21:19 +08:00
1808837298@qq.com 8101cd3ce3 feat: Add reasoning content support in OpenAI response handling 2025-02-21 18:52:51 +08:00
1808837298@qq.com 4a49d6c795 refactor: Improve message content parsing with robust type handling 2025-02-21 18:27:43 +08:00
1808837298@qq.com 4194f4bd21 refactor: Improve message content handling and quota error responses 2025-02-21 18:18:21 +08:00
1808837298@qq.com e1784f8981 refactor: Optimize sensitive word detection and text processing 2025-02-21 17:05:35 +08:00
1808837298@qq.com 78f9a30c39 feat: Enhance sensitive word detection with detailed logging 2025-02-21 16:57:30 +08:00
1808837298@qq.com 009333da8b refactor: Improve quota error messages with formatted quota display 2025-02-21 16:42:48 +08:00
1808837298@qq.com 23bfc06fd8 feat: Add base URL input with localized tooltip for channel configuration 2025-02-21 16:17:59 +08:00
1808837298@qq.com f64540cd1c feat: Add localization for notification and webhook settings 2025-02-21 15:36:24 +08:00
Calcium-Ion 5020091714 Merge pull request #775 from Calcium-Ion/model_mappping
refactor: Simplify model mapping and pricing logic across relay modules
2025-02-20 16:42:23 +08:00
1808837298@qq.com 1e0b414fc0 refactor: Simplify model mapping and pricing logic across relay modules 2025-02-20 16:41:46 +08:00
1808837298@qq.com 8dbac87e92 fix: Correct Ollama channel authentication header setting 2025-02-20 01:28:15 +08:00
Calcium-Ion db81b21f06 Merge pull request #773 from wellcoming/patch-1
fix: Fix Ollama channel authentication
2025-02-20 01:26:12 +08:00
Coming 368b5fbaa1 fix: Fix Ollama channel authentication 2025-02-20 00:52:30 +08:00
CalciumIon 1f6cb4bd5d feat: Improve mobile text truncation and sidebar visibility 2025-02-19 23:25:42 +08:00
1808837298@qq.com 5ef44f6898 feat: Improve image handling for Ollama channels 2025-02-19 20:45:42 +08:00
1808837298@qq.com 604a5b1ad5 feat: Enhance Ollama channel support with additional request parameters #771 2025-02-19 19:58:34 +08:00
1808837298@qq.com cc61f85bdd fix: Remove redundant error handling in distributor and relay modules 2025-02-19 18:47:28 +08:00
1808837298@qq.com 4ea2556b29 refactor: Replace manual goroutine creation with gopool.Go 2025-02-19 18:38:29 +08:00
Calcium-Ion ff66890dd2 Merge pull request #770 from Calcium-Ion/refactor_notify
feat: Add user notification settings and multiple notification methods
2025-02-19 14:54:54 +07:00
1808837298@qq.com 8a34d61654 chore: update env name and README 2025-02-19 15:54:33 +08:00
1808837298@qq.com 67f02e0a6a docs: Add proxy usage information note in SystemSetting component 2025-02-19 15:45:09 +08:00
1808837298@qq.com 097d02eb8b feat: Implement comprehensive webhook notification system 2025-02-19 15:40:54 +08:00
1808837298@qq.com 1ae0a38485 refactor: Optimize user caching and token retrieval methods 2025-02-19 15:12:26 +08:00
Calcium-Ion 45dd96e717 Merge pull request #768 from lgphone/main
bugfix: 配置文件 .env.example 示例配置错误
2025-02-18 19:35:08 +07:00
lgphone 10311f60e1 Update .env.example
修复示例配置中MySQL的DSN错误问题
2025-02-18 19:18:54 +08:00
Calcium-Ion 869fe957ae Merge pull request #763 from Sh1n3zZ/support-imagen-3.0-generate-002
feat: add Gemini Imagen image generation support
2025-02-18 15:32:32 +07:00
1808837298@qq.com af9d03140c fix: Extend temperature handling for OpenAI-like models
- Add support for suppressing temperature for o1 models
- Expand model prefix check to include 'o1' alongside 'o3' models
2025-02-18 16:00:56 +08:00
1808837298@qq.com 83e161a1d4 refactor: Simplify root user notification and remove global email variable
- Remove global `RootUserEmail` variable
- Modify channel testing and user notification methods to use `GetRootUser()`
- Update user cache and notification service to use more consistent user base type
- Add new channel test notification type
2025-02-18 15:59:17 +08:00
1808837298@qq.com 9a41c04416 feat: Implement notification rate limiting mechanism
- Add in-memory and Redis-based notification rate limiting
- Create configurable hourly notification limits
- Implement notification limit checking for user notifications
- Add environment variables for customizing notification limits
2025-02-18 15:30:43 +08:00
1808837298@qq.com e0ce8bf2b3 refactor: Improve CompletionRatio handling with thread-safe access and initialization 2025-02-18 15:01:43 +08:00
1808837298@qq.com 0fcd243f56 feat: Add user notification settings with quota warning and multiple notification methods
- Implement user notification settings with email and webhook options
- Add new user settings for quota warning threshold and notification preferences
- Create backend API and database support for user notification configuration
- Enhance frontend personal settings with notification configuration UI
- Support custom notification email and webhook URL
- Add service layer for sending user notifications
2025-02-18 14:54:21 +08:00
Sh1n3zZ 873a79f28f feat: add Gemini Imagen image generation support 2025-02-18 01:41:58 +08:00
1808837298@qq.com a353ea3eee Merge remote-tracking branch 'origin/main' 2025-02-17 18:15:13 +08:00
1808837298@qq.com 1cfe06c3d8 feat: Add support for DeepSeek completions endpoint 2025-02-17 18:15:01 +08:00
Calcium-Ion e661578515 Merge pull request #735 from jyc001/main
feat:Add Supoorts to FIM
2025-02-17 14:37:06 +07:00
1808837298@qq.com 65faa158b6 refactor: Optimize channel testing and model menu generation (fix #761) 2025-02-15 19:12:28 +08:00
1808837298@qq.com d43c5d6003 refactor: Improve channel property update mechanism (fix #761) 2025-02-15 15:30:55 +08:00
Calcium-Ion 27a1e49366 Merge pull request #759 from nightcoffee/patch-1
feat: add 火山引擎 support stream options
2025-02-15 14:22:04 +07:00
nightcoffee 3688d8f968 feat: add 火山引擎 support stream options 2025-02-15 04:55:57 +08:00
1808837298@qq.com 2869dbf60a feat: Enhance VolcEngine channel support with bot model routing (fix #757) 2025-02-15 00:10:58 +08:00
1808837298@qq.com 9e2433e2a6 fix: Improve OpenAI stream data parsing and handling 2025-02-14 23:52:25 +08:00
1808837298@qq.com 025b6d1226 feat: Add automatic channel disabling based on configurable keywords
- Introduce AutomaticDisableKeywords setting to dynamically control channel disabling
- Implement AC search for matching error messages against disable keywords
- Add frontend UI for configuring automatic disable keywords
- Update localization with new keyword-based channel disabling feature
- Refactor sensitive word and AC search logic to support multiple keyword lists
2025-02-13 16:39:17 +08:00
1808837298@qq.com 5665b1a58b refactor: Optimize log retrieval with separate channel name fetching (fix #751)
- Remove inline channel join in log queries
- Implement separate channel name lookup for logs
- Improve performance by fetching channel names in a single query
- Ensure channel names are correctly associated with logs
2025-02-12 19:19:13 +08:00
1808837298@qq.com ed888fb717 feat: Add invite link banner for specific channel type 2025-02-12 17:48:48 +08:00
1808837298@qq.com e84d602e17 refactor: Optimize Dockerfile for Go build process
- Use alpine-based Golang image for smaller build size
- Simplify Go build command by removing static linking flag
- Improve Docker multi-stage build configuration
2025-02-12 17:18:23 +08:00
1808837298@qq.com cc5e683784 docs: Update README with detailed Docker deployment and update instructions 2025-02-12 16:54:53 +08:00
1808837298@qq.com 6fe7b15cd0 fix: Update BaseURL placeholder text and label in channel edit page 2025-02-12 15:39:18 +08:00
1808837298@qq.com 61ad1adbda feat: Improve embedding request handling and support across channels
- Update EmbeddingRequest DTO to support more flexible input types
- Add input parsing method to handle various input formats
- Implement ConvertEmbeddingRequest for multiple channel adaptors
- Remove relayMode parameter from EmbeddingHelper
- Add input validation for embedding requests
- Simplify embedding request conversion for different channels
2025-02-12 14:39:36 +08:00
1808837298@qq.com babbbfb346 feat: Add Baidu Qianfan V2 channel support #725
- Update channel constants to include Baidu V2 channel
- Create new Baidu V2 adaptor for relay
- Add Baidu V2 models and channel configuration
- Update relay adaptor to support Baidu V2 channel
- Modify web channel constants to include Baidu V2 option
2025-02-12 00:07:02 +08:00
1808837298@qq.com 4d6bce1ddd feat: Add support for VolcEngine (Doubao) channel #313 #734 2025-02-11 23:47:15 +08:00
Calcium-Ion e514764816 Merge pull request #714 from NitroRCr/main
feat:  添加 AIaW 的聊天链接
2025-02-11 22:17:49 +07:00
Calcium-Ion b21ba28167 Merge pull request #723 from kuwork/main
Support for MokaAI M3E
2025-02-11 22:16:18 +07:00
1808837298@qq.com 86d93c4694 fix: adjust max tokens configuration in test request builder
- Update max tokens default value to 10
2025-02-11 20:00:05 +08:00
1808837298@qq.com 93511cfbf9 feat: enhance OpenAI request and response DTOs
- Add `Prefix` and `ReasoningContent` fields to Message struct
- Add getter and setter methods for `Prefix`
- Make `ToolCall.ID` field optional (fix #749)
2025-02-11 19:54:54 +08:00
1808837298@qq.com 305be19bb2 chore: disable cgo 2025-02-11 18:51:27 +08:00
1808837298@qq.com cb458caefd chore: disable cgo 2025-02-11 18:51:09 +08:00
1808837298@qq.com 0be6c5ee69 chore: replace sqlite lib with prue go lib 2025-02-11 18:34:34 +08:00
1808837298@qq.com 51b13ce038 chore: update CI 2025-02-11 18:23:20 +08:00
1808837298@qq.com 0486041952 update CI 2025-02-11 17:44:54 +08:00
1808837298@qq.com 8f3752c423 feat: enhance session store security and configuration
- Add 30-day max age for session cookies
- Enable HttpOnly flag
- Set SameSite to strict mode
2025-02-11 17:06:51 +08:00
1808837298@qq.com 94c10d8def fix: update session store configuration
- Change session cookie path from "/api" to "/"
- Remove HttpOnly flag
2025-02-11 15:53:15 +08:00
1808837298@qq.com 6a9c0fd2c0 feat: configure session store options for API routes
- Set session cookie path to "/api"
- Disable secure flag for local development
- Enable HttpOnly flag for improved security
2025-02-11 15:45:24 +08:00
Calcium-Ion 14399d6176 Merge pull request #746 from zjjxwhh/main
fix: always use modelMapping in channel test
2025-02-11 12:21:06 +07:00
1808837298@qq.com d4855da092 chore: update CI 2025-02-11 13:14:38 +08:00
zjjxwhh 5bebd3760a fix: always use modelMapping in channel test 2025-02-10 22:39:56 +08:00
1808837298@qq.com 147d0f211b chore: update CI 2025-02-10 21:59:41 +08:00
1808837298@qq.com 6d021b20b7 fix: replace context-based user ID with session-based retrieval #741
- Update user and wechat controllers to use sessions for user ID
- Modify ID retrieval to use `session.Get("id")` instead of `c.GetInt("id")`
- Cast session ID to int when creating user object
2025-02-10 20:52:33 +08:00
1808837298@qq.com e65238a327 fix: CI #744 2025-02-10 20:39:04 +08:00
1808837298@qq.com 451d23a129 Merge remote-tracking branch 'origin/main' 2025-02-10 20:34:11 +08:00
1808837298@qq.com bb1358ca47 refactor: improve SSE response handling in Playground
- Simplify event listener logic for streaming responses
- Add null-safe checks for payload content
- Optimize message generation and completion flow
2025-02-10 20:24:14 +08:00
Calcium-Ion 220946cfc3 Merge pull request #736 from xy3xy3/main
更正硅基流动的SenseVoiceSmall模型名字
2025-02-09 12:23:34 +07:00
Calcium-Ion b7a947ee78 Merge pull request #742 from HynoR/chore/ds
chore: 同步deepseek价格
2025-02-09 12:23:10 +07:00
HynoR efe01aee42 chore: 同步deepseek价格 2025-02-09 12:35:37 +08:00
xy3 30cbcac15a 更正硅基流动的SenseVoiceSmall模型名字 2025-02-08 11:54:08 +08:00
e. 2c255b6598 Merge pull request #2 from jyc001/dev
fix: correct JSON tags for `Prompt` and `Suffix` in `GeneralOpenAIReq…
2025-02-08 00:37:37 +08:00
e. bec1b752d6 fix: correct JSON tags for Prompt and Suffix in GeneralOpenAIRequest 2025-02-08 00:36:42 +08:00
e. ca10ec1a0c Merge pull request #1 from jyc001/dev
Dev
2025-02-08 00:25:49 +08:00
e. cbca378e61 feat: add Suffix to GeneralOpenAIRequest in order to support FIM 2025-02-08 00:25:08 +08:00
e. 02bb1c6580 feat add FIM support for siliconflow 2025-02-08 00:23:35 +08:00
1808837298@qq.com 276f9991c5 fix: channels model_mapping 2025-02-06 19:51:33 +08:00
1808837298@qq.com df848c3a1b fix: update logs table total count display
- Replace `logs.length` with `logCount` in pagination information
- Ensure accurate total log count is displayed in the logs table
2025-02-06 14:56:23 +08:00
Calcium-Ion 4eefb778cf Merge pull request #727 from HynoR/feat/autogemini
chore: 同步gemini模型
2025-02-06 13:43:13 +07:00
1808837298@qq.com c8a8526ff3 feat: modify channel model_mapping column type to TEXT
- Change `ModelMapping` column type from varchar(1024) to TEXT in channels table
- Add MySQL migration script to alter column type during database initialization
- Improve database schema flexibility for storing complex model mappings
2025-02-06 14:35:14 +08:00
HynoR f062c3596d chore: sync gemini aistudio model 2025-02-06 13:32:19 +08:00
kuwork fdebb6e6e8 Merge branch 'main' into main 2025-02-04 22:52:37 +08:00
1808837298@qq.com 906516fb90 feat: add SOCKS5 proxy authentication support
- Enhance `NewProxyHttpClient` to handle SOCKS5 proxy authentication
- Extract username and password from proxy URL for SOCKS5 proxy configuration
- Provide optional authentication for SOCKS5 proxy connections
2025-02-04 18:10:25 +08:00
1808837298@qq.com 881be9a3ec feat: add demo site configuration flag
- Introduce `DemoSiteEnabled` variable in operation settings
- Provide a configurable flag to enable/disable demo site functionality
2025-02-04 14:15:01 +08:00
1808837298@qq.com 80f60109cf feat: add Azure default API version configuration
- Introduce `AZURE_DEFAULT_API_VERSION` environment variable
- Set default Azure API version to `2024-12-01-preview`
- Update README documentation for new environment configuration
- Modify Azure channel relay to use default API version when not specified
2025-02-03 22:38:23 +08:00
1808837298@qq.com c2702a7125 feat: enhance model name handling and logging
- Add `RecodeModelName` to `RelayInfo` struct for more flexible model name tracking
- Update text relay and quota consumption to use `RecodeModelName`
- Move reasoning effort from admin info to other info in log generation
- Ensure consistent model name handling across relay components
2025-02-03 15:06:46 +08:00
1808837298@qq.com e641fb346e feat: add reasoning effort logging and display
- Add `ReasoningEffort` field to `RelayInfo` struct
- Update log generation to include reasoning effort in admin info
- Modify logs table component to display reasoning effort when available
- Preserve reasoning effort information during request processing
2025-02-03 14:44:40 +08:00
1808837298@qq.com 587ed2afae fix: improve reasoning effort model suffix handling
- Remove model name suffixes after extracting reasoning effort
- Update upstream model name to reflect the base model
- Ensure clean model name is passed to the upstream service
2025-02-03 14:34:00 +08:00
1808837298@qq.com b010700391 fix: update reasoning effort model suffix parsing
- Modify model suffix parsing to use hyphen-separated suffixes
- Ensure consistent parsing of `-high`, `-medium`, and `-low` reasoning effort indicators
2025-02-03 14:23:26 +08:00
1808837298@qq.com 0b2585ed52 feat: add reasoning effort configuration for models
- Support setting reasoning effort via model name suffix
- Add `-high`, `-medium`, and `-low` suffixes to control reasoning effort
- Update README with new model configuration option
- Modify OpenAI adaptor to handle reasoning effort settings
2025-02-03 14:22:34 +08:00
1808837298@qq.com 14f6ba91eb feat: add other_setting docs link 2025-02-02 22:18:37 +08:00
1808837298@qq.com 5c8af189eb feat: support channel request proxy 2025-02-02 22:15:06 +08:00
1808837298@qq.com c81e23b0e4 f*** o3-mini 2025-02-01 14:11:34 +08:00
1808837298@qq.com 709dd8fc40 Merge remote-tracking branch 'origin/main' 2025-02-01 13:41:38 +08:00
1808837298@qq.com 4131304c1f feat: add support for o3-mini models in model ratio and request handling 2025-02-01 13:41:25 +08:00
Calcium-Ion 4d862f10af Merge pull request #694 from yinuan-i/main
feat: 新增渠道管理与模型列表获取
2025-01-27 12:34:57 +07:00
1808837298@qq.com 4470ef97f2 fix: clear channel name in user logs 2025-01-27 13:31:24 +08:00
1808837298@qq.com b6bae9dc3a feat: enhance model ratio lookup with case-insensitive and direct matching 2025-01-26 16:07:41 +08:00
1808837298@qq.com 2993ce37a9 fix: update DeepSeek reasoner model ratio check 2025-01-25 23:09:14 +08:00
Calcium-Ion 56c39ef135 Merge pull request #715 from seefs001/main 2025-01-25 13:00:23 +07:00
Seefs ae94a74e87 Merge remote-tracking branch 'origin/main' 2025-01-25 12:59:28 +07:00
Seefs 68bdb7002d fix: display docker build error 2025-01-25 12:58:08 +07:00
Seefs 761e2fb7cc Merge branch 'Calcium-Ion:main' into main 2025-01-25 12:56:08 +07:00
Seefs aab25415ab fix: remove ffmpeg-tools 2025-01-25 12:55:40 +07:00
Calcium-Ion 01f4a5ff9b Merge pull request #713 from seefs001/main 2025-01-25 12:55:01 +07:00
NitroRCr 259e1ba3e2 feat: add chat link for AIaW 2025-01-25 11:57:54 +08:00
Seefs b96133eaf7 fix: log filename format 2025-01-24 21:09:54 +07:00
Jerry fcc32ffbc9 Fix M3E not working 2025-01-23 05:54:39 +08:00
1808837298@qq.com 7d402cff8c chore: update Node.js version in CI workflows from 16 to 18 2025-01-22 13:47:41 +08:00
Calcium-Ion f2e1447cc6 Merge pull request #710 from hubutui/main
Fix temperature not being set to 0 due to json omitempty
2025-01-22 12:44:48 +07:00
1808837298@qq.com a2867cceb8 chore: add ffmpeg-tools to Dockerfile for enhanced multimedia processing 2025-01-22 13:41:46 +08:00
1808837298@qq.com 1ae20bdd89 refactor: update log queries to explicitly reference 'logs' table for clarity and consistency 2025-01-22 13:37:32 +08:00
Jerry 215846bf73 fix : chanel test did not refresh 2025-01-22 13:16:06 +08:00
H. 675cb9b7f3 Merge branch 'Calcium-Ion:main' into main 2025-01-22 13:12:14 +08:00
Jerry 8e2059b898 Support for MokaAI M3E 2025-01-22 04:21:08 +08:00
1808837298@qq.com 71c75b4ea2 CI: update workflows 2025-01-21 16:24:07 +08:00
Butui Hu 6e710f3210 Fix temperature not being set to 0 due to json omitempty
The issue was caused by the `omitempty` tag in the Go struct, which prevented the `temperature` field from being included in the JSON output when it was set to 0.

Signed-off-by: Butui Hu <hot123tea123@gmail.com>
2025-01-21 12:54:09 +08:00
Calcium-Ion 7df2e3c80b Merge pull request #705 from maranello-o/main
fix: incorrect whisper audio usage
2025-01-21 11:21:04 +07:00
Calcium-Ion dd9ddbe75a Merge pull request #699 from detecti1/feat/show-log-with-channel-name
Feat: 日志查询增加渠道名称显示
2025-01-21 11:17:13 +07:00
Calcium-Ion 57d867f0b7 Merge pull request #709 from HynoR/feat/update-ratio
feat: 更新模型和模型倍率
2025-01-21 11:16:04 +07:00
HynoR 09bd33f0a5 feat: 更新模型和模型倍率 2025-01-21 00:53:10 +08:00
沈浩 dab21d5263 fix: incorrect whisper audio usage 2025-01-17 18:12:05 +08:00
H. 67da837763 Merge branch 'Calcium-Ion:main' into main 2025-01-13 13:42:30 +08:00
Lilo 37226d6589 Fix JSON parsing error when record.other is empty string 2025-01-09 17:07:28 +08:00
Lilo a335d9e1f4 Add channel name (tooltip / detail) to logs 2025-01-09 17:07:28 +08:00
1808837298@qq.com b604cab599 Update IP restriction messages for clarity in English localization and placeholder text in EditToken component. Enhanced user guidance by specifying that leaving the IP field blank means no restrictions. 2025-01-08 16:52:31 +08:00
mango 37aa5ec3b4 fix(batch add model list): fix the issue of fetching model list failure in batch add channel 2025-01-07 12:42:37 +08:00
mango 6fddeb6d2f feat(channel model list): modify fetching model list in add channel to fetch by type 2025-01-07 12:40:36 +08:00
mango d5e5b856dc feat(channel balance): add channel balance for siliconflow and deepseek 2025-01-07 12:15:55 +08:00
Calcium-Ion 6f47c4d47f Merge pull request #693 from Calcium-Ion/refactor-auth
refactor: access_token auth
2025-01-06 17:55:20 +08:00
1808837298@qq.com a3fe6c03b8 Adjust streaming timeout for OpenAI models in OaiStreamHandler
- Implemented conditional logic to double the streaming timeout for models starting with "o1" or "o3".
- Improved handling of streaming timeout configuration to enhance performance based on model type.
2025-01-06 17:52:33 +08:00
1808837298@qq.com e10531670e Update Dockerfile to use Bun for package management and build process
- Changed base image from Node.js to Bun for improved performance.
- Replaced npm install with bun install for dependency management.
- Updated build command to use Bun for building the application.
- Added new bun.lockb file to track Bun dependencies.
2025-01-06 16:37:21 +08:00
1808837298@qq.com aa2ac4766e Enhance user search functionality to support ID and keyword searches. Updated query conditions to allow searching by user ID alongside username, email, and display name. Improved handling of numeric and string keywords in search queries. 2025-01-06 15:20:38 +08:00
Calcium-Ion f599aede05 Merge pull request #692 from Calcium-Ion/fix-channel-model-length
Fix channel model length issue
2025-01-05 22:13:04 +08:00
1808837298@qq.com f11148bccf revert cache.go 2025-01-05 22:12:39 +08:00
1808837298@qq.com 530e846ac1 refactor: access_token auth 2025-01-05 22:08:23 +08:00
Calcium-Ion 7baa204e9c Fix model name length validation limit 2025-01-05 22:02:46 +08:00
Calcium-Ion 3dff0d498f 2025-01-05 22:01:36 +08:00
Calcium-Ion e275229597 Fix channel model length issue
Fixes #691

---

For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/Calcium-Ion/new-api/issues/691?shareId=XXXX-XXXX-XXXX-XXXX).
2025-01-05 21:55:25 +08:00
1808837298@qq.com cb5a13920f fix: update linux-release workflow to install gcc-aarch64-linux-gnu non-interactively 2025-01-05 17:59:29 +08:00
1808837298@qq.com 4aaa99a3d4 chore: change workflow runners to self-hosted for Docker and release jobs 2025-01-05 17:17:57 +08:00
1808837298@qq.com aea201ca77 fix: update iframe styling and permissions in ChatPage component 2025-01-04 22:24:47 +08:00
1808837298@qq.com e954913f19 refactor: realtime log render 2025-01-04 17:54:02 +08:00
1808837298@qq.com 44a097243e refactor: realtime i18n 2025-01-04 17:46:06 +08:00
1808837298@qq.com 1798595afc refactor: realtime quota 2025-01-04 15:46:35 +08:00
Calcium-Ion c073debcf5 Merge pull request #689 from iszcz/new512 2025-01-03 20:47:56 +08:00
iszcz 4fc2e49518 Update model-ratio.go 2025-01-03 20:42:46 +08:00
1808837298@qq.com d953b98417 feat: support gpt-4o-mini-realtime-preview 2025-01-03 18:51:09 +08:00
1808837298@qq.com 16afc5b3d6 fix: retry prompt tokens 2025-01-02 16:33:00 +08:00
Calcium-Ion a835920f83 Merge pull request #686 from delph1s/main
fix: try to fix pgsql #685
2025-01-02 00:17:02 +08:00
delph1s 7244adada5 fix: try to fix pgsql #685 2025-01-02 00:14:16 +08:00
Calcium-Ion eec3aa3bbd Update README.md 2024-12-31 22:19:37 +08:00
Calcium-Ion b664a2208b Merge pull request #683 from iszcz/new512
Update channel-test.go
2024-12-31 20:22:57 +08:00
CalciumIon d15074c860 fix: error page size opts 2024-12-31 15:51:15 +08:00
CalciumIon 97d0e6d2cd feat: implement pagination and total count for redemptions API #386
- Updated GetAllRedemptions and SearchRedemptions functions to return total count along with paginated results.
- Modified API endpoints to accept page size as a parameter, enhancing flexibility in data retrieval.
- Adjusted RedemptionsTable component to support pagination and display total count, improving user experience.
- Ensured consistent handling of pagination across related components, including LogsTable and UsersTable.
2024-12-31 15:28:25 +08:00
CalciumIon 7fec5fa1b3 feat: enhance user search functionality with pagination support
- Updated SearchUsers function to include pagination parameters (startIdx and num) for improved user search results.
- Modified API response structure to return paginated data, including total user count and current page information.
- Adjusted UsersTable component to handle pagination and search parameters, ensuring a seamless user experience.
- Added internationalization support for new search functionality in the UI.
2024-12-31 15:02:59 +08:00
CalciumIon 5a39d2e171 feat: enhance user management and pagination features #518
- Updated GetAllUsers function to return total user count along with paginated results, improving data handling in user retrieval.
- Modified GetAllUsers API endpoint to accept page size as a parameter, allowing for dynamic pagination.
- Enhanced UsersTable component to support customizable page sizes and improved pagination logic.
- Added error handling for empty username and password in AddUser component.
- Updated LogsTable component to display pagination information in a user-friendly format.
2024-12-31 14:52:55 +08:00
iszcz 60a3d1b066 Update channel-test.go 2024-12-31 12:49:13 +08:00
CalciumIon 0a845ae69f fix: try to fix pgsql #682 2024-12-31 02:10:19 +08:00
CalciumIon e0b7300239 fix: try to fix pgsql #682 2024-12-31 02:06:30 +08:00
CalciumIon 2de4de0006 fix redis 2024-12-30 22:05:41 +08:00
CalciumIon 3514287e38 docs: update README 2024-12-30 20:56:54 +08:00
CalciumIon 72d7e3eb81 refactor: update group handling and rendering logic
- Changed the structure of usableGroups in GetUserGroups to store additional information (ratio and description) for each group.
- Introduced a new renderRatio function to visually represent group ratios with color coding.
- Updated the Playground and EditToken components to utilize the new group structure and rendering options.
- Enhanced the renderGroupOption function for better UI representation of group options.
- Fixed minor comments and improved code readability.
2024-12-30 19:51:00 +08:00
Calcium-Ion cdf837bf11 Merge pull request #679 from kingxjs/main
fix: use document to build input fix copy command
2024-12-30 18:02:21 +08:00
Calcium-Ion d575739079 Merge pull request #677 from mageia/master
修复 PostgreSQL 中用户组查询错误
2024-12-30 18:01:51 +08:00
CalciumIon 629b2605cd Merge branch 'main'
# Conflicts:
#	model/user.go
2024-12-30 18:00:59 +08:00
Calcium-Ion 2ea4701eba Merge pull request #680 from Calcium-Ion/refactor_redis
Refactor redis
2024-12-30 17:55:07 +08:00
CalciumIon 6e002a5f0f feat: enhance environment variable handling and security features 2024-12-30 17:24:19 +08:00
迷糊虫 e266bc9121 使用原生document构建input再次尝试复制命令 2024-12-30 17:13:49 +08:00
CalciumIon aefd53b683 refactor: token cache logic 2024-12-30 17:10:48 +08:00
Mageia 9ebdb08e21 修复 PostgreSQL 中用户组查询错误
- 修复 model/user.go 中的 SQL 查询,使用双引号将 group 列名括起来
- 对于 PostgreSQL 数据库,`group` 是保留关键字,需要用双引号括起来避免语法错误。该修改确保了代码在 PostgreSQL 和其他数据库(如 MySQL)中都能正常工作。
2024-12-30 10:23:55 +08:00
Calcium-Ion 7da427c20d Merge pull request #676 from Calcium-Ion/refactor_redis
refactor: user cache logic
2024-12-29 17:55:52 +08:00
CalciumIon c6ae827385 refactor: remove redundant group column handling in user queries 2024-12-29 17:02:30 +08:00
CalciumIon 966cdc1f7f refactor: user cache logic 2024-12-29 16:50:26 +08:00
Calcium-Ion 837804927d Merge pull request #674 from Yan-Zero/main
fix: Gemini 函数调用的文本转义,以及其他文件类型的 Base64 支持
2024-12-29 13:11:02 +08:00
Yan 1cdb03d821 fix: Gemini 其他文件类型的支持(Base64URL) 2024-12-29 10:11:39 +08:00
Yan 9fc2f6a3b5 fix: Gemini 函数调用的文本转义 2024-12-29 06:11:44 +08:00
CalciumIon dfa3f661a5 fix: playground request_start_time 2024-12-29 01:03:02 +08:00
CalciumIon 892e23d2f1 fix: prevent setting models to null in PersonalSetting component 2024-12-29 00:24:02 +08:00
CalciumIon e326679b72 feat: add multi-file type support for Gemini and Claude
- Add file data DTO for structured file handling
- Implement file decoder service
- Update Claude and Gemini relay channels to handle various file types
- Reorganize worker service to cf_worker for clarity
- Update token counter and image service for new file types
2024-12-29 00:00:24 +08:00
CalciumIon 22244a9c5e chore: update language in index.html to Chinese 2024-12-28 20:43:26 +08:00
Calcium-Ion 32440f2ea7 Merge pull request #673 from Yan-Zero/main
fix: 转义 Gemini 工具调用中的反斜杠
2024-12-28 19:49:31 +08:00
Yan b7b2347263 fix: 转义 Gemini 工具调用中的反斜杠 2024-12-28 18:29:48 +08:00
Calcium-Ion b632c650bf Merge pull request #672 from Yan-Zero/main
fix: add index in the tool calls when chat by stream (gemini)
2024-12-28 18:20:33 +08:00
Yan 49a0db1da8 fix: add index in the tool calls when chat by stream (gemini) 2024-12-28 17:56:31 +08:00
CalciumIon 46fcb691d5 refactor: Playground controller 2024-12-28 16:47:56 +08:00
CalciumIon c917ce64db refactor: streamline log processing by introducing formatUserLogs function 2024-12-28 16:40:29 +08:00
CalciumIon 9e25f05930 refactor: enhance log retrieval and user interaction in LogsTable component 2024-12-28 15:34:28 +08:00
CalciumIon 05e389fe6a fix #663 2024-12-27 21:59:05 +08:00
CalciumIon 6093ae6f30 fix: prevent duplicate models in user group retrieval 2024-12-27 21:25:44 +08:00
CalciumIon e671b0d357 refactor: improve user group handling and add GetUserUsableGroups function
- Introduced a new function `GetUserUsableGroupsCopy` to return a copy of user usable groups.
- Updated `GetUserUsableGroups` to utilize the new function for better encapsulation.
- Changed variable names from `UserUsableGroups` to `userUsableGroups` for consistency.
- Enhanced `GetUserUsableGroups` logic to ensure it returns a copy of the groups, preventing unintended modifications.
2024-12-27 21:19:22 +08:00
CalciumIon 040d33f78b update dockerignore 2024-12-27 20:49:58 +08:00
CalciumIon fe944b3c8d fix: oauth bind 2024-12-27 18:32:11 +08:00
CalciumIon 5bbc730a6f feat: update o1 default token encoder 2024-12-27 15:03:10 +08:00
CalciumIon 62469a5226 feat: support azure stream_options 2024-12-26 22:51:06 +08:00
CalciumIon bb48f5dcae update model ratio 2024-12-26 16:03:22 +08:00
Calcium-Ion b3899a4635 Merge pull request #661 from tenacioustommy/fix-title-schema
fix delete title schema
2024-12-26 14:27:07 +08:00
Calcium-Ion b71e50339c Merge pull request #662 from xqx333/main
fix 重试过程多次获取图片
2024-12-26 14:26:50 +08:00
CalciumIon 340b2a230f fix: update render function for quota display in Detail page 2024-12-26 14:25:44 +08:00
xqx333 541e07e6e7 Update relay-text.go
在上下文中存入promptTokens,避免重试过程重复计算
2024-12-26 02:00:04 +08:00
tenacious 18e308bdf7 fix delete title schema 2024-12-26 00:24:45 +08:00
CalciumIon 620f9f19b1 fix: validate number input in renderQuotaNumberWithDigit and improve data handling in Detail page
- Added input validation to ensure that the `num` parameter in `renderQuotaNumberWithDigit` is a valid number, returning 0 for invalid inputs.
- Updated the `Detail` component to use `datum['rawQuota']` instead of `datum['Usage']` for rendering quota values, ensuring more accurate data representation.
- Enhanced data aggregation logic to handle cases where quota values may be missing or invalid, improving overall data integrity in charts and tables.
- Removed unnecessary time granularity calculations and streamlined the data processing for better performance.
2024-12-25 23:16:35 +08:00
CalciumIon 0d1ba65592 refactor: migrate group ratio and user usable groups logic to new setting package
- Replaced references to common.GroupRatio and common.UserUsableGroups with corresponding functions from the new setting package across multiple controllers and services.
- Introduced new setting functions for managing group ratios and user usable groups, enhancing code organization and maintainability.
- Updated related functions to ensure consistent behavior with the new setting package integration.
2024-12-25 19:31:12 +08:00
CalciumIon b9e3331fb3 fix typo 2024-12-25 18:44:45 +08:00
CalciumIon a48679fb4a fix: update MaxCompletionTokens for model prefix handling in buildTestRequest function 2024-12-25 17:55:20 +08:00
CalciumIon 314ad072ae fix: correct user retrieval in GetPricing function 2024-12-25 14:29:52 +08:00
CalciumIon f9e06342c6 fix: resolve pricing calculation issue (#659) 2024-12-25 14:26:43 +08:00
CalciumIon fe29ca04d5 feat: Implement batch tagging functionality for channels
- Added a new endpoint to batch set tags for multiple channels, allowing users to update tags efficiently.
- Introduced a new `BatchSetChannelTag` function in the controller to handle incoming requests and validate parameters.
- Updated the `BatchSetChannelTag` method in the model to manage database transactions and ensure data integrity during tag updates.
- Enhanced the ChannelsTable component in the frontend to support batch tag setting, including UI elements for user interaction.
- Updated localization files to include new translation keys related to batch operations and tag settings.
2024-12-25 14:19:00 +08:00
CalciumIon 61450cc1eb fix: update searchUsers function to include searchKeyword and searchGroup parameters 2024-12-25 13:44:55 +08:00
Calcium-Ion 38d4c7b7de Merge pull request #656 from Yan-Zero/main
fix: gemini function call
2024-12-25 13:38:34 +08:00
CalciumIon 6f9f7a0bfa fix: #657 2024-12-24 22:30:05 +08:00
CalciumIon 975b4a4679 fix: get upstream models 2024-12-24 20:48:21 +08:00
Yan a6989dfb7e Merge branch 'Calcium-Ion:main' into main 2024-12-24 20:46:16 +08:00
Yan 411d3a6251 fix: gemini func call 2024-12-24 20:46:02 +08:00
CalciumIon d429483f4c feat: Enhance pricing functionality with user group support
- Updated the GetPricing function in the backend to include user group information, allowing for dynamic adjustment of group ratios based on the user's group.
- Implemented logic to filter group ratios based on the user's usable groups, improving the accuracy of pricing data returned.
- Modified the ModelPricing component to utilize the new usable group data, ensuring only relevant groups are displayed in the UI.
- Enhanced state management in the frontend to accommodate the new usable group information, improving user experience and data consistency.
2024-12-24 19:23:29 +08:00
CalciumIon 4bc8052a6e feat: Update localization and enhance token editing functionality
- Added new translation keys for English localization in `en.json`, including "Token group, default is the your's group" and "IP whitelist (do not overly trust this function)".
- Refactored `EditToken.js` to utilize the `useTranslation` hook for improved internationalization, ensuring all user-facing strings are translatable.
- Updated error and success messages to use translation functions, enhancing user experience for non-English speakers.
- Improved UI elements to support localization, including labels, placeholders, and button texts, ensuring consistency across the token editing interface.
2024-12-24 18:40:18 +08:00
CalciumIon b46efdc8c2 feat: Add FetchModels endpoint and refactor FetchUpstreamModels
- Introduced a new `FetchModels` endpoint to retrieve model IDs from a specified base URL and API key, enhancing flexibility for different channel types.
- Refactored `FetchUpstreamModels` to simplify base URL handling and improve error messages during response parsing.
- Updated API routes to include the new endpoint and adjusted the frontend to utilize the new fetch mechanism for model lists.
- Removed outdated checks for channel type in the frontend, streamlining the model fetching process.
2024-12-24 18:02:08 +08:00
CalciumIon 1e22f40518 feat: Enhance LogsTable component with mobile support and date handling improvements
- Added mobile-specific date pickers for start and end timestamps in the LogsTable component, improving user experience on mobile devices.
- Updated the input handling for date values to ensure valid date formats are maintained.
- Introduced a new translation key for "时间范围" (Time range) in the English locale file to support localization efforts.
2024-12-24 15:44:11 +08:00
CalciumIon a78f363af9 Merge remote-tracking branch 'origin/main' 2024-12-24 14:48:43 +08:00
CalciumIon 7e8adb5b34 feat: Enhance logging functionality with group support
- Added a new 'group' parameter to various logging functions, including RecordConsumeLog, GetAllLogs, and GetUserLogs, to allow for more granular log tracking.
- Updated the logs table component to display group information, improving the visibility of log data.
- Refactored related functions to accommodate the new group parameter, ensuring consistent handling across the application.
- Improved the initialization of the group column for PostgreSQL compatibility.
2024-12-24 14:48:11 +08:00
Calcium-Ion 87692e606f Merge pull request #652 from Yan-Zero/main
fix: mutil func call in gemini
2024-12-23 20:50:31 +08:00
CalciumIon 6288b26f35 Merge remote-tracking branch 'origin/main' 2024-12-23 20:48:31 +08:00
CalciumIon 72be58523c feat: Add request start time context key and update middleware
- Introduced a new constant `ContextKeyRequestStartTime` to store the request start time in the context, enhancing request tracking.
- Updated the `Distribute` middleware to set the request start time in the context using the new constant.
- Modified the `GenRelayInfo` function to retrieve the request start time from the context, ensuring accurate timing information is used in relay operations.
2024-12-23 20:48:10 +08:00
Yan dbd412f852 fix: mutil func call in gemini 2024-12-23 01:26:14 +08:00
Calcium-Ion 344ebade79 Merge pull request #651 from tenacioustommy/fix-gemini-json
fix-gemini-json-schema
2024-12-23 00:04:08 +08:00
CalciumIon 424af80c77 feat: Enhance GeminiChatHandler to include RelayInfo
- Updated the GeminiChatHandler function to accept an additional parameter, RelayInfo, allowing for better context handling during chat operations.
- Modified the DoResponse method in the Adaptor to pass RelayInfo to GeminiChatHandler, ensuring consistent usage of upstream model information.
- Enhanced the GeminiChatStreamHandler to utilize the upstream model name from RelayInfo, improving response accuracy and data representation in Gemini requests.
2024-12-23 00:02:15 +08:00
CalciumIon f2f7f64151 refactor: Remove unused context and logging in CovertGemini2OpenAI function
- Eliminated the unused `context` import and the logging of `geminiRequest` in the `CovertGemini2OpenAI` function, improving code cleanliness and reducing unnecessary overhead.
- This change enhances the maintainability of the code by removing redundant elements that do not contribute to functionality.
2024-12-22 23:54:11 +08:00
CalciumIon 26e2170bd4 feat: Add FunctionResponse type and enhance GeminiPart structure
- Introduced a new `FunctionResponse` type to encapsulate function call responses, improving the clarity of data handling.
- Updated the `GeminiPart` struct to include the new `FunctionResponse` field, allowing for better representation of function call results in Gemini requests.
- Modified the `CovertGemini2OpenAI` function to handle tool calls more effectively by setting the message role and appending function responses to the Gemini parts, enhancing the integration with OpenAI and Gemini systems.
2024-12-22 23:53:25 +08:00
tenacious 8a5d0d0f50 fix-gemini-json 2024-12-22 23:48:09 +08:00
CalciumIon 69c590d439 feat: Introduce settings package and refactor constants
- Added a new `setting` package to replace the `constant` package for configuration management, improving code organization and clarity.
- Moved various configuration variables such as `ServerAddress`, `PayAddress`, and `SensitiveWords` to the new `setting` package.
- Updated references throughout the codebase to use the new `setting` package, ensuring consistent access to configuration values.
- Introduced new files for managing chat settings and midjourney settings, enhancing modularity and maintainability of the code.
2024-12-22 17:24:29 +08:00
CalciumIon 86b9dc6314 refactor: Update Message methods to use pointer receivers
- Refactored ParseToolCalls, SetToolCalls, IsStringContent, and ParseContent methods in the Message struct to use pointer receivers, improving efficiency and consistency in handling mutable state.
- Enhanced code readability and maintainability by ensuring all relevant methods operate on the pointer receiver, aligning with Go best practices.
2024-12-22 16:30:18 +08:00
CalciumIon 18ed379597 refactor: Update SetToolCalls method to use pointer receiver
- Changed the SetToolCalls method to use a pointer receiver for the Message struct, allowing for modifications to the original instance.
- This change improves the method's efficiency and aligns with Go best practices for mutating struct methods.
2024-12-22 16:22:55 +08:00
CalciumIon 464220cdf1 refactor: Update OpenAI request and message handling
- Changed the type of ToolCalls in the Message struct from `any` to `json.RawMessage` for better type safety and clarity.
- Introduced ParseToolCalls and SetToolCalls methods to handle ToolCalls more effectively, improving code readability and maintainability.
- Updated the ParseContent method to work with the new MediaContent type instead of MediaMessage, enhancing the structure of content parsing.
- Refactored Gemini relay functions to utilize the new ToolCalls handling methods, streamlining the integration with OpenAI and Gemini systems.
2024-12-22 16:20:30 +08:00
Calcium-Ion 9e6d0b2aed Merge pull request #648 from palboss/main
解决  #534 用户管理-管理用户-查询报错 (SQLSTATE 42601)-postgresql
2024-12-22 14:37:34 +08:00
CalciumIon 7d0f26c9cd refactor: Simplify Gemini function parameter handling
- Removed redundant checks for non-empty properties in function parameters.
- Set function parameters to nil when no properties are needed, streamlining the logic for handling Gemini requests.
- Improved code clarity and maintainability by eliminating unnecessary complexity.
2024-12-22 14:35:21 +08:00
CalciumIon d13103b667 feat: Enhance Gemini function parameter handling
- Added logic to ensure that function parameters have non-empty properties.
- Implemented checks to add a default empty property if no parameters are needed.
- Updated the required field to match existing properties, improving the robustness of the Gemini function integration.
2024-12-22 14:29:14 +08:00
borland ad15c0cb38 Update user.go 2024-12-22 00:03:00 +08:00
borland 57a24bed51 Update user.go 2024-12-22 00:02:28 +08:00
CalciumIon 191131b2a6 feat: Enhance LogsTable to render group information
- Added `renderGroup` function to improve the display of log data by rendering the 'group' information in the LogsTable component.
- Updated the rendering logic to utilize the new function, enhancing the UI's clarity and usability for grouped logs.
2024-12-21 20:28:26 +08:00
CalciumIon 095277e64f feat: Add log information generation and enhance LogsTable component
- Introduced `log_info_generate.go` to implement functions for generating various log information, including text, WebSocket, and audio details.
- Enhanced `LogsTable` component to display the 'group' information from the log data, improving the visibility of grouped logs in the UI.
2024-12-21 20:24:22 +08:00
CalciumIon 23d1c84503 Merge branch 'feat/o1'
# Conflicts:
#	dto/openai_request.go
2024-12-21 16:45:45 +08:00
Calcium-Ion 12335d924a Merge pull request #645 from MartialBE/gemini_res_format
支持gemini结构化输出
2024-12-21 16:40:51 +08:00
MartialBE a8fa11f9d0 feat: support for Gemini structured output. 2024-12-21 16:01:17 +08:00
HynoR 45583ecc86 Merge remote-tracking branch 'origin/feat/o1' into feat/o1 2024-12-20 23:14:20 +08:00
HynoR 616f41d4c9 feat: 适配o1模型 2024-12-20 23:14:10 +08:00
TAKO 86f5606b83 Merge branch 'Calcium-Ion:main' into feat/o1 2024-12-20 22:12:26 +08:00
HynoR d2ce69f8e9 feat: 适配o1模型 2024-12-20 22:09:02 +08:00
HynoR 11408d011d feat: 适配o1模型 2024-12-20 22:07:53 +08:00
CalciumIon 1888a10394 refactor: Enhance error handling in Gemini request conversion
- Updated `CovertGemini2OpenAI` function to return an error alongside the GeminiChatRequest, improving error reporting for image processing.
- Modified `ConvertRequest` methods in both `adaptor.go` files to handle potential errors from the Gemini conversion, ensuring robust request handling.
- Improved clarity and maintainability of the code by explicitly managing error cases during request conversion.
2024-12-20 21:50:58 +08:00
CalciumIon 0725c8aac9 feat: Add GEMINI_VISION_MAX_IMAGE_NUM configuration
- Introduced `GEMINI_VISION_MAX_IMAGE_NUM` to README files for better user guidance.
- Updated `env.go` to retrieve the maximum image number from environment variables, defaulting to 16.
- Modified image handling logic in `relay-gemini.go` to respect the new configuration, allowing disabling of the limit by setting it to -1.
- Removed hardcoded constant for maximum image number in `constant.go` to streamline configuration management.
2024-12-20 21:36:23 +08:00
Calcium-Ion 1ae143c55a Merge pull request #636 from HynoR/fix/smfix
fix: 修复添加模型切换模式时,初始化空值导致的判断问题
2024-12-20 20:33:01 +08:00
Calcium-Ion d083d1ec33 Merge pull request #641 from HynoR/main
chore: 更新遗漏的gemini模型
2024-12-20 20:32:47 +08:00
Calcium-Ion d61524bee7 Merge pull request #642 from MartialBE/fix_gemini_thinking
fix: 修复gemini thinking在stream下会内容丢失
2024-12-20 20:32:23 +08:00
MartialBE 8504023d03 fix: Fix the issue where Gemini loses content when converting OpenAI format in the stream. 2024-12-20 20:24:49 +08:00
HynoR 105f13b2de chore: 更新gemini模型 2024-12-20 19:17:56 +08:00
HynoR c4c10d29f9 chore: 更新gemini模型 2024-12-20 19:14:53 +08:00
TAKO e95d65207f Merge branch 'Calcium-Ion:main' into fix/smfix 2024-12-20 19:11:32 +08:00
CalciumIon 1dfdb44d7f feat: Add new experimental Gemini versions to ModelList
- Included additional versions: "gemini-2.0-flash-thinking-exp" and "gemini-2.0-flash-thinking-exp-1219".
- Added comments to categorize versions as old, experimental, and flash experimental for better clarity.
2024-12-20 13:26:51 +08:00
CalciumIon 3a97be9f12 feat: support gemini-2.0-flash-thinking #639 #637 2024-12-20 13:20:07 +08:00
HynoR 8170ed6b48 fix: 修复添加模型切换模式时,初始化空值导致的判断问题 2024-12-19 19:08:04 +08:00
CalciumIon 7569947544 refactor: Improve channel status update logic and clean up code
- Simplified conditional checks in UpdateChannelStatusById function in channel.go to enhance readability.
- Commented out unused image number check in relay-gemini.go for clarity.
- Updated JSON field in en.json for currency consistency, changing "元" from "RMB/CNY" to "CNY" and added a space in "实付金额:" for formatting.
2024-12-17 15:33:16 +08:00
Calcium-Ion b973d927df Merge pull request #631 from xqx333/main
fix
2024-12-17 14:39:39 +08:00
xqx333 8514572452 Update channel.go 2024-12-17 14:30:31 +08:00
Calcium-Ion a3bb88c2df Merge pull request #630 from xqx333/main
修复自动禁用会对数据库进行多次更新的问题
2024-12-17 12:22:27 +08:00
xqx333 5664d55702 Update channel.go 2024-12-17 12:11:24 +08:00
xqx333 3339773193 Update cache.go 2024-12-17 12:10:05 +08:00
CalciumIon c6ec3c95ef refactor: Update SystemInstructions type in GeminiChatRequest and adjust handling in CovertGemini2OpenAI
- Changed SystemInstructions from *GeminiPart to *GeminiChatContent in GeminiChatRequest for improved structure.
- Updated CovertGemini2OpenAI function to accommodate the new SystemInstructions type, ensuring proper handling of message content.
2024-12-16 22:41:23 +08:00
CalciumIon a78bbc0c11 fix: Correct JSON field name for SystemInstructions in GeminiChatRequest
- Updated the JSON field name from "system_instructions" to "system_instruction" to ensure consistency and accuracy in the data structure.
2024-12-16 22:12:56 +08:00
CalciumIon 4fbd5f8c93 feat: Enhance Home component to support language messaging
- Added language messaging functionality to the iframe in the Home component.
- This update ensures that the iframe receives the current language setting, improving localization support.
2024-12-16 21:10:46 +08:00
Calcium-Ion 30a4f9ed4f Merge pull request #628 from Calcium-Ion/pr482-merge
merge 428
2024-12-16 21:05:58 +08:00
CalciumIon d0d8f6eb80 feat: Enhance HeaderBar to support language change messaging
- Added functionality to post a message to the iframe when the language is changed.
- This update improves localization support by ensuring that the iframe content updates according to the selected language.
2024-12-16 21:05:02 +08:00
CalciumIon fd2f6ed440 Merge remote-tracking branch 'guoruqiang/main' into pr482-merge
# Conflicts:
#	README.md
#	web/src/components/HeaderBar.js
#	web/src/components/SiderBar.js
2024-12-16 20:56:53 +08:00
CalciumIon cd6b3296ed feat: support gemini SystemInstructions #408 2024-12-16 20:19:29 +08:00
CalciumIon c3d4de67f2 Update README.md 2024-12-16 18:12:17 +08:00
Calcium-Ion be5ed9167d Merge pull request #625 from QAbot-zh/fix/bindwechat
fix bindWeChat tips
2024-12-15 20:59:47 +08:00
Q.A.zh 838af0b05f fix bindWeChat tips 2024-12-15 12:53:16 +00:00
Calcium-Ion ebf74150f6 Merge pull request #624 from Calcium-Ion/channel-setting
feat: Add collapsible section for available models in PersonalSettings
2024-12-15 16:32:09 +08:00
CalciumIon 31d9771a51 feat: Add collapsible section for available models in PersonalSettings 2024-12-15 16:31:00 +08:00
Calcium-Ion bb7d357517 Merge pull request #623 from Calcium-Ion/channel-setting
feat: implement channel settings configuration
2024-12-15 15:55:13 +08:00
CalciumIon 0ad03de153 feat: implement channel settings configuration
fix #620
2024-12-15 15:52:41 +08:00
CalciumIon 3fd054cfa8 feat: Enhance Operation Settings with Group and Model Ratio Management
- Added new components for GroupRatioSettings and ModelRatioSettings to manage group and model ratios.
- Integrated tabs in OperationSetting to switch between model and visual ratio settings.
- Updated translations for new settings and improved existing ones in the English locale file.
- Refactored ModelSettingsVisualEditor to support dynamic pricing and ratio configurations.

This update improves the user interface for managing operational settings, enhancing usability and localization support.
2024-12-14 22:13:31 +08:00
Calcium-Ion 1c4718899d Merge pull request #618 from HynoR/feat/modeledit
feat: 可视化模型定价编辑器
2024-12-14 21:32:05 +08:00
Calcium-Ion 5f74f7c3f2 Merge pull request #617 from kingxjs/main
Available models could not be populated when adding a new channel
2024-12-14 21:31:05 +08:00
Calcium-Ion 94ece1da70 Merge pull request #622 from Calcium-Ion/i18n-fix
feat: Enhance i18n support
2024-12-14 14:12:27 +08:00
CalciumIon 22487a8aaf feat: Refactor App and ChannelsTable components for improved i18n support
- Removed redundant user and status loading logic from the App component, centralizing it in the PageLayout component for better maintainability.
- Enhanced the ChannelsTable component by integrating translation functions for various UI elements, ensuring consistent localization of titles and modal messages.
- Updated the English locale file with new translation keys for sub-channel modifications, improving the overall localization coverage.
- Streamlined the code structure in multiple components to enhance readability and performance.
2024-12-14 14:09:30 +08:00
CalciumIon a06e5619d8 feat: Enhance i18n support in Home component and update translations
- Integrated translation functions in the Home component to support dynamic localization for various UI elements, improving accessibility for users in different languages.
- Added new translation keys for "Telegram authentication", "Linux DO authentication", and "License" in the English locale file, expanding the localization coverage.
- Updated existing text elements to utilize translation functions, ensuring consistency in language display across the application.
2024-12-14 12:58:10 +08:00
CalciumIon 1a15c216f0 feat: Implement status loading in App component and refactor SiderBar
- Added a new function to load status data from the API in the App component, enhancing the application's ability to display real-time status updates.
- Integrated error handling for API calls to improve user feedback in case of connection issues.
- Removed the redundant status loading logic from the SiderBar component, streamlining the code and ensuring a single source of truth for status management.
- Updated the useEffect hook in SiderBar to maintain sidebar collapse state based on local storage, improving user experience.
2024-12-14 12:57:56 +08:00
CalciumIon d78dd9c5ba Update README 2024-12-13 23:48:18 +08:00
CalciumIon 49a4fc13a5 refactor: Remove unused translation function calls in LogsTable component
- Eliminated unnecessary calls to the translation function in the LogsTable component, streamlining the code and improving performance.
- This change enhances readability and reduces potential overhead from unused localization logic.
2024-12-13 22:34:10 +08:00
CalciumIon 8b54c4750d Update README.en.md 2024-12-13 21:21:28 +08:00
CalciumIon a6d012bde1 Add README.en.md 2024-12-13 20:21:34 +08:00
CalciumIon 527aa2395f Update README.md 2024-12-13 20:15:50 +08:00
CalciumIon 342b673f89 fix: Refine sider visibility logic in HeaderBar component
- Updated the click handler in HeaderBar to correctly toggle the visibility of the sider based on the current item selection.
- Ensured that the sider is hidden when navigating to the home item and displayed conditionally for other items, improving the user interface responsiveness.
2024-12-13 19:28:09 +08:00
Calcium-Ion ed1a6bc0f8 feat: support i18n
feat: support i18n
2024-12-13 19:24:15 +08:00
CalciumIon a771ecbe0b feat: Integrate i18n support and enhance UI text localization
- Added internationalization (i18n) support across various components, enabling dynamic language switching and improved user experience.
- Updated multiple components to utilize translation functions for labels, buttons, and messages, ensuring consistent language display.
- Enhanced the user interface by refining text elements in the ChannelsTable, LogsTable, and various settings pages, improving clarity and accessibility.
- Adjusted CSS styles for better responsiveness and layout consistency across different screen sizes.
2024-12-13 19:03:14 +08:00
HynoR 9f274608b6 feat: 增加价格和倍率的互斥验证,优化模型名称输入提示 2024-12-13 14:45:49 +08:00
HynoR 16b32cc6c2 feat: 优化模型设置可视化编辑器,增强输入验证和提示信息 2024-12-13 14:42:02 +08:00
HynoR b0c595d1d4 feat: 添加保存功能并优化模型数据提交逻辑 2024-12-13 14:29:43 +08:00
TAKO 0a2c6a5d6a Merge branch 'Calcium-Ion:main' into feat/modeledit 2024-12-13 14:11:31 +08:00
HynoR 12efd1fb50 feat: 添加模型设置可视化编辑器组件 2024-12-13 14:10:38 +08:00
迷糊虫 4ae37b942d Update EditChannel.js
Fixes an issue with the OpenAI models interface where it fails to get a list of models.
2024-12-13 13:36:03 +08:00
CalciumIon 3f501f4531 fix: Adjust inner padding style in PageLayout component
- Updated the overflowY style to always be 'auto', ensuring consistent scrolling behavior.
- Maintained conditional inner padding based on the styleState, enhancing layout responsiveness.
2024-12-12 23:34:14 +08:00
CalciumIon 77230ce534 feat: init i18n 2024-12-12 23:32:55 +08:00
CalciumIon 1de933b60c fix: Refine sider visibility and inner padding logic in StyleProvider component
- Consolidated the logic for managing sider visibility and inner padding based on the current pathname, improving responsiveness to navigation changes.
- Ensured that the sider is hidden on specific paths and adjusted inner padding accordingly, enhancing the user interface experience on both mobile and desktop views.
2024-12-12 20:52:22 +08:00
CalciumIon 094ff77656 fix: Update label truncation logic in Playground and adjust sider visibility in HeaderBar
- Modified the group label truncation in the Playground component to shorten labels exceeding 16 characters for better mobile display.
- Corrected the conditional rendering logic in the HeaderBar to toggle the sider visibility based on its current state, enhancing user interface responsiveness.
2024-12-12 20:39:49 +08:00
CalciumIon f65d6eb7af fix: Correct inner padding and sider visibility logic in HeaderBar, PageLayout, and SiderBar components
- Updated the click handler in HeaderBar to toggle inner padding and sider visibility correctly based on the selected item.
- Adjusted the conditional rendering of SiderBar in PageLayout to ensure it displays when the sider is shown.
- Refined the inner padding logic in SiderBar to maintain consistent behavior when selecting items.
- Introduced a new function in Style context to manage sider visibility based on the current pathname, enhancing responsiveness to navigation changes.
2024-12-12 20:31:40 +08:00
CalciumIon 20e0e7c64b refactor: Simplify average calculations in Detail component
- Streamlined the calculation of average RPM and average TPM by removing unnecessary function calls and directly applying the `toFixed(3)` method within the JSX.
- Improved code readability and maintainability by reducing the number of lines and enhancing clarity in the calculation logic.
2024-12-12 19:21:08 +08:00
CalciumIon e54bb12a96 feat: 兼容OpenAI格式下设置gemini模型联网搜索 #615 2024-12-12 17:58:25 +08:00
CalciumIon ee03e71e81 feat: add model gemini-2.0-flash-exp 2024-12-12 17:21:37 +08:00
CalciumIon 81fc064c64 feat: Enhance group label display in Playground component
- Updated the group selection input to truncate long labels on mobile devices, ensuring better readability and a cleaner interface.
- Implemented a conditional label adjustment that shortens group names exceeding 18 characters, appending '...' for clarity.
2024-12-12 16:35:13 +08:00
Calcium-Ion 24190bf355 Merge pull request #616 from Calcium-Ion/panel
feat: 完善数据看板功能
2024-12-12 16:19:27 +08:00
CalciumIon 330dc1a7c6 feat: Enhance quota data handling and CSS styling
- Updated the `increaseQuotaData` function to include `tokenUsed` parameter for better quota tracking.
- Modified the `GetAllQuotaDates` function to sum `token_used` alongside `count` and `quota` for comprehensive data retrieval.
- Improved CSS styles for better layout responsiveness, including padding adjustments for navigation elements and description cards.
2024-12-12 16:18:14 +08:00
CalciumIon c719b02424 feat: Update SiderBar and Detail components for improved navigation and data visualization
- Removed the '模型价格' (Pricing) link from the SiderBar for a cleaner interface.
- Added a new '数据看板' (Data Dashboard) link to the SiderBar, enhancing navigation options.
- Refactored the Detail component to include user context and style context for better state management.
- Introduced new state variables to track token consumption and updated data handling for charts.
- Enhanced the layout with additional cards and tabs for displaying user quota and usage statistics.
- Improved data processing logic for pie and line charts, ensuring accurate representation of user data.
2024-12-12 16:11:17 +08:00
CalciumIon 74a307f10c feat: Enhance color mapping and chart rendering in Detail component
- Added base and extended color palettes for improved model color mapping.
- Introduced a new `modelToColor` function to dynamically assign colors based on model names.
- Updated the Detail component to utilize the new color mapping for pie and line charts.
- Refactored chart data handling to support dynamic color assignment and improved data visualization.
- Cleaned up unused state variables and optimized data loading logic for better performance.
2024-12-12 14:56:16 +08:00
CalciumIon 7ae97088c3 chore: Update dependencies and refactor JSON handling #614
- Removed the `bytedance/sonic` dependency and replaced its usage with the standard `encoding/json` package for JSON marshalling in `relay-text.go`.
- Updated `go.mod` to reflect the removal of `sonic` and adjusted the version of `sonic/loader`.
- Cleaned up `go.sum` to ensure consistency with the updated dependencies.
2024-12-12 14:14:24 +08:00
Calcium-Ion 4a51ef50dc Merge pull request #613 from Calcium-Ion/mobile
feat: Add pricing link to HeaderBar component
2024-12-11 23:14:45 +08:00
Calcium-Ion b071c81189 Merge pull request #612 from Calcium-Ion/mobile
feat: Refactor style management for inner padding in layout components
2024-12-11 23:14:10 +08:00
CalciumIon b30137ef33 feat: Add pricing link to HeaderBar component
- Introduced a new '定价' (Pricing) item in the HeaderBar navigation for better accessibility to pricing information.
- Updated routing to include the new '/pricing' path.
- Adjusted user display in the HeaderBar for mobile responsiveness, hiding the username on smaller screens for a cleaner interface.
2024-12-11 23:13:46 +08:00
CalciumIon 430fbefde6 feat: Refactor style management for inner padding in layout components
- Updated HeaderBar, PageLayout, and SiderBar components to manage inner padding state based on selected items.
- Replaced `isChatPage` state with `shouldInnerPadding` in Style context for better clarity and functionality.
- Enhanced user experience by dynamically adjusting content padding based on navigation selections.
2024-12-11 23:08:52 +08:00
Calcium-Ion 7c67a8d29b Merge pull request #611 from Calcium-Ion/mobile
feat: 前端美化
2024-12-11 21:41:09 +08:00
CalciumIon 52aa31e07d feat: Update model lists and enhance model retrieval in Adaptor
- Refactored ModelList in the gemini constant to include new models and remove outdated ones.
- Modified the GetModelList function in the Adaptor to consolidate model lists from multiple sources, ensuring a comprehensive and updated list is returned.
- Commented out deprecated models in the vertex constants for clarity and future reference.
2024-12-11 21:39:41 +08:00
CalciumIon 75b144d670 feat: Add filtering and search functionality to model selection in EditChannel and EditTagModal
- Implemented filter and search position options in the model selection dropdowns for both EditChannel and EditTagModal components.
- Enhanced user experience by allowing users to easily find and select models from a potentially large list.
2024-12-11 21:33:30 +08:00
CalciumIon 5872e8a829 feat: Add custom model input functionality in EditTagModal
- Introduced a new input field for adding custom model names in the EditTagModal component.
- Implemented logic to handle the addition of custom models, including validation to prevent duplicates.
- Enhanced user experience by providing feedback when attempting to add existing models.
- Updated state management to reflect changes in the model options dynamically.
2024-12-11 21:31:29 +08:00
CalciumIon ab2326d5a0 feat: Update user group handling in Playground component
- Enhanced the Playground component to prioritize the user's group by moving it to the front of the local group options if it exists.
- Improved user experience by ensuring the default group selection reflects the user's current group, if available.
2024-12-11 21:25:50 +08:00
CalciumIon 7f7fcd36d2 feat: Implement chat page state management in layout and sidebar
- Added `isChatPage` state to the Style context to manage chat page layout.
- Updated `PageLayout` component to adjust padding based on the chat page state.
- Enhanced `SiderBar` component to dispatch chat page state changes when chat-related items are selected.
2024-12-11 21:17:46 +08:00
CalciumIon c76a0fedc3 feat: Add renderModelPriceSimple function and update LogsTable component
- Introduced a new helper function `renderModelPriceSimple` to simplify the rendering of model price information.
- Updated the `LogsTable` component to utilize `renderModelPriceSimple`, enhancing the display of model pricing and grouping information.
- Removed the previous implementation of `renderModelPrice` from the `LogsTable` for cleaner code.
2024-12-11 21:06:26 +08:00
CalciumIon 187d356e69 refactor: Simplify PersonalSetting component layout
- Moved footer content from the Card component to a separate Descriptions component for better structure.
- Maintained the display of user quota, historical consumption, and request count while improving readability.
2024-12-11 20:36:44 +08:00
Calcium-Ion 363bcbbacb Merge pull request #610 from Calcium-Ion/mobile
feat: Update dependencies and restructure Playground component
2024-12-11 18:28:11 +08:00
CalciumIon 62875a7000 feat: Update dependencies and restructure Playground component
- Upgraded @douyinfe/semi-ui from version 2.63.1 to 2.69.1 in package.json.
- Updated pnpm-lock.yaml to reflect new dependency versions and lockfile format.
- Moved Playground component to a new directory structure under pages.
- Enhanced Playground component with new features and improved user experience.
2024-12-11 18:27:30 +08:00
Calcium-Ion ab3bf484bf Merge pull request #609 from Calcium-Ion/mobile
feat: 界面美化
2024-12-11 17:33:32 +08:00
CalciumIon 6b9e80e949 feat: Enhance EditRedemption component with default name handling 2024-12-11 17:28:59 +08:00
CalciumIon b381d99438 feat: 首页优化 2024-12-11 17:19:03 +08:00
CalciumIon aa7d5f51ec feat: 侧边栏移动端优化 2024-12-11 16:11:27 +08:00
CalciumIon e6540e26e2 feat: 优化playground搜索模型功能 2024-12-10 23:48:55 +08:00
CalciumIon d497899876 fix: 编辑标签文字错误 2024-12-09 23:45:12 +08:00
CalciumIon 2a2ac8029b fix: edit channel weight and priority 2024-12-09 21:26:17 +08:00
CalciumIon 727236e6e7 fix: 渠道标签开启下使用ID排序出错 2024-12-09 20:38:03 +08:00
CalciumIon 10789e4940 feat: update playground roleConfig 2024-12-09 15:03:04 +08:00
Calcium-Ion e82909fe25 Merge pull request #605 from jochne/patch-1
Update relay-xunfei.go
2024-12-08 18:50:56 +08:00
jochne 94a8c13956 Update relay-xunfei.go
按照讯飞的最新文档,Spark Lite请求地址,对应的domain参数为lite
参考来源:https://www.xfyun.cn/doc/spark/Web.html#_1-接口说明
2024-12-08 01:04:43 +08:00
CalciumIon f11208d543 fix: telegram register 2024-12-07 18:08:51 +08:00
Calcium-Ion b2e2f37aab Merge pull request #600 from wzxjohn/upstream
feat: support Azure Comm Service SMTP
2024-12-07 15:24:17 +08:00
wzxjohn b354af02a4 feat: support Azure Comm Service SMTP 2024-12-07 00:37:11 +08:00
Calcium-Ion dd2beebaec Merge pull request #597 from daggeryu/patch-3
fix 关键词搜索加标签聚合时,大于1个空标签渠道无法展开的问题
2024-12-06 22:10:08 +08:00
CalciumIon c296dfbe0e feat: 兼容渠道搜索下标签聚合功能 2024-12-06 22:03:50 +08:00
CalciumIon 246c3fa451 feat: add gemini tool_calls finish reason 2024-12-06 14:31:27 +08:00
daggeryu 4c1aa96ab7 fix 关键词搜索加标签聚合时,大于1个空标签渠道无法展开的问题 2024-12-05 23:21:20 +08:00
Calcium-Ion 6cc34fba5c Merge pull request #596 from daggeryu/patch-2
fix: 关键词搜索时无法展开测试模型
2024-12-05 22:53:05 +08:00
Calcium-Ion 5cf997497c 删除无用代码 2024-12-05 22:52:49 +08:00
daggeryu 7ed8ef67ec fix: 关键词搜索时无法展开测试模型 2024-12-05 22:41:55 +08:00
Calcium-Ion b8755fde78 Update README.md 2024-12-05 18:07:36 +08:00
CalciumIon f9cf073a8f feat: add deepseek channel type 2024-12-05 17:50:08 +08:00
CalciumIon 498b240409 feat: update go-epay 2024-12-05 17:45:54 +08:00
CalciumIon d0395194d1 Update go.mod 2024-12-05 15:25:42 +08:00
Calcium-Ion 12054a562a Merge pull request #594 from Calcium-Ion/gzip
fix: 标签分组功能开启时无法展开测试模型
2024-12-05 14:42:16 +08:00
CalciumIon cfe080e441 fix: 标签分组功能开启时无法展开测试模型 2024-12-05 14:41:40 +08:00
Calcium-Ion f2e575e893 Merge pull request #593 from Calcium-Ion/gzip
feat: support br
2024-12-04 23:56:14 +08:00
CalciumIon 270857e370 feat: support br 2024-12-04 23:53:02 +08:00
Calcium-Ion d148e3434b Merge pull request #592 from Calcium-Ion/gzip
feat: support gzip
2024-12-04 23:25:19 +08:00
CalciumIon 407d03a0ba feat: support gzip 2024-12-04 23:24:46 +08:00
CalciumIon c709ab7c0a fix: email panic 2024-12-04 22:08:47 +08:00
Calcium-Ion d7f4ea0676 Merge pull request #591 from Calcium-Ion/no-cache
feat: add Cache-Control header to API requests
2024-12-04 20:53:22 +08:00
CalciumIon 4cf9ba46b8 feat: add Cache-Control header to API requests 2024-12-04 20:51:55 +08:00
Calcium-Ion bf7e6434ae Merge pull request #590 from iszcz/new512
realtime令牌额度检测和http
2024-12-04 19:49:57 +08:00
iszcz 8a1d16955a 1 2024-12-04 16:20:42 +08:00
CalciumIon deabfda278 Update docker-compose.yml 2024-12-03 16:48:38 +08:00
Calcium-Ion 11cf89cc55 Merge pull request #589 from mrhaoji/main
fix: 360智脑接口地址更新
2024-12-03 13:41:14 +08:00
Benny 192fc094fc fix: 360智能接口地址更新 2024-12-02 15:59:08 +00:00
Calcium-Ion 191ebac499 Merge pull request #588 from iszcz/new512
渠道tag编辑名称
2024-12-01 23:14:35 +08:00
iszcz 89631463ab Update EditTagModal.js 2024-12-01 22:36:51 +08:00
Calcium-Ion db1becbb29 Update README.md 2024-12-01 21:58:36 +08:00
CalciumIon 222818f9fb Merge remote-tracking branch 'origin/main' 2024-12-01 13:59:13 +08:00
CalciumIon a22a0e8285 refactor: improve validation logic and error handling in relay-text.go
- Simplified validation checks for MaxTokens and Messages fields.
- Enhanced error messages for better clarity.
- Updated goroutine to avoid passing context unnecessarily.
2024-12-01 08:24:41 +08:00
G.RQ d420ac0953 Merge branch 'Calcium-Ion:main' into main 2024-09-25 14:40:51 +08:00
GuoRuqiang 94abc05d7e 撤回修改 2024-09-25 06:39:32 +00:00
GuoRuqiang cf14b2d409 更新LoginForm 提示 2024-09-25 06:38:17 +00:00
G.RQ dc3f6a302e Update README.md
增加主要变更说明15. 支持使用路由/chat2link 进入聊天界面
2024-09-22 23:09:45 +08:00
GuoRuqiang 370147c6ad 使用postMessage向iframe传参theme-mode,实现切换子页面主题的功能
子页面的js示例
```
<script>
    // 接收父页面的主题模式
    window.addEventListener('message', function(event) {
        if (event.data.themeMode) {
            var theme = event.data.themeMode;
            // 测试是否正确接受到theme-mode的值
            // console.log('Received theme mode from parent:', theme);
            applyTheme(theme);
        }
    });

    // 定义一个函数来应用主题
    function applyTheme(theme) {
        var body = document.body;
        if (theme === 'dark') {
            body.classList.add("dark-mode");
            document.getElementById("darkModeToggle").checked = true;
        } else {
            body.classList.remove("dark-mode");
            document.getElementById("darkModeToggle").checked = false;
        }
    }
</script>
```
2024-09-22 14:09:03 +00:00
GuoRuqiang eb210f6069 手动合并upstream 2024-09-22 14:04:15 +00:00
GuoRuqiang 446ca3806b 聊天按钮适配移动端 2024-09-20 04:45:33 +00:00
G.RQ f4c681d37f Merge branch 'Calcium-Ion:main' into main 2024-09-19 15:19:46 +08:00
GuoRuqiang 7ebe52eabe Merge branch 'Calcium-Ion:main' into main 2024-09-18 20:53:24 +08:00
GuoRuqiang 68ef8491cb update HeaderBar 2024-09-18 10:29:25 +00:00
418 changed files with 52106 additions and 21430 deletions
+7
View File
@@ -0,0 +1,7 @@
.github
.git
*.md
.vscode
.gitignore
Makefile
docs
+2 -6
View File
@@ -10,9 +10,9 @@
# 数据库相关配置
# 数据库连接字符串
# SQL_DSN=mysql://user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true
# SQL_DSN=user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true
# 日志数据库连接字符串
# LOG_SQL_DSN=mysql://user:password@tcp(127.0.0.1:3306)/logdb?parseTime=true
# LOG_SQL_DSN=user:password@tcp(127.0.0.1:3306)/logdb?parseTime=true
# SQLite数据库路径
# SQLITE_PATH=/path/to/sqlite.db
# 数据库最大空闲连接数
@@ -50,10 +50,6 @@
# CHANNEL_TEST_FREQUENCY=10
# 生成默认token
# GENERATE_DEFAULT_TOKEN=false
# Gemini 安全设置
# GEMINI_SAFETY_SETTING=BLOCK_NONE
# Gemini版本设置
# GEMINI_MODEL_MAP=gemini-1.0-pro:v1
# Cohere 安全设置
# COHERE_SAFETY_SETTING=NONE
# 是否统计图片token
@@ -1,14 +1,15 @@
name: Publish Docker image (amd64)
name: Publish Docker image (alpha)
on:
push:
tags:
- '*'
branches:
- alpha
workflow_dispatch:
inputs:
name:
description: 'reason'
description: "reason"
required: false
jobs:
push_to_registries:
name: Push Docker image to multiple registries
@@ -18,37 +19,44 @@ jobs:
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Save version info
run: |
git describe --tags > VERSION
echo "alpha-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)" > VERSION
- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to the Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
calciumion/new-api
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=alpha
type=raw,value=alpha-{{date 'YYYYMMDD'}}-{{sha}}
- name: Build and push Docker images
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
+8 -14
View File
@@ -1,15 +1,9 @@
name: Publish Docker image (arm64)
name: Publish Docker image (Multi Registries)
on:
push:
tags:
- '*'
- '!*-alpha*'
workflow_dispatch:
inputs:
name:
description: 'reason'
required: false
jobs:
push_to_registries:
name: Push Docker image to multiple registries
@@ -19,26 +13,26 @@ jobs:
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Save version info
run: |
git describe --tags > VERSION
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to the Container registry
uses: docker/login-action@v2
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -46,14 +40,14 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v5
with:
images: |
calciumion/new-api
ghcr.io/${{ github.repository }}
- name: Build and push Docker images
uses: docker/build-push-action@v3
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
+10 -5
View File
@@ -3,6 +3,11 @@ permissions:
contents: write
on:
workflow_dispatch:
inputs:
name:
description: 'reason'
required: false
push:
tags:
- '*'
@@ -15,16 +20,16 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- uses: oven-sh/setup-bun@v2
with:
node-version: 16
bun-version: latest
- name: Build Frontend
env:
CI: ""
run: |
cd web
npm install
REACT_APP_VERSION=$(git describe --tags) npm run build
bun install
DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(git describe --tags) bun run build
cd ..
- name: Set up Go
uses: actions/setup-go@v3
@@ -38,7 +43,7 @@ jobs:
- name: Build Backend (arm64)
run: |
sudo apt-get update
sudo apt-get install gcc-aarch64-linux-gnu
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api-arm64
- name: Release
+9 -4
View File
@@ -3,6 +3,11 @@ permissions:
contents: write
on:
workflow_dispatch:
inputs:
name:
description: 'reason'
required: false
push:
tags:
- '*'
@@ -15,16 +20,16 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- uses: oven-sh/setup-bun@v2
with:
node-version: 16
bun-version: latest
- name: Build Frontend
env:
CI: ""
run: |
cd web
npm install
REACT_APP_VERSION=$(git describe --tags) npm run build
bun install
DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(git describe --tags) bun run build
cd ..
- name: Set up Go
uses: actions/setup-go@v3
+9 -4
View File
@@ -3,6 +3,11 @@ permissions:
contents: write
on:
workflow_dispatch:
inputs:
name:
description: 'reason'
required: false
push:
tags:
- '*'
@@ -18,16 +23,16 @@ jobs:
uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
- uses: oven-sh/setup-bun@v2
with:
node-version: 16
bun-version: latest
- name: Build Frontend
env:
CI: ""
run: |
cd web
npm install
REACT_APP_VERSION=$(git describe --tags) npm run build
bun install
DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(git describe --tags) bun run build
cd ..
- name: Set up Go
uses: actions/setup-go@v3
+3
View File
@@ -8,3 +8,6 @@ build
logs
web/dist
.env
one-api
.DS_Store
tiktoken_cache
+10 -8
View File
@@ -1,31 +1,33 @@
FROM node:16 as builder
FROM oven/bun:latest AS builder
WORKDIR /build
COPY web/package.json .
RUN npm install
RUN bun install
COPY ./web .
COPY ./VERSION .
RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) npm run build
RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
FROM golang AS builder2
FROM golang:alpine AS builder2
ENV GO111MODULE=on \
CGO_ENABLED=1 \
CGO_ENABLED=0 \
GOOS=linux
WORKDIR /build
ADD go.mod go.sum ./
RUN go mod download
COPY . .
COPY --from=builder /build/dist ./web/dist
RUN go build -ldflags "-s -w -X 'one-api/common.Version=$(cat VERSION)' -extldflags '-static'" -o one-api
RUN go build -ldflags "-s -w -X 'one-api/common.Version=$(cat VERSION)'" -o one-api
FROM alpine
RUN apk update \
&& apk upgrade \
&& apk add --no-cache ca-certificates tzdata \
&& update-ca-certificates 2>/dev/null || true
&& apk add --no-cache ca-certificates tzdata ffmpeg \
&& update-ca-certificates
COPY --from=builder2 /build/one-api /
EXPOSE 3000
+194
View File
@@ -0,0 +1,194 @@
<p align="right">
<a href="./README.md">中文</a> | <strong>English</strong>
</p>
<div align="center">
![new-api](/web/public/logo.png)
# New API
🍥 Next-Generation Large Model Gateway and AI Asset Management System
<a href="https://trendshift.io/repositories/8227" target="_blank"><img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<p align="center">
<a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
<img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="license">
</a>
<a href="https://github.com/Calcium-Ion/new-api/releases/latest">
<img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="release">
</a>
<a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
<img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
</a>
<a href="https://hub.docker.com/r/CalciumIon/new-api">
<img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
</a>
<a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
<img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
</a>
</p>
</div>
## 📝 Project Description
> [!NOTE]
> This is an open-source project developed based on [One API](https://github.com/songquanpeng/one-api)
> [!IMPORTANT]
> - This project is for personal learning purposes only, with no guarantee of stability or technical support.
> - Users must comply with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**, and must not use it for illegal purposes.
> - According to the [《Interim Measures for the Management of Generative Artificial Intelligence Services》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm), please do not provide any unregistered generative AI services to the public in China.
## 📚 Documentation
For detailed documentation, please visit our official Wiki: [https://docs.newapi.pro/](https://docs.newapi.pro/)
You can also access the AI-generated DeepWiki:
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
## ✨ Key Features
New API offers a wide range of features, please refer to [Features Introduction](https://docs.newapi.pro/wiki/features-introduction) for details:
1. 🎨 Brand new UI interface
2. 🌍 Multi-language support
3. 💰 Online recharge functionality (YiPay)
4. 🔍 Support for querying usage quotas with keys (works with [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool))
5. 🔄 Compatible with the original One API database
6. 💵 Support for pay-per-use model pricing
7. ⚖️ Support for weighted random channel selection
8. 📈 Data dashboard (console)
9. 🔒 Token grouping and model restrictions
10. 🤖 Support for more authorization login methods (LinuxDO, Telegram, OIDC)
11. 🔄 Support for Rerank models (Cohere and Jina), [API Documentation](https://docs.newapi.pro/api/jinaai-rerank)
12. ⚡ Support for OpenAI Realtime API (including Azure channels), [API Documentation](https://docs.newapi.pro/api/openai-realtime)
13. ⚡ Support for Claude Messages format, [API Documentation](https://docs.newapi.pro/api/anthropic-chat)
14. Support for entering chat interface via /chat2link route
15. 🧠 Support for setting reasoning effort through model name suffixes:
1. OpenAI o-series models
- Add `-high` suffix for high reasoning effort (e.g.: `o3-mini-high`)
- Add `-medium` suffix for medium reasoning effort (e.g.: `o3-mini-medium`)
- Add `-low` suffix for low reasoning effort (e.g.: `o3-mini-low`)
2. Claude thinking models
- Add `-thinking` suffix to enable thinking mode (e.g.: `claude-3-7-sonnet-20250219-thinking`)
16. 🔄 Thinking-to-content functionality
17. 🔄 Model rate limiting for users
18. 💰 Cache billing support, which allows billing at a set ratio when cache is hit:
1. Set the `Prompt Cache Ratio` option in `System Settings-Operation Settings`
2. Set `Prompt Cache Ratio` in the channel, range 0-1, e.g., setting to 0.5 means billing at 50% when cache is hit
3. Supported channels:
- [x] OpenAI
- [x] Azure
- [x] DeepSeek
- [x] Claude
## Model Support
This version supports multiple models, please refer to [API Documentation-Relay Interface](https://docs.newapi.pro/api) for details:
1. Third-party models **gpts** (gpt-4-gizmo-*)
2. Third-party channel [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy) interface, [API Documentation](https://docs.newapi.pro/api/midjourney-proxy-image)
3. Third-party channel [Suno API](https://github.com/Suno-API/Suno-API) interface, [API Documentation](https://docs.newapi.pro/api/suno-music)
4. Custom channels, supporting full call address input
5. Rerank models ([Cohere](https://cohere.ai/) and [Jina](https://jina.ai/)), [API Documentation](https://docs.newapi.pro/api/jinaai-rerank)
6. Claude Messages format, [API Documentation](https://docs.newapi.pro/api/anthropic-chat)
7. Dify, currently only supports chatflow
## Environment Variable Configuration
For detailed configuration instructions, please refer to [Installation Guide-Environment Variables Configuration](https://docs.newapi.pro/installation/environment-variables):
- `GENERATE_DEFAULT_TOKEN`: Whether to generate initial tokens for newly registered users, default is `false`
- `STREAMING_TIMEOUT`: Streaming response timeout, default is 60 seconds
- `DIFY_DEBUG`: Whether to output workflow and node information for Dify channels, default is `true`
- `FORCE_STREAM_OPTION`: Whether to override client stream_options parameter, default is `true`
- `GET_MEDIA_TOKEN`: Whether to count image tokens, default is `true`
- `GET_MEDIA_TOKEN_NOT_STREAM`: Whether to count image tokens in non-streaming cases, default is `true`
- `UPDATE_TASK`: Whether to update asynchronous tasks (Midjourney, Suno), default is `true`
- `COHERE_SAFETY_SETTING`: Cohere model safety settings, options are `NONE`, `CONTEXTUAL`, `STRICT`, default is `NONE`
- `GEMINI_VISION_MAX_IMAGE_NUM`: Maximum number of images for Gemini models, default is `16`
- `MAX_FILE_DOWNLOAD_MB`: Maximum file download size in MB, default is `20`
- `CRYPTO_SECRET`: Encryption key used for encrypting database content
- `AZURE_DEFAULT_API_VERSION`: Azure channel default API version, default is `2025-04-01-preview`
- `NOTIFICATION_LIMIT_DURATION_MINUTE`: Notification limit duration, default is `10` minutes
- `NOTIFY_LIMIT_COUNT`: Maximum number of user notifications within the specified duration, default is `2`
- `ERROR_LOG_ENABLED=true`: Whether to record and display error logs, default is `false`
## Deployment
For detailed deployment guides, please refer to [Installation Guide-Deployment Methods](https://docs.newapi.pro/installation):
> [!TIP]
> Latest Docker image: `calciumion/new-api:latest`
### Multi-machine Deployment Considerations
- Environment variable `SESSION_SECRET` must be set, otherwise login status will be inconsistent across multiple machines
- If sharing Redis, `CRYPTO_SECRET` must be set, otherwise Redis content cannot be accessed across multiple machines
### Deployment Requirements
- Local database (default): SQLite (Docker deployment must mount the `/data` directory)
- Remote database: MySQL version >= 5.7.8, PgSQL version >= 9.6
### Deployment Methods
#### Using BaoTa Panel Docker Feature
Install BaoTa Panel (version **9.2.0** or above), find **New-API** in the application store and install it.
[Tutorial with images](./docs/BT.md)
#### Using Docker Compose (Recommended)
```shell
# Download the project
git clone https://github.com/Calcium-Ion/new-api.git
cd new-api
# Edit docker-compose.yml as needed
# Start
docker-compose up -d
```
#### Using Docker Image Directly
```shell
# Using SQLite
docker run --name new-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/new-api:/data calciumion/new-api:latest
# Using MySQL
docker run --name new-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/new-api:/data calciumion/new-api:latest
```
## Channel Retry and Cache
Channel retry functionality has been implemented, you can set the number of retries in `Settings->Operation Settings->General Settings`. It is **recommended to enable caching**.
### Cache Configuration Method
1. `REDIS_CONN_STRING`: Set Redis as cache
2. `MEMORY_CACHE_ENABLED`: Enable memory cache (no need to set manually if Redis is set)
## API Documentation
For detailed API documentation, please refer to [API Documentation](https://docs.newapi.pro/api):
- [Chat API](https://docs.newapi.pro/api/openai-chat)
- [Image API](https://docs.newapi.pro/api/openai-image)
- [Rerank API](https://docs.newapi.pro/api/jinaai-rerank)
- [Realtime API](https://docs.newapi.pro/api/openai-realtime)
- [Claude Chat API (messages)](https://docs.newapi.pro/api/anthropic-chat)
## Related Projects
- [One API](https://github.com/songquanpeng/one-api): Original project
- [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy): Midjourney interface support
- [chatnio](https://github.com/Deeptrain-Community/chatnio): Next-generation AI one-stop B/C-end solution
- [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool): Query usage quota with key
Other projects based on New API:
- [new-api-horizon](https://github.com/Calcium-Ion/new-api-horizon): High-performance optimized version of New API
- [VoAPI](https://github.com/VoAPI/VoAPI): Frontend beautified version based on New API
## Help and Support
If you have any questions, please refer to [Help and Support](https://docs.newapi.pro/support):
- [Community Interaction](https://docs.newapi.pro/support/community-interaction)
- [Issue Feedback](https://docs.newapi.pro/support/feedback-issues)
- [FAQ](https://docs.newapi.pro/support/faq)
## 🌟 Star History
[![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
+151 -111
View File
@@ -1,156 +1,196 @@
<p align="right">
<strong>中文</strong> | <a href="./README.en.md">English</a>
</p>
<div align="center">
![new-api](/web/public/logo.png)
# New API
🍥新一代大模型网关与AI资产管理系统
<a href="https://trendshift.io/repositories/8227" target="_blank"><img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<p align="center">
<a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
<img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="license">
</a>
<a href="https://github.com/Calcium-Ion/new-api/releases/latest">
<img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="release">
</a>
<a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
<img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
</a>
<a href="https://hub.docker.com/r/CalciumIon/new-api">
<img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
</a>
<a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
<img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
</a>
<a href="https://coderabbit.ai">
<img src="https://img.shields.io/coderabbit/prs/github/QuantumNous/new-api?utm_source=oss&utm_medium=github&utm_campaign=QuantumNous%2Fnew-api&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews" alt="CodeRabbit Pull Request Reviews">
</a>
</p>
</div>
> [!NOTE]
## 📝 项目说明
> [!NOTE]
> 本项目为开源项目,在[One API](https://github.com/songquanpeng/one-api)的基础上进行二次开发
> [!IMPORTANT]
> 使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途
> 本项目仅供个人学习使用,不保证稳定性,且不提供任何技术支持
> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。
> [!IMPORTANT]
> - 本项目仅供个人学习使用,不保证稳定性,且不提供任何技术支持
> - 使用者必须在遵循 OpenAI 的[使用条款](https://openai.com/policies/terms-of-use)以及**法律法规**的情况下使用,不得用于非法用途
> - 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。
## 📚 文档
详细文档请访问我们的官方Wiki[https://docs.newapi.pro/](https://docs.newapi.pro/)
也可访问AI生成的DeepWiki:
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
## ✨ 主要特性
New API提供了丰富的功能,详细特性请参考[特性说明](https://docs.newapi.pro/wiki/features-introduction)
1. 🎨 全新的UI界面
2. 🌍 多语言支持
3. 💰 支持在线充值功能(易支付)
4. 🔍 支持用key查询使用额度(配合[neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool)
5. 🔄 兼容原版One API的数据库
6. 💵 支持模型按次数收费
7. ⚖️ 支持渠道加权随机
8. 📈 数据看板(控制台)
9. 🔒 令牌分组、模型限制
10. 🤖 支持更多授权登陆方式(LinuxDO,Telegram、OIDC
11. 🔄 支持Rerank模型(Cohere和Jina),[接口文档](https://docs.newapi.pro/api/jinaai-rerank)
12. ⚡ 支持OpenAI Realtime API(包括Azure渠道),[接口文档](https://docs.newapi.pro/api/openai-realtime)
13. ⚡ 支持Claude Messages 格式,[接口文档](https://docs.newapi.pro/api/anthropic-chat)
14. 支持使用路由/chat2link进入聊天界面
15. 🧠 支持通过模型名称后缀设置 reasoning effort
1. OpenAI o系列模型
- 添加后缀 `-high` 设置为 high reasoning effort (例如: `o3-mini-high`)
- 添加后缀 `-medium` 设置为 medium reasoning effort (例如: `o3-mini-medium`)
- 添加后缀 `-low` 设置为 low reasoning effort (例如: `o3-mini-low`)
2. Claude 思考模型
- 添加后缀 `-thinking` 启用思考模式 (例如: `claude-3-7-sonnet-20250219-thinking`)
16. 🔄 思考转内容功能
17. 🔄 针对用户的模型限流功能
18. 💰 缓存计费支持,开启后可以在缓存命中时按照设定的比例计费:
1.`系统设置-运营设置` 中设置 `提示缓存倍率` 选项
2. 在渠道中设置 `提示缓存倍率`,范围 0-1,例如设置为 0.5 表示缓存命中时按照 50% 计费
3. 支持的渠道:
- [x] OpenAI
- [x] Azure
- [x] DeepSeek
- [x] Claude
## 模型支持
此版本支持多种模型,详情请参考[接口文档-中继接口](https://docs.newapi.pro/api)
1. 第三方模型 **gpts** gpt-4-gizmo-*
2. 第三方渠道[Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy)接口,[接口文档](https://docs.newapi.pro/api/midjourney-proxy-image)
3. 第三方渠道[Suno API](https://github.com/Suno-API/Suno-API)接口,[接口文档](https://docs.newapi.pro/api/suno-music)
4. 自定义渠道,支持填入完整调用地址
5. Rerank模型([Cohere](https://cohere.ai/)和[Jina](https://jina.ai/)),[接口文档](https://docs.newapi.pro/api/jinaai-rerank)
6. Claude Messages 格式,[接口文档](https://docs.newapi.pro/api/anthropic-chat)
7. Dify,当前仅支持chatflow
## 环境变量配置
详细配置说明请参考[安装指南-环境变量配置](https://docs.newapi.pro/installation/environment-variables)
- `GENERATE_DEFAULT_TOKEN`:是否为新注册用户生成初始令牌,默认为 `false`
- `STREAMING_TIMEOUT`:流式回复超时时间,默认60秒
- `DIFY_DEBUG`:Dify渠道是否输出工作流和节点信息,默认 `true`
- `FORCE_STREAM_OPTION`:是否覆盖客户端stream_options参数,默认 `true`
- `GET_MEDIA_TOKEN`:是否统计图片token,默认 `true`
- `GET_MEDIA_TOKEN_NOT_STREAM`:非流情况下是否统计图片token,默认 `true`
- `UPDATE_TASK`:是否更新异步任务(Midjourney、Suno),默认 `true`
- `COHERE_SAFETY_SETTING`Cohere模型安全设置,可选值为 `NONE`, `CONTEXTUAL`, `STRICT`,默认 `NONE`
- `GEMINI_VISION_MAX_IMAGE_NUM`Gemini模型最大图片数量,默认 `16`
- `MAX_FILE_DOWNLOAD_MB`: 最大文件下载大小,单位MB,默认 `20`
- `CRYPTO_SECRET`:加密密钥,用于加密数据库内容
- `AZURE_DEFAULT_API_VERSION`Azure渠道默认API版本,默认 `2025-04-01-preview`
- `NOTIFICATION_LIMIT_DURATION_MINUTE`:通知限制持续时间,默认 `10`分钟
- `NOTIFY_LIMIT_COUNT`:用户通知在指定持续时间内的最大数量,默认 `2`
- `ERROR_LOG_ENABLED=true`: 是否记录并显示错误日志,默认`false`
## 部署
详细部署指南请参考[安装指南-部署方式](https://docs.newapi.pro/installation)
> [!TIP]
> 最新版Docker镜像:`calciumion/new-api:latest`
> 默认账号root 密码123456
> 更新指令:
> ```
> docker run --rm -v /var/run/docker.sock:/var/run/docker.sock containrrr/watchtower -cR
> ```
### 多机部署注意事项
- 必须设置环境变量 `SESSION_SECRET`,否则会导致多机部署时登录状态不一致
- 如果公用Redis,必须设置 `CRYPTO_SECRET`,否则会导致多机部署时Redis内容无法获取
## 主要变更
此分叉版本的主要变更如下:
1. 全新的UI界面(部分界面还待更新)
2. 添加[Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy)接口的支持,[对接文档](Midjourney.md)
3. 支持在线充值功能,可在系统设置中设置,当前支持的支付接口:
+ [x] 易支付
4. 支持用key查询使用额度:
+ 配合项目[neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool)可实现用key查询使用
5. 渠道显示已使用额度,支持指定组织访问
6. 分页支持选择每页显示数量
7. 兼容原版One API的数据库,可直接使用原版数据库(one-api.db)
8. 支持模型按次数收费,可在 系统设置-运营设置 中设置
9. 支持渠道**加权随机**
10. 数据看板
11. 可设置令牌能调用的模型
12. 支持Telegram授权登录。
1. 系统设置-配置登录注册-允许通过Telegram登录
2. 对[@Botfather](https://t.me/botfather)输入指令/setdomain
3. 选择你的bot,然后输入http(s)://你的网站地址/login
4. Telegram Bot 名称是bot username 去掉@后的字符串
13. 添加 [Suno API](https://github.com/Suno-API/Suno-API)接口的支持,[对接文档](Suno.md)
14. 支持Rerank模型,目前仅兼容Cohere和Jina,可接入Dify[对接文档](Rerank.md)
15. **[OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/integration)** - 支持OpenAI的Realtime API,支持Azure渠道。
## 模型支持
此版本额外支持以下模型:
1. 第三方模型 **gps** gpt-4-gizmo-*
2. 智谱glm-4vglm-4v识图
3. Anthropic Claude 3
4. [Ollama](https://github.com/ollama/ollama?tab=readme-ov-file),添加渠道时,密钥可以随便填写,默认的请求地址是[http://localhost:11434](http://localhost:11434),如果需要修改请在渠道中修改
5. [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy)接口,[对接文档](Midjourney.md)
6. [零一万物](https://platform.lingyiwanwu.com/)
7. 自定义渠道,支持填入完整调用地址
8. [Suno API](https://github.com/Suno-API/Suno-API) 接口,[对接文档](Suno.md)
9. Rerank模型,目前支持[Cohere](https://cohere.ai/)和[Jina](https://jina.ai/)[对接文档](Rerank.md)
10. Dify
11. Vertex AI,目前兼容ClaudeGeminiLlama3.1
您可以在渠道中添加自定义模型gpt-4-gizmo-*,此模型并非OpenAI官方模型,而是第三方模型,使用官方key无法调用。
## 比原版One API多出的配置
- `GENERATE_DEFAULT_TOKEN`:是否为新注册用户生成初始令牌,默认为 `false`
- `STREAMING_TIMEOUT`:设置流式一次回复的超时时间,默认为 60 秒。
- `DIFY_DEBUG`:设置 Dify 渠道是否输出工作流和节点信息到客户端,默认为 `true`
- `FORCE_STREAM_OPTION`:是否覆盖客户端stream_options参数,请求上游返回流模式usage,默认为 `true`,建议开启,不影响客户端传入stream_options参数返回结果。
- `GET_MEDIA_TOKEN`:是统计图片token,默认为 `true`,关闭后将不再在本地计算图片token,可能会导致和上游计费不同,此项覆盖 `GET_MEDIA_TOKEN_NOT_STREAM` 选项作用。
- `GET_MEDIA_TOKEN_NOT_STREAM`:是否在非流(`stream=false`)情况下统计图片token,默认为 `true`
- `UPDATE_TASK`:是否更新异步任务(Midjourney、Suno),默认为 `true`,关闭后将不会更新任务进度。
- `GEMINI_MODEL_MAP`Gemini模型指定版本(v1/v1beta),使用“模型:版本”指定,","分隔,例如:-e GEMINI_MODEL_MAP="gemini-1.5-pro-latest:v1beta,gemini-1.5-pro-001:v1beta",为空则使用默认配置(v1beta)
- `COHERE_SAFETY_SETTING`Cohere模型[安全设置](https://docs.cohere.com/docs/safety-modes#overview),可选值为 `NONE`, `CONTEXTUAL``STRICT`,默认为 `NONE`
## 部署
### 部署要求
- 本地数据库(默认):SQLiteDocker 部署默认使用 SQLite必须挂载 `/data` 目录到宿主机
- 远程数据库:MySQL 版本 >= 5.7.8PgSQL 版本 >= 9.6
- 本地数据库(默认):SQLite(Docker部署必须挂载`/data`目录)
- 远程数据库:MySQL版本 >= 5.7.8PgSQL版本 >= 9.6
### 使用宝塔面板Docker功能部署
安装宝塔面板 (**9.2.0版本**及以上),前往 [宝塔面板](https://www.bt.cn/new/download.html) 官网,选择正式版的脚本下载安装
安装后登录宝塔面板,在菜单栏中点击 Docker ,首次进入会提示安装 Docker 服务,点击立即安装,按提示完成安装
安装完成后在应用商店中找到 **New-API** ,点击安装,配置基本选项 即可完成安装
[图文教程](BT.md)
### 部署方式
### 基于 Docker 进行部署
### 使用 Docker Compose 部署(推荐)
#### 使用宝塔面板Docker功能部署
安装宝塔面板(**9.2.0版本**及以上),在应用商店中找到**New-API**安装即可。
[图文教程](./docs/BT.md)
#### 使用Docker Compose部署(推荐)
```shell
# 下载项目
git clone https://github.com/Calcium-Ion/new-api.git
cd new-api
# 按需编辑 docker-compose.yml
# 按需编辑docker-compose.yml
# 启动
docker-compose up -d
```
### 直接使用 Docker 镜像
#### 直接使用Docker镜像
```shell
# 使用 SQLite 的部署命令:
# 使用SQLite
docker run --name new-api -d --restart always -p 3000:3000 -e TZ=Asia/Shanghai -v /home/ubuntu/data/new-api:/data calciumion/new-api:latest
# 使用 MySQL 的部署命令,在上面的基础上添加 `-e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi"`,请自行修改数据库连接参数。
# 例如:
# 使用MySQL
docker run --name new-api -d --restart always -p 3000:3000 -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" -e TZ=Asia/Shanghai -v /home/ubuntu/data/new-api:/data calciumion/new-api:latest
```
## 渠道重试
渠道重试功能已经实现,可以在`设置->运营设置->通用设置`设置重试次数,**建议开启缓存**功能。
如果开启了重试功能,第一次重试使用同优先级,第二次重试使用下一个优先级,以此类推。
## 渠道重试与缓存
渠道重试功能已经实现,可以在`设置->运营设置->通用设置`设置重试次数,**建议开启缓存**功能。
### 缓存设置方法
1. `REDIS_CONN_STRING`:设置之后将使用 Redis 作为缓存使用。
+ 例子:`REDIS_CONN_STRING=redis://default:redispw@localhost:49153`
2. `MEMORY_CACHE_ENABLED`:启用内存缓存(如果设置了`REDIS_CONN_STRING`,则无需手动设置),会导致用户额度的更新存在一定的延迟,可选值为 `true``false`,未设置则默认为 `false`
+ 例子:`MEMORY_CACHE_ENABLED=true`
### 为什么有的时候没有重试
这些错误码不会重试:400,504,524
### 我想让400也重试
`渠道->编辑`中,将`状态码复写`改为
```json
{
"400": "500"
}
```
可以实现400错误转为500错误,从而重试
1. `REDIS_CONN_STRING`:设置Redis作为缓存
2. `MEMORY_CACHE_ENABLED`:启用内存缓存(设置了Redis则无需手动设置)
## Midjourney接口设置文档
[对接文档](Midjourney.md)
## 接口文档
## Suno接口设置文档
[对接文档](Suno.md)
详细接口文档请参考[接口文档](https://docs.newapi.pro/api)
## 界面截图
![796df8d287b7b7bd7853b2497e7df511](https://github.com/user-attachments/assets/255b5e97-2d3a-4434-b4fa-e922ad88ff5a)
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/ad0e7aae-0203-471c-9716-2d83768927d4)
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/3ca0b282-00ff-4c96-bf9d-e29ef615c605)
夜间模式
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/1c66b593-bb9e-4757-9720-ff2759539242)
![image](https://github.com/Calcium-Ion/new-api/assets/61247483/af9a07ee-5101-4b3d-8bd9-ae21a4fd7e9e)
## 交流群
<img src="https://github.com/user-attachments/assets/9ca0bc82-e057-4230-a28d-9f198fa022e3" width="200">
- [聊天接口(Chat](https://docs.newapi.pro/api/openai-chat)
- [图像接口(Image](https://docs.newapi.pro/api/openai-image)
- [重排序接口(Rerank](https://docs.newapi.pro/api/jinaai-rerank)
- [实时对话接口(Realtime](https://docs.newapi.pro/api/openai-realtime)
- [Claude聊天接口(messages](https://docs.newapi.pro/api/anthropic-chat)
## 相关项目
- [One API](https://github.com/songquanpeng/one-api):原版项目
- [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy)Midjourney接口支持
- [chatnio](https://github.com/Deeptrain-Community/chatnio):下一代 AI 一站式 B/C 端解决方案
- [chatnio](https://github.com/Deeptrain-Community/chatnio):下一代AI一站式B/C端解决方案
- [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool):用key查询使用额度
## Star History
其他基于New API的项目:
- [new-api-horizon](https://github.com/Calcium-Ion/new-api-horizon)New API高性能优化版
## 帮助支持
如有问题,请参考[帮助支持](https://docs.newapi.pro/support)
- [社区交流](https://docs.newapi.pro/support/community-interaction)
- [反馈问题](https://docs.newapi.pro/support/feedback-issues)
- [常见问题](https://docs.newapi.pro/support/faq)
## 🌟 Star History
[![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
+44 -26
View File
@@ -1,8 +1,8 @@
package common
import (
"os"
"strconv"
//"os"
//"strconv"
"sync"
"time"
@@ -15,8 +15,9 @@ var SystemName = "New API"
var Footer = ""
var Logo = ""
var TopUpLink = ""
var ChatLink = ""
var ChatLink2 = ""
// var ChatLink = ""
// var ChatLink2 = ""
var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
var DisplayInCurrencyEnabled = true
var DisplayTokenStatEnabled = true
@@ -30,6 +31,7 @@ var DefaultCollapseSidebar = false // default value of collapse sidebar
// Any options with "Secret", "Token" in its key won't be return by GetOptions
var SessionSecret = uuid.New().String()
var CryptoSecret = uuid.New().String()
var OptionMap map[string]string
var OptionMapRWMutex sync.RWMutex
@@ -60,9 +62,13 @@ var EmailDomainWhitelist = []string{
"yahoo.com",
"foxmail.com",
}
var EmailLoginAuthServerList = []string{
"smtp.sendcloud.net",
"smtp.azurecomm.net",
}
var DebugEnabled = os.Getenv("DEBUG") == "true"
var MemoryCacheEnabled = os.Getenv("MEMORY_CACHE_ENABLED") == "true"
var DebugEnabled bool
var MemoryCacheEnabled bool
var LogConsumeEnabled = true
@@ -75,7 +81,6 @@ var SMTPToken = ""
var GitHubClientId = ""
var GitHubClientSecret = ""
var LinuxDOClientId = ""
var LinuxDOClientSecret = ""
@@ -100,24 +105,24 @@ var PreConsumedQuota = 500
var RetryTimes = 0
var RootUserEmail = ""
//var RootUserEmail = ""
var IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
var IsMasterNode bool
var requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
var RequestInterval = time.Duration(requestInterval) * time.Second
var requestInterval int
var RequestInterval time.Duration
var SyncFrequency = GetEnvOrDefault("SYNC_FREQUENCY", 60) // unit is second
var SyncFrequency int // unit is second
var BatchUpdateEnabled = false
var BatchUpdateInterval = GetEnvOrDefault("BATCH_UPDATE_INTERVAL", 5)
var BatchUpdateInterval int
var RelayTimeout = GetEnvOrDefault("RELAY_TIMEOUT", 0) // unit is second
var RelayTimeout int // unit is second
var GeminiSafetySetting = GetEnvOrDefaultString("GEMINI_SAFETY_SETTING", "BLOCK_NONE")
var GeminiSafetySetting string
// https://docs.cohere.com/docs/safety-modes Type; NONE/CONTEXTUAL/STRICT
var CohereSafetySetting = GetEnvOrDefaultString("COHERE_SAFETY_SETTING", "NONE")
var CohereSafetySetting string
const (
RequestIdKey = "X-Oneapi-Request-Id"
@@ -144,13 +149,13 @@ var (
// All duration's unit is seconds
// Shouldn't larger then RateLimitKeyExpirationDuration
var (
GlobalApiRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_API_RATE_LIMIT_ENABLE", true)
GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
GlobalApiRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_API_RATE_LIMIT_DURATION", 180))
GlobalApiRateLimitEnable bool
GlobalApiRateLimitNum int
GlobalApiRateLimitDuration int64
GlobalWebRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_WEB_RATE_LIMIT_ENABLE", true)
GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
GlobalWebRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT_DURATION", 180))
GlobalWebRateLimitEnable bool
GlobalWebRateLimitNum int
GlobalWebRateLimitDuration int64
UploadRateLimitNum = 10
UploadRateLimitDuration int64 = 60
@@ -229,8 +234,14 @@ const (
ChannelTypeSiliconFlow = 40
ChannelTypeVertexAi = 41
ChannelTypeMistral = 42
ChannelTypeDummy // this one is only for count, do not add any channel after this
ChannelTypeDeepSeek = 43
ChannelTypeMokaAI = 44
ChannelTypeVolcEngine = 45
ChannelTypeBaiduV2 = 46
ChannelTypeXinference = 47
ChannelTypeXai = 48
ChannelTypeCoze = 49
ChannelTypeDummy // this one is only for count, do not add any channel after this
)
@@ -254,7 +265,7 @@ var ChannelBaseURLs = []string{
"https://open.bigmodel.cn", // 16
"https://dashscope.aliyuncs.com", // 17
"", // 18
"https://ai.360.cn", // 19
"https://api.360.cn", // 19
"https://openrouter.ai/api", // 20
"https://api.aiproxy.io", // 21
"https://fastgpt.run/api/openapi", // 22
@@ -272,10 +283,17 @@ var ChannelBaseURLs = []string{
"https://api.cohere.ai", //34
"https://api.minimax.chat", //35
"", //36
"", //37
"https://api.dify.ai", //37
"https://api.jina.ai", //38
"https://api.cloudflare.com", //39
"https://api.siliconflow.cn", //40
"", //41
"https://api.mistral.ai", //42
"https://api.deepseek.com", //43
"https://api.moka.ai", //44
"https://ark.cn-beijing.volces.com", //45
"https://qianfan.baidubce.com", //46
"", //47
"https://api.x.ai", //48
"https://api.coze.cn", //49
}
+18 -1
View File
@@ -1,6 +1,23 @@
package common
import "golang.org/x/crypto/bcrypt"
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"golang.org/x/crypto/bcrypt"
)
func GenerateHMACWithKey(key []byte, data string) string {
h := hmac.New(sha256.New, key)
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func GenerateHMAC(data string) string {
h := hmac.New(sha256.New, []byte(CryptoSecret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func Password2Hash(password string) (string, error) {
passwordBytes := []byte(password)
+1 -1
View File
@@ -44,7 +44,7 @@ var fieldReplacer = strings.NewReplacer(
"\r", "\\r")
var dataReplacer = strings.NewReplacer(
"\n", "\ndata:",
"\n", "\n",
"\r", "\\r")
type CustomEvent struct {
+8
View File
@@ -1,7 +1,15 @@
package common
const (
DatabaseTypeMySQL = "mysql"
DatabaseTypeSQLite = "sqlite"
DatabaseTypePostgreSQL = "postgres"
)
var UsingSQLite = false
var UsingPostgreSQL = false
var LogSqlType = DatabaseTypeSQLite // Default to SQLite for logging SQL queries
var UsingMySQL = false
var UsingClickHouse = false
var SQLitePath = "one-api.db?_busy_timeout=5000"
+16 -7
View File
@@ -5,19 +5,28 @@ import (
"encoding/base64"
"fmt"
"net/smtp"
"slices"
"strings"
"time"
)
func generateMessageID() string {
domain := strings.Split(SMTPAccount, "@")[1]
return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), GetRandomString(12), domain)
func generateMessageID() (string, error) {
split := strings.Split(SMTPFrom, "@")
if len(split) < 2 {
return "", fmt.Errorf("invalid SMTP account")
}
domain := strings.Split(SMTPFrom, "@")[1]
return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), GetRandomString(12), domain), nil
}
func SendEmail(subject string, receiver string, content string) error {
if SMTPFrom == "" { // for compatibility
SMTPFrom = SMTPAccount
}
id, err2 := generateMessageID()
if err2 != nil {
return err2
}
if SMTPServer == "" && SMTPAccount == "" {
return fmt.Errorf("SMTP 服务器未配置")
}
@@ -28,7 +37,7 @@ func SendEmail(subject string, receiver string, content string) error {
"Date: %s\r\n"+
"Message-ID: %s\r\n"+ // 添加 Message-ID 头
"Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n",
receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), generateMessageID(), content))
receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), id, content))
auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort)
to := strings.Split(receiver, ";")
@@ -71,11 +80,11 @@ func SendEmail(subject string, receiver string, content string) error {
if err != nil {
return err
}
} else if isOutlookServer(SMTPAccount) {
} else if isOutlookServer(SMTPAccount) || slices.Contains(EmailLoginAuthServerList, SMTPServer) {
auth = LoginAuth(SMTPAccount, SMTPToken)
err = smtp.SendMail(addr, auth, SMTPAccount, to, mail)
err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
} else {
err = smtp.SendMail(addr, auth, SMTPAccount, to, mail)
err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
}
return err
}
-13
View File
@@ -1,22 +1,9 @@
package common
import (
"fmt"
"runtime/debug"
"time"
)
func SafeGoroutine(f func()) {
go func() {
defer func() {
if r := recover(); r != nil {
SysError(fmt.Sprintf("child goroutine panic occured: error: %v, stack: %s", r, string(debug.Stack())))
}
}()
f()
}()
}
func SafeSendBool(ch chan bool, value bool) (closed bool) {
defer func() {
// Recover from panic if one occured. A panic would mean the channel was closed.
+24
View File
@@ -0,0 +1,24 @@
package common
import (
"context"
"fmt"
"github.com/bytedance/gopkg/util/gopool"
"math"
)
var relayGoPool gopool.Pool
func init() {
relayGoPool = gopool.NewPool("gopool.RelayPool", math.MaxInt32, gopool.NewConfig())
relayGoPool.SetPanicHandler(func(ctx context.Context, i interface{}) {
if stopChan, ok := ctx.Value("stop_chan").(chan bool); ok {
SafeSendBool(stopChan, true)
}
SysError(fmt.Sprintf("panic in gopool.RelayPool: %v", i))
})
}
func RelayCtxGo(ctx context.Context, f func()) {
relayGoPool.CtxGo(ctx, f)
}
-48
View File
@@ -1,48 +0,0 @@
package common
import (
"encoding/json"
"errors"
)
var GroupRatio = map[string]float64{
"default": 1,
"vip": 1,
"svip": 1,
}
func GroupRatio2JSONString() string {
jsonBytes, err := json.Marshal(GroupRatio)
if err != nil {
SysError("error marshalling model ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateGroupRatioByJSONString(jsonStr string) error {
GroupRatio = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &GroupRatio)
}
func GetGroupRatio(name string) float64 {
ratio, ok := GroupRatio[name]
if !ok {
SysError("group ratio not found: " + name)
return 1
}
return ratio
}
func CheckGroupRatio(jsonStr string) error {
checkGroupRatio := make(map[string]float64)
err := json.Unmarshal([]byte(jsonStr), &checkGroupRatio)
if err != nil {
return err
}
for name, ratio := range checkGroupRatio {
if ratio < 0 {
return errors.New("group ratio must be not less than 0: " + name)
}
}
return nil
}
+35 -1
View File
@@ -6,6 +6,8 @@ import (
"log"
"os"
"path/filepath"
"strconv"
"time"
)
var (
@@ -22,7 +24,7 @@ func printHelp() {
fmt.Println("Usage: one-api [--port <port>] [--log-dir <log directory>] [--version] [--help]")
}
func init() {
func LoadEnv() {
flag.Parse()
if *PrintVersion {
@@ -45,6 +47,11 @@ func init() {
SessionSecret = ss
}
}
if os.Getenv("CRYPTO_SECRET") != "" {
CryptoSecret = os.Getenv("CRYPTO_SECRET")
} else {
CryptoSecret = SessionSecret
}
if os.Getenv("SQLITE_PATH") != "" {
SQLitePath = os.Getenv("SQLITE_PATH")
}
@@ -61,4 +68,31 @@ func init() {
}
}
}
// Initialize variables from constants.go that were using environment variables
DebugEnabled = os.Getenv("DEBUG") == "true"
MemoryCacheEnabled = os.Getenv("MEMORY_CACHE_ENABLED") == "true"
IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
// Parse requestInterval and set RequestInterval
requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
RequestInterval = time.Duration(requestInterval) * time.Second
// Initialize variables with GetEnvOrDefault
SyncFrequency = GetEnvOrDefault("SYNC_FREQUENCY", 60)
BatchUpdateInterval = GetEnvOrDefault("BATCH_UPDATE_INTERVAL", 5)
RelayTimeout = GetEnvOrDefault("RELAY_TIMEOUT", 0)
// Initialize string variables with GetEnvOrDefaultString
GeminiSafetySetting = GetEnvOrDefaultString("GEMINI_SAFETY_SETTING", "BLOCK_NONE")
CohereSafetySetting = GetEnvOrDefaultString("COHERE_SAFETY_SETTING", "NONE")
// Initialize rate limit variables
GlobalApiRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_API_RATE_LIMIT_ENABLE", true)
GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
GlobalApiRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_API_RATE_LIMIT_DURATION", 180))
GlobalWebRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_WEB_RATE_LIMIT_ENABLE", true)
GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
GlobalWebRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT_DURATION", 180))
}
+18
View File
@@ -0,0 +1,18 @@
package common
import (
"bytes"
"encoding/json"
)
func DecodeJson(data []byte, v any) error {
return json.NewDecoder(bytes.NewReader(data)).Decode(v)
}
func DecodeJsonStr(data string, v any) error {
return DecodeJson(StringToByteSlice(data), v)
}
func EncodeJson(v any) ([]byte, error) {
return json.Marshal(v)
}
+89
View File
@@ -0,0 +1,89 @@
package limiter
import (
"context"
_ "embed"
"fmt"
"github.com/go-redis/redis/v8"
"one-api/common"
"sync"
)
//go:embed lua/rate_limit.lua
var rateLimitScript string
type RedisLimiter struct {
client *redis.Client
limitScriptSHA string
}
var (
instance *RedisLimiter
once sync.Once
)
func New(ctx context.Context, r *redis.Client) *RedisLimiter {
once.Do(func() {
// 预加载脚本
limitSHA, err := r.ScriptLoad(ctx, rateLimitScript).Result()
if err != nil {
common.SysLog(fmt.Sprintf("Failed to load rate limit script: %v", err))
}
instance = &RedisLimiter{
client: r,
limitScriptSHA: limitSHA,
}
})
return instance
}
func (rl *RedisLimiter) Allow(ctx context.Context, key string, opts ...Option) (bool, error) {
// 默认配置
config := &Config{
Capacity: 10,
Rate: 1,
Requested: 1,
}
// 应用选项模式
for _, opt := range opts {
opt(config)
}
// 执行限流
result, err := rl.client.EvalSha(
ctx,
rl.limitScriptSHA,
[]string{key},
config.Requested,
config.Rate,
config.Capacity,
).Int()
if err != nil {
return false, fmt.Errorf("rate limit failed: %w", err)
}
return result == 1, nil
}
// Config 配置选项模式
type Config struct {
Capacity int64
Rate int64
Requested int64
}
type Option func(*Config)
func WithCapacity(c int64) Option {
return func(cfg *Config) { cfg.Capacity = c }
}
func WithRate(r int64) Option {
return func(cfg *Config) { cfg.Rate = r }
}
func WithRequested(n int64) Option {
return func(cfg *Config) { cfg.Requested = n }
}
+44
View File
@@ -0,0 +1,44 @@
-- 令牌桶限流器
-- KEYS[1]: 限流器唯一标识
-- ARGV[1]: 请求令牌数 (通常为1)
-- ARGV[2]: 令牌生成速率 (每秒)
-- ARGV[3]: 桶容量
local key = KEYS[1]
local requested = tonumber(ARGV[1])
local rate = tonumber(ARGV[2])
local capacity = tonumber(ARGV[3])
-- 获取当前时间(Redis服务器时间)
local now = redis.call('TIME')
local nowInSeconds = tonumber(now[1])
-- 获取桶状态
local bucket = redis.call('HMGET', key, 'tokens', 'last_time')
local tokens = tonumber(bucket[1])
local last_time = tonumber(bucket[2])
-- 初始化桶(首次请求或过期)
if not tokens or not last_time then
tokens = capacity
last_time = nowInSeconds
else
-- 计算新增令牌
local elapsed = nowInSeconds - last_time
local add_tokens = elapsed * rate
tokens = math.min(capacity, tokens + add_tokens)
last_time = nowInSeconds
end
-- 判断是否允许请求
local allowed = false
if tokens >= requested then
tokens = tokens - requested
allowed = true
end
---- 更新桶状态并设置过期时间
redis.call('HMSET', key, 'tokens', tokens, 'last_time', last_time)
--redis.call('EXPIRE', key, math.ceil(capacity / rate) + 60) -- 适当延长过期时间
return allowed and 1 or 0
+12 -3
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"github.com/bytedance/gopkg/util/gopool"
"github.com/gin-gonic/gin"
"io"
"log"
@@ -36,7 +37,7 @@ func SetupLogger() {
setupLogLock.Unlock()
setupLogWorking = false
}()
logPath := filepath.Join(*LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102")))
logPath := filepath.Join(*LogDir, fmt.Sprintf("oneapi-%s.log", time.Now().Format("20060102150405")))
fd, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal("failed to open log file")
@@ -80,9 +81,9 @@ func logHelper(ctx context.Context, level string, msg string) {
if logCount > maxLogCount && !setupLogWorking {
logCount = 0
setupLogWorking = true
go func() {
gopool.Go(func() {
SetupLogger()
}()
})
}
}
@@ -100,6 +101,14 @@ func LogQuota(quota int) string {
}
}
func FormatQuota(quota int) string {
if DisplayInCurrencyEnabled {
return fmt.Sprintf("%.6f", float64(quota)/QuotaPerUnit)
} else {
return fmt.Sprintf("%d", quota)
}
}
// LogJson 仅供测试使用 only for test
func LogJson(ctx context.Context, msg string, obj any) {
jsonStr, err := json.Marshal(obj)
-461
View File
@@ -1,461 +0,0 @@
package common
import (
"encoding/json"
"strings"
"sync"
)
// from songquanpeng/one-api
const (
USD2RMB = 7.3 // 暂定 1 USD = 7.3 RMB
USD = 500 // $0.002 = 1 -> $1 = 500
RMB = USD / USD2RMB
)
// modelRatio
// https://platform.openai.com/docs/models/model-endpoint-compatibility
// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf
// https://openai.com/pricing
// TODO: when a new api is enabled, check the pricing here
// 1 === $0.002 / 1K tokens
// 1 === ¥0.014 / 1k tokens
var defaultModelRatio = map[string]float64{
//"midjourney": 50,
"gpt-4-gizmo-*": 15,
"gpt-4o-gizmo-*": 2.5,
"gpt-4-all": 15,
"gpt-4o-all": 15,
"gpt-4": 15,
//"gpt-4-0314": 15, //deprecated
"gpt-4-0613": 15,
"gpt-4-32k": 30,
//"gpt-4-32k-0314": 30, //deprecated
"gpt-4-32k-0613": 30,
"gpt-4-1106-preview": 5, // $10 / 1M tokens
"gpt-4-0125-preview": 5, // $10 / 1M tokens
"gpt-4-turbo-preview": 5, // $10 / 1M tokens
"gpt-4-vision-preview": 5, // $10 / 1M tokens
"gpt-4-1106-vision-preview": 5, // $10 / 1M tokens
"chatgpt-4o-latest": 2.5, // $5 / 1M tokens
"gpt-4o": 1.25, // $2.5 / 1M tokens
"gpt-4o-audio-preview": 1.25, // $2.5 / 1M tokens
"gpt-4o-audio-preview-2024-10-01": 1.25, // $2.5 / 1M tokens
"gpt-4o-2024-05-13": 2.5, // $5 / 1M tokens
"gpt-4o-2024-08-06": 1.25, // $2.5 / 1M tokens
"gpt-4o-2024-11-20": 1.25, // $2.5 / 1M tokens
"gpt-4o-realtime-preview": 2.5,
"o1-preview": 7.5,
"o1-preview-2024-09-12": 7.5,
"o1-mini": 1.5,
"o1-mini-2024-09-12": 1.5,
"gpt-4o-mini": 0.075,
"gpt-4o-mini-2024-07-18": 0.075,
"gpt-4-turbo": 5, // $0.01 / 1K tokens
"gpt-4-turbo-2024-04-09": 5, // $0.01 / 1K tokens
//"gpt-3.5-turbo-0301": 0.75, //deprecated
"gpt-3.5-turbo": 0.25,
"gpt-3.5-turbo-0613": 0.75,
"gpt-3.5-turbo-16k": 1.5, // $0.003 / 1K tokens
"gpt-3.5-turbo-16k-0613": 1.5,
"gpt-3.5-turbo-instruct": 0.75, // $0.0015 / 1K tokens
"gpt-3.5-turbo-1106": 0.5, // $0.001 / 1K tokens
"gpt-3.5-turbo-0125": 0.25,
"babbage-002": 0.2, // $0.0004 / 1K tokens
"davinci-002": 1, // $0.002 / 1K tokens
"text-ada-001": 0.2,
"text-babbage-001": 0.25,
"text-curie-001": 1,
//"text-davinci-002": 10,
//"text-davinci-003": 10,
"text-davinci-edit-001": 10,
"code-davinci-edit-001": 10,
"whisper-1": 15, // $0.006 / minute -> $0.006 / 150 words -> $0.006 / 200 tokens -> $0.03 / 1k tokens
"tts-1": 7.5, // 1k characters -> $0.015
"tts-1-1106": 7.5, // 1k characters -> $0.015
"tts-1-hd": 15, // 1k characters -> $0.03
"tts-1-hd-1106": 15, // 1k characters -> $0.03
"davinci": 10,
"curie": 10,
"babbage": 10,
"ada": 10,
"text-embedding-3-small": 0.01,
"text-embedding-3-large": 0.065,
"text-embedding-ada-002": 0.05,
"text-search-ada-doc-001": 10,
"text-moderation-stable": 0.1,
"text-moderation-latest": 0.1,
"claude-instant-1": 0.4, // $0.8 / 1M tokens
"claude-2.0": 4, // $8 / 1M tokens
"claude-2.1": 4, // $8 / 1M tokens
"claude-3-haiku-20240307": 0.125, // $0.25 / 1M tokens
"claude-3-5-haiku-20241022": 0.5, // $1 / 1M tokens
"claude-3-sonnet-20240229": 1.5, // $3 / 1M tokens
"claude-3-5-sonnet-20240620": 1.5,
"claude-3-5-sonnet-20241022": 1.5,
"claude-3-opus-20240229": 7.5, // $15 / 1M tokens
"ERNIE-4.0-8K": 0.120 * RMB,
"ERNIE-3.5-8K": 0.012 * RMB,
"ERNIE-3.5-8K-0205": 0.024 * RMB,
"ERNIE-3.5-8K-1222": 0.012 * RMB,
"ERNIE-Bot-8K": 0.024 * RMB,
"ERNIE-3.5-4K-0205": 0.012 * RMB,
"ERNIE-Speed-8K": 0.004 * RMB,
"ERNIE-Speed-128K": 0.004 * RMB,
"ERNIE-Lite-8K-0922": 0.008 * RMB,
"ERNIE-Lite-8K-0308": 0.003 * RMB,
"ERNIE-Tiny-8K": 0.001 * RMB,
"BLOOMZ-7B": 0.004 * RMB,
"Embedding-V1": 0.002 * RMB,
"bge-large-zh": 0.002 * RMB,
"bge-large-en": 0.002 * RMB,
"tao-8k": 0.002 * RMB,
"PaLM-2": 1,
"gemini-pro": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
"gemini-pro-vision": 1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
"gemini-1.0-pro-vision-001": 1,
"gemini-1.0-pro-001": 1,
"gemini-1.5-pro-latest": 1.75, // $3.5 / 1M tokens
"gemini-1.5-pro-exp-0827": 1.75, // $3.5 / 1M tokens
"gemini-1.5-flash-latest": 1,
"gemini-1.5-flash-exp-0827": 1,
"gemini-1.0-pro-latest": 1,
"gemini-1.0-pro-vision-latest": 1,
"gemini-ultra": 1,
"chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
"chatglm_pro": 0.7143, // ¥0.01 / 1k tokens
"chatglm_std": 0.3572, // ¥0.005 / 1k tokens
"chatglm_lite": 0.1429, // ¥0.002 / 1k tokens
"glm-4": 7.143, // ¥0.1 / 1k tokens
"glm-4v": 0.05 * RMB, // ¥0.05 / 1k tokens
"glm-4-alltools": 0.1 * RMB, // ¥0.1 / 1k tokens
"glm-3-turbo": 0.3572,
"glm-4-plus": 0.05 * RMB,
"glm-4-0520": 0.1 * RMB,
"glm-4-air": 0.001 * RMB,
"glm-4-airx": 0.01 * RMB,
"glm-4-long": 0.001 * RMB,
"glm-4-flash": 0,
"glm-4v-plus": 0.01 * RMB,
"qwen-turbo": 0.8572, // ¥0.012 / 1k tokens
"qwen-plus": 10, // ¥0.14 / 1k tokens
"text-embedding-v1": 0.05, // ¥0.0007 / 1k tokens
"SparkDesk-v1.1": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v2.1": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v3.1": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v3.5": 1.2858, // ¥0.018 / 1k tokens
"SparkDesk-v4.0": 1.2858,
"360GPT_S2_V9": 0.8572, // ¥0.012 / 1k tokens
"360gpt-turbo": 0.0858, // ¥0.0012 / 1k tokens
"360gpt-turbo-responsibility-8k": 0.8572, // ¥0.012 / 1k tokens
"360gpt-pro": 0.8572, // ¥0.012 / 1k tokens
"embedding-bert-512-v1": 0.0715, // ¥0.001 / 1k tokens
"embedding_s1_v1": 0.0715, // ¥0.001 / 1k tokens
"semantic_similarity_s1_v1": 0.0715, // ¥0.001 / 1k tokens
"hunyuan": 7.143, // ¥0.1 / 1k tokens // https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
// https://platform.lingyiwanwu.com/docs#-计费单元
// 已经按照 7.2 来换算美元价格
"yi-34b-chat-0205": 0.18,
"yi-34b-chat-200k": 0.864,
"yi-vl-plus": 0.432,
"yi-large": 20.0 / 1000 * RMB,
"yi-medium": 2.5 / 1000 * RMB,
"yi-vision": 6.0 / 1000 * RMB,
"yi-medium-200k": 12.0 / 1000 * RMB,
"yi-spark": 1.0 / 1000 * RMB,
"yi-large-rag": 25.0 / 1000 * RMB,
"yi-large-turbo": 12.0 / 1000 * RMB,
"yi-large-preview": 20.0 / 1000 * RMB,
"yi-large-rag-preview": 25.0 / 1000 * RMB,
"command": 0.5,
"command-nightly": 0.5,
"command-light": 0.5,
"command-light-nightly": 0.5,
"command-r": 0.25,
"command-r-plus": 1.5,
"command-r-08-2024": 0.075,
"command-r-plus-08-2024": 1.25,
"deepseek-chat": 0.07,
"deepseek-coder": 0.07,
// Perplexity online 模型对搜索额外收费,有需要应自行调整,此处不计入搜索费用
"llama-3-sonar-small-32k-chat": 0.2 / 1000 * USD,
"llama-3-sonar-small-32k-online": 0.2 / 1000 * USD,
"llama-3-sonar-large-32k-chat": 1 / 1000 * USD,
"llama-3-sonar-large-32k-online": 1 / 1000 * USD,
}
var defaultModelPrice = map[string]float64{
"suno_music": 0.1,
"suno_lyrics": 0.01,
"dall-e-3": 0.04,
"gpt-4-gizmo-*": 0.1,
"mj_imagine": 0.1,
"mj_variation": 0.1,
"mj_reroll": 0.1,
"mj_blend": 0.1,
"mj_modal": 0.1,
"mj_zoom": 0.1,
"mj_shorten": 0.1,
"mj_high_variation": 0.1,
"mj_low_variation": 0.1,
"mj_pan": 0.1,
"mj_inpaint": 0,
"mj_custom_zoom": 0,
"mj_describe": 0.05,
"mj_upscale": 0.05,
"swap_face": 0.05,
"mj_upload": 0.05,
}
var (
modelPriceMap map[string]float64 = nil
modelPriceMapMutex = sync.RWMutex{}
)
var (
modelRatioMap map[string]float64 = nil
modelRatioMapMutex = sync.RWMutex{}
)
var CompletionRatio map[string]float64 = nil
var defaultCompletionRatio = map[string]float64{
"gpt-4-gizmo-*": 2,
"gpt-4o-gizmo-*": 3,
"gpt-4-all": 2,
}
func GetModelPriceMap() map[string]float64 {
modelPriceMapMutex.Lock()
defer modelPriceMapMutex.Unlock()
if modelPriceMap == nil {
modelPriceMap = defaultModelPrice
}
return modelPriceMap
}
func ModelPrice2JSONString() string {
GetModelPriceMap()
jsonBytes, err := json.Marshal(modelPriceMap)
if err != nil {
SysError("error marshalling model price: " + err.Error())
}
return string(jsonBytes)
}
func UpdateModelPriceByJSONString(jsonStr string) error {
modelPriceMapMutex.Lock()
defer modelPriceMapMutex.Unlock()
modelPriceMap = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &modelPriceMap)
}
// GetModelPrice 返回模型的价格,如果模型不存在则返回-1,false
func GetModelPrice(name string, printErr bool) (float64, bool) {
GetModelPriceMap()
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
if strings.HasPrefix(name, "gpt-4o-gizmo") {
name = "gpt-4o-gizmo-*"
}
price, ok := modelPriceMap[name]
if !ok {
if printErr {
SysError("model price not found: " + name)
}
return -1, false
}
return price, true
}
func GetModelRatioMap() map[string]float64 {
modelRatioMapMutex.Lock()
defer modelRatioMapMutex.Unlock()
if modelRatioMap == nil {
modelRatioMap = defaultModelRatio
}
return modelRatioMap
}
func ModelRatio2JSONString() string {
GetModelRatioMap()
jsonBytes, err := json.Marshal(modelRatioMap)
if err != nil {
SysError("error marshalling model ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateModelRatioByJSONString(jsonStr string) error {
modelRatioMapMutex.Lock()
defer modelRatioMapMutex.Unlock()
modelRatioMap = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &modelRatioMap)
}
func GetModelRatio(name string) float64 {
GetModelRatioMap()
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
ratio, ok := modelRatioMap[name]
if !ok {
SysError("model ratio not found: " + name)
return 30
}
return ratio
}
func DefaultModelRatio2JSONString() string {
jsonBytes, err := json.Marshal(defaultModelRatio)
if err != nil {
SysError("error marshalling model ratio: " + err.Error())
}
return string(jsonBytes)
}
func GetDefaultModelRatioMap() map[string]float64 {
return defaultModelRatio
}
func CompletionRatio2JSONString() string {
if CompletionRatio == nil {
CompletionRatio = defaultCompletionRatio
}
jsonBytes, err := json.Marshal(CompletionRatio)
if err != nil {
SysError("error marshalling completion ratio: " + err.Error())
}
return string(jsonBytes)
}
func UpdateCompletionRatioByJSONString(jsonStr string) error {
CompletionRatio = make(map[string]float64)
return json.Unmarshal([]byte(jsonStr), &CompletionRatio)
}
func GetCompletionRatio(name string) float64 {
if strings.HasPrefix(name, "gpt-4-gizmo") {
name = "gpt-4-gizmo-*"
}
if strings.HasPrefix(name, "gpt-4o-gizmo") {
name = "gpt-4o-gizmo-*"
}
if strings.HasPrefix(name, "gpt-4") && !strings.HasSuffix(name, "-all") && !strings.HasSuffix(name, "-gizmo-*") {
if strings.HasPrefix(name, "gpt-4o") {
if name == "gpt-4o-2024-05-13" {
return 3
}
return 4
}
if strings.HasPrefix(name, "gpt-4-turbo") || strings.HasSuffix(name, "preview") {
return 3
}
return 2
}
if strings.HasPrefix(name, "o1-") {
return 4
}
if name == "chatgpt-4o-latest" {
return 3
}
if strings.Contains(name, "claude-instant-1") {
return 3
} else if strings.Contains(name, "claude-2") {
return 3
} else if strings.Contains(name, "claude-3") {
return 5
}
if strings.HasPrefix(name, "gpt-3.5") {
if name == "gpt-3.5-turbo" || strings.HasSuffix(name, "0125") {
// https://openai.com/blog/new-embedding-models-and-api-updates
// Updated GPT-3.5 Turbo model and lower pricing
return 3
}
if strings.HasSuffix(name, "1106") {
return 2
}
return 4.0 / 3.0
}
if strings.HasPrefix(name, "mistral-") {
return 3
}
if strings.HasPrefix(name, "gemini-") {
return 4
}
if strings.HasPrefix(name, "command") {
switch name {
case "command-r":
return 3
case "command-r-plus":
return 5
case "command-r-08-2024":
return 4
case "command-r-plus-08-2024":
return 4
default:
return 2
}
}
if strings.HasPrefix(name, "deepseek") {
return 2
}
if strings.HasPrefix(name, "ERNIE-Speed-") {
return 2
} else if strings.HasPrefix(name, "ERNIE-Lite-") {
return 2
} else if strings.HasPrefix(name, "ERNIE-Character") {
return 2
} else if strings.HasPrefix(name, "ERNIE-Functions") {
return 2
}
switch name {
case "llama2-70b-4096":
return 0.8 / 0.64
case "llama3-8b-8192":
return 2
case "llama3-70b-8192":
return 0.79 / 0.59
}
if ratio, ok := CompletionRatio[name]; ok {
return ratio
}
return 1
}
func GetAudioRatio(name string) float64 {
if strings.HasPrefix(name, "gpt-4o-realtime") {
return 20
} else if strings.HasPrefix(name, "gpt-4o-audio") {
return 40
}
return 20
}
func GetAudioCompletionRatio(name string) float64 {
if strings.HasPrefix(name, "gpt-4o-realtime") {
return 2
}
return 2
}
//func GetAudioPricePerMinute(name string) float64 {
// if strings.HasPrefix(name, "gpt-4o-realtime") {
// return 0.06
// }
// return 0.06
//}
//
//func GetAudioCompletionPricePerMinute(name string) float64 {
// if strings.HasPrefix(name, "gpt-4o-realtime") {
// return 0.24
// }
// return 0.24
//}
func GetCompletionRatioMap() map[string]float64 {
if CompletionRatio == nil {
CompletionRatio = defaultCompletionRatio
}
return CompletionRatio
}
+236 -19
View File
@@ -2,9 +2,15 @@ package common
import (
"context"
"github.com/go-redis/redis/v8"
"errors"
"fmt"
"os"
"reflect"
"strconv"
"time"
"github.com/go-redis/redis/v8"
"gorm.io/gorm"
)
var RDB *redis.Client
@@ -26,6 +32,7 @@ func InitRedisClient() (err error) {
if err != nil {
FatalLog("failed to parse Redis connection string: " + err.Error())
}
opt.PoolSize = GetEnvOrDefault("REDIS_POOL_SIZE", 10)
RDB = redis.NewClient(opt)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -35,6 +42,10 @@ func InitRedisClient() (err error) {
if err != nil {
FatalLog("Redis ping test failed: " + err.Error())
}
if DebugEnabled {
SysLog(fmt.Sprintf("Redis connected to %s", opt.Addr))
SysLog(fmt.Sprintf("Redis database: %d", opt.DB))
}
return err
}
@@ -47,48 +58,202 @@ func ParseRedisOption() *redis.Options {
}
func RedisSet(key string, value string, expiration time.Duration) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis SET: key=%s, value=%s, expiration=%v", key, value, expiration))
}
ctx := context.Background()
return RDB.Set(ctx, key, value, expiration).Err()
}
func RedisGet(key string) (string, error) {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis GET: key=%s", key))
}
ctx := context.Background()
return RDB.Get(ctx, key).Result()
val, err := RDB.Get(ctx, key).Result()
return val, err
}
func RedisExpire(key string, expiration time.Duration) error {
ctx := context.Background()
return RDB.Expire(ctx, key, expiration).Err()
}
func RedisGetEx(key string, expiration time.Duration) (string, error) {
ctx := context.Background()
return RDB.GetSet(ctx, key, expiration).Result()
}
//func RedisExpire(key string, expiration time.Duration) error {
// ctx := context.Background()
// return RDB.Expire(ctx, key, expiration).Err()
//}
//
//func RedisGetEx(key string, expiration time.Duration) (string, error) {
// ctx := context.Background()
// return RDB.GetSet(ctx, key, expiration).Result()
//}
func RedisDel(key string) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis DEL: key=%s", key))
}
ctx := context.Background()
return RDB.Del(ctx, key).Err()
}
func RedisDecrease(key string, value int64) error {
func RedisDelKey(key string) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis DEL Key: key=%s", key))
}
ctx := context.Background()
return RDB.Del(ctx, key).Err()
}
func RedisHSetObj(key string, obj interface{}, expiration time.Duration) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis HSET: key=%s, obj=%+v, expiration=%v", key, obj, expiration))
}
ctx := context.Background()
data := make(map[string]interface{})
// 使用反射遍历结构体字段
v := reflect.ValueOf(obj).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
// Skip DeletedAt field
if field.Type.String() == "gorm.DeletedAt" {
continue
}
// 处理指针类型
if value.Kind() == reflect.Ptr {
if value.IsNil() {
data[field.Name] = ""
continue
}
value = value.Elem()
}
// 处理布尔类型
if value.Kind() == reflect.Bool {
data[field.Name] = strconv.FormatBool(value.Bool())
continue
}
// 其他类型直接转换为字符串
data[field.Name] = fmt.Sprintf("%v", value.Interface())
}
txn := RDB.TxPipeline()
txn.HSet(ctx, key, data)
// 只有在 expiration 大于 0 时才设置过期时间
if expiration > 0 {
txn.Expire(ctx, key, expiration)
}
_, err := txn.Exec(ctx)
if err != nil {
return fmt.Errorf("failed to execute transaction: %w", err)
}
return nil
}
func RedisHGetObj(key string, obj interface{}) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis HGETALL: key=%s", key))
}
ctx := context.Background()
result, err := RDB.HGetAll(ctx, key).Result()
if err != nil {
return fmt.Errorf("failed to load hash from Redis: %w", err)
}
if len(result) == 0 {
return fmt.Errorf("key %s not found in Redis", key)
}
// Handle both pointer and non-pointer values
val := reflect.ValueOf(obj)
if val.Kind() != reflect.Ptr {
return fmt.Errorf("obj must be a pointer to a struct, got %T", obj)
}
v := val.Elem()
if v.Kind() != reflect.Struct {
return fmt.Errorf("obj must be a pointer to a struct, got pointer to %T", v.Interface())
}
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := t.Field(i)
fieldName := field.Name
if value, ok := result[fieldName]; ok {
fieldValue := v.Field(i)
// Handle pointer types
if fieldValue.Kind() == reflect.Ptr {
if value == "" {
continue
}
if fieldValue.IsNil() {
fieldValue.Set(reflect.New(fieldValue.Type().Elem()))
}
fieldValue = fieldValue.Elem()
}
// Enhanced type handling for Token struct
switch fieldValue.Kind() {
case reflect.String:
fieldValue.SetString(value)
case reflect.Int, reflect.Int64:
intValue, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse int field %s: %w", fieldName, err)
}
fieldValue.SetInt(intValue)
case reflect.Bool:
boolValue, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("failed to parse bool field %s: %w", fieldName, err)
}
fieldValue.SetBool(boolValue)
case reflect.Struct:
// Special handling for gorm.DeletedAt
if fieldValue.Type().String() == "gorm.DeletedAt" {
if value != "" {
timeValue, err := time.Parse(time.RFC3339, value)
if err != nil {
return fmt.Errorf("failed to parse DeletedAt field %s: %w", fieldName, err)
}
fieldValue.Set(reflect.ValueOf(gorm.DeletedAt{Time: timeValue, Valid: true}))
}
}
default:
return fmt.Errorf("unsupported field type: %s for field %s", fieldValue.Kind(), fieldName)
}
}
}
return nil
}
// RedisIncr Add this function to handle atomic increments
func RedisIncr(key string, delta int64) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis INCR: key=%s, delta=%d", key, delta))
}
// 检查键的剩余生存时间
ttlCmd := RDB.TTL(context.Background(), key)
ttl, err := ttlCmd.Result()
if err != nil {
// 失败则尝试直接减少
return RDB.DecrBy(context.Background(), key, value).Err()
if err != nil && !errors.Is(err, redis.Nil) {
return fmt.Errorf("failed to get TTL: %w", err)
}
// 如果剩余生存时间大于0,则进行减少操作
// 只有在 key 存在且有 TTL 时才需要特殊处理
if ttl > 0 {
ctx := context.Background()
// 开始一个Redis事务
txn := RDB.TxPipeline()
// 减少余额
decrCmd := txn.DecrBy(ctx, key, value)
decrCmd := txn.IncrBy(ctx, key, delta)
if err := decrCmd.Err(); err != nil {
return err // 如果减少失败,则直接返回错误
}
@@ -99,8 +264,60 @@ func RedisDecrease(key string, value int64) error {
// 执行事务
_, err = txn.Exec(ctx)
return err
} else {
_ = RedisDel(key)
}
return nil
}
func RedisHIncrBy(key, field string, delta int64) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis HINCRBY: key=%s, field=%s, delta=%d", key, field, delta))
}
ttlCmd := RDB.TTL(context.Background(), key)
ttl, err := ttlCmd.Result()
if err != nil && !errors.Is(err, redis.Nil) {
return fmt.Errorf("failed to get TTL: %w", err)
}
if ttl > 0 {
ctx := context.Background()
txn := RDB.TxPipeline()
incrCmd := txn.HIncrBy(ctx, key, field, delta)
if err := incrCmd.Err(); err != nil {
return err
}
txn.Expire(ctx, key, ttl)
_, err = txn.Exec(ctx)
return err
}
return nil
}
func RedisHSetField(key, field string, value interface{}) error {
if DebugEnabled {
SysLog(fmt.Sprintf("Redis HSET field: key=%s, field=%s, value=%v", key, field, value))
}
ttlCmd := RDB.TTL(context.Background(), key)
ttl, err := ttlCmd.Result()
if err != nil && !errors.Is(err, redis.Nil) {
return fmt.Errorf("failed to get TTL: %w", err)
}
if ttl > 0 {
ctx := context.Background()
txn := RDB.TxPipeline()
hsetCmd := txn.HSet(ctx, key, field, value)
if err := hsetCmd.Err(); err != nil {
return err
}
txn.Expire(ctx, key, ttl)
_, err = txn.Exec(ctx)
return err
}
return nil
}
-46
View File
@@ -1,46 +0,0 @@
package common
import (
"encoding/json"
)
var UserUsableGroups = map[string]string{
"default": "默认分组",
"vip": "vip分组",
}
func UserUsableGroups2JSONString() string {
jsonBytes, err := json.Marshal(UserUsableGroups)
if err != nil {
SysError("error marshalling user groups: " + err.Error())
}
return string(jsonBytes)
}
func UpdateUserUsableGroupsByJSONString(jsonStr string) error {
UserUsableGroups = make(map[string]string)
return json.Unmarshal([]byte(jsonStr), &UserUsableGroups)
}
func GetUserUsableGroups(userGroup string) map[string]string {
if userGroup == "" {
// 如果userGroup为空,返回UserUsableGroups
return UserUsableGroups
}
// 如果userGroup不在UserUsableGroups中,返回UserUsableGroups + userGroup
if _, ok := UserUsableGroups[userGroup]; !ok {
appendUserUsableGroups := make(map[string]string)
for k, v := range UserUsableGroups {
appendUserUsableGroups[k] = v
}
appendUserUsableGroups[userGroup] = "用户分组"
return appendUserUsableGroups
}
// 如果userGroup在UserUsableGroups中,返回UserUsableGroups
return UserUsableGroups
}
func GroupInUserUsableGroups(groupName string) bool {
_, ok := UserUsableGroups[groupName]
return ok
}
+79 -1
View File
@@ -1,20 +1,27 @@
package common
import (
"bytes"
"context"
crand "crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/google/uuid"
"html/template"
"io"
"log"
"math/big"
"math/rand"
"net"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"time"
"github.com/google/uuid"
"github.com/pkg/errors"
)
func OpenBrowser(url string) {
@@ -206,3 +213,74 @@ func RandomSleep() {
// Sleep for 0-3000 ms
time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
}
func GetPointer[T any](v T) *T {
return &v
}
func Any2Type[T any](data any) (T, error) {
var zero T
bytes, err := json.Marshal(data)
if err != nil {
return zero, err
}
var res T
err = json.Unmarshal(bytes, &res)
if err != nil {
return zero, err
}
return res, nil
}
// SaveTmpFile saves data to a temporary file. The filename would be apppended with a random string.
func SaveTmpFile(filename string, data io.Reader) (string, error) {
f, err := os.CreateTemp(os.TempDir(), filename)
if err != nil {
return "", errors.Wrapf(err, "failed to create temporary file %s", filename)
}
defer f.Close()
_, err = io.Copy(f, data)
if err != nil {
return "", errors.Wrapf(err, "failed to copy data to temporary file %s", filename)
}
return f.Name(), nil
}
// GetAudioDuration returns the duration of an audio file in seconds.
func GetAudioDuration(ctx context.Context, filename string, ext string) (float64, error) {
// ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 {{input}}
c := exec.CommandContext(ctx, "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filename)
output, err := c.Output()
if err != nil {
return 0, errors.Wrap(err, "failed to get audio duration")
}
durationStr := string(bytes.TrimSpace(output))
if durationStr == "N/A" {
// Create a temporary output file name
tmpFp, err := os.CreateTemp("", "audio-*"+ext)
if err != nil {
return 0, errors.Wrap(err, "failed to create temporary file")
}
tmpName := tmpFp.Name()
// Close immediately so ffmpeg can open the file on Windows.
_ = tmpFp.Close()
defer os.Remove(tmpName)
// ffmpeg -y -i filename -vcodec copy -acodec copy <tmpName>
ffmpegCmd := exec.CommandContext(ctx, "ffmpeg", "-y", "-i", filename, "-vcodec", "copy", "-acodec", "copy", tmpName)
if err := ffmpegCmd.Run(); err != nil {
return 0, errors.Wrap(err, "failed to run ffmpeg")
}
// Recalculate the duration of the new file
c = exec.CommandContext(ctx, "ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", tmpName)
output, err := c.Output()
if err != nil {
return 0, errors.Wrap(err, "failed to get audio duration after ffmpeg")
}
durationStr = string(bytes.TrimSpace(output))
}
return strconv.ParseFloat(durationStr, 64)
}
+5
View File
@@ -0,0 +1,5 @@
package constant
import "time"
var AzureNoRemoveDotTime = time.Date(2025, time.May, 10, 0, 0, 0, 0, time.UTC).Unix()
+21
View File
@@ -0,0 +1,21 @@
package constant
import "one-api/common"
// 使用函数来避免初始化顺序带来的赋值问题
func RedisKeyCacheSeconds() int {
return common.SyncFrequency
}
// Cache keys
const (
UserGroupKeyFmt = "user_group:%d"
UserQuotaKeyFmt = "user_quota:%d"
UserEnabledKeyFmt = "user_enabled:%d"
UserUsernameKeyFmt = "user_name:%d"
)
const (
TokenFiledRemainQuota = "RemainQuota"
TokenFieldGroup = "Group"
)
+7
View File
@@ -0,0 +1,7 @@
package constant
var (
ForceFormat = "force_format" // ForceFormat 强制格式化为OpenAI格式
ChanelSettingProxy = "proxy" // Proxy 代理
ChannelSettingThinkingToContent = "thinking_to_content" // ThinkingToContent
)
+10
View File
@@ -0,0 +1,10 @@
package constant
const (
ContextKeyRequestStartTime = "request_start_time"
ContextKeyUserSetting = "user_setting"
ContextKeyUserQuota = "user_quota"
ContextKeyUserStatus = "user_status"
ContextKeyUserEmail = "user_email"
ContextKeyUserGroup = "user_group"
)
+45 -32
View File
@@ -1,42 +1,55 @@
package constant
import (
"fmt"
"one-api/common"
"os"
"strings"
)
var StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
var DifyDebug = common.GetEnvOrDefaultBool("DIFY_DEBUG", true)
var StreamingTimeout int
var DifyDebug bool
var MaxFileDownloadMB int
var ForceStreamOption bool
var GetMediaToken bool
var GetMediaTokenNotStream bool
var UpdateTask bool
var AzureDefaultAPIVersion string
var GeminiVisionMaxImageNum int
var NotifyLimitCount int
var NotificationLimitDurationMinute int
var GenerateDefaultToken bool
var ErrorLogEnabled bool
// ForceStreamOption 覆盖请求参数,强制返回usage信息
var ForceStreamOption = common.GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
var GetMediaToken = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN", true)
var GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STREAM", true)
var UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
var GeminiModelMap = map[string]string{
"gemini-1.0-pro": "v1",
}
//var GeminiModelMap = map[string]string{
// "gemini-1.0-pro": "v1",
//}
func InitEnv() {
modelVersionMapStr := strings.TrimSpace(os.Getenv("GEMINI_MODEL_MAP"))
if modelVersionMapStr == "" {
return
}
for _, pair := range strings.Split(modelVersionMapStr, ",") {
parts := strings.Split(pair, ":")
if len(parts) == 2 {
GeminiModelMap[parts[0]] = parts[1]
} else {
common.SysError(fmt.Sprintf("invalid model version map: %s", pair))
}
}
}
StreamingTimeout = common.GetEnvOrDefault("STREAMING_TIMEOUT", 60)
DifyDebug = common.GetEnvOrDefaultBool("DIFY_DEBUG", true)
MaxFileDownloadMB = common.GetEnvOrDefault("MAX_FILE_DOWNLOAD_MB", 20)
// ForceStreamOption 覆盖请求参数,强制返回usage信息
ForceStreamOption = common.GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
GetMediaToken = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN", true)
GetMediaTokenNotStream = common.GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STREAM", true)
UpdateTask = common.GetEnvOrDefaultBool("UPDATE_TASK", true)
AzureDefaultAPIVersion = common.GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2025-04-01-preview")
GeminiVisionMaxImageNum = common.GetEnvOrDefault("GEMINI_VISION_MAX_IMAGE_NUM", 16)
NotifyLimitCount = common.GetEnvOrDefault("NOTIFY_LIMIT_COUNT", 2)
NotificationLimitDurationMinute = common.GetEnvOrDefault("NOTIFICATION_LIMIT_DURATION_MINUTE", 10)
// GenerateDefaultToken 是否生成初始令牌,默认关闭。
GenerateDefaultToken = common.GetEnvOrDefaultBool("GENERATE_DEFAULT_TOKEN", false)
// 是否启用错误日志
ErrorLogEnabled = common.GetEnvOrDefaultBool("ERROR_LOG_ENABLED", false)
// 是否生成初始令牌,默认关闭。
var GenerateDefaultToken = common.GetEnvOrDefaultBool("GENERATE_DEFAULT_TOKEN", false)
//modelVersionMapStr := strings.TrimSpace(os.Getenv("GEMINI_MODEL_MAP"))
//if modelVersionMapStr == "" {
// return
//}
//for _, pair := range strings.Split(modelVersionMapStr, ",") {
// parts := strings.Split(pair, ":")
// if len(parts) == 2 {
// GeminiModelMap[parts[0]] = parts[1]
// } else {
// common.SysError(fmt.Sprintf("invalid model version map: %s", pair))
// }
//}
}
+9
View File
@@ -0,0 +1,9 @@
package constant
var (
FinishReasonStop = "stop"
FinishReasonToolCalls = "tool_calls"
FinishReasonLength = "length"
FinishReasonFunctionCall = "function_call"
FinishReasonContentFilter = "content_filter"
)
-6
View File
@@ -1,11 +1,5 @@
package constant
var MjNotifyEnabled = false
var MjAccountFilterEnabled = false
var MjModeClearEnabled = false
var MjForwardUrlEnabled = true
var MjActionCheckSuccessEnabled = true
const (
MjErrorUnknown = 5
MjRequestError = 4
+3
View File
@@ -0,0 +1,3 @@
package constant
var Setup = false
+16
View File
@@ -0,0 +1,16 @@
package constant
var (
UserSettingNotifyType = "notify_type" // QuotaWarningType 额度预警类型
UserSettingQuotaWarningThreshold = "quota_warning_threshold" // QuotaWarningThreshold 额度预警阈值
UserSettingWebhookUrl = "webhook_url" // WebhookUrl webhook地址
UserSettingWebhookSecret = "webhook_secret" // WebhookSecret webhook密钥
UserSettingNotificationEmail = "notification_email" // NotificationEmail 通知邮箱地址
UserAcceptUnsetRatioModel = "accept_unset_model_ratio_model" // AcceptUnsetRatioModel 是否接受未设置价格的模型
UserSettingRecordIpLog = "record_ip_log" // 是否记录请求和错误日志IP
)
var (
NotifyTypeEmail = "email" // Email 邮件
NotifyTypeWebhook = "webhook" // Webhook
)
+1 -1
View File
@@ -21,7 +21,7 @@ func GetSubscription(c *gin.Context) {
usedQuota = token.UsedQuota
} else {
userId := c.GetInt("id")
remainQuota, err = model.GetUserQuota(userId)
remainQuota, err = model.GetUserQuota(userId, false)
usedQuota, err = model.GetUserUsedQuota(userId)
}
if expiredTime <= 0 {
+113 -3
View File
@@ -78,6 +78,43 @@ type APGC2DGPTUsageResponse struct {
TotalUsed float64 `json:"total_used"`
}
type SiliconFlowUsageResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Status bool `json:"status"`
Data struct {
ID string `json:"id"`
Name string `json:"name"`
Image string `json:"image"`
Email string `json:"email"`
IsAdmin bool `json:"isAdmin"`
Balance string `json:"balance"`
Status string `json:"status"`
Introduction string `json:"introduction"`
Role string `json:"role"`
ChargeBalance string `json:"chargeBalance"`
TotalBalance string `json:"totalBalance"`
Category string `json:"category"`
} `json:"data"`
}
type DeepSeekUsageResponse struct {
IsAvailable bool `json:"is_available"`
BalanceInfos []struct {
Currency string `json:"currency"`
TotalBalance string `json:"total_balance"`
GrantedBalance string `json:"granted_balance"`
ToppedUpBalance string `json:"topped_up_balance"`
} `json:"balance_infos"`
}
type OpenRouterCreditResponse struct {
Data struct {
TotalCredits float64 `json:"total_credits"`
TotalUsage float64 `json:"total_usage"`
} `json:"data"`
}
// GetAuthHeader get auth header
func GetAuthHeader(token string) http.Header {
h := http.Header{}
@@ -185,6 +222,57 @@ func updateChannelAPI2GPTBalance(channel *model.Channel) (float64, error) {
return response.TotalRemaining, nil
}
func updateChannelSiliconFlowBalance(channel *model.Channel) (float64, error) {
url := "https://api.siliconflow.cn/v1/user/info"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
response := SiliconFlowUsageResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
if response.Code != 20000 {
return 0, fmt.Errorf("code: %d, message: %s", response.Code, response.Message)
}
balance, err := strconv.ParseFloat(response.Data.TotalBalance, 64)
if err != nil {
return 0, err
}
channel.UpdateBalance(balance)
return balance, nil
}
func updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {
url := "https://api.deepseek.com/user/balance"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
response := DeepSeekUsageResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
index := -1
for i, balanceInfo := range response.BalanceInfos {
if balanceInfo.Currency == "CNY" {
index = i
break
}
}
if index == -1 {
return 0, errors.New("currency CNY not found")
}
balance, err := strconv.ParseFloat(response.BalanceInfos[index].TotalBalance, 64)
if err != nil {
return 0, err
}
channel.UpdateBalance(balance)
return balance, nil
}
func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
url := "https://api.aigc2d.com/dashboard/billing/credit_grants"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
@@ -200,6 +288,22 @@ func updateChannelAIGC2DBalance(channel *model.Channel) (float64, error) {
return response.TotalAvailable, nil
}
func updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {
url := "https://openrouter.ai/api/v1/credits"
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
return 0, err
}
response := OpenRouterCreditResponse{}
err = json.Unmarshal(body, &response)
if err != nil {
return 0, err
}
balance := response.Data.TotalCredits - response.Data.TotalUsage
channel.UpdateBalance(balance)
return balance, nil
}
func updateChannelBalance(channel *model.Channel) (float64, error) {
baseURL := common.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() == "" {
@@ -222,6 +326,12 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
return updateChannelAPI2GPTBalance(channel)
case common.ChannelTypeAIGC2D:
return updateChannelAIGC2DBalance(channel)
case common.ChannelTypeSiliconFlow:
return updateChannelSiliconFlowBalance(channel)
case common.ChannelTypeDeepSeek:
return updateChannelDeepSeekBalance(channel)
case common.ChannelTypeOpenRouter:
return updateChannelOpenRouterBalance(channel)
default:
return 0, errors.New("尚未实现")
}
@@ -300,9 +410,9 @@ func updateAllChannelsBalance() error {
continue
}
// TODO: support Azure
if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
continue
}
//if channel.Type != common.ChannelTypeOpenAI && channel.Type != common.ChannelTypeCustom {
// continue
//}
balance, err := updateChannelBalance(channel)
if err != nil {
continue
+94 -50
View File
@@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/bytedance/gopkg/util/gopool"
"io"
"math"
"net/http"
@@ -18,12 +17,15 @@ import (
"one-api/relay"
relaycommon "one-api/relay/common"
"one-api/relay/constant"
"one-api/relay/helper"
"one-api/service"
"strconv"
"strings"
"sync"
"time"
"github.com/bytedance/gopkg/util/gopool"
"github.com/gin-gonic/gin"
)
@@ -32,14 +34,29 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
if channel.Type == common.ChannelTypeMidjourney {
return errors.New("midjourney channel test is not supported"), nil
}
if channel.Type == common.ChannelTypeMidjourneyPlus {
return errors.New("midjourney plus channel test is not supported!!!"), nil
}
if channel.Type == common.ChannelTypeSunoAPI {
return errors.New("suno channel test is not supported"), nil
}
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
requestPath := "/v1/chat/completions"
// 先判断是否为 Embedding 模型
if strings.Contains(strings.ToLower(testModel), "embedding") ||
strings.HasPrefix(testModel, "m3e") || // m3e 系列模型
strings.Contains(testModel, "bge-") || // bge 系列模型
strings.Contains(testModel, "embed") ||
channel.Type == common.ChannelTypeMokaAI { // 其他 embedding 模型
requestPath = "/v1/embeddings" // 修改请求路径
}
c.Request = &http.Request{
Method: "POST",
URL: &url.URL{Path: "/v1/chat/completions"},
URL: &url.URL{Path: requestPath}, // 使用动态路径
Body: nil,
Header: make(http.Header),
}
@@ -51,31 +68,34 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
if len(channel.GetModels()) > 0 {
testModel = channel.GetModels()[0]
} else {
testModel = "gpt-3.5-turbo"
}
}
} else {
modelMapping := *channel.ModelMapping
if modelMapping != "" && modelMapping != "{}" {
modelMap := make(map[string]string)
err := json.Unmarshal([]byte(modelMapping), &modelMap)
if err != nil {
return err, service.OpenAIErrorWrapperLocal(err, "unmarshal_model_mapping_failed", http.StatusInternalServerError)
}
if modelMap[testModel] != "" {
testModel = modelMap[testModel]
testModel = "gpt-4o-mini"
}
}
}
cache, err := model.GetUserCache(1)
if err != nil {
return err, nil
}
cache.WriteContext(c)
c.Request.Header.Set("Authorization", "Bearer "+channel.Key)
c.Request.Header.Set("Content-Type", "application/json")
c.Set("channel", channel.Type)
c.Set("base_url", channel.GetBaseURL())
group, _ := model.GetUserGroup(1, false)
c.Set("group", group)
middleware.SetupContextForSelectedChannel(c, channel, testModel)
meta := relaycommon.GenRelayInfo(c)
info := relaycommon.GenRelayInfo(c)
err = helper.ModelMappedHelper(c, info)
if err != nil {
return err, nil
}
testModel = info.UpstreamModelName
apiType, _ := constant.ChannelType2APIType(channel.Type)
adaptor := relay.GetAdaptor(apiType)
if adaptor == nil {
@@ -83,12 +103,19 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
}
request := buildTestRequest(testModel)
meta.UpstreamModelName = testModel
common.SysLog(fmt.Sprintf("testing channel %d with model %s", channel.Id, testModel))
// 创建一个用于日志的 info 副本,移除 ApiKey
logInfo := *info
logInfo.ApiKey = ""
common.SysLog(fmt.Sprintf("testing channel %d with model %s , info %+v ", channel.Id, testModel, logInfo))
adaptor.Init(meta)
priceData, err := helper.ModelPriceHelper(c, info, 0, int(request.MaxTokens))
if err != nil {
return err, nil
}
convertedRequest, err := adaptor.ConvertRequest(c, meta, request)
adaptor.Init(info)
convertedRequest, err := adaptor.ConvertOpenAIRequest(c, info, request)
if err != nil {
return err, nil
}
@@ -98,7 +125,7 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
}
requestBody := bytes.NewBuffer(jsonData)
c.Request.Body = io.NopCloser(requestBody)
resp, err := adaptor.DoRequest(c, meta, requestBody)
resp, err := adaptor.DoRequest(c, info, requestBody)
if err != nil {
return err, nil
}
@@ -106,11 +133,11 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
if resp != nil {
httpResp = resp.(*http.Response)
if httpResp.StatusCode != http.StatusOK {
err := service.RelayErrorHandler(httpResp)
err := service.RelayErrorHandler(httpResp, true)
return fmt.Errorf("status code %d: %s", httpResp.StatusCode, err.Error.Message), err
}
}
usageA, respErr := adaptor.DoResponse(c, httpResp, meta)
usageA, respErr := adaptor.DoResponse(c, httpResp, info)
if respErr != nil {
return fmt.Errorf("%s", respErr.Error.Message), respErr
}
@@ -123,25 +150,25 @@ func testChannel(channel *model.Channel, testModel string) (err error, openAIErr
if err != nil {
return err, nil
}
modelPrice, usePrice := common.GetModelPrice(testModel, false)
modelRatio := common.GetModelRatio(testModel)
completionRatio := common.GetCompletionRatio(testModel)
ratio := modelRatio
info.PromptTokens = usage.PromptTokens
quota := 0
if !usePrice {
quota = usage.PromptTokens + int(math.Round(float64(usage.CompletionTokens)*completionRatio))
quota = int(math.Round(float64(quota) * ratio))
if ratio != 0 && quota <= 0 {
if !priceData.UsePrice {
quota = usage.PromptTokens + int(math.Round(float64(usage.CompletionTokens)*priceData.CompletionRatio))
quota = int(math.Round(float64(quota) * priceData.ModelRatio))
if priceData.ModelRatio != 0 && quota <= 0 {
quota = 1
}
} else {
quota = int(modelPrice * common.QuotaPerUnit)
quota = int(priceData.ModelPrice * common.QuotaPerUnit)
}
tok := time.Now()
milliseconds := tok.Sub(tik).Milliseconds()
consumedTime := float64(milliseconds) / 1000.0
other := service.GenerateTextOtherInfo(c, meta, modelRatio, 1, completionRatio, modelPrice)
model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, testModel, "模型测试", quota, "模型测试", 0, quota, int(consumedTime), false, other)
other := service.GenerateTextOtherInfo(c, info, priceData.ModelRatio, priceData.GroupRatioInfo.GroupRatio, priceData.CompletionRatio,
usage.PromptTokensDetails.CachedTokens, priceData.CacheRatio, priceData.ModelPrice, priceData.GroupRatioInfo.GroupSpecialRatio)
model.RecordConsumeLog(c, 1, channel.Id, usage.PromptTokens, usage.CompletionTokens, info.OriginModelName, "模型测试",
quota, "模型测试", 0, quota, int(consumedTime), false, info.Group, other)
common.SysLog(fmt.Sprintf("testing channel #%d, response: \n%s", channel.Id, string(respBody)))
return nil, nil
}
@@ -151,15 +178,32 @@ func buildTestRequest(model string) *dto.GeneralOpenAIRequest {
Model: "", // this will be set later
Stream: false,
}
if strings.HasPrefix(model, "o1-") {
testRequest.MaxCompletionTokens = 1
} else {
testRequest.MaxTokens = 1
// 先判断是否为 Embedding 模型
if strings.Contains(strings.ToLower(model), "embedding") || // 其他 embedding 模型
strings.HasPrefix(model, "m3e") || // m3e 系列模型
strings.Contains(model, "bge-") {
testRequest.Model = model
// Embedding 请求
testRequest.Input = []string{"hello world"}
return testRequest
}
content, _ := json.Marshal("hi")
// 并非Embedding 模型
if strings.HasPrefix(model, "o") {
testRequest.MaxCompletionTokens = 10
} else if strings.Contains(model, "thinking") {
if !strings.Contains(model, "claude") {
testRequest.MaxTokens = 50
}
} else if strings.Contains(model, "gemini") {
testRequest.MaxTokens = 300
} else {
testRequest.MaxTokens = 10
}
testMessage := dto.Message{
Role: "user",
Content: content,
Content: "hi",
}
testRequest.Model = model
testRequest.Messages = append(testRequest.Messages, testMessage)
@@ -210,9 +254,7 @@ var testAllChannelsLock sync.Mutex
var testAllChannelsRunning bool = false
func testAllChannels(notify bool) error {
if common.RootUserEmail == "" {
common.RootUserEmail = model.GetRootUserEmail()
}
testAllChannelsLock.Lock()
if testAllChannelsRunning {
testAllChannelsLock.Unlock()
@@ -229,6 +271,13 @@ func testAllChannels(notify bool) error {
disableThreshold = 10000000 // a impossible value
}
gopool.Go(func() {
// 使用 defer 确保无论如何都会重置运行状态,防止死锁
defer func() {
testAllChannelsLock.Lock()
testAllChannelsRunning = false
testAllChannelsLock.Unlock()
}()
for _, channel := range channels {
isChannelEnabled := channel.Status == common.ChannelStatusEnabled
tik := time.Now()
@@ -263,14 +312,9 @@ func testAllChannels(notify bool) error {
channel.UpdateResponseTime(milliseconds)
time.Sleep(common.RequestInterval)
}
testAllChannelsLock.Lock()
testAllChannelsRunning = false
testAllChannelsLock.Unlock()
if notify {
err := common.SendEmail("通道测试完成", common.RootUserEmail, "通道测试完成,如果没有收到禁用通知,说明所有通道都正常")
if err != nil {
common.SysError(fmt.Sprintf("failed to send email: %s", err.Error()))
}
service.NotifyRootUser(dto.NotifyTypeChannelTest, "通道测试完成", "所有通道测试完成")
}
})
return nil
+243 -42
View File
@@ -43,47 +43,54 @@ type OpenAIModelsResponse struct {
func GetAllChannels(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 0 {
p = 0
if p < 1 {
p = 1
}
if pageSize < 0 {
if pageSize < 1 {
pageSize = common.ItemsPerPage
}
channelData := make([]*model.Channel, 0)
idSort, _ := strconv.ParseBool(c.Query("id_sort"))
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
var total int64
if enableTagMode {
tags, err := model.GetPaginatedTags(p*pageSize, pageSize)
// tag 分页:先分页 tag,再取各 tag 下 channels
tags, err := model.GetPaginatedTags((p-1)*pageSize, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
for _, tag := range tags {
if tag != nil && *tag != "" {
tagChannel, err := model.GetChannelsByTag(*tag)
tagChannel, err := model.GetChannelsByTag(*tag, idSort)
if err == nil {
channelData = append(channelData, tagChannel...)
}
}
}
// 计算 tag 总数用于分页
total, _ = model.CountAllTags()
} else {
channels, err := model.GetAllChannels(p*pageSize, pageSize, false, idSort)
channels, err := model.GetAllChannels((p-1)*pageSize, pageSize, false, idSort)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
channelData = channels
total, _ = model.CountAllChannels()
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": channelData,
"data": gin.H{
"items": channelData,
"total": total,
"page": p,
"page_size": pageSize,
},
})
return
}
@@ -97,6 +104,7 @@ func FetchUpstreamModels(c *gin.Context) {
})
return
}
channel, err := model.GetChannelById(id, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
@@ -105,39 +113,50 @@ func FetchUpstreamModels(c *gin.Context) {
})
return
}
if channel.Type != common.ChannelTypeOpenAI {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "仅支持 OpenAI 类型渠道",
})
return
//if channel.Type != common.ChannelTypeOpenAI {
// c.JSON(http.StatusOK, gin.H{
// "success": false,
// "message": "仅支持 OpenAI 类型渠道",
// })
// return
//}
baseURL := common.ChannelBaseURLs[channel.Type]
if channel.GetBaseURL() != "" {
baseURL = channel.GetBaseURL()
}
url := fmt.Sprintf("%s/v1/models", baseURL)
switch channel.Type {
case common.ChannelTypeGemini:
url = fmt.Sprintf("%s/v1beta/openai/models", baseURL)
case common.ChannelTypeAli:
url = fmt.Sprintf("%s/compatible-mode/v1/models", baseURL)
}
url := fmt.Sprintf("%s/v1/models", *channel.BaseURL)
body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
result := OpenAIModelsResponse{}
err = json.Unmarshal(body, &result)
if err != nil {
var result OpenAIModelsResponse
if err = json.Unmarshal(body, &result); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
}
if !result.Success {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "上游返回错误",
"message": fmt.Sprintf("解析响应失败: %s", err.Error()),
})
return
}
var ids []string
for _, model := range result.Data {
ids = append(ids, model.ID)
id := model.ID
if channel.Type == common.ChannelTypeGemini {
id = strings.TrimPrefix(id, "models/")
}
ids = append(ids, id)
}
c.JSON(http.StatusOK, gin.H{
@@ -168,18 +187,40 @@ func SearchChannels(c *gin.Context) {
group := c.Query("group")
modelKeyword := c.Query("model")
idSort, _ := strconv.ParseBool(c.Query("id_sort"))
channels, err := model.SearchChannels(keyword, group, modelKeyword, idSort)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
enableTagMode, _ := strconv.ParseBool(c.Query("tag_mode"))
channelData := make([]*model.Channel, 0)
if enableTagMode {
tags, err := model.SearchTags(keyword, group, modelKeyword, idSort)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
for _, tag := range tags {
if tag != nil && *tag != "" {
tagChannel, err := model.GetChannelsByTag(*tag, idSort)
if err == nil {
channelData = append(channelData, tagChannel...)
}
}
}
} else {
channels, err := model.SearchChannels(keyword, group, modelKeyword, idSort)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
channelData = channels
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": channels,
"data": channelData,
})
return
}
@@ -250,6 +291,17 @@ func AddChannel(c *gin.Context) {
}
localChannel := channel
localChannel.Key = key
// Validate the length of the model name
models := strings.Split(localChannel.Models, ",")
for _, model := range models {
if len(model) > 255 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": fmt.Sprintf("模型名称过长: %s", model),
})
return
}
}
channels = append(channels, localChannel)
}
err = model.BatchInsertChannels(channels)
@@ -395,7 +447,8 @@ func EditTagChannels(c *gin.Context) {
}
type ChannelBatch struct {
Ids []int `json:"ids"`
Ids []int `json:"ids"`
Tag *string `json:"tag"`
}
func DeleteChannelBatch(c *gin.Context) {
@@ -470,3 +523,151 @@ func UpdateChannel(c *gin.Context) {
})
return
}
func FetchModels(c *gin.Context) {
var req struct {
BaseURL string `json:"base_url"`
Type int `json:"type"`
Key string `json:"key"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "Invalid request",
})
return
}
baseURL := req.BaseURL
if baseURL == "" {
baseURL = common.ChannelBaseURLs[req.Type]
}
client := &http.Client{}
url := fmt.Sprintf("%s/v1/models", baseURL)
request, err := http.NewRequest("GET", url, nil)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
// remove line breaks and extra spaces.
key := strings.TrimSpace(req.Key)
// If the key contains a line break, only take the first part.
key = strings.Split(key, "\n")[0]
request.Header.Set("Authorization", "Bearer "+key)
response, err := client.Do(request)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
//check status code
if response.StatusCode != http.StatusOK {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "Failed to fetch models",
})
return
}
defer response.Body.Close()
var result struct {
Data []struct {
ID string `json:"id"`
} `json:"data"`
}
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
var models []string
for _, model := range result.Data {
models = append(models, model.ID)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": models,
})
}
func BatchSetChannelTag(c *gin.Context) {
channelBatch := ChannelBatch{}
err := c.ShouldBindJSON(&channelBatch)
if err != nil || len(channelBatch.Ids) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "参数错误",
})
return
}
err = model.BatchSetChannelTag(channelBatch.Ids, channelBatch.Tag)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": len(channelBatch.Ids),
})
return
}
func GetTagModels(c *gin.Context) {
tag := c.Query("tag")
if tag == "" {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": "tag不能为空",
})
return
}
channels, err := model.GetChannelsByTag(tag, false) // Assuming false for idSort is fine here
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": err.Error(),
})
return
}
var longestModels string
maxLength := 0
// Find the longest models string among all channels with the given tag
for _, channel := range channels {
if channel.Models != "" {
currentModels := strings.Split(channel.Models, ",")
if len(currentModels) > maxLength {
maxLength = len(currentModels)
longestModels = channel.Models
}
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": longestModels,
})
return
}
+103
View File
@@ -0,0 +1,103 @@
// 用于迁移检测的旧键,该文件下个版本会删除
package controller
import (
"encoding/json"
"net/http"
"one-api/common"
"one-api/model"
"github.com/gin-gonic/gin"
)
// MigrateConsoleSetting 迁移旧的控制台相关配置到 console_setting.*
func MigrateConsoleSetting(c *gin.Context) {
// 读取全部 option
opts, err := model.AllOption()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()})
return
}
// 建立 map
valMap := map[string]string{}
for _, o := range opts {
valMap[o.Key] = o.Value
}
// 处理 APIInfo
if v := valMap["ApiInfo"]; v != "" {
var arr []map[string]interface{}
if err := json.Unmarshal([]byte(v), &arr); err == nil {
if len(arr) > 50 {
arr = arr[:50]
}
bytes, _ := json.Marshal(arr)
model.UpdateOption("console_setting.api_info", string(bytes))
}
model.UpdateOption("ApiInfo", "")
}
// Announcements 直接搬
if v := valMap["Announcements"]; v != "" {
model.UpdateOption("console_setting.announcements", v)
model.UpdateOption("Announcements", "")
}
// FAQ 转换
if v := valMap["FAQ"]; v != "" {
var arr []map[string]interface{}
if err := json.Unmarshal([]byte(v), &arr); err == nil {
out := []map[string]interface{}{}
for _, item := range arr {
q, _ := item["question"].(string)
if q == "" {
q, _ = item["title"].(string)
}
a, _ := item["answer"].(string)
if a == "" {
a, _ = item["content"].(string)
}
if q != "" && a != "" {
out = append(out, map[string]interface{}{"question": q, "answer": a})
}
}
if len(out) > 50 {
out = out[:50]
}
bytes, _ := json.Marshal(out)
model.UpdateOption("console_setting.faq", string(bytes))
}
model.UpdateOption("FAQ", "")
}
// Uptime Kuma 迁移到新的 groups 结构(console_setting.uptime_kuma_groups
url := valMap["UptimeKumaUrl"]
slug := valMap["UptimeKumaSlug"]
if url != "" && slug != "" {
// 仅当同时存在 URL 与 Slug 时才进行迁移
groups := []map[string]interface{}{
{
"id": 1,
"categoryName": "old",
"url": url,
"slug": slug,
"description": "",
},
}
bytes, _ := json.Marshal(groups)
model.UpdateOption("console_setting.uptime_kuma_groups", string(bytes))
}
// 清空旧键内容
if url != "" {
model.UpdateOption("UptimeKumaUrl", "")
}
if slug != "" {
model.UpdateOption("UptimeKumaSlug", "")
}
// 删除旧键记录
oldKeys := []string{"ApiInfo", "Announcements", "FAQ", "UptimeKumaUrl", "UptimeKumaSlug"}
model.DB.Where("key IN ?", oldKeys).Delete(&model.Option{})
// 重新加载 OptionMap
model.InitOptionMap()
common.SysLog("console setting migrated")
c.JSON(http.StatusOK, gin.H{"success": true, "message": "migrated"})
}
+19 -9
View File
@@ -1,15 +1,16 @@
package controller
import (
"github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/model"
"one-api/setting"
"github.com/gin-gonic/gin"
)
func GetGroups(c *gin.Context) {
groupNames := make([]string, 0)
for groupName, _ := range common.GroupRatio {
for groupName, _ := range setting.GetGroupRatioCopy() {
groupNames = append(groupNames, groupName)
}
c.JSON(http.StatusOK, gin.H{
@@ -20,15 +21,24 @@ func GetGroups(c *gin.Context) {
}
func GetUserGroups(c *gin.Context) {
usableGroups := make(map[string]string)
usableGroups := make(map[string]map[string]interface{})
userGroup := ""
userId := c.GetInt("id")
userGroup, _ = model.CacheGetUserGroup(userId)
for groupName, _ := range common.GroupRatio {
userGroup, _ = model.GetUserGroup(userId, false)
for groupName, ratio := range setting.GetGroupRatioCopy() {
// UserUsableGroups contains the groups that the user can use
userUsableGroups := common.GetUserUsableGroups(userGroup)
if _, ok := userUsableGroups[groupName]; ok {
usableGroups[groupName] = userUsableGroups[groupName]
userUsableGroups := setting.GetUserUsableGroups(userGroup)
if desc, ok := userUsableGroups[groupName]; ok {
usableGroups[groupName] = map[string]interface{}{
"ratio": ratio,
"desc": desc,
}
}
}
if setting.GroupInUserUsableGroups("auto") {
usableGroups["auto"] = map[string]interface{}{
"ratio": "自动",
"desc": setting.GetUsableGroupDescription("auto"),
}
}
c.JSON(http.StatusOK, gin.H{
+9
View File
@@ -0,0 +1,9 @@
package controller
import (
"github.com/gin-gonic/gin"
)
func GetImage(c *gin.Context) {
}
+9 -5
View File
@@ -25,7 +25,8 @@ func GetAllLogs(c *gin.Context) {
tokenName := c.Query("token_name")
modelName := c.Query("model_name")
channel, _ := strconv.Atoi(c.Query("channel"))
logs, total, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, (p-1)*pageSize, pageSize, channel)
group := c.Query("group")
logs, total, err := model.GetAllLogs(logType, startTimestamp, endTimestamp, modelName, username, tokenName, (p-1)*pageSize, pageSize, channel, group)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -63,7 +64,8 @@ func GetUserLogs(c *gin.Context) {
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
tokenName := c.Query("token_name")
modelName := c.Query("model_name")
logs, total, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, (p-1)*pageSize, pageSize)
group := c.Query("group")
logs, total, err := model.GetUserLogs(userId, logType, startTimestamp, endTimestamp, modelName, tokenName, (p-1)*pageSize, pageSize, group)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -146,7 +148,8 @@ func GetLogsStat(c *gin.Context) {
username := c.Query("username")
modelName := c.Query("model_name")
channel, _ := strconv.Atoi(c.Query("channel"))
stat := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel)
group := c.Query("group")
stat := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel, group)
//tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, "")
c.JSON(http.StatusOK, gin.H{
"success": true,
@@ -168,7 +171,8 @@ func GetLogsSelfStat(c *gin.Context) {
tokenName := c.Query("token_name")
modelName := c.Query("model_name")
channel, _ := strconv.Atoi(c.Query("channel"))
quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel)
group := c.Query("group")
quotaNum := model.SumUsedQuota(logType, startTimestamp, endTimestamp, modelName, username, tokenName, channel, group)
//tokenNum := model.SumUsedToken(logType, startTimestamp, endTimestamp, modelName, username, tokenName)
c.JSON(200, gin.H{
"success": true,
@@ -192,7 +196,7 @@ func DeleteHistoryLogs(c *gin.Context) {
})
return
}
count, err := model.DeleteOldLog(targetTimestamp)
count, err := model.DeleteOldLog(c.Request.Context(), targetTimestamp, 100)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
+40 -26
View File
@@ -7,13 +7,12 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"log"
"net/http"
"one-api/common"
"one-api/constant"
"one-api/dto"
"one-api/model"
"one-api/service"
"one-api/setting"
"strconv"
"time"
)
@@ -159,7 +158,7 @@ func UpdateMidjourneyTaskBulk() {
common.LogError(ctx, "UpdateMidjourneyTask task error: "+err.Error())
} else {
if shouldReturnQuota {
err = model.IncreaseUserQuota(task.UserId, task.Quota)
err = model.IncreaseUserQuota(task.UserId, task.Quota, false)
if err != nil {
common.LogError(ctx, "fail to increase user quota: "+err.Error())
}
@@ -215,8 +214,12 @@ func checkMjTaskNeedUpdate(oldTask *model.Midjourney, newTask dto.MidjourneyDto)
func GetAllMidjourney(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
if p < 0 {
p = 0
if p < 1 {
p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
}
// 解析其他查询参数
@@ -227,31 +230,38 @@ func GetAllMidjourney(c *gin.Context) {
EndTimestamp: c.Query("end_timestamp"),
}
logs := model.GetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams)
if logs == nil {
logs = make([]*model.Midjourney, 0)
}
if constant.MjForwardUrlEnabled {
for i, midjourney := range logs {
midjourney.ImageUrl = constant.ServerAddress + "/mj/image/" + midjourney.MjId
logs[i] = midjourney
items := model.GetAllTasks((p-1)*pageSize, pageSize, queryParams)
total := model.CountAllTasks(queryParams)
if setting.MjForwardUrlEnabled {
for i, midjourney := range items {
midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId
items[i] = midjourney
}
}
c.JSON(200, gin.H{
"success": true,
"message": "",
"data": logs,
"data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
})
}
func GetUserMidjourney(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
if p < 0 {
p = 0
if p < 1 {
p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
}
userId := c.GetInt("id")
log.Printf("userId = %d \n", userId)
queryParams := model.TaskQueryParams{
MjID: c.Query("mj_id"),
@@ -259,19 +269,23 @@ func GetUserMidjourney(c *gin.Context) {
EndTimestamp: c.Query("end_timestamp"),
}
logs := model.GetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams)
if logs == nil {
logs = make([]*model.Midjourney, 0)
}
if constant.MjForwardUrlEnabled {
for i, midjourney := range logs {
midjourney.ImageUrl = constant.ServerAddress + "/mj/image/" + midjourney.MjId
logs[i] = midjourney
items := model.GetAllUserTask(userId, (p-1)*pageSize, pageSize, queryParams)
total := model.CountAllUserTask(userId, queryParams)
if setting.MjForwardUrlEnabled {
for i, midjourney := range items {
midjourney.ImageUrl = setting.ServerAddress + "/mj/image/" + midjourney.MjId
items[i] = midjourney
}
}
c.JSON(200, gin.H{
"success": true,
"message": "",
"data": logs,
"data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
})
}
+75 -38
View File
@@ -6,7 +6,12 @@ import (
"net/http"
"one-api/common"
"one-api/constant"
"one-api/middleware"
"one-api/model"
"one-api/setting"
"one-api/setting/console_setting"
"one-api/setting/operation_setting"
"one-api/setting/system_setting"
"strings"
"github.com/gin-gonic/gin"
@@ -21,52 +26,84 @@ func TestStatus(c *gin.Context) {
})
return
}
// 获取HTTP统计信息
httpStats := middleware.GetStats()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "Server is running",
"success": true,
"message": "Server is running",
"http_stats": httpStats,
})
return
}
func GetStatus(c *gin.Context) {
cs := console_setting.GetConsoleSetting()
data := gin.H{
"version": common.Version,
"start_time": common.StartTime,
"email_verification": common.EmailVerificationEnabled,
"github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId,
"linuxdo_oauth": common.LinuxDOOAuthEnabled,
"linuxdo_client_id": common.LinuxDOClientId,
"telegram_oauth": common.TelegramOAuthEnabled,
"telegram_bot_name": common.TelegramBotName,
"system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled,
"server_address": setting.ServerAddress,
"price": setting.Price,
"min_topup": setting.MinTopUp,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"docs_link": operation_setting.GetGeneralSetting().DocsLink,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
"enable_drawing": common.DrawingEnabled,
"enable_task": common.TaskEnabled,
"enable_data_export": common.DataExportEnabled,
"data_export_default_time": common.DataExportDefaultTime,
"default_collapse_sidebar": common.DefaultCollapseSidebar,
"enable_online_topup": setting.PayAddress != "" && setting.EpayId != "" && setting.EpayKey != "",
"mj_notify_enabled": setting.MjNotifyEnabled,
"chats": setting.Chats,
"demo_site_enabled": operation_setting.DemoSiteEnabled,
"self_use_mode_enabled": operation_setting.SelfUseModeEnabled,
"default_use_auto_group": setting.DefaultUseAutoGroup,
// 面板启用开关
"api_info_enabled": cs.ApiInfoEnabled,
"uptime_kuma_enabled": cs.UptimeKumaEnabled,
"announcements_enabled": cs.AnnouncementsEnabled,
"faq_enabled": cs.FAQEnabled,
"oidc_enabled": system_setting.GetOIDCSettings().Enabled,
"oidc_client_id": system_setting.GetOIDCSettings().ClientId,
"oidc_authorization_endpoint": system_setting.GetOIDCSettings().AuthorizationEndpoint,
"setup": constant.Setup,
}
// 根据启用状态注入可选内容
if cs.ApiInfoEnabled {
data["api_info"] = console_setting.GetApiInfo()
}
if cs.AnnouncementsEnabled {
data["announcements"] = console_setting.GetAnnouncements()
}
if cs.FAQEnabled {
data["faq"] = console_setting.GetFAQ()
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": gin.H{
"version": common.Version,
"start_time": common.StartTime,
"email_verification": common.EmailVerificationEnabled,
"github_oauth": common.GitHubOAuthEnabled,
"github_client_id": common.GitHubClientId,
"linuxdo_oauth": common.LinuxDOOAuthEnabled,
"linuxdo_client_id": common.LinuxDOClientId,
"telegram_oauth": common.TelegramOAuthEnabled,
"telegram_bot_name": common.TelegramBotName,
"system_name": common.SystemName,
"logo": common.Logo,
"footer_html": common.Footer,
"wechat_qrcode": common.WeChatAccountQRCodeImageURL,
"wechat_login": common.WeChatAuthEnabled,
"server_address": constant.ServerAddress,
"price": constant.Price,
"min_topup": constant.MinTopUp,
"turnstile_check": common.TurnstileCheckEnabled,
"turnstile_site_key": common.TurnstileSiteKey,
"top_up_link": common.TopUpLink,
"chat_link": common.ChatLink,
"chat_link2": common.ChatLink2,
"quota_per_unit": common.QuotaPerUnit,
"display_in_currency": common.DisplayInCurrencyEnabled,
"enable_batch_update": common.BatchUpdateEnabled,
"enable_drawing": common.DrawingEnabled,
"enable_task": common.TaskEnabled,
"enable_data_export": common.DataExportEnabled,
"data_export_default_time": common.DataExportDefaultTime,
"default_collapse_sidebar": common.DefaultCollapseSidebar,
"enable_online_topup": constant.PayAddress != "" && constant.EpayId != "" && constant.EpayKey != "",
"mj_notify_enabled": constant.MjNotifyEnabled,
"chats": constant.Chats,
},
"data": data,
})
return
}
@@ -207,7 +244,7 @@ func SendPasswordResetEmail(c *gin.Context) {
}
code := common.GenerateVerificationCode(0)
common.RegisterVerificationCodeWithKey(email, code, common.PasswordResetPurpose)
link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", constant.ServerAddress, email, code)
link := fmt.Sprintf("%s/user/reset?email=%s&token=%s", setting.ServerAddress, email, code)
subject := fmt.Sprintf("%s密码重置", common.SystemName)
content := fmt.Sprintf("<p>您好,你正在进行%s密码重置。</p>"+
"<p>点击 <a href='%s'>此处</a> 进行密码重置。</p>"+
+24 -3
View File
@@ -2,7 +2,6 @@ package controller
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/constant"
@@ -15,6 +14,9 @@ import (
"one-api/relay/channel/moonshot"
relaycommon "one-api/relay/common"
relayconstant "one-api/relay/constant"
"one-api/setting"
"github.com/gin-gonic/gin"
)
// https://platform.openai.com/docs/api-reference/models/list
@@ -166,7 +168,7 @@ func ListModels(c *gin.Context) {
}
} else {
userId := c.GetInt("id")
userGroup, err := model.GetUserGroup(userId)
userGroup, err := model.GetUserGroup(userId, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -179,7 +181,19 @@ func ListModels(c *gin.Context) {
if tokenGroup != "" {
group = tokenGroup
}
models := model.GetGroupModels(group)
var models []string
if tokenGroup == "auto" {
for _, autoGroup := range setting.AutoGroups {
groupModels := model.GetGroupModels(autoGroup)
for _, g := range groupModels {
if !common.StringsContains(models, g) {
models = append(models, g)
}
}
}
} else {
models = model.GetGroupModels(group)
}
for _, s := range models {
if _, ok := openAIModelsMap[s]; ok {
userOpenAiModels = append(userOpenAiModels, openAIModelsMap[s])
@@ -216,6 +230,13 @@ func DashboardListModels(c *gin.Context) {
})
}
func EnabledListModels(c *gin.Context) {
c.JSON(200, gin.H{
"success": true,
"data": model.GetEnabledModels(),
})
}
func RetrieveModel(c *gin.Context) {
modelId := c.Param("model")
if aiModel, ok := openAIModelsMap[modelId]; ok {
+240
View File
@@ -0,0 +1,240 @@
package controller
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"one-api/common"
"one-api/model"
"one-api/setting"
"one-api/setting/system_setting"
"strconv"
"strings"
"time"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
type OidcResponse struct {
AccessToken string `json:"access_token"`
IDToken string `json:"id_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
Scope string `json:"scope"`
}
type OidcUser struct {
OpenID string `json:"sub"`
Email string `json:"email"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Picture string `json:"picture"`
}
func getOidcUserInfoByCode(code string) (*OidcUser, error) {
if code == "" {
return nil, errors.New("无效的参数")
}
values := url.Values{}
values.Set("client_id", system_setting.GetOIDCSettings().ClientId)
values.Set("client_secret", system_setting.GetOIDCSettings().ClientSecret)
values.Set("code", code)
values.Set("grant_type", "authorization_code")
values.Set("redirect_uri", fmt.Sprintf("%s/oauth/oidc", setting.ServerAddress))
formData := values.Encode()
req, err := http.NewRequest("POST", system_setting.GetOIDCSettings().TokenEndpoint, strings.NewReader(formData))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
client := http.Client{
Timeout: 5 * time.Second,
}
res, err := client.Do(req)
if err != nil {
common.SysLog(err.Error())
return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
}
defer res.Body.Close()
var oidcResponse OidcResponse
err = json.NewDecoder(res.Body).Decode(&oidcResponse)
if err != nil {
return nil, err
}
if oidcResponse.AccessToken == "" {
common.SysError("OIDC 获取 Token 失败,请检查设置!")
return nil, errors.New("OIDC 获取 Token 失败,请检查设置!")
}
req, err = http.NewRequest("GET", system_setting.GetOIDCSettings().UserInfoEndpoint, nil)
if err != nil {
return nil, err
}
req.Header.Set("Authorization", "Bearer "+oidcResponse.AccessToken)
res2, err := client.Do(req)
if err != nil {
common.SysLog(err.Error())
return nil, errors.New("无法连接至 OIDC 服务器,请稍后重试!")
}
defer res2.Body.Close()
if res2.StatusCode != http.StatusOK {
common.SysError("OIDC 获取用户信息失败!请检查设置!")
return nil, errors.New("OIDC 获取用户信息失败!请检查设置!")
}
var oidcUser OidcUser
err = json.NewDecoder(res2.Body).Decode(&oidcUser)
if err != nil {
return nil, err
}
if oidcUser.OpenID == "" || oidcUser.Email == "" {
common.SysError("OIDC 获取用户信息为空!请检查设置!")
return nil, errors.New("OIDC 获取用户信息为空!请检查设置!")
}
return &oidcUser, nil
}
func OidcAuth(c *gin.Context) {
session := sessions.Default(c)
state := c.Query("state")
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": "state is empty or not same",
})
return
}
username := session.Get("username")
if username != nil {
OidcBind(c)
return
}
if !system_setting.GetOIDCSettings().Enabled {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "管理员未开启通过 OIDC 登录以及注册",
})
return
}
code := c.Query("code")
oidcUser, err := getOidcUserInfoByCode(code)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
user := model.User{
OidcId: oidcUser.OpenID,
}
if model.IsOidcIdAlreadyTaken(user.OidcId) {
err := user.FillUserByOidcId()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
} else {
if common.RegisterEnabled {
user.Email = oidcUser.Email
if oidcUser.PreferredUsername != "" {
user.Username = oidcUser.PreferredUsername
} else {
user.Username = "oidc_" + strconv.Itoa(model.GetMaxUserId()+1)
}
if oidcUser.Name != "" {
user.DisplayName = oidcUser.Name
} else {
user.DisplayName = "OIDC User"
}
err := user.Insert(0)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
} else {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "管理员关闭了新用户注册",
})
return
}
}
if user.Status != common.UserStatusEnabled {
c.JSON(http.StatusOK, gin.H{
"message": "用户已被封禁",
"success": false,
})
return
}
setupLogin(&user, c)
}
func OidcBind(c *gin.Context) {
if !system_setting.GetOIDCSettings().Enabled {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "管理员未开启通过 OIDC 登录以及注册",
})
return
}
code := c.Query("code")
oidcUser, err := getOidcUserInfoByCode(code)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
user := model.User{
OidcId: oidcUser.OpenID,
}
if model.IsOidcIdAlreadyTaken(user.OidcId) {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "该 OIDC 账户已被绑定",
})
return
}
session := sessions.Default(c)
id := session.Get("id")
// id := c.GetInt("id") // critical bug!
user.Id = id.(int)
err = user.FillUserById()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
user.OidcId = oidcUser.OpenID
err = user.Update(false)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "bind",
})
return
}
+66 -1
View File
@@ -5,6 +5,9 @@ import (
"net/http"
"one-api/common"
"one-api/model"
"one-api/setting"
"one-api/setting/console_setting"
"one-api/setting/system_setting"
"strings"
"github.com/gin-gonic/gin"
@@ -50,6 +53,14 @@ func UpdateOption(c *gin.Context) {
})
return
}
case "oidc.enabled":
if option.Value == "true" && system_setting.GetOIDCSettings().ClientId == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无法启用 OIDC 登录,请先填入 OIDC Client Id 以及 OIDC Client Secret",
})
return
}
case "LinuxDOOAuthEnabled":
if option.Value == "true" && common.LinuxDOClientId == "" {
c.JSON(http.StatusOK, gin.H{
@@ -80,10 +91,64 @@ func UpdateOption(c *gin.Context) {
"success": false,
"message": "无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!",
})
return
}
case "TelegramOAuthEnabled":
if option.Value == "true" && common.TelegramBotToken == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无法启用 Telegram OAuth,请先填入 Telegram Bot Token",
})
return
}
case "GroupRatio":
err = common.CheckGroupRatio(option.Value)
err = setting.CheckGroupRatio(option.Value)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
case "ModelRequestRateLimitGroup":
err = setting.CheckModelRequestRateLimitGroup(option.Value)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
case "console_setting.api_info":
err = console_setting.ValidateConsoleSettings(option.Value, "ApiInfo")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
case "console_setting.announcements":
err = console_setting.ValidateConsoleSettings(option.Value, "Announcements")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
case "console_setting.faq":
err = console_setting.ValidateConsoleSettings(option.Value, "FAQ")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
case "console_setting.uptime_kuma_groups":
err = console_setting.ValidateConsoleSettings(option.Value, "UptimeKumaGroups")
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
+70
View File
@@ -0,0 +1,70 @@
package controller
import (
"errors"
"fmt"
"net/http"
"one-api/common"
"one-api/constant"
"one-api/dto"
"one-api/middleware"
"one-api/model"
"one-api/service"
"one-api/setting"
"time"
"github.com/gin-gonic/gin"
)
func Playground(c *gin.Context) {
var openaiErr *dto.OpenAIErrorWithStatusCode
defer func() {
if openaiErr != nil {
c.JSON(openaiErr.StatusCode, gin.H{
"error": openaiErr.Error,
})
}
}()
useAccessToken := c.GetBool("use_access_token")
if useAccessToken {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("暂不支持使用 access token"), "access_token_not_supported", http.StatusBadRequest)
return
}
playgroundRequest := &dto.PlayGroundRequest{}
err := common.UnmarshalBodyReusable(c, playgroundRequest)
if err != nil {
openaiErr = service.OpenAIErrorWrapperLocal(err, "unmarshal_request_failed", http.StatusBadRequest)
return
}
if playgroundRequest.Model == "" {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("请选择模型"), "model_required", http.StatusBadRequest)
return
}
c.Set("original_model", playgroundRequest.Model)
group := playgroundRequest.Group
userGroup := c.GetString("group")
if group == "" {
group = userGroup
} else {
if !setting.GroupInUserUsableGroups(group) && group != userGroup {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("无权访问该分组"), "group_not_allowed", http.StatusForbidden)
return
}
c.Set("group", group)
}
c.Set("token_name", "playground-"+group)
channel, finalGroup, err := model.CacheGetRandomSatisfiedChannel(c, group, playgroundRequest.Model, 0)
if err != nil {
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", finalGroup, playgroundRequest.Model)
openaiErr = service.OpenAIErrorWrapperLocal(errors.New(message), "get_playground_channel_failed", http.StatusInternalServerError)
return
}
middleware.SetupContextForSelectedChannel(c, channel, playgroundRequest.Model)
c.Set(constant.ContextKeyRequestStartTime, time.Now())
Relay(c)
}
+38 -7
View File
@@ -1,22 +1,53 @@
package controller
import (
"github.com/gin-gonic/gin"
"one-api/common"
"one-api/model"
"one-api/setting"
"one-api/setting/operation_setting"
"github.com/gin-gonic/gin"
)
func GetPricing(c *gin.Context) {
pricing := model.GetPricing()
userId, exists := c.Get("id")
usableGroup := map[string]string{}
groupRatio := map[string]float64{}
for s, f := range setting.GetGroupRatioCopy() {
groupRatio[s] = f
}
var group string
if exists {
user, err := model.GetUserCache(userId.(int))
if err == nil {
group = user.Group
for g := range groupRatio {
ratio, ok := setting.GetGroupGroupRatio(group, g)
if ok {
groupRatio[g] = ratio
}
}
}
}
usableGroup = setting.GetUserUsableGroups(group)
// check groupRatio contains usableGroup
for group := range setting.GetGroupRatioCopy() {
if _, ok := usableGroup[group]; !ok {
delete(groupRatio, group)
}
}
c.JSON(200, gin.H{
"success": true,
"data": pricing,
"group_ratio": common.GroupRatio,
"success": true,
"data": pricing,
"group_ratio": groupRatio,
"usable_group": usableGroup,
})
}
func ResetModelRatio(c *gin.Context) {
defaultStr := common.DefaultModelRatio2JSONString()
defaultStr := operation_setting.DefaultModelRatio2JSONString()
err := model.UpdateOption("ModelRatio", defaultStr)
if err != nil {
c.JSON(200, gin.H{
@@ -25,7 +56,7 @@ func ResetModelRatio(c *gin.Context) {
})
return
}
err = common.UpdateModelRatioByJSONString(defaultStr)
err = operation_setting.UpdateModelRatioByJSONString(defaultStr)
if err != nil {
c.JSON(200, gin.H{
"success": false,
+67 -8
View File
@@ -1,19 +1,25 @@
package controller
import (
"github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/model"
"strconv"
"errors"
"github.com/gin-gonic/gin"
)
func GetAllRedemptions(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 0 {
p = 0
}
redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage)
if pageSize < 1 {
pageSize = common.ItemsPerPage
}
redemptions, total, err := model.GetAllRedemptions((p-1)*pageSize, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -24,14 +30,27 @@ func GetAllRedemptions(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": redemptions,
"data": gin.H{
"items": redemptions,
"total": total,
"page": p,
"page_size": pageSize,
},
})
return
}
func SearchRedemptions(c *gin.Context) {
keyword := c.Query("keyword")
redemptions, err := model.SearchRedemptions(keyword)
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 0 {
p = 0
}
if pageSize < 1 {
pageSize = common.ItemsPerPage
}
redemptions, total, err := model.SearchRedemptions(keyword, (p-1)*pageSize, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -42,7 +61,12 @@ func SearchRedemptions(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": redemptions,
"data": gin.H{
"items": redemptions,
"total": total,
"page": p,
"page_size": pageSize,
},
})
return
}
@@ -103,6 +127,10 @@ func AddRedemption(c *gin.Context) {
})
return
}
if err := validateExpiredTime(redemption.ExpiredTime); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
var keys []string
for i := 0; i < redemption.Count; i++ {
key := common.GetUUID()
@@ -112,6 +140,7 @@ func AddRedemption(c *gin.Context) {
Key: key,
CreatedTime: common.GetTimestamp(),
Quota: redemption.Quota,
ExpiredTime: redemption.ExpiredTime,
}
err = cleanRedemption.Insert()
if err != nil {
@@ -168,12 +197,18 @@ func UpdateRedemption(c *gin.Context) {
})
return
}
if statusOnly != "" {
cleanRedemption.Status = redemption.Status
} else {
if statusOnly == "" {
if err := validateExpiredTime(redemption.ExpiredTime); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
return
}
// If you add more fields, please also update redemption.Update()
cleanRedemption.Name = redemption.Name
cleanRedemption.Quota = redemption.Quota
cleanRedemption.ExpiredTime = redemption.ExpiredTime
}
if statusOnly != "" {
cleanRedemption.Status = redemption.Status
}
err = cleanRedemption.Update()
if err != nil {
@@ -190,3 +225,27 @@ func UpdateRedemption(c *gin.Context) {
})
return
}
func DeleteInvalidRedemption(c *gin.Context) {
rows, err := model.DeleteInvalidRedemptions()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": rows,
})
return
}
func validateExpiredTime(expired int64) error {
if expired != 0 && expired < common.GetTimestamp() {
return errors.New("过期时间不能早于当前时间")
}
return nil
}
+89 -69
View File
@@ -4,27 +4,30 @@ import (
"bytes"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"io"
"log"
"net/http"
"one-api/common"
constant2 "one-api/constant"
"one-api/dto"
"one-api/middleware"
"one-api/model"
"one-api/relay"
"one-api/relay/constant"
relayconstant "one-api/relay/constant"
"one-api/relay/helper"
"one-api/service"
"strings"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
)
func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode {
var err *dto.OpenAIErrorWithStatusCode
switch relayMode {
case relayconstant.RelayModeImagesGenerations:
err = relay.ImageHelper(c, relayMode)
case relayconstant.RelayModeImagesGenerations, relayconstant.RelayModeImagesEdits:
err = relay.ImageHelper(c)
case relayconstant.RelayModeAudioSpeech:
fallthrough
case relayconstant.RelayModeAudioTranslation:
@@ -33,73 +36,38 @@ func relayHandler(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode
err = relay.AudioHelper(c)
case relayconstant.RelayModeRerank:
err = relay.RerankHelper(c, relayMode)
case relayconstant.RelayModeEmbeddings:
err = relay.EmbeddingHelper(c)
case relayconstant.RelayModeResponses:
err = relay.ResponsesHelper(c)
case relayconstant.RelayModeGemini:
err = relay.GeminiHelper(c)
default:
err = relay.TextHelper(c)
}
if constant2.ErrorLogEnabled && err != nil {
// 保存错误日志到mysql中
userId := c.GetInt("id")
tokenName := c.GetString("token_name")
modelName := c.GetString("original_model")
tokenId := c.GetInt("token_id")
userGroup := c.GetString("group")
channelId := c.GetInt("channel_id")
other := make(map[string]interface{})
other["error_type"] = err.Error.Type
other["error_code"] = err.Error.Code
other["status_code"] = err.StatusCode
other["channel_id"] = channelId
other["channel_name"] = c.GetString("channel_name")
other["channel_type"] = c.GetInt("channel_type")
model.RecordErrorLog(c, userId, channelId, modelName, tokenName, err.Error.Message, tokenId, 0, false, userGroup, other)
}
return err
}
func wsHandler(c *gin.Context, ws *websocket.Conn, relayMode int) *dto.OpenAIErrorWithStatusCode {
var err *dto.OpenAIErrorWithStatusCode
switch relayMode {
default:
err = relay.TextHelper(c)
}
return err
}
func Playground(c *gin.Context) {
var openaiErr *dto.OpenAIErrorWithStatusCode
defer func() {
if openaiErr != nil {
c.JSON(openaiErr.StatusCode, gin.H{
"error": openaiErr.Error,
})
}
}()
useAccessToken := c.GetBool("use_access_token")
if useAccessToken {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("暂不支持使用 access token"), "access_token_not_supported", http.StatusBadRequest)
return
}
playgroundRequest := &dto.PlayGroundRequest{}
err := common.UnmarshalBodyReusable(c, playgroundRequest)
if err != nil {
openaiErr = service.OpenAIErrorWrapperLocal(err, "unmarshal_request_failed", http.StatusBadRequest)
return
}
if playgroundRequest.Model == "" {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("请选择模型"), "model_required", http.StatusBadRequest)
return
}
c.Set("original_model", playgroundRequest.Model)
group := playgroundRequest.Group
userGroup := c.GetString("group")
if group == "" {
group = userGroup
} else {
if !common.GroupInUserUsableGroups(group) && group != userGroup {
openaiErr = service.OpenAIErrorWrapperLocal(errors.New("无权访问该分组"), "group_not_allowed", http.StatusForbidden)
return
}
c.Set("group", group)
}
c.Set("token_name", "playground-"+group)
channel, err := model.CacheGetRandomSatisfiedChannel(group, playgroundRequest.Model, 0)
if err != nil {
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", group, playgroundRequest.Model)
openaiErr = service.OpenAIErrorWrapperLocal(errors.New(message), "get_playground_channel_failed", http.StatusInternalServerError)
return
}
middleware.SetupContextForSelectedChannel(c, channel, playgroundRequest.Model)
Relay(c)
}
func Relay(c *gin.Context) {
relayMode := constant.Path2RelayMode(c.Request.URL.Path)
requestId := c.GetString(common.RequestIdKey)
@@ -135,6 +103,7 @@ func Relay(c *gin.Context) {
if openaiErr != nil {
if openaiErr.StatusCode == http.StatusTooManyRequests {
common.LogError(c, fmt.Sprintf("origin 429 error: %s", openaiErr.Error.Message))
openaiErr.Error.Message = "当前分组上游负载已饱和,请稍后再试"
}
openaiErr.Error.Message = common.MessageWithRequestId(openaiErr.Error.Message, requestId)
@@ -159,7 +128,7 @@ func WssRelay(c *gin.Context) {
if err != nil {
openaiErr := service.OpenAIErrorWrapper(err, "get_channel_failed", http.StatusInternalServerError)
service.WssError(c, ws, openaiErr.Error)
helper.WssError(c, ws, openaiErr.Error)
return
}
@@ -201,7 +170,51 @@ func WssRelay(c *gin.Context) {
openaiErr.Error.Message = "当前分组上游负载已饱和,请稍后再试"
}
openaiErr.Error.Message = common.MessageWithRequestId(openaiErr.Error.Message, requestId)
service.WssError(c, ws, openaiErr.Error)
helper.WssError(c, ws, openaiErr.Error)
}
}
func RelayClaude(c *gin.Context) {
//relayMode := constant.Path2RelayMode(c.Request.URL.Path)
requestId := c.GetString(common.RequestIdKey)
group := c.GetString("group")
originalModel := c.GetString("original_model")
var claudeErr *dto.ClaudeErrorWithStatusCode
for i := 0; i <= common.RetryTimes; i++ {
channel, err := getChannel(c, group, originalModel, i)
if err != nil {
common.LogError(c, err.Error())
claudeErr = service.ClaudeErrorWrapperLocal(err, "get_channel_failed", http.StatusInternalServerError)
break
}
claudeErr = claudeRequest(c, channel)
if claudeErr == nil {
return // 成功处理请求,直接返回
}
openaiErr := service.ClaudeErrorToOpenAIError(claudeErr)
go processChannelError(c, channel.Id, channel.Type, channel.Name, channel.GetAutoBan(), openaiErr)
if !shouldRetry(c, openaiErr, common.RetryTimes-i) {
break
}
}
useChannel := c.GetStringSlice("use_channel")
if len(useChannel) > 1 {
retryLogStr := fmt.Sprintf("重试:%s", strings.Trim(strings.Join(strings.Fields(fmt.Sprint(useChannel)), "->"), "[]"))
common.LogInfo(c, retryLogStr)
}
if claudeErr != nil {
claudeErr.Error.Message = common.MessageWithRequestId(claudeErr.Error.Message, requestId)
c.JSON(claudeErr.StatusCode, gin.H{
"type": "error",
"error": claudeErr.Error,
})
}
}
@@ -219,6 +232,13 @@ func wssRequest(c *gin.Context, ws *websocket.Conn, relayMode int, channel *mode
return relay.WssHelper(c, ws)
}
func claudeRequest(c *gin.Context, channel *model.Channel) *dto.ClaudeErrorWithStatusCode {
addUsedChannel(c, channel.Id)
requestBody, _ := common.GetRequestBody(c)
c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
return relay.ClaudeHelper(c)
}
func addUsedChannel(c *gin.Context, channelId int) {
useChannel := c.GetStringSlice("use_channel")
useChannel = append(useChannel, fmt.Sprintf("%d", channelId))
@@ -239,7 +259,7 @@ func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*m
AutoBan: &autoBanInt,
}, nil
}
channel, err := model.CacheGetRandomSatisfiedChannel(group, originalModel, retryCount)
channel, _, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, retryCount)
if err != nil {
return nil, errors.New(fmt.Sprintf("获取重试渠道失败: %s", err.Error()))
}
@@ -368,7 +388,7 @@ func RelayTask(c *gin.Context) {
retryTimes = 0
}
for i := 0; shouldRetryTaskRelay(c, channelId, taskErr, retryTimes) && i < retryTimes; i++ {
channel, err := model.CacheGetRandomSatisfiedChannel(group, originalModel, i)
channel, _, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, i)
if err != nil {
common.LogError(c, fmt.Sprintf("CacheGetRandomSatisfiedChannel failed: %s", err.Error()))
break
+181
View File
@@ -0,0 +1,181 @@
package controller
import (
"github.com/gin-gonic/gin"
"one-api/common"
"one-api/constant"
"one-api/model"
"one-api/setting/operation_setting"
"time"
)
type Setup struct {
Status bool `json:"status"`
RootInit bool `json:"root_init"`
DatabaseType string `json:"database_type"`
}
type SetupRequest struct {
Username string `json:"username"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
SelfUseModeEnabled bool `json:"SelfUseModeEnabled"`
DemoSiteEnabled bool `json:"DemoSiteEnabled"`
}
func GetSetup(c *gin.Context) {
setup := Setup{
Status: constant.Setup,
}
if constant.Setup {
c.JSON(200, gin.H{
"success": true,
"data": setup,
})
return
}
setup.RootInit = model.RootUserExists()
if common.UsingMySQL {
setup.DatabaseType = "mysql"
}
if common.UsingPostgreSQL {
setup.DatabaseType = "postgres"
}
if common.UsingSQLite {
setup.DatabaseType = "sqlite"
}
c.JSON(200, gin.H{
"success": true,
"data": setup,
})
}
func PostSetup(c *gin.Context) {
// Check if setup is already completed
if constant.Setup {
c.JSON(400, gin.H{
"success": false,
"message": "系统已经初始化完成",
})
return
}
// Check if root user already exists
rootExists := model.RootUserExists()
var req SetupRequest
err := c.ShouldBindJSON(&req)
if err != nil {
c.JSON(400, gin.H{
"success": false,
"message": "请求参数有误",
})
return
}
// If root doesn't exist, validate and create admin account
if !rootExists {
// Validate username length: max 12 characters to align with model.User validation
if len(req.Username) > 12 {
c.JSON(400, gin.H{
"success": false,
"message": "用户名长度不能超过12个字符",
})
return
}
// Validate password
if req.Password != req.ConfirmPassword {
c.JSON(400, gin.H{
"success": false,
"message": "两次输入的密码不一致",
})
return
}
if len(req.Password) < 8 {
c.JSON(400, gin.H{
"success": false,
"message": "密码长度至少为8个字符",
})
return
}
// Create root user
hashedPassword, err := common.Password2Hash(req.Password)
if err != nil {
c.JSON(500, gin.H{
"success": false,
"message": "系统错误: " + err.Error(),
})
return
}
rootUser := model.User{
Username: req.Username,
Password: hashedPassword,
Role: common.RoleRootUser,
Status: common.UserStatusEnabled,
DisplayName: "Root User",
AccessToken: nil,
Quota: 100000000,
}
err = model.DB.Create(&rootUser).Error
if err != nil {
c.JSON(500, gin.H{
"success": false,
"message": "创建管理员账号失败: " + err.Error(),
})
return
}
}
// Set operation modes
operation_setting.SelfUseModeEnabled = req.SelfUseModeEnabled
operation_setting.DemoSiteEnabled = req.DemoSiteEnabled
// Save operation modes to database for persistence
err = model.UpdateOption("SelfUseModeEnabled", boolToString(req.SelfUseModeEnabled))
if err != nil {
c.JSON(500, gin.H{
"success": false,
"message": "保存自用模式设置失败: " + err.Error(),
})
return
}
err = model.UpdateOption("DemoSiteEnabled", boolToString(req.DemoSiteEnabled))
if err != nil {
c.JSON(500, gin.H{
"success": false,
"message": "保存演示站点模式设置失败: " + err.Error(),
})
return
}
// Update setup status
constant.Setup = true
setup := model.Setup{
Version: common.Version,
InitializedAt: time.Now().Unix(),
}
err = model.DB.Create(&setup).Error
if err != nil {
c.JSON(500, gin.H{
"success": false,
"message": "系统初始化失败: " + err.Error(),
})
return
}
c.JSON(200, gin.H{
"success": true,
"message": "系统初始化成功",
})
}
func boolToString(b bool) string {
if b {
return "true"
}
return "false"
}
+32 -16
View File
@@ -153,13 +153,13 @@ func updateSunoTaskAll(ctx context.Context, channelId int, taskIds []string, tas
if responseItem.FailReason != "" || task.Status == model.TaskStatusFailure {
common.LogInfo(ctx, task.TaskID+" 构建失败,"+task.FailReason)
task.Progress = "100%"
err = model.CacheUpdateUserQuota(task.UserId)
//err = model.CacheUpdateUserQuota(task.UserId) ?
if err != nil {
common.LogError(ctx, "error update user quota cache: "+err.Error())
} else {
quota := task.Quota
if quota != 0 {
err = model.IncreaseUserQuota(task.UserId, quota)
err = model.IncreaseUserQuota(task.UserId, quota, false)
if err != nil {
common.LogError(ctx, "fail to increase user quota: "+err.Error())
}
@@ -224,9 +224,14 @@ func checkTaskNeedUpdate(oldTask *model.Task, newTask dto.SunoDataResponse) bool
func GetAllTask(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
if p < 0 {
p = 0
if p < 1 {
p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
}
startTimestamp, _ := strconv.ParseInt(c.Query("start_timestamp"), 10, 64)
endTimestamp, _ := strconv.ParseInt(c.Query("end_timestamp"), 10, 64)
// 解析其他查询参数
@@ -237,24 +242,32 @@ func GetAllTask(c *gin.Context) {
Action: c.Query("action"),
StartTimestamp: startTimestamp,
EndTimestamp: endTimestamp,
ChannelID: c.Query("channel_id"),
}
logs := model.TaskGetAllTasks(p*common.ItemsPerPage, common.ItemsPerPage, queryParams)
if logs == nil {
logs = make([]*model.Task, 0)
}
items := model.TaskGetAllTasks((p-1)*pageSize, pageSize, queryParams)
total := model.TaskCountAllTasks(queryParams)
c.JSON(200, gin.H{
"success": true,
"message": "",
"data": logs,
"data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
})
}
func GetUserTask(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
if p < 0 {
p = 0
if p < 1 {
p = 1
}
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if pageSize <= 0 {
pageSize = common.ItemsPerPage
}
userId := c.GetInt("id")
@@ -271,14 +284,17 @@ func GetUserTask(c *gin.Context) {
EndTimestamp: endTimestamp,
}
logs := model.TaskGetAllUserTask(userId, p*common.ItemsPerPage, common.ItemsPerPage, queryParams)
if logs == nil {
logs = make([]*model.Task, 0)
}
items := model.TaskGetAllUserTask(userId, (p-1)*pageSize, pageSize, queryParams)
total := model.TaskCountAllUserTask(userId, queryParams)
c.JSON(200, gin.H{
"success": true,
"message": "",
"data": logs,
"data": gin.H{
"items": items,
"total": total,
"page": p,
"page_size": pageSize,
},
})
}
+12 -4
View File
@@ -12,15 +12,15 @@ func GetAllTokens(c *gin.Context) {
userId := c.GetInt("id")
p, _ := strconv.Atoi(c.Query("p"))
size, _ := strconv.Atoi(c.Query("size"))
if p < 0 {
p = 0
if p < 1 {
p = 1
}
if size <= 0 {
size = common.ItemsPerPage
} else if size > 100 {
size = 100
}
tokens, err := model.GetAllUserTokens(userId, p*size, size)
tokens, err := model.GetAllUserTokens(userId, (p-1)*size, size)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -28,10 +28,18 @@ func GetAllTokens(c *gin.Context) {
})
return
}
// Get total count for pagination
total, _ := model.CountUserTokens(userId)
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": tokens,
"data": gin.H{
"items": tokens,
"total": total,
"page": p,
"page_size": size,
},
})
return
}
+47 -31
View File
@@ -2,64 +2,76 @@ package controller
import (
"fmt"
"github.com/Calcium-Ion/go-epay/epay"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"log"
"net/url"
"one-api/common"
"one-api/constant"
"one-api/model"
"one-api/service"
"one-api/setting"
"strconv"
"sync"
"time"
"github.com/Calcium-Ion/go-epay/epay"
"github.com/gin-gonic/gin"
"github.com/samber/lo"
"github.com/shopspring/decimal"
)
type EpayRequest struct {
Amount int `json:"amount"`
Amount int64 `json:"amount"`
PaymentMethod string `json:"payment_method"`
TopUpCode string `json:"top_up_code"`
}
type AmountRequest struct {
Amount int `json:"amount"`
Amount int64 `json:"amount"`
TopUpCode string `json:"top_up_code"`
}
func GetEpayClient() *epay.Client {
if constant.PayAddress == "" || constant.EpayId == "" || constant.EpayKey == "" {
if setting.PayAddress == "" || setting.EpayId == "" || setting.EpayKey == "" {
return nil
}
withUrl, err := epay.NewClient(&epay.Config{
PartnerID: constant.EpayId,
Key: constant.EpayKey,
}, constant.PayAddress)
PartnerID: setting.EpayId,
Key: setting.EpayKey,
}, setting.PayAddress)
if err != nil {
return nil
}
return withUrl
}
func getPayMoney(amount float64, group string) float64 {
func getPayMoney(amount int64, group string) float64 {
dAmount := decimal.NewFromInt(amount)
if !common.DisplayInCurrencyEnabled {
amount = amount / common.QuotaPerUnit
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
dAmount = dAmount.Div(dQuotaPerUnit)
}
// 别问为什么用float64,问就是这么点钱没必要
topupGroupRatio := common.GetTopupGroupRatio(group)
if topupGroupRatio == 0 {
topupGroupRatio = 1
}
payMoney := amount * constant.Price * topupGroupRatio
return payMoney
dTopupGroupRatio := decimal.NewFromFloat(topupGroupRatio)
dPrice := decimal.NewFromFloat(setting.Price)
payMoney := dAmount.Mul(dPrice).Mul(dTopupGroupRatio)
return payMoney.InexactFloat64()
}
func getMinTopup() int {
minTopup := constant.MinTopUp
func getMinTopup() int64 {
minTopup := setting.MinTopUp
if !common.DisplayInCurrencyEnabled {
minTopup = minTopup * int(common.QuotaPerUnit)
dMinTopup := decimal.NewFromInt(int64(minTopup))
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
minTopup = int(dMinTopup.Mul(dQuotaPerUnit).IntPart())
}
return minTopup
return int64(minTopup)
}
func RequestEpay(c *gin.Context) {
@@ -75,27 +87,26 @@ func RequestEpay(c *gin.Context) {
}
id := c.GetInt("id")
group, err := model.CacheGetUserGroup(id)
group, err := model.GetUserGroup(id, true)
if err != nil {
c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"})
return
}
payMoney := getPayMoney(float64(req.Amount), group)
payMoney := getPayMoney(req.Amount, group)
if payMoney < 0.01 {
c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"})
return
}
var payType epay.PurchaseType
payType := "wxpay"
if req.PaymentMethod == "zfb" {
payType = epay.Alipay
payType = "alipay"
}
if req.PaymentMethod == "wx" {
req.PaymentMethod = "wxpay"
payType = epay.WechatPay
payType = "wxpay"
}
callBackAddress := service.GetCallbackAddress()
returnUrl, _ := url.Parse(constant.ServerAddress + "/log")
returnUrl, _ := url.Parse(setting.ServerAddress + "/console/log")
notifyUrl, _ := url.Parse(callBackAddress + "/api/user/epay/notify")
tradeNo := fmt.Sprintf("%s%d", common.GetRandomString(6), time.Now().Unix())
tradeNo = fmt.Sprintf("USR%dNO%s", id, tradeNo)
@@ -119,7 +130,9 @@ func RequestEpay(c *gin.Context) {
}
amount := req.Amount
if !common.DisplayInCurrencyEnabled {
amount = amount / int(common.QuotaPerUnit)
dAmount := decimal.NewFromInt(int64(amount))
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
amount = dAmount.Div(dQuotaPerUnit).IntPart()
}
topUp := &model.TopUp{
UserId: id,
@@ -211,13 +224,16 @@ func EpayNotify(c *gin.Context) {
}
//user, _ := model.GetUserById(topUp.UserId, false)
//user.Quota += topUp.Amount * 500000
err = model.IncreaseUserQuota(topUp.UserId, topUp.Amount*int(common.QuotaPerUnit))
dAmount := decimal.NewFromInt(int64(topUp.Amount))
dQuotaPerUnit := decimal.NewFromFloat(common.QuotaPerUnit)
quotaToAdd := int(dAmount.Mul(dQuotaPerUnit).IntPart())
err = model.IncreaseUserQuota(topUp.UserId, quotaToAdd, true)
if err != nil {
log.Printf("易支付回调更新用户失败: %v", topUp)
return
}
log.Printf("易支付回调更新用户成功 %v", topUp)
model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", common.LogQuota(topUp.Amount*int(common.QuotaPerUnit)), topUp.Money))
model.RecordLog(topUp.UserId, model.LogTypeTopup, fmt.Sprintf("使用在线充值成功,充值金额: %v,支付金额:%f", common.LogQuota(quotaToAdd), topUp.Money))
}
} else {
log.Printf("易支付异常回调: %v", verifyInfo)
@@ -237,12 +253,12 @@ func RequestAmount(c *gin.Context) {
return
}
id := c.GetInt("id")
group, err := model.CacheGetUserGroup(id)
group, err := model.GetUserGroup(id, true)
if err != nil {
c.JSON(200, gin.H{"message": "error", "data": "获取用户分组失败"})
return
}
payMoney := getPayMoney(float64(req.Amount), group)
payMoney := getPayMoney(req.Amount, group)
if payMoney <= 0.01 {
c.JSON(200, gin.H{"message": "error", "data": "充值金额过低"})
return
+154
View File
@@ -0,0 +1,154 @@
package controller
import (
"context"
"encoding/json"
"errors"
"net/http"
"one-api/setting/console_setting"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/sync/errgroup"
)
const (
requestTimeout = 30 * time.Second
httpTimeout = 10 * time.Second
uptimeKeySuffix = "_24"
apiStatusPath = "/api/status-page/"
apiHeartbeatPath = "/api/status-page/heartbeat/"
)
type Monitor struct {
Name string `json:"name"`
Uptime float64 `json:"uptime"`
Status int `json:"status"`
Group string `json:"group,omitempty"`
}
type UptimeGroupResult struct {
CategoryName string `json:"categoryName"`
Monitors []Monitor `json:"monitors"`
}
func getAndDecode(ctx context.Context, client *http.Client, url string, dest interface{}) error {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return errors.New("non-200 status")
}
return json.NewDecoder(resp.Body).Decode(dest)
}
func fetchGroupData(ctx context.Context, client *http.Client, groupConfig map[string]interface{}) UptimeGroupResult {
url, _ := groupConfig["url"].(string)
slug, _ := groupConfig["slug"].(string)
categoryName, _ := groupConfig["categoryName"].(string)
result := UptimeGroupResult{
CategoryName: categoryName,
Monitors: []Monitor{},
}
if url == "" || slug == "" {
return result
}
baseURL := strings.TrimSuffix(url, "/")
var statusData struct {
PublicGroupList []struct {
ID int `json:"id"`
Name string `json:"name"`
MonitorList []struct {
ID int `json:"id"`
Name string `json:"name"`
} `json:"monitorList"`
} `json:"publicGroupList"`
}
var heartbeatData struct {
HeartbeatList map[string][]struct {
Status int `json:"status"`
} `json:"heartbeatList"`
UptimeList map[string]float64 `json:"uptimeList"`
}
g, gCtx := errgroup.WithContext(ctx)
g.Go(func() error {
return getAndDecode(gCtx, client, baseURL+apiStatusPath+slug, &statusData)
})
g.Go(func() error {
return getAndDecode(gCtx, client, baseURL+apiHeartbeatPath+slug, &heartbeatData)
})
if g.Wait() != nil {
return result
}
for _, pg := range statusData.PublicGroupList {
if len(pg.MonitorList) == 0 {
continue
}
for _, m := range pg.MonitorList {
monitor := Monitor{
Name: m.Name,
Group: pg.Name,
}
monitorID := strconv.Itoa(m.ID)
if uptime, exists := heartbeatData.UptimeList[monitorID+uptimeKeySuffix]; exists {
monitor.Uptime = uptime
}
if heartbeats, exists := heartbeatData.HeartbeatList[monitorID]; exists && len(heartbeats) > 0 {
monitor.Status = heartbeats[0].Status
}
result.Monitors = append(result.Monitors, monitor)
}
}
return result
}
func GetUptimeKumaStatus(c *gin.Context) {
groups := console_setting.GetUptimeKumaGroups()
if len(groups) == 0 {
c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": []UptimeGroupResult{}})
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), requestTimeout)
defer cancel()
client := &http.Client{Timeout: httpTimeout}
results := make([]UptimeGroupResult, len(groups))
g, gCtx := errgroup.WithContext(ctx)
for i, group := range groups {
i, group := i, group
g.Go(func() error {
results[i] = fetchGroupData(gCtx, client, group)
return nil
})
}
g.Wait()
c.JSON(http.StatusOK, gin.H{"success": true, "message": "", "data": results})
}
+193 -15
View File
@@ -4,15 +4,18 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"one-api/common"
"one-api/model"
"one-api/setting"
"strconv"
"strings"
"sync"
"one-api/constant"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"one-api/constant"
)
type LoginRequest struct {
@@ -223,6 +226,9 @@ func Register(c *gin.Context) {
UnlimitedQuota: true,
ModelLimitsEnabled: false,
}
if setting.DefaultUseAutoGroup {
token.Group = "auto"
}
if err := token.Insert(); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -241,10 +247,14 @@ func Register(c *gin.Context) {
func GetAllUsers(c *gin.Context) {
p, _ := strconv.Atoi(c.Query("p"))
if p < 0 {
p = 0
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 1 {
p = 1
}
users, err := model.GetAllUsers(p*common.ItemsPerPage, common.ItemsPerPage)
if pageSize < 0 {
pageSize = common.ItemsPerPage
}
users, total, err := model.GetAllUsers((p-1)*pageSize, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -255,7 +265,12 @@ func GetAllUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": users,
"data": gin.H{
"items": users,
"total": total,
"page": p,
"page_size": pageSize,
},
})
return
}
@@ -263,7 +278,16 @@ func GetAllUsers(c *gin.Context) {
func SearchUsers(c *gin.Context) {
keyword := c.Query("keyword")
group := c.Query("group")
users, err := model.SearchUsers(keyword, group)
p, _ := strconv.Atoi(c.Query("p"))
pageSize, _ := strconv.Atoi(c.Query("page_size"))
if p < 1 {
p = 1
}
if pageSize < 0 {
pageSize = common.ItemsPerPage
}
startIdx := (p - 1) * pageSize
users, total, err := model.SearchUsers(keyword, group, startIdx, pageSize)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -274,7 +298,12 @@ func SearchUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": users,
"data": gin.H{
"items": users,
"total": total,
"page": p,
"page_size": pageSize,
},
})
return
}
@@ -433,6 +462,9 @@ func GetSelf(c *gin.Context) {
})
return
}
// Hide admin remarks: set to empty to trigger omitempty tag, ensuring the remark field is not included in JSON returned to regular users
user.Remark = ""
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
@@ -446,7 +478,7 @@ func GetUserModels(c *gin.Context) {
if err != nil {
id = c.GetInt("id")
}
user, err := model.GetUserById(id, true)
user, err := model.GetUserCache(id)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -454,7 +486,15 @@ func GetUserModels(c *gin.Context) {
})
return
}
models := model.GetGroupModels(user.Group)
groups := setting.GetUserUsableGroups(user.Group)
var models []string
for group := range groups {
for _, g := range model.GetGroupModels(group) {
if !common.StringsContains(models, g) {
models = append(models, g)
}
}
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
@@ -558,7 +598,14 @@ func UpdateSelf(c *gin.Context) {
user.Password = "" // rollback to what it should be
cleanUser.Password = ""
}
updatePassword := user.Password != ""
updatePassword, err := checkUpdatePassword(user.OriginalPassword, user.Password, cleanUser.Id)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
if err := cleanUser.Update(updatePassword); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
@@ -574,6 +621,23 @@ func UpdateSelf(c *gin.Context) {
return
}
func checkUpdatePassword(originalPassword string, newPassword string, userId int) (updatePassword bool, err error) {
var currentUser *model.User
currentUser, err = model.GetUserById(userId, true)
if err != nil {
return
}
if !common.ValidatePasswordAndHash(originalPassword, currentUser.Password) {
err = fmt.Errorf("原密码错误")
return
}
if newPassword == "" {
return
}
updatePassword = true
return
}
func DeleteUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
@@ -813,9 +877,10 @@ func EmailBind(c *gin.Context) {
})
return
}
id := c.GetInt("id")
session := sessions.Default(c)
id := session.Get("id")
user := model.User{
Id: id,
Id: id.(int),
}
err := user.FillUserById()
if err != nil {
@@ -835,9 +900,6 @@ func EmailBind(c *gin.Context) {
})
return
}
if user.Role == common.RoleRootUser {
common.RootUserEmail = email
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
@@ -879,3 +941,119 @@ func TopUp(c *gin.Context) {
})
return
}
type UpdateUserSettingRequest struct {
QuotaWarningType string `json:"notify_type"`
QuotaWarningThreshold float64 `json:"quota_warning_threshold"`
WebhookUrl string `json:"webhook_url,omitempty"`
WebhookSecret string `json:"webhook_secret,omitempty"`
NotificationEmail string `json:"notification_email,omitempty"`
AcceptUnsetModelRatioModel bool `json:"accept_unset_model_ratio_model"`
RecordIpLog bool `json:"record_ip_log"`
}
func UpdateUserSetting(c *gin.Context) {
var req UpdateUserSettingRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无效的参数",
})
return
}
// 验证预警类型
if req.QuotaWarningType != constant.NotifyTypeEmail && req.QuotaWarningType != constant.NotifyTypeWebhook {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无效的预警类型",
})
return
}
// 验证预警阈值
if req.QuotaWarningThreshold <= 0 {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "预警阈值必须大于0",
})
return
}
// 如果是webhook类型,验证webhook地址
if req.QuotaWarningType == constant.NotifyTypeWebhook {
if req.WebhookUrl == "" {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "Webhook地址不能为空",
})
return
}
// 验证URL格式
if _, err := url.ParseRequestURI(req.WebhookUrl); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无效的Webhook地址",
})
return
}
}
// 如果是邮件类型,验证邮箱地址
if req.QuotaWarningType == constant.NotifyTypeEmail && req.NotificationEmail != "" {
// 验证邮箱格式
if !strings.Contains(req.NotificationEmail, "@") {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无效的邮箱地址",
})
return
}
}
userId := c.GetInt("id")
user, err := model.GetUserById(userId, true)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}
// 构建设置
settings := map[string]interface{}{
constant.UserSettingNotifyType: req.QuotaWarningType,
constant.UserSettingQuotaWarningThreshold: req.QuotaWarningThreshold,
"accept_unset_model_ratio_model": req.AcceptUnsetModelRatioModel,
constant.UserSettingRecordIpLog: req.RecordIpLog,
}
// 如果是webhook类型,添加webhook相关设置
if req.QuotaWarningType == constant.NotifyTypeWebhook {
settings[constant.UserSettingWebhookUrl] = req.WebhookUrl
if req.WebhookSecret != "" {
settings[constant.UserSettingWebhookSecret] = req.WebhookSecret
}
}
// 如果提供了通知邮箱,添加到设置中
if req.QuotaWarningType == constant.NotifyTypeEmail && req.NotificationEmail != "" {
settings[constant.UserSettingNotificationEmail] = req.NotificationEmail
}
// 更新用户设置
user.SetSetting(settings)
if err := user.Update(false); err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "更新设置失败: " + err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "设置已更新",
})
}
+4 -2
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"net/http"
"one-api/common"
@@ -142,9 +143,10 @@ func WeChatBind(c *gin.Context) {
})
return
}
id := c.GetInt("id")
session := sessions.Default(c)
id := session.Get("id")
user := model.User{
Id: id,
Id: id.(int),
}
err = user.FillUserById()
if err != nil {
+4 -2
View File
@@ -14,8 +14,10 @@ services:
environment:
- SQL_DSN=root:123456@tcp(mysql:3306)/new-api # Point to the mysql service
- REDIS_CONN_STRING=redis://redis
- SESSION_SECRET=random_string # 修改为随机字符串
- TZ=Asia/Shanghai
- ERROR_LOG_ENABLED=true # 是否启用错误日志记录
# - TIKTOKEN_CACHE_DIR=./tiktoken_cache # 如果需要使用tiktoken_cache,请取消注释
# - SESSION_SECRET=random_string # 多机部署时设置,必须修改这个随机字符串!!!!!!!
# - NODE_TYPE=slave # Uncomment for slave node in multi-node deployment
# - SYNC_FREQUENCY=60 # Uncomment if regular database syncing is needed
# - FRONTEND_BASE_URL=https://openai.justsong.cn # Uncomment for multi-node deployment with front-end URL
@@ -24,7 +26,7 @@ services:
- redis
- mysql
healthcheck:
test: [ "CMD-SHELL", "wget -q -O - http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk -F: '{print $2}'" ]
test: ["CMD-SHELL", "wget -q -O - http://localhost:3000/api/status | grep -o '\"success\":\\s*true' | awk -F: '{print $$2}'"]
interval: 30s
timeout: 10s
retries: 3
+53
View File
@@ -0,0 +1,53 @@
# API 鉴权文档
## 认证方式
### Access Token
对于需要鉴权的 API 接口,必须同时提供以下两个请求头来进行 Access Token 认证:
1. **请求头中的 `Authorization` 字段**
将 Access Token 放置于 HTTP 请求头部的 `Authorization` 字段中,格式如下:
```
Authorization: <your_access_token>
```
其中 `<your_access_token>` 需要替换为实际的 Access Token 值。
2. **请求头中的 `New-Api-User` 字段**
将用户 ID 放置于 HTTP 请求头部的 `New-Api-User` 字段中,格式如下:
```
New-Api-User: <your_user_id>
```
其中 `<your_user_id>` 需要替换为实际的用户 ID。
**注意:**
* **必须同时提供 `Authorization` 和 `New-Api-User` 两个请求头才能通过鉴权。**
* 如果只提供其中一个请求头,或者两个请求头都未提供,则会返回 `401 Unauthorized` 错误。
* 如果 `Authorization` 中的 Access Token 无效,则会返回 `401 Unauthorized` 错误,并提示“无权进行此操作,access token 无效”。
* 如果 `New-Api-User` 中的用户 ID 与 Access Token 不匹配,则会返回 `401 Unauthorized` 错误,并提示“无权进行此操作,与登录用户不匹配,请重新登录”。
* 如果没有提供 `New-Api-User` 请求头,则会返回 `401 Unauthorized` 错误,并提示“无权进行此操作,未提供 New-Api-User”。
* 如果 `New-Api-User` 请求头格式错误,则会返回 `401 Unauthorized` 错误,并提示“无权进行此操作,New-Api-User 格式错误”。
* 如果用户已被禁用,则会返回 `403 Forbidden` 错误,并提示“用户已被封禁”。
* 如果用户权限不足,则会返回 `403 Forbidden` 错误,并提示“无权进行此操作,权限不足”。
* 如果用户信息无效,则会返回 `403 Forbidden` 错误,并提示“无权进行此操作,用户信息无效”。
## Curl 示例
假设您的 Access Token 为 `access_token`,用户 ID 为 `123`,要访问的 API 接口为 `/api/user/self`,则可以使用以下 curl 命令:
```bash
curl -X GET \
-H "Authorization: access_token" \
-H "New-Api-User: 123" \
https://your-domain.com/api/user/self
```
请将 `access_token`、`123` 和 `https://your-domain.com` 替换为实际的值。
View File
+33
View File
@@ -0,0 +1,33 @@
# 渠道而外设置说明
该配置用于设置一些额外的渠道参数,可以通过 JSON 对象进行配置。主要包含以下两个设置项:
1. force_format
- 用于标识是否对数据进行强制格式化为 OpenAI 格式
- 类型为布尔值,设置为 true 时启用强制格式化
2. proxy
- 用于配置网络代理
- 类型为字符串,填写代理地址(例如 socks5 协议的代理地址)
3. thinking_to_content
- 用于标识是否将思考内容`reasoning_content`转换为`<think>`标签拼接到内容中返回
- 类型为布尔值,设置为 true 时启用思考内容转换
--------------------------------------------------------------
## JSON 格式示例
以下是一个示例配置,启用强制格式化并设置了代理地址:
```json
{
"force_format": true,
"thinking_to_content": true,
"proxy": "socks5://xxxxxxx"
}
```
--------------------------------------------------------------
通过调整上述 JSON 配置中的值,可以灵活控制渠道的额外行为,比如是否进行格式化以及使用特定的网络代理。
+3 -3
View File
@@ -1,3 +1,3 @@
密钥为环境变量SESSION_SECRET
![8285bba413e770fe9620f1bf9b40d44e](https://github.com/user-attachments/assets/7a6fc03e-c457-45e4-b8f9-184508fc26b0)
密钥为环境变量SESSION_SECRET
![8285bba413e770fe9620f1bf9b40d44e](https://github.com/user-attachments/assets/7a6fc03e-c457-45e4-b8f9-184508fc26b0)
+1 -1
View File
@@ -13,7 +13,7 @@ Request:
```json
{
"model": "rerank-multilingual-v3.0",
"model": "jina-reranker-v2-base-multilingual",
"query": "What is the capital of the United States?",
"top_n": 3,
"documents": [
View File
+257
View File
@@ -0,0 +1,257 @@
package dto
import (
"encoding/json"
"one-api/common"
)
type ClaudeMetadata struct {
UserId string `json:"user_id"`
}
type ClaudeMediaMessage struct {
Type string `json:"type,omitempty"`
Text *string `json:"text,omitempty"`
Model string `json:"model,omitempty"`
Source *ClaudeMessageSource `json:"source,omitempty"`
Usage *ClaudeUsage `json:"usage,omitempty"`
StopReason *string `json:"stop_reason,omitempty"`
PartialJson *string `json:"partial_json,omitempty"`
Role string `json:"role,omitempty"`
Thinking string `json:"thinking,omitempty"`
Signature string `json:"signature,omitempty"`
Delta string `json:"delta,omitempty"`
CacheControl json.RawMessage `json:"cache_control,omitempty"`
// tool_calls
Id string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Input any `json:"input,omitempty"`
Content any `json:"content,omitempty"`
ToolUseId string `json:"tool_use_id,omitempty"`
}
func (c *ClaudeMediaMessage) SetText(s string) {
c.Text = &s
}
func (c *ClaudeMediaMessage) GetText() string {
if c.Text == nil {
return ""
}
return *c.Text
}
func (c *ClaudeMediaMessage) IsStringContent() bool {
if c.Content == nil {
return false
}
_, ok := c.Content.(string)
if ok {
return true
}
return false
}
func (c *ClaudeMediaMessage) GetStringContent() string {
if c.Content == nil {
return ""
}
switch c.Content.(type) {
case string:
return c.Content.(string)
case []any:
var contentStr string
for _, contentItem := range c.Content.([]any) {
contentMap, ok := contentItem.(map[string]any)
if !ok {
continue
}
if contentMap["type"] == ContentTypeText {
if subStr, ok := contentMap["text"].(string); ok {
contentStr += subStr
}
}
}
return contentStr
}
return ""
}
func (c *ClaudeMediaMessage) GetJsonRowString() string {
jsonContent, _ := json.Marshal(c)
return string(jsonContent)
}
func (c *ClaudeMediaMessage) SetContent(content any) {
c.Content = content
}
func (c *ClaudeMediaMessage) ParseMediaContent() []ClaudeMediaMessage {
mediaContent, _ := common.Any2Type[[]ClaudeMediaMessage](c.Content)
return mediaContent
}
type ClaudeMessageSource struct {
Type string `json:"type"`
MediaType string `json:"media_type,omitempty"`
Data any `json:"data,omitempty"`
Url string `json:"url,omitempty"`
}
type ClaudeMessage struct {
Role string `json:"role"`
Content any `json:"content"`
}
func (c *ClaudeMessage) IsStringContent() bool {
if c.Content == nil {
return false
}
_, ok := c.Content.(string)
return ok
}
func (c *ClaudeMessage) GetStringContent() string {
if c.Content == nil {
return ""
}
switch c.Content.(type) {
case string:
return c.Content.(string)
case []any:
var contentStr string
for _, contentItem := range c.Content.([]any) {
contentMap, ok := contentItem.(map[string]any)
if !ok {
continue
}
if contentMap["type"] == ContentTypeText {
if subStr, ok := contentMap["text"].(string); ok {
contentStr += subStr
}
}
}
return contentStr
}
return ""
}
func (c *ClaudeMessage) SetStringContent(content string) {
c.Content = content
}
func (c *ClaudeMessage) ParseContent() ([]ClaudeMediaMessage, error) {
return common.Any2Type[[]ClaudeMediaMessage](c.Content)
}
type Tool struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
InputSchema map[string]interface{} `json:"input_schema"`
}
type InputSchema struct {
Type string `json:"type"`
Properties any `json:"properties,omitempty"`
Required any `json:"required,omitempty"`
}
type ClaudeRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt,omitempty"`
System any `json:"system,omitempty"`
Messages []ClaudeMessage `json:"messages,omitempty"`
MaxTokens uint `json:"max_tokens,omitempty"`
MaxTokensToSample uint `json:"max_tokens_to_sample,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
//ClaudeMetadata `json:"metadata,omitempty"`
Stream bool `json:"stream,omitempty"`
Tools any `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
Thinking *Thinking `json:"thinking,omitempty"`
}
type Thinking struct {
Type string `json:"type"`
BudgetTokens *int `json:"budget_tokens,omitempty"`
}
func (c *Thinking) GetBudgetTokens() int {
if c.BudgetTokens == nil {
return 0
}
return *c.BudgetTokens
}
func (c *ClaudeRequest) IsStringSystem() bool {
_, ok := c.System.(string)
return ok
}
func (c *ClaudeRequest) GetStringSystem() string {
if c.IsStringSystem() {
return c.System.(string)
}
return ""
}
func (c *ClaudeRequest) SetStringSystem(system string) {
c.System = system
}
func (c *ClaudeRequest) ParseSystem() []ClaudeMediaMessage {
mediaContent, _ := common.Any2Type[[]ClaudeMediaMessage](c.System)
return mediaContent
}
type ClaudeError struct {
Type string `json:"type,omitempty"`
Message string `json:"message,omitempty"`
}
type ClaudeErrorWithStatusCode struct {
Error ClaudeError `json:"error"`
StatusCode int `json:"status_code"`
LocalError bool
}
type ClaudeResponse struct {
Id string `json:"id,omitempty"`
Type string `json:"type"`
Role string `json:"role,omitempty"`
Content []ClaudeMediaMessage `json:"content,omitempty"`
Completion string `json:"completion,omitempty"`
StopReason string `json:"stop_reason,omitempty"`
Model string `json:"model,omitempty"`
Error *ClaudeError `json:"error,omitempty"`
Usage *ClaudeUsage `json:"usage,omitempty"`
Index *int `json:"index,omitempty"`
ContentBlock *ClaudeMediaMessage `json:"content_block,omitempty"`
Delta *ClaudeMediaMessage `json:"delta,omitempty"`
Message *ClaudeMediaMessage `json:"message,omitempty"`
}
// set index
func (c *ClaudeResponse) SetIndex(i int) {
c.Index = &i
}
// get index
func (c *ClaudeResponse) GetIndex() int {
if c.Index == nil {
return 0
}
return *c.Index
}
type ClaudeUsage struct {
InputTokens int `json:"input_tokens"`
CacheCreationInputTokens int `json:"cache_creation_input_tokens"`
CacheReadInputTokens int `json:"cache_read_input_tokens"`
OutputTokens int `json:"output_tokens"`
}
+14 -8
View File
@@ -1,14 +1,20 @@
package dto
import "encoding/json"
type ImageRequest struct {
Model string `json:"model"`
Prompt string `json:"prompt" binding:"required"`
N int `json:"n,omitempty"`
Size string `json:"size,omitempty"`
Quality string `json:"quality,omitempty"`
ResponseFormat string `json:"response_format,omitempty"`
Style string `json:"style,omitempty"`
User string `json:"user,omitempty"`
Model string `json:"model"`
Prompt string `json:"prompt" binding:"required"`
N int `json:"n,omitempty"`
Size string `json:"size,omitempty"`
Quality string `json:"quality,omitempty"`
ResponseFormat string `json:"response_format,omitempty"`
Style string `json:"style,omitempty"`
User string `json:"user,omitempty"`
ExtraFields json.RawMessage `json:"extra_fields,omitempty"`
Background string `json:"background,omitempty"`
Moderation string `json:"moderation,omitempty"`
OutputFormat string `json:"output_format,omitempty"`
}
type ImageResponse struct {
+57
View File
@@ -0,0 +1,57 @@
package dto
type EmbeddingOptions struct {
Seed int `json:"seed,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopK int `json:"top_k,omitempty"`
TopP *float64 `json:"top_p,omitempty"`
FrequencyPenalty *float64 `json:"frequency_penalty,omitempty"`
PresencePenalty *float64 `json:"presence_penalty,omitempty"`
NumPredict int `json:"num_predict,omitempty"`
NumCtx int `json:"num_ctx,omitempty"`
}
type EmbeddingRequest struct {
Model string `json:"model"`
Input any `json:"input"`
EncodingFormat string `json:"encoding_format,omitempty"`
Dimensions int `json:"dimensions,omitempty"`
User string `json:"user,omitempty"`
Seed float64 `json:"seed,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
}
func (r EmbeddingRequest) ParseInput() []string {
if r.Input == nil {
return nil
}
var input []string
switch r.Input.(type) {
case string:
input = []string{r.Input.(string)}
case []any:
input = make([]string, 0, len(r.Input.([]any)))
for _, item := range r.Input.([]any) {
if str, ok := item.(string); ok {
input = append(input, str)
}
}
}
return input
}
type EmbeddingResponseItem struct {
Object string `json:"object"`
Index int `json:"index"`
Embedding []float64 `json:"embedding"`
}
type EmbeddingResponse struct {
Object string `json:"object"`
Data []EmbeddingResponseItem `json:"data"`
Model string `json:"model"`
Usage `json:"usage"`
}
+8
View File
@@ -0,0 +1,8 @@
package dto
type LocalFileData struct {
MimeType string
Base64Data string
Url string
Size int64
}
+25
View File
@@ -0,0 +1,25 @@
package dto
type Notify struct {
Type string `json:"type"`
Title string `json:"title"`
Content string `json:"content"`
Values []interface{} `json:"values"`
}
const ContentValueParam = "{{value}}"
const (
NotifyTypeQuotaExceed = "quota_exceed"
NotifyTypeChannelUpdate = "channel_update"
NotifyTypeChannelTest = "channel_test"
)
func NewNotify(t string, title string, content string, values []interface{}) Notify {
return Notify{
Type: t,
Title: title,
Content: content,
Values: values,
}
}
+552 -100
View File
@@ -1,63 +1,95 @@
package dto
import "encoding/json"
import (
"encoding/json"
"one-api/common"
"strings"
)
type ResponseFormat struct {
Type string `json:"type,omitempty"`
Type string `json:"type,omitempty"`
JsonSchema *FormatJsonSchema `json:"json_schema,omitempty"`
}
type FormatJsonSchema struct {
Description string `json:"description,omitempty"`
Name string `json:"name"`
Schema any `json:"schema,omitempty"`
Strict any `json:"strict,omitempty"`
}
type GeneralOpenAIRequest struct {
Model string `json:"model,omitempty"`
Messages []Message `json:"messages,omitempty"`
Prompt any `json:"prompt,omitempty"`
Stream bool `json:"stream,omitempty"`
StreamOptions *StreamOptions `json:"stream_options,omitempty"`
MaxTokens uint `json:"max_tokens,omitempty"`
MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
Stop any `json:"stop,omitempty"`
N int `json:"n,omitempty"`
Input any `json:"input,omitempty"`
Instruction string `json:"instruction,omitempty"`
Size string `json:"size,omitempty"`
Functions any `json:"functions,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
ResponseFormat any `json:"response_format,omitempty"`
EncodingFormat any `json:"encoding_format,omitempty"`
Seed float64 `json:"seed,omitempty"`
Tools []ToolCall `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
User string `json:"user,omitempty"`
LogProbs bool `json:"logprobs,omitempty"`
TopLogProbs int `json:"top_logprobs,omitempty"`
Dimensions int `json:"dimensions,omitempty"`
Modalities any `json:"modalities,omitempty"`
Audio any `json:"audio,omitempty"`
Model string `json:"model,omitempty"`
Messages []Message `json:"messages,omitempty"`
Prompt any `json:"prompt,omitempty"`
Prefix any `json:"prefix,omitempty"`
Suffix any `json:"suffix,omitempty"`
Stream bool `json:"stream,omitempty"`
StreamOptions *StreamOptions `json:"stream_options,omitempty"`
MaxTokens uint `json:"max_tokens,omitempty"`
MaxCompletionTokens uint `json:"max_completion_tokens,omitempty"`
ReasoningEffort string `json:"reasoning_effort,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
Stop any `json:"stop,omitempty"`
N int `json:"n,omitempty"`
Input any `json:"input,omitempty"`
Instruction string `json:"instruction,omitempty"`
Size string `json:"size,omitempty"`
Functions json.RawMessage `json:"functions,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
ResponseFormat *ResponseFormat `json:"response_format,omitempty"`
EncodingFormat json.RawMessage `json:"encoding_format,omitempty"`
Seed float64 `json:"seed,omitempty"`
ParallelTooCalls *bool `json:"parallel_tool_calls,omitempty"`
Tools []ToolCallRequest `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"`
User string `json:"user,omitempty"`
LogProbs bool `json:"logprobs,omitempty"`
TopLogProbs int `json:"top_logprobs,omitempty"`
Dimensions int `json:"dimensions,omitempty"`
Modalities json.RawMessage `json:"modalities,omitempty"`
Audio json.RawMessage `json:"audio,omitempty"`
EnableThinking any `json:"enable_thinking,omitempty"` // ali
ExtraBody json.RawMessage `json:"extra_body,omitempty"`
WebSearchOptions *WebSearchOptions `json:"web_search_options,omitempty"`
// OpenRouter Params
Reasoning json.RawMessage `json:"reasoning,omitempty"`
// Ali Qwen Params
VlHighResolutionImages json.RawMessage `json:"vl_high_resolution_images,omitempty"`
}
type OpenAITools struct {
Type string `json:"type"`
Function OpenAIFunction `json:"function"`
func (r *GeneralOpenAIRequest) ToMap() map[string]any {
result := make(map[string]any)
data, _ := common.EncodeJson(r)
_ = common.DecodeJson(data, &result)
return result
}
type OpenAIFunction struct {
type ToolCallRequest struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Function FunctionRequest `json:"function"`
}
type FunctionRequest struct {
Description string `json:"description,omitempty"`
Name string `json:"name"`
Parameters any `json:"parameters,omitempty"`
Arguments string `json:"arguments,omitempty"`
}
type StreamOptions struct {
IncludeUsage bool `json:"include_usage,omitempty"`
}
func (r GeneralOpenAIRequest) GetMaxTokens() int {
func (r *GeneralOpenAIRequest) GetMaxTokens() int {
return int(r.MaxTokens)
}
func (r GeneralOpenAIRequest) ParseInput() []string {
func (r *GeneralOpenAIRequest) ParseInput() []string {
if r.Input == nil {
return nil
}
@@ -77,23 +109,87 @@ func (r GeneralOpenAIRequest) ParseInput() []string {
}
type Message struct {
Role string `json:"role"`
Content json.RawMessage `json:"content"`
Name *string `json:"name,omitempty"`
ToolCalls any `json:"tool_calls,omitempty"`
ToolCallId string `json:"tool_call_id,omitempty"`
Role string `json:"role"`
Content any `json:"content"`
Name *string `json:"name,omitempty"`
Prefix *bool `json:"prefix,omitempty"`
ReasoningContent string `json:"reasoning_content,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
ToolCalls json.RawMessage `json:"tool_calls,omitempty"`
ToolCallId string `json:"tool_call_id,omitempty"`
parsedContent []MediaContent
//parsedStringContent *string
}
type MediaMessage struct {
type MediaContent struct {
Type string `json:"type"`
Text string `json:"text"`
Text string `json:"text,omitempty"`
ImageUrl any `json:"image_url,omitempty"`
InputAudio any `json:"input_audio,omitempty"`
File any `json:"file,omitempty"`
VideoUrl any `json:"video_url,omitempty"`
// OpenRouter Params
CacheControl json.RawMessage `json:"cache_control,omitempty"`
}
func (m *MediaContent) GetImageMedia() *MessageImageUrl {
if m.ImageUrl != nil {
if _, ok := m.ImageUrl.(*MessageImageUrl); ok {
return m.ImageUrl.(*MessageImageUrl)
}
if itemMap, ok := m.ImageUrl.(map[string]any); ok {
out := &MessageImageUrl{
Url: common.Interface2String(itemMap["url"]),
Detail: common.Interface2String(itemMap["detail"]),
MimeType: common.Interface2String(itemMap["mime_type"]),
}
return out
}
}
return nil
}
func (m *MediaContent) GetInputAudio() *MessageInputAudio {
if m.InputAudio != nil {
if _, ok := m.InputAudio.(*MessageInputAudio); ok {
return m.InputAudio.(*MessageInputAudio)
}
if itemMap, ok := m.InputAudio.(map[string]any); ok {
out := &MessageInputAudio{
Data: common.Interface2String(itemMap["data"]),
Format: common.Interface2String(itemMap["format"]),
}
return out
}
}
return nil
}
func (m *MediaContent) GetFile() *MessageFile {
if m.File != nil {
if _, ok := m.File.(*MessageFile); ok {
return m.File.(*MessageFile)
}
if itemMap, ok := m.File.(map[string]any); ok {
out := &MessageFile{
FileName: common.Interface2String(itemMap["file_name"]),
FileData: common.Interface2String(itemMap["file_data"]),
FileId: common.Interface2String(itemMap["file_id"]),
}
return out
}
}
return nil
}
type MessageImageUrl struct {
Url string `json:"url"`
Detail string `json:"detail"`
Url string `json:"url"`
Detail string `json:"detail"`
MimeType string
}
func (m *MessageImageUrl) IsRemoteImage() bool {
return strings.HasPrefix(m.Url, "http")
}
type MessageInputAudio struct {
@@ -101,95 +197,451 @@ type MessageInputAudio struct {
Format string `json:"format"`
}
type MessageFile struct {
FileName string `json:"filename,omitempty"`
FileData string `json:"file_data,omitempty"`
FileId string `json:"file_id,omitempty"`
}
type MessageVideoUrl struct {
Url string `json:"url"`
}
const (
ContentTypeText = "text"
ContentTypeImageURL = "image_url"
ContentTypeInputAudio = "input_audio"
ContentTypeFile = "file"
ContentTypeVideoUrl = "video_url" // 阿里百炼视频识别
)
func (m Message) StringContent() string {
var stringContent string
if err := json.Unmarshal(m.Content, &stringContent); err == nil {
return stringContent
func (m *Message) GetPrefix() bool {
if m.Prefix == nil {
return false
}
return string(m.Content)
return *m.Prefix
}
func (m *Message) SetPrefix(prefix bool) {
m.Prefix = &prefix
}
func (m *Message) ParseToolCalls() []ToolCallRequest {
if m.ToolCalls == nil {
return nil
}
var toolCalls []ToolCallRequest
if err := json.Unmarshal(m.ToolCalls, &toolCalls); err == nil {
return toolCalls
}
return toolCalls
}
func (m *Message) SetToolCalls(toolCalls any) {
toolCallsJson, _ := json.Marshal(toolCalls)
m.ToolCalls = toolCallsJson
}
func (m *Message) StringContent() string {
switch m.Content.(type) {
case string:
return m.Content.(string)
case []any:
var contentStr string
for _, contentItem := range m.Content.([]any) {
contentMap, ok := contentItem.(map[string]any)
if !ok {
continue
}
if contentMap["type"] == ContentTypeText {
if subStr, ok := contentMap["text"].(string); ok {
contentStr += subStr
}
}
}
return contentStr
}
return ""
}
func (m *Message) SetNullContent() {
m.Content = nil
m.parsedContent = nil
}
func (m *Message) SetStringContent(content string) {
jsonContent, _ := json.Marshal(content)
m.Content = jsonContent
m.Content = content
m.parsedContent = nil
}
func (m Message) IsStringContent() bool {
var stringContent string
if err := json.Unmarshal(m.Content, &stringContent); err == nil {
func (m *Message) SetMediaContent(content []MediaContent) {
m.Content = content
m.parsedContent = content
}
func (m *Message) IsStringContent() bool {
_, ok := m.Content.(string)
if ok {
return true
}
return false
}
func (m Message) ParseContent() []MediaMessage {
var contentList []MediaMessage
var stringContent string
if err := json.Unmarshal(m.Content, &stringContent); err == nil {
contentList = append(contentList, MediaMessage{
func (m *Message) ParseContent() []MediaContent {
if m.Content == nil {
return nil
}
if len(m.parsedContent) > 0 {
return m.parsedContent
}
var contentList []MediaContent
// 先尝试解析为字符串
content, ok := m.Content.(string)
if ok {
contentList = []MediaContent{{
Type: ContentTypeText,
Text: stringContent,
})
Text: content,
}}
m.parsedContent = contentList
return contentList
}
var arrayContent []json.RawMessage
// 尝试解析为数组
//var arrayContent []map[string]interface{}
arrayContent, ok := m.Content.([]any)
if !ok {
return contentList
}
for _, contentItemAny := range arrayContent {
mediaItem, ok := contentItemAny.(MediaContent)
if ok {
contentList = append(contentList, mediaItem)
continue
}
contentItem, ok := contentItemAny.(map[string]any)
if !ok {
continue
}
contentType, ok := contentItem["type"].(string)
if !ok {
continue
}
switch contentType {
case ContentTypeText:
if text, ok := contentItem["text"].(string); ok {
contentList = append(contentList, MediaContent{
Type: ContentTypeText,
Text: text,
})
}
case ContentTypeImageURL:
imageUrl := contentItem["image_url"]
temp := &MessageImageUrl{
Detail: "high",
}
switch v := imageUrl.(type) {
case string:
temp.Url = v
case map[string]interface{}:
url, ok1 := v["url"].(string)
detail, ok2 := v["detail"].(string)
if ok2 {
temp.Detail = detail
}
if ok1 {
temp.Url = url
}
}
contentList = append(contentList, MediaContent{
Type: ContentTypeImageURL,
ImageUrl: temp,
})
case ContentTypeInputAudio:
if audioData, ok := contentItem["input_audio"].(map[string]interface{}); ok {
data, ok1 := audioData["data"].(string)
format, ok2 := audioData["format"].(string)
if ok1 && ok2 {
temp := &MessageInputAudio{
Data: data,
Format: format,
}
contentList = append(contentList, MediaContent{
Type: ContentTypeInputAudio,
InputAudio: temp,
})
}
}
case ContentTypeFile:
if fileData, ok := contentItem["file"].(map[string]interface{}); ok {
fileId, ok3 := fileData["file_id"].(string)
if ok3 {
contentList = append(contentList, MediaContent{
Type: ContentTypeFile,
File: &MessageFile{
FileId: fileId,
},
})
} else {
fileName, ok1 := fileData["filename"].(string)
fileDataStr, ok2 := fileData["file_data"].(string)
if ok1 && ok2 {
contentList = append(contentList, MediaContent{
Type: ContentTypeFile,
File: &MessageFile{
FileName: fileName,
FileData: fileDataStr,
},
})
}
}
}
case ContentTypeVideoUrl:
if videoUrl, ok := contentItem["video_url"].(string); ok {
contentList = append(contentList, MediaContent{
Type: ContentTypeVideoUrl,
VideoUrl: &MessageVideoUrl{
Url: videoUrl,
},
})
}
}
}
if len(contentList) > 0 {
m.parsedContent = contentList
}
return contentList
}
// old code
/*func (m *Message) StringContent() string {
if m.parsedStringContent != nil {
return *m.parsedStringContent
}
var stringContent string
if err := json.Unmarshal(m.Content, &stringContent); err == nil {
m.parsedStringContent = &stringContent
return stringContent
}
contentStr := new(strings.Builder)
arrayContent := m.ParseContent()
for _, content := range arrayContent {
if content.Type == ContentTypeText {
contentStr.WriteString(content.Text)
}
}
stringContent = contentStr.String()
m.parsedStringContent = &stringContent
return stringContent
}
func (m *Message) SetNullContent() {
m.Content = nil
m.parsedStringContent = nil
m.parsedContent = nil
}
func (m *Message) SetStringContent(content string) {
jsonContent, _ := json.Marshal(content)
m.Content = jsonContent
m.parsedStringContent = &content
m.parsedContent = nil
}
func (m *Message) SetMediaContent(content []MediaContent) {
jsonContent, _ := json.Marshal(content)
m.Content = jsonContent
m.parsedContent = nil
m.parsedStringContent = nil
}
func (m *Message) IsStringContent() bool {
if m.parsedStringContent != nil {
return true
}
var stringContent string
if err := json.Unmarshal(m.Content, &stringContent); err == nil {
m.parsedStringContent = &stringContent
return true
}
return false
}
func (m *Message) ParseContent() []MediaContent {
if m.parsedContent != nil {
return m.parsedContent
}
var contentList []MediaContent
// 先尝试解析为字符串
var stringContent string
if err := json.Unmarshal(m.Content, &stringContent); err == nil {
contentList = []MediaContent{{
Type: ContentTypeText,
Text: stringContent,
}}
m.parsedContent = contentList
return contentList
}
// 尝试解析为数组
var arrayContent []map[string]interface{}
if err := json.Unmarshal(m.Content, &arrayContent); err == nil {
for _, contentItem := range arrayContent {
var contentMap map[string]any
if err := json.Unmarshal(contentItem, &contentMap); err != nil {
contentType, ok := contentItem["type"].(string)
if !ok {
continue
}
switch contentMap["type"] {
switch contentType {
case ContentTypeText:
if subStr, ok := contentMap["text"].(string); ok {
contentList = append(contentList, MediaMessage{
if text, ok := contentItem["text"].(string); ok {
contentList = append(contentList, MediaContent{
Type: ContentTypeText,
Text: subStr,
Text: text,
})
}
case ContentTypeImageURL:
if subObj, ok := contentMap["image_url"].(map[string]any); ok {
detail, ok := subObj["detail"]
if ok {
subObj["detail"] = detail.(string)
} else {
subObj["detail"] = "high"
}
contentList = append(contentList, MediaMessage{
Type: ContentTypeImageURL,
ImageUrl: MessageImageUrl{
Url: subObj["url"].(string),
Detail: subObj["detail"].(string),
},
})
} else if url, ok := contentMap["image_url"].(string); ok {
contentList = append(contentList, MediaMessage{
Type: ContentTypeImageURL,
ImageUrl: MessageImageUrl{
Url: url,
Detail: "high",
},
})
imageUrl := contentItem["image_url"]
temp := &MessageImageUrl{
Detail: "high",
}
switch v := imageUrl.(type) {
case string:
temp.Url = v
case map[string]interface{}:
url, ok1 := v["url"].(string)
detail, ok2 := v["detail"].(string)
if ok2 {
temp.Detail = detail
}
if ok1 {
temp.Url = url
}
}
contentList = append(contentList, MediaContent{
Type: ContentTypeImageURL,
ImageUrl: temp,
})
case ContentTypeInputAudio:
if subObj, ok := contentMap["input_audio"].(map[string]any); ok {
contentList = append(contentList, MediaMessage{
Type: ContentTypeInputAudio,
InputAudio: MessageInputAudio{
Data: subObj["data"].(string),
Format: subObj["format"].(string),
if audioData, ok := contentItem["input_audio"].(map[string]interface{}); ok {
data, ok1 := audioData["data"].(string)
format, ok2 := audioData["format"].(string)
if ok1 && ok2 {
temp := &MessageInputAudio{
Data: data,
Format: format,
}
contentList = append(contentList, MediaContent{
Type: ContentTypeInputAudio,
InputAudio: temp,
})
}
}
case ContentTypeFile:
if fileData, ok := contentItem["file"].(map[string]interface{}); ok {
fileId, ok3 := fileData["file_id"].(string)
if ok3 {
contentList = append(contentList, MediaContent{
Type: ContentTypeFile,
File: &MessageFile{
FileId: fileId,
},
})
} else {
fileName, ok1 := fileData["filename"].(string)
fileDataStr, ok2 := fileData["file_data"].(string)
if ok1 && ok2 {
contentList = append(contentList, MediaContent{
Type: ContentTypeFile,
File: &MessageFile{
FileName: fileName,
FileData: fileDataStr,
},
})
}
}
}
case ContentTypeVideoUrl:
if videoUrl, ok := contentItem["video_url"].(string); ok {
contentList = append(contentList, MediaContent{
Type: ContentTypeVideoUrl,
VideoUrl: &MessageVideoUrl{
Url: videoUrl,
},
})
}
}
}
return contentList
}
return nil
if len(contentList) > 0 {
m.parsedContent = contentList
}
return contentList
}*/
type WebSearchOptions struct {
SearchContextSize string `json:"search_context_size,omitempty"`
UserLocation json.RawMessage `json:"user_location,omitempty"`
}
type OpenAIResponsesRequest struct {
Model string `json:"model"`
Input json.RawMessage `json:"input,omitempty"`
Include json.RawMessage `json:"include,omitempty"`
Instructions json.RawMessage `json:"instructions,omitempty"`
MaxOutputTokens uint `json:"max_output_tokens,omitempty"`
Metadata json.RawMessage `json:"metadata,omitempty"`
ParallelToolCalls bool `json:"parallel_tool_calls,omitempty"`
PreviousResponseID string `json:"previous_response_id,omitempty"`
Reasoning *Reasoning `json:"reasoning,omitempty"`
ServiceTier string `json:"service_tier,omitempty"`
Store bool `json:"store,omitempty"`
Stream bool `json:"stream,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
Text json.RawMessage `json:"text,omitempty"`
ToolChoice json.RawMessage `json:"tool_choice,omitempty"`
Tools []ResponsesToolsCall `json:"tools,omitempty"`
TopP float64 `json:"top_p,omitempty"`
Truncation string `json:"truncation,omitempty"`
User string `json:"user,omitempty"`
}
type Reasoning struct {
Effort string `json:"effort,omitempty"`
Summary string `json:"summary,omitempty"`
}
type ResponsesToolsCall struct {
Type string `json:"type"`
// Web Search
UserLocation json.RawMessage `json:"user_location,omitempty"`
SearchContextSize string `json:"search_context_size,omitempty"`
// File Search
VectorStoreIds []string `json:"vector_store_ids,omitempty"`
MaxNumResults uint `json:"max_num_results,omitempty"`
Filters json.RawMessage `json:"filters,omitempty"`
// Computer Use
DisplayWidth uint `json:"display_width,omitempty"`
DisplayHeight uint `json:"display_height,omitempty"`
Environment string `json:"environment,omitempty"`
// Function
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Parameters json.RawMessage `json:"parameters,omitempty"`
}
+151 -27
View File
@@ -1,20 +1,10 @@
package dto
type TextResponseWithError struct {
Id string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Choices []OpenAITextResponseChoice `json:"choices"`
Data []OpenAIEmbeddingResponseItem `json:"data"`
Model string `json:"model"`
Usage `json:"usage"`
Error OpenAIError `json:"error"`
}
import "encoding/json"
type SimpleResponse struct {
Usage `json:"usage"`
Error OpenAIError `json:"error"`
Choices []OpenAITextResponseChoice `json:"choices"`
Usage `json:"usage"`
Error *OpenAIError `json:"error"`
}
type TextResponse struct {
@@ -38,6 +28,7 @@ type OpenAITextResponse struct {
Object string `json:"object"`
Created int64 `json:"created"`
Choices []OpenAITextResponseChoice `json:"choices"`
Error *OpenAIError `json:"error,omitempty"`
Usage `json:"usage"`
}
@@ -62,9 +53,11 @@ type ChatCompletionsStreamResponseChoice struct {
}
type ChatCompletionsStreamResponseChoiceDelta struct {
Content *string `json:"content,omitempty"`
Role string `json:"role,omitempty"`
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
Content *string `json:"content,omitempty"`
ReasoningContent *string `json:"reasoning_content,omitempty"`
Reasoning *string `json:"reasoning,omitempty"`
Role string `json:"role,omitempty"`
ToolCalls []ToolCallResponse `json:"tool_calls,omitempty"`
}
func (c *ChatCompletionsStreamResponseChoiceDelta) SetContentString(s string) {
@@ -78,20 +71,39 @@ func (c *ChatCompletionsStreamResponseChoiceDelta) GetContentString() string {
return *c.Content
}
type ToolCall struct {
// Index is not nil only in chat completion chunk object
Index *int `json:"index,omitempty"`
ID string `json:"id"`
Type any `json:"type"`
Function FunctionCall `json:"function"`
func (c *ChatCompletionsStreamResponseChoiceDelta) GetReasoningContent() string {
if c.ReasoningContent == nil && c.Reasoning == nil {
return ""
}
if c.ReasoningContent != nil {
return *c.ReasoningContent
}
return *c.Reasoning
}
type FunctionCall struct {
func (c *ChatCompletionsStreamResponseChoiceDelta) SetReasoningContent(s string) {
c.ReasoningContent = &s
c.Reasoning = &s
}
type ToolCallResponse struct {
// Index is not nil only in chat completion chunk object
Index *int `json:"index,omitempty"`
ID string `json:"id,omitempty"`
Type any `json:"type"`
Function FunctionResponse `json:"function"`
}
func (c *ToolCallResponse) SetIndex(i int) {
c.Index = &i
}
type FunctionResponse struct {
Description string `json:"description,omitempty"`
Name string `json:"name,omitempty"`
// call function with arguments in JSON format
Parameters any `json:"parameters,omitempty"` // request
Arguments string `json:"arguments,omitempty"`
Arguments string `json:"arguments"` // response
}
type ChatCompletionsStreamResponse struct {
@@ -104,6 +116,34 @@ type ChatCompletionsStreamResponse struct {
Usage *Usage `json:"usage"`
}
func (c *ChatCompletionsStreamResponse) IsToolCall() bool {
if len(c.Choices) == 0 {
return false
}
return len(c.Choices[0].Delta.ToolCalls) > 0
}
func (c *ChatCompletionsStreamResponse) GetFirstToolCall() *ToolCallResponse {
if c.IsToolCall() {
return &c.Choices[0].Delta.ToolCalls[0]
}
return nil
}
func (c *ChatCompletionsStreamResponse) Copy() *ChatCompletionsStreamResponse {
choices := make([]ChatCompletionsStreamResponseChoice, len(c.Choices))
copy(choices, c.Choices)
return &ChatCompletionsStreamResponse{
Id: c.Id,
Object: c.Object,
Created: c.Created,
Model: c.Model,
SystemFingerprint: c.SystemFingerprint,
Choices: choices,
Usage: c.Usage,
}
}
func (c *ChatCompletionsStreamResponse) GetSystemFingerprint() string {
if c.SystemFingerprint == nil {
return ""
@@ -128,9 +168,93 @@ type CompletionsStreamResponse struct {
}
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
PromptCacheHitTokens int `json:"prompt_cache_hit_tokens,omitempty"`
PromptTokensDetails InputTokenDetails `json:"prompt_tokens_details"`
CompletionTokenDetails OutputTokenDetails `json:"completion_tokens_details"`
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
InputTokensDetails *InputTokenDetails `json:"input_tokens_details"`
}
type InputTokenDetails struct {
CachedTokens int `json:"cached_tokens"`
CachedCreationTokens int `json:"-"`
TextTokens int `json:"text_tokens"`
AudioTokens int `json:"audio_tokens"`
ImageTokens int `json:"image_tokens"`
}
type OutputTokenDetails struct {
TextTokens int `json:"text_tokens"`
AudioTokens int `json:"audio_tokens"`
ReasoningTokens int `json:"reasoning_tokens"`
}
type OpenAIResponsesResponse struct {
ID string `json:"id"`
Object string `json:"object"`
CreatedAt int `json:"created_at"`
Status string `json:"status"`
Error *OpenAIError `json:"error,omitempty"`
IncompleteDetails *IncompleteDetails `json:"incomplete_details,omitempty"`
Instructions string `json:"instructions"`
MaxOutputTokens int `json:"max_output_tokens"`
Model string `json:"model"`
Output []ResponsesOutput `json:"output"`
ParallelToolCalls bool `json:"parallel_tool_calls"`
PreviousResponseID string `json:"previous_response_id"`
Reasoning *Reasoning `json:"reasoning"`
Store bool `json:"store"`
Temperature float64 `json:"temperature"`
ToolChoice string `json:"tool_choice"`
Tools []ResponsesToolsCall `json:"tools"`
TopP float64 `json:"top_p"`
Truncation string `json:"truncation"`
Usage *Usage `json:"usage"`
User json.RawMessage `json:"user"`
Metadata json.RawMessage `json:"metadata"`
}
type IncompleteDetails struct {
Reasoning string `json:"reasoning"`
}
type ResponsesOutput struct {
Type string `json:"type"`
ID string `json:"id"`
Status string `json:"status"`
Role string `json:"role"`
Content []ResponsesOutputContent `json:"content"`
}
type ResponsesOutputContent struct {
Type string `json:"type"`
Text string `json:"text"`
Annotations []interface{} `json:"annotations"`
}
const (
BuildInToolWebSearchPreview = "web_search_preview"
BuildInToolFileSearch = "file_search"
)
const (
BuildInCallWebSearchCall = "web_search_call"
)
const (
ResponsesOutputTypeItemAdded = "response.output_item.added"
ResponsesOutputTypeItemDone = "response.output_item.done"
)
// ResponsesStreamResponse 用于处理 /v1/responses 流式响应
type ResponsesStreamResponse struct {
Type string `json:"type"`
Response *OpenAIResponsesResponse `json:"response,omitempty"`
Delta string `json:"delta,omitempty"`
Item *ResponsesOutput `json:"item,omitempty"`
}
-12
View File
@@ -43,18 +43,6 @@ type RealtimeUsage struct {
OutputTokenDetails OutputTokenDetails `json:"output_token_details"`
}
type InputTokenDetails struct {
CachedTokens int `json:"cached_tokens"`
TextTokens int `json:"text_tokens"`
AudioTokens int `json:"audio_tokens"`
ImageTokens int `json:"image_tokens"`
}
type OutputTokenDetails struct {
TextTokens int `json:"text_tokens"`
AudioTokens int `json:"audio_tokens"`
}
type RealtimeSession struct {
Modalities []string `json:"modalities"`
Instructions string `json:"instructions"`
+16 -5
View File
@@ -5,18 +5,29 @@ type RerankRequest struct {
Query string `json:"query"`
Model string `json:"model"`
TopN int `json:"top_n"`
ReturnDocuments bool `json:"return_documents,omitempty"`
ReturnDocuments *bool `json:"return_documents,omitempty"`
MaxChunkPerDoc int `json:"max_chunk_per_doc,omitempty"`
OverLapTokens int `json:"overlap_tokens,omitempty"`
}
type RerankResponseDocument struct {
func (r *RerankRequest) GetReturnDocuments() bool {
if r.ReturnDocuments == nil {
return false
}
return *r.ReturnDocuments
}
type RerankResponseResult struct {
Document any `json:"document,omitempty"`
Index int `json:"index"`
RelevanceScore float64 `json:"relevance_score"`
}
type RerankResponse struct {
Results []RerankResponseDocument `json:"results"`
Usage Usage `json:"usage"`
type RerankDocument struct {
Text any `json:"text"`
}
type RerankResponse struct {
Results []RerankResponseResult `json:"results"`
Usage Usage `json:"usage"`
}
+31 -27
View File
@@ -1,40 +1,39 @@
module one-api
// +heroku goVersion go1.18
go 1.21
toolchain go1.22.4
go 1.23.4
require (
github.com/Calcium-Ion/go-epay v0.0.2
github.com/Calcium-Ion/go-epay v0.0.4
github.com/andybalholm/brotli v1.1.1
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/credentials v1.17.11
github.com/aws/aws-sdk-go-v2/service/bedrockruntime v1.7.4
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b
github.com/bytedance/sonic v1.12.4
github.com/gin-contrib/cors v1.4.0
github.com/gin-contrib/cors v1.7.2
github.com/gin-contrib/gzip v0.0.6
github.com/gin-contrib/sessions v0.0.5
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.19.0
github.com/glebarez/sqlite v1.9.0
github.com/go-playground/validator/v10 v10.20.0
github.com/go-redis/redis/v8 v8.11.5
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.0
github.com/jinzhu/copier v0.4.0
github.com/joho/godotenv v1.5.1
github.com/pkg/errors v0.9.1
github.com/pkoukk/tiktoken-go v0.1.7
github.com/samber/lo v1.39.0
github.com/shirou/gopsutil v3.21.11+incompatible
golang.org/x/crypto v0.26.0
golang.org/x/image v0.15.0
github.com/shopspring/decimal v1.4.0
github.com/tiktoken-go/tokenizer v0.6.2
golang.org/x/crypto v0.35.0
golang.org/x/image v0.23.0
golang.org/x/net v0.35.0
gorm.io/driver/mysql v1.4.3
gorm.io/driver/postgres v1.5.2
gorm.io/driver/sqlite v1.4.3
gorm.io/gorm v1.25.0
gorm.io/gorm v1.25.2
)
require (
@@ -43,50 +42,55 @@ require (
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
github.com/aws/smithy-go v1.20.2 // indirect
github.com/bytedance/sonic/loader v0.2.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.11.0 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-sql-driver/mysql v1.6.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.5.1 // indirect
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/yusufpapurcu/wmi v1.2.3 // indirect
golang.org/x/arch v0.12.0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
+68 -48
View File
@@ -1,5 +1,7 @@
github.com/Calcium-Ion/go-epay v0.0.2 h1:3knFBuaBFpHzsGeGQU/QxUqZSHh5s0+jGo0P62pJzWc=
github.com/Calcium-Ion/go-epay v0.0.2/go.mod h1:cxo/ZOg8ClvE3VAnCmEzbuyAZINSq7kFEN9oHj5WQ2U=
github.com/Calcium-Ion/go-epay v0.0.4 h1:C96M7WfRLadcIVscWzwLiYs8etI1wrDmtFMuK2zP22A=
github.com/Calcium-Ion/go-epay v0.0.4/go.mod h1:cxo/ZOg8ClvE3VAnCmEzbuyAZINSq7kFEN9oHj5WQ2U=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0 h1:onfun1RA+KcxaMk1lfrRnwCd1UUuOjJM/lri5eM1qMs=
github.com/anknown/ahocorasick v0.0.0-20190904063843-d75dbd5169c0/go.mod h1:4yg+jNTYlDEzBjhGS96v+zjyA3lfXlFd5CiTLIkPBLI=
github.com/anknown/darts v0.0.0-20151216065714-83ff685239e6 h1:HblK3eJHq54yET63qPCTJnks3loDse5xRmmqHgHzwoI=
@@ -20,11 +22,10 @@ github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b h1:LTGVFpNmNHhj0vhOlfgWueFJ32eK9blaIlHR2ciXOT0=
github.com/bytedance/gopkg v0.0.0-20220118071334-3db87571198b/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q=
github.com/bytedance/sonic v1.12.4 h1:9Csb3c9ZJhfUWeMtpCDCq6BUoH5ogfDFLUgQ/jG+R0k=
github.com/bytedance/sonic v1.12.4/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E=
github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
@@ -37,14 +38,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
@@ -57,6 +60,10 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -72,12 +79,13 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@@ -89,6 +97,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
@@ -101,14 +111,12 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.1 h1:5I9etrGkLrN+2XPCsi6XLlV5DITbSL/xBZdmAxFcXPI=
github.com/jackc/pgx/v5 v5.5.1/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
@@ -139,9 +147,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -157,15 +162,16 @@ github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg=
github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkoukk/tiktoken-go v0.1.7 h1:qOBHXX4PHtvIvmOtyg1EeKlwFRiMKAcoMp4Q+bLQDmw=
github.com/pkoukk/tiktoken-go v0.1.7/go.mod h1:9NiV+i9mJKGj1rYOT+njbv+ZwA/zJxYdewGl6qVatpg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
@@ -173,9 +179,12 @@ github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -183,9 +192,11 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tiktoken-go/tokenizer v0.6.2 h1:t0GN2DvcUZSFWT/62YOgoqb10y7gSXBGs0A+4VCQK+g=
github.com/tiktoken-go/tokenizer v0.6.2/go.mod h1:6UCYI/DtOallbmL7sSy30p6YQv60qNyU/4aVigPOx6w=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -196,25 +207,28 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg=
golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -225,14 +239,14 @@ golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
@@ -258,10 +272,16 @@ gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0=
gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho=
gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
-529
View File
@@ -1,529 +0,0 @@
{
"%.6f 额度": "%.6f quota",
"%d 点额度": "%d point quota",
"尚未实现": "Not yet implemented",
"余额不足": "Insufficient balance",
"危险操作": "Hazardous operations",
"输入你的账户名": "Enter your account name",
"确认删除": "Confirm Delete",
"确认绑定": "Confirm Binding",
"您正在删除自己的帐户,将清空所有数据且不可恢复": "You are deleting your account, all data will be cleared and unrecoverable.",
"\"通道「%s」(#%d)已被禁用\"": "\"Channel %s (#%d) has been disabled\"",
"通道「%s」(#%d)已被禁用,原因:%s": "Channel %s (#%d) has been disabled, reason: %s",
"测试已在运行中": "Test is already running",
"响应时间 %.2fs 超过阈值 %.2fs": "Response time %.2fs exceeds threshold %.2fs",
"通道测试完成": "Channel test completed",
"通道测试完成,如果没有收到禁用通知,说明所有通道都正常": "Channel test completed, if you have not received the disable notification, it means that all channels are normal",
"无法连接至 GitHub 服务器,请稍后重试!": "Unable to connect to GitHub server, please try again later!",
"返回值非法,用户字段为空,请稍后重试!": "The return value is illegal, the user field is empty, please try again later!",
"管理员未开启通过 GitHub 登录以及注册": "The administrator did not turn on login and registration via GitHub",
"管理员关闭了新用户注册": "The administrator has turned off new user registration",
"用户已被封禁": "User has been banned",
"该 GitHub 账户已被绑定": "The GitHub account has been bound",
"邮箱地址已被占用": "Email address is occupied",
"%s邮箱验证邮件": "%s Email verification email",
"<p>您好,你正在进行%s邮箱验证。</p>": "<p>Hello, you are verifying %s email.</p>",
"<p>您的验证码为: <strong>%s</strong></p>": "<p>Your verification code is: <strong>%s</strong></p>",
"<p>验证码 %d 分钟内有效,如果不是本人操作,请忽略。</p>": "<p>The verification code is valid within %d minutes. If it is not your operation, please ignore it.</p>",
"无效的参数": "Invalid parameter",
"该邮箱地址未注册": "The email address is not registered",
"%s密码重置": "%s Password reset",
"<p>您好,你正在进行%s密码重置。</p>": "<p>Hello, you are resetting %s password.</p>",
"<p>点击<a href='%s'>此处</a>进行密码重置。</p>": "<p>Click <a href='%s'>here</a> to reset your password.</p>",
"<p>重置链接 %d 分钟内有效,如果不是本人操作,请忽略。</p>": "<p>The reset link is valid within %d minutes. If it is not your operation, please ignore it.</p>",
"重置链接非法或已过期": "Reset link is illegal or expired",
"无法启用 GitHub OAuth,请先填入 GitHub Client ID 以及 GitHub Client Secret": "Unable to enable GitHub OAuth, please fill in GitHub Client ID and GitHub Client Secret first!",
"无法启用微信登录,请先填入微信登录相关配置信息!": "Unable to enable WeChat login, please fill in the relevant configuration information for WeChat login first!",
"无法启用 Turnstile 校验,请先填入 Turnstile 校验相关配置信息!": "Unable to enable Turnstile verification, please fill in the relevant configuration information for Turnstile verification first!",
"兑换码名称长度必须在1-20之间": "The length of the redemption code name must be between 1-20",
"兑换码个数必须大于0": "The number of redemption codes must be greater than 0",
"一次兑换码批量生成的个数不能大于 100": "The number of redemption codes generated in a batch cannot be greater than 100",
"通过令牌「%s」使用模型 %s 消耗 %s(模型倍率 %.2f,分组倍率 %.2f": "Using model %s with token %s consumes %s (model rate %.2f, group rate %.2f)",
"当前分组上游负载已饱和,请稍后再试": "The current group load is saturated, please try again later",
"令牌名称过长": "Token name is too long",
"令牌已过期,无法启用,请先修改令牌过期时间,或者设置为永不过期": "The token has expired and cannot be enabled. Please modify the expiration time of the token, or set it to never expire.",
"令牌可用额度已用尽,无法启用,请先修改令牌剩余额度,或者设置为无限额度": "The available quota of the token has been used up and cannot be enabled. Please modify the remaining quota of the token, or set it to unlimited quota",
"管理员关闭了密码登录": "The administrator has turned off password login",
"无法保存会话信息,请重试": "Unable to save session information, please try again",
"管理员关闭了通过密码进行注册,请使用第三方账户验证的形式进行注册": "The administrator has turned off registration via password. Please use the form of third-party account verification to register",
"输入不合法 ": "Input is illegal ",
"管理员开启了邮箱验证,请输入邮箱地址和验证码": "The administrator has turned on email verification, please enter the email address and verification code",
"验证码错误或已过期": "Verification code error or expired",
"无权获取同级或更高等级用户的信息": "No permission to get information of users at the same level or higher",
"请重试,系统生成的 UUID 竟然重复了!": "Please try again, the system-generated UUID is actually duplicated!",
"输入不合法": "Input is illegal",
"无权更新同权限等级或更高权限等级的用户信息": "No permission to update user information with the same permission level or higher permission level",
"管理员将用户额度从 %s修改为 %s": "The administrator changed the user quota from %s to %s",
"无权删除同权限等级或更高权限等级的用户": "No permission to delete users with the same permission level or higher permission level",
"无法创建权限大于等于自己的用户": "Unable to create users with permissions greater than or equal to your own",
"用户不存在": "User does not exist",
"无法禁用超级管理员用户": "Unable to disable super administrator user",
"无法删除超级管理员用户": "Unable to delete super administrator user",
"普通管理员用户无法提升其他用户为管理员": "Ordinary administrator users cannot promote other users to administrators",
"该用户已经是管理员": "The user is already an administrator",
"无法降级超级管理员用户": "Unable to downgrade super administrator user",
"该用户已经是普通用户": "The user is already an ordinary user",
"管理员未开启通过微信登录以及注册": "The administrator has not enabled login and registration via WeChat",
"该微信账号已被绑定": "The WeChat account has been bound",
"无权进行此操作,未登录且未提供 access token": "No permission to perform this operation, not logged in and no access token provided",
"无权进行此操作,access token 无效": "No permission to perform this operation, access token is invalid",
"无权进行此操作,权限不足": "No permission to perform this operation, insufficient permissions",
"普通用户不支持指定渠道": "Ordinary users do not support specifying channels",
"无效的渠道 ID": "Invalid channel ID",
"该渠道已被禁用": "The channel has been disabled",
"无效的请求": "Invalid request",
"无可用渠道": "No available channels",
"Turnstile token 为空": "Turnstile token is empty",
"Turnstile 校验失败,请刷新重试!": "Turnstile verification failed, please refresh and try again!",
"id 为空!": "id is empty!",
"未提供兑换码": "No redemption code provided",
"无效的 user id": "Invalid user id",
"无效的兑换码": "Invalid redemption code",
"该兑换码已被使用": "The redemption code has been used",
"通过兑换码充值 %s": "Recharge %s through redemption code",
"未提供令牌": "No token provided",
"该令牌状态不可用": "The token status is not available",
"该令牌已过期": "The token has expired",
"该令牌额度已用尽": "The token quota has been used up",
"无效的令牌": "Invalid token",
"id 或 userId 为空!": "id or userId is empty!",
"quota 不能为负数!": "quota cannot be negative!",
"令牌额度不足": "Insufficient token quota",
"用户额度不足": "Insufficient user quota",
"您的额度即将用尽": "Your quota is about to run out",
"您的额度已用尽": "Your quota has been used up",
"%s,当前剩余额度为 %d,为了不影响您的使用,请及时充值。<br/>充值链接:<a href='%s'>%s</a>": "%s, the current remaining quota is %d, in order not to affect your use, please recharge in time. <br/> Recharge link: <a href='%s'>%s</a>",
"affCode 为空!": "affCode is empty!",
"新用户注册赠送 %s": "New user registration gives %s",
"使用邀请码赠送 %s": "Use invitation code to give %s",
"邀请用户赠送 %s": "Invite users to give %s",
"用户名或密码为空": "Username or password is empty",
"用户名或密码错误,或用户已被封禁": "Username or password is wrong, or user has been banned",
"email 为空!": "email is empty!",
"GitHub id 为空!": "GitHub id is empty!",
"WeChat id 为空!": "WeChat id is empty!",
"username 为空!": "username is empty!",
"邮箱地址或密码为空!": "Email address or password is empty!",
"OpenAI 接口聚合管理,支持多种渠道包括 Azure,可用于二次分发管理 key,仅单可执行文件,已打包好 Docker 镜像,一键部署,开箱即用": "OpenAI interface aggregation management, supports multiple channels including Azure, can be used for secondary distribution management key, only single executable file, Docker image has been packaged, one-click deployment, out of the box",
"未知类型": "Unknown type",
"不支持": "Not supported",
"操作成功完成!": "Operation completed successfully!",
"已启用": "Enabled",
"已禁用": "Disabled",
"未知状态": "Unknown status",
" 秒": "s",
" 分钟 ": " m ",
" 小时 ": " h ",
" 天 ": " d ",
" 个月 ": " M ",
" 年 ": " y ",
"未测试": "Not tested",
"通道 ${name} 测试成功,耗时 ${time.toFixed(2)} 秒。": "Channel ${name} test succeeded, time consumed ${time.toFixed(2)} s.",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "All enabled channels have been successfully tested, please refresh the page to view the results.",
"通道 ${name} 余额更新成功!": "Channel ${name} balance updated successfully!",
"已更新完毕所有已启用通道余额!": "The balance of all enabled channels has been updated!",
"搜索渠道的 ID,名称和密钥 ...": "Search for channel ID, name and key ...",
"名称": "Name",
"分组": "Group",
"类型": "Type",
"状态": "Status",
"响应时间": "Response time",
"余额": "Balance",
"操作": "Operation",
"未更新": "Not updated",
"测试": "Test",
"更新余额": "Update balance",
"删除": "Delete",
"删除渠道 {channel.name}": "Delete channel {channel.name}",
"禁用": "Disable",
"启用": "Enable",
"编辑": "Edit",
"添加新的渠道": "Add a new channel",
"测试所有已启用通道": "Test all enabled channels",
"更新所有已启用通道余额": "Update the balance of all enabled channels",
"刷新": "Refresh",
"处理中...": "Processing...",
"绑定成功!": "Binding succeeded!",
"登录成功!": "Login succeeded!",
"操作失败,重定向至登录界面中...": "Operation failed, redirecting to the login page...",
"出现错误,第 ${count} 次重试中...": "An error occurred, retrying for the ${count} time...",
"首页": "Home",
"渠道": "Channel",
"令牌": "Token",
"兑换": "Redeem",
"充值": "Recharge",
"用户": "User",
"日志": "Log",
"设置": "Settings",
"关于": "About",
"聊天": "Chat",
"注销成功!": "Logout succeeded!",
"注销": "Logout",
"登录": "Login",
"注册": "Register",
"加载{name}中...": "Loading {name}...",
"未登录或登录已过期,请重新登录!": "Not logged in or login has expired, please log in again!",
"用户登录": "User login",
"\"用户名\"": "\"Username\"",
"\"密码\"": "\"Password\"",
"忘记密码?": "Forget password?",
"点击重置": "Click to reset",
" 没有账户?": "; No account?",
"点击注册": "Click to register",
"微信扫码关注公众号,输入「验证码」获取验证码(三分钟内有效)": "Scan the QR code of WeChat to follow the official account, enter \"verification code\" to get the verification code (valid within three minutes)",
"\"验证码\"": "\"Verification code\"",
"全部用户": "All users",
"当前用户": "Current user",
"'全部'": "'All'",
"'充值'": "'Recharge'",
"'消费'": "'Consumption'",
"'管理'": "'Management'",
"'系统'": "'System'",
" 充值 ": " Recharge ",
" 消费 ": " Consumption ",
" 管理 ": " Management ",
" 系统 ": " System ",
" 未知 ": " Unknown ",
"时间": "Time",
"详情": "Details",
"选择模式": "Select mode",
"选择明细分类": "Select details category",
"模型倍率不是合法的 JSON 字符串": "Model rate is not a valid JSON string",
"分组倍率不是合法的 JSON 字符串": "Group rate is not a valid JSON string",
"通用设置": "General Settings",
"充值链接": "Recharge Link",
"例如发卡网站的购买链接": "For example, the purchase link of the card issuing website",
"聊天页面链接": "Chat Page Link",
"例如 ChatGPT Next Web 的部署地址": "For example, the deployment address of ChatGPT Next Web",
"单位美元额度": "Unit Dollar Quota",
"一单位货币能兑换的额度": "Quota that can be exchanged for one unit of currency",
"启用额度消费日志记录": "Enable quota consumption log recording",
"以货币形式显示额度": "Display quota in the form of currency",
"相关 API 显示令牌额度而非用户额度": "Related API displays token quota instead of user quota",
"保存通用设置": "Save General Settings",
"监控设置": "Monitoring Settings",
"最长响应时间": "Longest Response Time",
"单位秒": "Unit in seconds",
"当运行通道全部测试时": "When all operating channels are tested",
"超过此时间将自动禁用通道": "Channels will be automatically disabled if this time is exceeded",
"额度提醒阈值": "Quota reminder threshold",
"低于此额度时将发送邮件提醒用户": "Email will be sent to remind users when the quota is below this",
"失败时自动禁用通道": "Automatically disable the channel when it fails",
"保存监控设置": "Save Monitoring Settings",
"额度设置": "Quota Settings",
"新用户初始额度": "Initial quota for new users",
"例如": "For example",
"请求预扣费额度": "Request for pre-deducted quota",
"请求结束后多退少补": "Refund more or less after the request ends",
"邀请新用户奖励额度": "Invite new users to reward quota",
"新用户使用邀请码奖励额度": "New user rewards quota using invitation code",
"保存额度设置": "Save Quota Settings",
"倍率设置": "Rate Settings",
"模型倍率": "Model rate",
"为一个 JSON 文本": "Is a JSON text",
"键为模型名称": "Key is model name",
"值为倍率": "Value is the rate",
"分组倍率": "Group rate",
"键为分组名称": "Key is group name",
"保存倍率设置": "Save Rate Settings",
"已是最新版本": "Is the latest version",
"检查更新": "Check for updates",
"公告": "Announcement",
"在此输入新的公告内容,支持 Markdown & HTML 代码": "Enter the new announcement content here, supports Markdown & HTML code",
"保存公告": "Save Announcement",
"个性化设置": "Personalization Settings",
"系统名称": "System Name",
"在此输入系统名称": "Enter the system name here",
"设置系统名称": "Set system name",
"图片地址": "Image URL",
"在此输入 Logo 图片地址": "Enter the Logo image URL here",
"首页内容": "Home Page Content",
"在此输入首页内容,支持 Markdown & HTML 代码,设置后首页的状态信息将不再显示。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为首页": "Enter the homepage content here, supports Markdown & HTML code. Once set, the status information of the homepage will not be displayed. If a link is entered, it will be used as the src attribute of the iframe, allowing you to set any webpage as the homepage.",
"保存首页内容": "Save Home Page Content",
"在此输入新的关于内容,支持 Markdown & HTML 代码。如果输入的是一个链接,则会使用该链接作为 iframe 的 src 属性,这允许你设置任意网页作为关于页面": "Enter new about content here, supports Markdown & HTML code. If a link is entered, it will be used as the src attribute of the iframe, allowing you to set any webpage as the about page.",
"保存关于": "Save About",
"移除 One API 的版权标识必须首先获得授权,项目维护需要花费大量精力,如果本项目对你有意义,请主动支持本项目": "Removal of One API copyright mark must first be authorized. Project maintenance requires a lot of effort. If this project is meaningful to you, please actively support it.",
"页脚": "Footer",
"在此输入新的页脚,留空则使用默认页脚,支持 HTML 代码": "Enter the new footer here, leave blank to use the default footer, supports HTML code.",
"设置页脚": "Set Footer",
"新版本": "New Version",
"关闭": "Close",
"密码已重置并已复制到剪贴板": "Password has been reset and copied to clipboard",
"密码重置确认": "Password Reset Confirmation",
"邮箱地址": "Email Address",
"提交": "Submit",
"请稍后几秒重试": "Please retry in a few seconds",
"正在检查用户环境": "Checking user environment",
"重置邮件发送成功": "Reset mail sent successfully",
"请检查邮箱": "Please check your email",
"密码重置": "Password Reset",
"令牌已重置并已复制到剪贴板": "Token has been reset and copied to clipboard",
"邀请链接已复制到剪切板": "Invitation link has been copied to clipboard",
"微信账户绑定成功": "WeChat account binding succeeded",
"验证码发送成功": "Verification code sent successfully",
"邮箱账户绑定成功": "Email account binding succeeded",
"注意": "Note",
"此处生成的令牌用于系统管理": "The token generated here is used for system management",
"而非用于请求 OpenAI 相关的服务": "Not for requesting OpenAI related services",
"请知悉": "Please be aware",
"更新个人信息": "Update Personal Information",
"生成系统访问令牌": "Generate System Access Token",
"复制邀请链接": "Copy Invitation Link",
"账号绑定": "Account Binding",
"绑定微信账号": "Bind WeChat Account",
"微信扫码关注公众号": "Scan the QR code with WeChat to follow the official account",
"输入": "Enter",
"验证码": "Verification Code",
"获取验证码": "Get Verification Code",
"三分钟内有效": "Valid for three minutes",
"绑定": "Bind",
"绑定 GitHub 账号": "Bind GitHub Account",
"绑定邮箱地址": "Bind Email Address",
"输入邮箱地址": "Enter Email Address",
"未使用": "Unused",
"已使用": "Used",
"操作成功完成": "Operation successfully completed",
"搜索兑换码的 ID 和名称": "Search for ID and name",
"额度": "Quota",
"创建时间": "Creation Time",
"兑换时间": "Redemption Time",
"尚未兑换": "Not yet redeemed",
"已复制到剪贴板": "Copied to clipboard",
"无法复制到剪贴板": "Unable to copy to clipboard",
"请手动复制": "Please copy manually",
"已将兑换码填入搜索框": "The voucher code has been filled into the search box",
"复制": "Copy",
"添加新的兑换码": "Add a new voucher",
"密码长度不得小于 8 位": "Password length must not be less than 8 characters",
"两次输入的密码不一致": "The two passwords entered do not match",
"注册成功": "Registration succeeded",
"请稍后几秒重试,Turnstile 正在检查用户环境": "Please retry in a few seconds, Turnstile is checking user environment",
"验证码发送成功,请检查你的邮箱": "Verification code sent successfully, please check your email",
"新用户注册": "New User Registration",
"输入用户名,最长 12 位": "Enter username, up to 12 characters",
"输入密码,最短 8 位,最长 20 位": "Enter password, at least 8 characters and up to 20 characters",
"输入验证码": "Enter Verification Code",
"已有账户": "Already have an account",
"点击登录": "Click to log in",
"服务器地址": "Server Address",
"更新服务器地址": "Update Server Address",
"配置登录注册": "Configure Login/Registration",
"允许通过密码进行登录": "Allow login via password",
"允许通过密码进行注册": "Allow registration via password",
"通过密码注册时需要进行邮箱验证": "Email verification is required when registering via password",
"允许通过 GitHub 账户登录 & 注册": "Allow login & registration via GitHub account",
"允许通过微信登录 & 注册": "Allow login & registration via WeChat",
"允许新用户注册(此项为否时,新用户将无法以任何方式进行注册": "Allow new user registration (if this option is off, new users will not be able to register in any way",
"启用 Turnstile 用户校验": "Enable Turnstile user verification",
"配置 SMTP": "Configure SMTP",
"用以支持系统的邮件发送": "To support the system email sending",
"SMTP 服务器地址": "SMTP Server Address",
"例如:smtp.qq.com": "For example: smtp.qq.com",
"SMTP 端口": "SMTP Port",
"默认: 587": "Default: 587",
"SMTP 账户": "SMTP Account",
"通常是邮箱地址": "Usually an email address",
"发送者邮箱": "Sender email",
"通常和邮箱地址保持一致": "Usually consistent with the email address",
"SMTP 访问凭证": "SMTP Access Credential",
"敏感信息不会发送到前端显示": "Sensitive information will not be displayed in the frontend",
"保存 SMTP 设置": "Save SMTP Settings",
"配置 GitHub OAuth App": "Configure GitHub OAuth App",
"配置 Linuxdo OAuth App": "Configure Linuxdo OAuth App",
"用以支持通过 GitHub 进行登录注册": "To support login & registration via GitHub",
"点击此处": "Click here",
"管理你的 GitHub OAuth App": "Manage your GitHub OAuth App",
"输入你注册的 GitHub OAuth APP 的 ID": "Enter your registered GitHub OAuth APP ID",
"保存 GitHub OAuth 设置": "Save GitHub OAuth Settings",
"配置 WeChat Server": "Configure WeChat Server",
"用以支持通过微信进行登录注册": "To support login & registration via WeChat",
"了解 WeChat Server": "Learn about WeChat Server",
"WeChat Server 访问凭证": "WeChat Server Access Credential",
"微信公众号二维码图片链接": "WeChat Public Account QR Code Image Link",
"输入一个图片链接": "Enter an image link",
"保存 WeChat Server 设置": "Save WeChat Server Settings",
"配置 Turnstile": "Configure Turnstile",
"用以支持用户校验": "To support user verification",
"管理你的 Turnstile Sites,推荐选择 Invisible Widget Type": "Manage your Turnstile Sites, recommend selecting Invisible Widget Type",
"输入你注册的 Turnstile Site Key": "Enter your registered Turnstile Site Key",
"保存 Turnstile 设置": "Save Turnstile Settings",
"已过期": "Expired",
"已耗尽": "Exhausted",
"搜索令牌的名称 ...": "Search for the name of the token...",
"已用额度": "Quota used",
"剩余额度": "Remaining quota",
"过期时间": "Expiration time",
"无": "None",
"无限制": "Unlimited",
"永不过期": "Never expires",
"无法复制到剪贴板,请手动复制,已将令牌填入搜索框": "Unable to copy to clipboard, please copy manually, the token has been entered into the search box",
"删除令牌": "Delete Token",
"添加新的令牌": "Add New Token",
"普通用户": "Regular User",
"管理员": "Admin",
"超级管理员": "Super Admin",
"未知身份": "Unknown Identity",
"已激活": "Activated",
"已封禁": "Banned",
"搜索用户的 ID,用户名,显示名称,以及邮箱地址 ...": "Search user ID, username, display name, and email address...",
"用户名": "Username",
"统计信息": "Statistics",
"用户角色": "User Role",
"未绑定邮箱地址": "Email not bound",
"请求次数": "Number of Requests",
"提升": "Promote",
"降级": "Demote",
"删除用户": "Delete User",
"添加新的用户": "Add New User",
"自定义": "Custom",
"等价金额": "Equivalent Amount",
"未登录或登录已过期,请重新登录": "Not logged in or login has expired, please log in again",
"请求次数过多,请稍后再试": "Too many requests, please try again later",
"服务器内部错误,请联系管理员": "Server internal error, please contact the administrator",
"本站仅作演示之用,无服务端": "This site is for demonstration purposes only, no server-side",
"超级管理员未设置充值链接!": "Super administrator has not set the recharge link!",
"错误:": "Error: ",
"新版本可用:${data.version},请使用快捷键 Shift + F5 刷新页面": "New version available: ${data.version}, please refresh the page using shortcut Shift + F5",
"无法正常连接至服务器": "Unable to connect to the server normally",
"管理渠道": "Manage Channels",
"系统状况": "System Status",
"系统信息": "System Information",
"系统信息总览": "System Information Overview",
"版本": "Version",
"源码": "Source Code",
"启动时间": "Startup Time",
"系统配置": "System Configuration",
"系统配置总览": "System Configuration Overview",
"邮箱验证": "Email Verification",
"未启用": "Not Enabled",
"GitHub 身份验证": "GitHub Authentication",
"微信身份验证": "WeChat Authentication",
"Turnstile 用户校验": "Turnstile User Verification",
"创建新的渠道": "Create New Channel",
"镜像": "Mirror",
"请输入镜像站地址,格式为:https://domain.com,可不填,不填则使用渠道默认值": "Please enter the mirror site address, the format is: https://domain.com, it can be left blank, if left blank, the default value of the channel will be used",
"模型": "Model",
"请选择该通道所支持的模型": "Please select the model supported by the channel",
"填入基础模型": "Fill in the basic model",
"填入所有模型": "Fill in all models",
"清除所有模型": "Clear all models",
"密钥": "Key",
"请输入密钥": "Please enter the key",
"批量创建": "Batch Create",
"更新渠道信息": "Update Channel Information",
"我的令牌": "My Tokens",
"管理兑换码": "Manage Redeem Codes",
"兑换码": "Redeem Code",
"管理用户": "Manage Users",
"额度明细": "Quota Details",
"个人设置": "Personal Settings",
"运营设置": "Operation Settings",
"系统设置": "System Settings",
"其他设置": "Other Settings",
"项目仓库地址": "Project Repository Address",
"可在设置页面设置关于内容,支持 HTML & Markdown": "You can set the content about in the settings page, support HTML & Markdown",
"由{' '}": "built by{' '}",
"构建,源代码遵循{' '}": ", the source code licensed under{' '}",
"MIT 协议": "MIT License",
"充值额度": "Recharge Quota",
"获取兑换码": "Get Redeem Code",
"一个月后过期": "Expires after one month",
"一天后过期": "Expires after one day",
"一小时后过期": "Expires after one hour",
"一分钟后过期": "Expires after one minute",
"创建新的令牌": "Create New Token",
"注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。": "Note that the quota of the token is only used to limit the maximum quota usage of the token itself, and the actual usage is limited by the remaining quota of the account.",
"设为无限额度": "Set to unlimited quota",
"更新令牌信息": "Update Token Information",
"请输入充值码!": "Please enter the recharge code!",
"请输入名称": "Please enter a name",
"请输入密钥,一行一个": "Please enter the key, one per line",
"请输入额度": "Please enter the quota",
"令牌创建成功": "Token created successfully",
"令牌更新成功": "Token updated successfully",
"充值成功!": "Recharge successful!",
"更新用户信息": "Update User Information",
"请输入新的用户名": "Please enter a new username",
"密码": "Password",
"请输入新的密码": "Please enter a new password",
"显示名称": "Display Name",
"请输入新的显示名称": "Please enter a new display name",
"已绑定的 GitHub 账户": "GitHub Account Bound",
"此项只读,需要用户通过个人设置页面的相关绑定按钮进行绑定,不可直接修改": "This item is read-only. Users need to bind through the relevant binding button on the personal settings page, and cannot be modified directly",
"已绑定的微信账户": "WeChat Account Bound",
"已绑定的邮箱账户": "Email Account Bound",
"用户信息更新成功!": "User information updated successfully!",
"模型倍率 %.2f,分组倍率 %.2f": "model rate %.2f, group rate %.2f",
"使用明细(总消耗额度:{renderQuota(stat.quota)}": "Usage Details (Total Consumption Quota: {renderQuota(stat.quota)})",
"用户名称": "User Name",
"令牌名称": "Token Name",
"留空则查询全部用户": "Leave blank to query all users",
"留空则查询全部令牌": "Leave blank to query all tokens",
"模型名称": "Model Name",
"留空则查询全部模型": "Leave blank to query all models",
"起始时间": "Start Time",
"结束时间": "End Time",
"查询": "Query",
"提示": "Prompt",
"补全": "Completion",
"消耗额度": "Used Quota",
"可选值": "Optional Values",
"渠道不存在:%d": "Channel does not exist: %d",
"数据库一致性已被破坏,请联系管理员": "Database consistency has been broken, please contact the administrator",
"使用近似的方式估算 token 数以减少计算量": "Estimate the number of tokens in an approximate way to reduce computational load",
"请填写ChannelName和ChannelKey": "Please fill in the ChannelName and ChannelKey!",
"请至少选择一个Model": "Please select at least one Model!",
"加载首页内容失败": "Failed to load the homepage content",
"加载关于内容失败": "Failed to load the About content",
"兑换码更新成功!": "Redemption code updated successfully!",
"兑换码创建成功!": "Redemption code created successfully!",
"用户账户创建成功!": "User account created successfully!",
"生成数量": "Generate quantity",
"请输入生成数量": "Please enter the quantity to generate",
"创建新用户账户": "Create new user account",
"渠道更新成功!": "Channel updated successfully!",
"渠道创建成功!": "Channel created successfully!",
"请选择分组": "Please select a group",
"更新兑换码信息": "Update redemption code information",
"创建新的兑换码": "Create a new redemption code",
"请在系统设置页面编辑分组倍率以添加新的分组:": "Please edit the group ratio in the system settings page to add a new group:",
"未找到所请求的页面": "The requested page was not found",
"过期时间格式错误!": "Expiration time format error!",
"请输入过期时间,格式为 yyyy-MM-dd HH:mm:ss-1 表示无限制": "Please enter the expiration time, the format is yyyy-MM-dd HH:mm:ss, -1 means no limit",
"此项可选,为一个 JSON 文本,键为用户请求的模型名称,值为要替换的模型名称,例如:": "This is optional, it's a JSON text, the key is the model name requested by the user, and the value is the model name to be replaced, for example:",
"此项可选,输入镜像站地址,格式为:": "This is optional, enter the mirror site address, the format is:",
"模型映射": "Model mapping",
"请输入默认 API 版本,例如:2023-03-15-preview,该配置可以被实际的请求查询参数所覆盖": "Please enter the default API version, for example: 2023-03-15-preview, this configuration can be overridden by the actual request query parameters",
"默认": "Default",
"图片演示": "Image demo",
"参数替换为你的部署名称(模型名称中的点会被剔除)": "Replace the parameter with your deployment name (dots in the model name will be removed)",
"模型映射必须是合法的 JSON 格式!": "Model mapping must be in valid JSON format!",
"取消无限额度": "Cancel unlimited quota",
"取消": "Cancel",
"请输入新的剩余额度": "Please enter the new remaining quota",
"请输入单个兑换码中包含的额度": "Please enter the quota included in a single redemption code",
"请输入用户名": "Please enter username",
"请输入显示名称": "Please enter display name",
"请输入密码": "Please enter password",
"模型部署名称必须和模型名称保持一致": "The model deployment name must be consistent with the model name",
",因为 One API 会把请求体中的 model": ", because One API will take the model in the request body",
"请输入 AZURE_OPENAI_ENDPOINT": "Please enter AZURE_OPENAI_ENDPOINT",
"请输入自定义渠道的 Base URL": "Please enter the Base URL of the custom channel",
"Homepage URL 填": "Fill in the Homepage URL",
"Authorization callback URL 填": "Fill in the Authorization callback URL",
"请为通道命名": "Please name the channel",
"此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:": "This is optional, used to modify the model name in the request body, it's a JSON string, the key is the model name in the request, and the value is the model name to be replaced, for example:",
"模型重定向": "Model redirection",
"请输入渠道对应的鉴权密钥": "Please enter the authentication key corresponding to the channel",
"注意,": "Note that, ",
",图片演示。": "related image demo.",
"令牌创建成功,请在列表页面点击复制获取令牌!": "Token created successfully, please click copy on the list page to get the token!",
"代理": "Proxy",
"此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com": "This is optional, used to make API calls through the proxy site, please enter the proxy site address, the format is: https://domain.com",
"取消密码登录将导致所有未绑定其他登录方式的用户(包括管理员)无法通过密码登录,确认取消?": "Canceling password login will cause all users (including administrators) who have not bound other login methods to be unable to log in via password, confirm cancel?",
"按照如下格式输入:": "Enter in the following format:",
"模型版本": "Model version",
"请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1": "Please enter the version of the Starfire model, note that it is the version number in the interface address, for example: v2.1",
"点击查看": "click to view",
"请确保已在 Azure 上创建了 gpt-35-turbo 模型,并且 apiVersion 已正确填写!": "Please make sure that the gpt-35-turbo model has been created on Azure, and the apiVersion has been filled in correctly!"
}
-61
View File
@@ -1,61 +0,0 @@
import argparse
import json
import os
def list_file_paths(path):
file_paths = []
for root, dirs, files in os.walk(path):
if "node_modules" in dirs:
dirs.remove("node_modules")
if "build" in dirs:
dirs.remove("build")
if "i18n" in dirs:
dirs.remove("i18n")
for file in files:
file_path = os.path.join(root, file)
if file_path.endswith("png") or file_path.endswith("ico") or file_path.endswith("db") or file_path.endswith("exe"):
continue
file_paths.append(file_path)
for dir in dirs:
dir_path = os.path.join(root, dir)
file_paths += list_file_paths(dir_path)
return file_paths
def replace_keys_in_repository(repo_path, json_file_path):
with open(json_file_path, 'r', encoding="utf-8") as json_file:
key_value_pairs = json.load(json_file)
pairs = []
for key, value in key_value_pairs.items():
pairs.append((key, value))
pairs.sort(key=lambda x: len(x[0]), reverse=True)
files = list_file_paths(repo_path)
print('Total files: {}'.format(len(files)))
for file_path in files:
replace_keys_in_file(file_path, pairs)
def replace_keys_in_file(file_path, pairs):
try:
with open(file_path, 'r', encoding="utf-8") as file:
content = file.read()
for key, value in pairs:
content = content.replace(key, value)
with open(file_path, 'w', encoding="utf-8") as file:
file.write(content)
except UnicodeDecodeError:
print('UnicodeDecodeError: {}'.format(file_path))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Replace keys in repository.')
parser.add_argument('--repository_path', help='Path to repository')
parser.add_argument('--json_file_path', help='Path to JSON file')
args = parser.parse_args()
replace_keys_in_repository(args.repository_path, args.json_file_path)
+40 -12
View File
@@ -12,6 +12,7 @@ import (
"one-api/model"
"one-api/router"
"one-api/service"
"one-api/setting/operation_setting"
"os"
"strconv"
@@ -33,9 +34,11 @@ var indexPage []byte
func main() {
err := godotenv.Load(".env")
if err != nil {
common.SysLog("Can't load .env file")
common.SysLog("Support for .env file is disabled: " + err.Error())
}
common.LoadEnv()
common.SetupLogger()
common.SysLog("New API " + common.Version + " started")
if os.Getenv("GIN_MODE") != "debug" {
@@ -49,6 +52,9 @@ func main() {
if err != nil {
common.FatalLog("failed to initialize database: " + err.Error())
}
model.CheckSetup()
// Initialize SQL Database
err = model.InitLogDB()
if err != nil {
@@ -67,10 +73,15 @@ func main() {
common.FatalLog("failed to initialize Redis: " + err.Error())
}
// Initialize model settings
operation_setting.InitRatioSettings()
// Initialize constants
constant.InitEnv()
// Initialize options
model.InitOptionMap()
service.InitTokenEncoders()
if common.RedisEnabled {
// for compatibility with old versions
common.MemoryCacheEnabled = true
@@ -78,16 +89,28 @@ func main() {
if common.MemoryCacheEnabled {
common.SysLog("memory cache enabled")
common.SysError(fmt.Sprintf("sync frequency: %d seconds", common.SyncFrequency))
model.InitChannelCache()
}
if common.RedisEnabled {
go model.SyncTokenCache(common.SyncFrequency)
}
if common.MemoryCacheEnabled {
go model.SyncOptions(common.SyncFrequency)
// Add panic recovery and retry for InitChannelCache
func() {
defer func() {
if r := recover(); r != nil {
common.SysError(fmt.Sprintf("InitChannelCache panic: %v, retrying once", r))
// Retry once
_, fixErr := model.FixAbility()
if fixErr != nil {
common.SysError(fmt.Sprintf("InitChannelCache failed: %s", fixErr.Error()))
}
}
}()
model.InitChannelCache()
}()
go model.SyncChannelCache(common.SyncFrequency)
}
// 热更新配置
go model.SyncOptions(common.SyncFrequency)
// 数据看板
go model.UpdateQuotaData()
@@ -120,15 +143,13 @@ func main() {
}
if os.Getenv("ENABLE_PPROF") == "true" {
go func() {
gopool.Go(func() {
log.Println(http.ListenAndServe("0.0.0.0:8005", nil))
}()
})
go common.Monitor()
common.SysLog("pprof enabled")
}
service.InitTokenEncoders()
// Initialize HTTP server
server := gin.New()
server.Use(gin.CustomRecovery(func(c *gin.Context, err any) {
@@ -146,6 +167,13 @@ func main() {
middleware.SetUpLogger(server)
// Initialize session store
store := cookie.NewStore([]byte(common.SessionSecret))
store.Options(sessions.Options{
Path: "/",
MaxAge: 2592000, // 30 days
HttpOnly: true,
Secure: false,
SameSite: http.SameSiteStrictMode,
})
server.Use(sessions.Sessions("session", store))
router.SetRouter(server, buildFS, indexPage)
+1 -1
View File
@@ -7,7 +7,7 @@ all: build-frontend start-backend
build-frontend:
@echo "Building frontend..."
@cd $(FRONTEND_DIR) && npm install && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) npm run build
@cd $(FRONTEND_DIR) && bun install && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
start-backend:
@echo "Starting backend dev server..."
+54 -31
View File
@@ -1,13 +1,14 @@
package middleware
import (
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/model"
"strconv"
"strings"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
func validUserInfo(username string, role int) bool {
@@ -64,35 +65,33 @@ func authHelper(c *gin.Context, minRole int) {
return
}
}
if !useAccessToken {
// get header New-Api-User
apiUserIdStr := c.Request.Header.Get("New-Api-User")
if apiUserIdStr == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,请刷新页面或清空缓存后重试",
})
c.Abort()
return
}
apiUserId, err := strconv.Atoi(apiUserIdStr)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,登录信息无效,请重新登录",
})
c.Abort()
return
// get header New-Api-User
apiUserIdStr := c.Request.Header.Get("New-Api-User")
if apiUserIdStr == "" {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,未提供 New-Api-User",
})
c.Abort()
return
}
apiUserId, err := strconv.Atoi(apiUserIdStr)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,New-Api-User 格式错误",
})
c.Abort()
return
}
if id != apiUserId {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,与登录用户不匹配,请重新登录",
})
c.Abort()
return
}
}
if id != apiUserId {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "无权进行此操作,New-Api-User 与登录用户不匹配",
})
c.Abort()
return
}
if status.(int) == common.UserStatusDisabled {
c.JSON(http.StatusOK, gin.H{
@@ -176,6 +175,26 @@ func TokenAuth() func(c *gin.Context) {
}
c.Request.Header.Set("Authorization", "Bearer "+key)
}
// 检查path包含/v1/messages
if strings.Contains(c.Request.URL.Path, "/v1/messages") {
// 从x-api-key中获取key
key := c.Request.Header.Get("x-api-key")
if key != "" {
c.Request.Header.Set("Authorization", "Bearer "+key)
}
}
// gemini api 从query中获取key
if strings.HasPrefix(c.Request.URL.Path, "/v1beta/models/") {
skKey := c.Query("key")
if skKey != "" {
c.Request.Header.Set("Authorization", "Bearer "+skKey)
}
// 从x-goog-api-key header中获取key
xGoogKey := c.Request.Header.Get("x-goog-api-key")
if xGoogKey != "" {
c.Request.Header.Set("Authorization", "Bearer "+xGoogKey)
}
}
key := c.Request.Header.Get("Authorization")
parts := make([]string, 0)
key = strings.TrimPrefix(key, "Bearer ")
@@ -201,15 +220,19 @@ func TokenAuth() func(c *gin.Context) {
abortWithOpenAiMessage(c, http.StatusUnauthorized, err.Error())
return
}
userEnabled, err := model.CacheIsUserEnabled(token.UserId)
userCache, err := model.GetUserCache(token.UserId)
if err != nil {
abortWithOpenAiMessage(c, http.StatusInternalServerError, err.Error())
return
}
userEnabled := userCache.Status == common.UserStatusEnabled
if !userEnabled {
abortWithOpenAiMessage(c, http.StatusForbidden, "用户已被封禁")
return
}
userCache.WriteContext(c)
c.Set("id", token.UserId)
c.Set("token_id", token.Id)
c.Set("token_key", token.Key)
+63 -13
View File
@@ -10,8 +10,10 @@ import (
"one-api/model"
relayconstant "one-api/relay/constant"
"one-api/service"
"one-api/setting"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
@@ -30,7 +32,6 @@ func Distribute() func(c *gin.Context) {
return
}
}
userId := c.GetInt("id")
var channel *model.Channel
channelId, ok := c.Get("specific_channel_id")
modelRequest, shouldSelectChannel, err := getModelRequest(c)
@@ -38,18 +39,20 @@ func Distribute() func(c *gin.Context) {
abortWithOpenAiMessage(c, http.StatusBadRequest, "Invalid request, "+err.Error())
return
}
userGroup, _ := model.CacheGetUserGroup(userId)
userGroup := c.GetString(constant.ContextKeyUserGroup)
tokenGroup := c.GetString("token_group")
if tokenGroup != "" {
// check common.UserUsableGroups[userGroup]
if _, ok := common.GetUserUsableGroups(userGroup)[tokenGroup]; !ok {
if _, ok := setting.GetUserUsableGroups(userGroup)[tokenGroup]; !ok {
abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("令牌分组 %s 已被禁用", tokenGroup))
return
}
// check group in common.GroupRatio
if _, ok := common.GroupRatio[tokenGroup]; !ok {
abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("分组 %s 已被弃用", tokenGroup))
return
if !setting.ContainsGroupRatio(tokenGroup) {
if tokenGroup != "auto" {
abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("分组 %s 已被弃用", tokenGroup))
return
}
}
userGroup = tokenGroup
}
@@ -94,9 +97,14 @@ func Distribute() func(c *gin.Context) {
}
if shouldSelectChannel {
channel, err = model.CacheGetRandomSatisfiedChannel(userGroup, modelRequest.Model, 0)
var selectGroup string
channel, selectGroup, err = model.CacheGetRandomSatisfiedChannel(c, userGroup, modelRequest.Model, 0)
if err != nil {
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", userGroup, modelRequest.Model)
showGroup := userGroup
if userGroup == "auto" {
showGroup = fmt.Sprintf("auto(%s)", selectGroup)
}
message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", showGroup, modelRequest.Model)
// 如果错误,但是渠道不为空,说明是数据库一致性问题
if channel != nil {
common.SysError(fmt.Sprintf("渠道不存在:%d", channel.Id))
@@ -112,6 +120,7 @@ func Distribute() func(c *gin.Context) {
}
}
}
c.Set(constant.ContextKeyRequestStartTime, time.Now())
SetupContextForSelectedChannel(c, channel, modelRequest.Model)
c.Next()
}
@@ -132,17 +141,14 @@ func getModelRequest(c *gin.Context) (*ModelRequest, bool, error) {
midjourneyRequest := dto.MidjourneyRequest{}
err = common.UnmarshalBodyReusable(c, &midjourneyRequest)
if err != nil {
abortWithMidjourneyMessage(c, http.StatusBadRequest, constant.MjErrorUnknown, "无效的请求, "+err.Error())
return nil, false, err
}
midjourneyModel, mjErr, success := service.GetMjRequestModel(relayMode, &midjourneyRequest)
if mjErr != nil {
abortWithMidjourneyMessage(c, http.StatusBadRequest, mjErr.Code, mjErr.Description)
return nil, false, fmt.Errorf(mjErr.Description)
}
if midjourneyModel == "" {
if !success {
abortWithMidjourneyMessage(c, http.StatusBadRequest, constant.MjErrorUnknown, "无效的请求, 无法解析模型")
return nil, false, fmt.Errorf("无效的请求, 无法解析模型")
} else {
// task fetch, task fetch by condition, notify
@@ -163,11 +169,18 @@ func getModelRequest(c *gin.Context) (*ModelRequest, bool, error) {
}
c.Set("platform", string(constant.TaskPlatformSuno))
c.Set("relay_mode", relayMode)
} else if !strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") {
} else if strings.HasPrefix(c.Request.URL.Path, "/v1beta/models/") {
// Gemini API 路径处理: /v1beta/models/gemini-2.0-flash:generateContent
relayMode := relayconstant.RelayModeGemini
modelName := extractModelNameFromGeminiPath(c.Request.URL.Path)
if modelName != "" {
modelRequest.Model = modelName
}
c.Set("relay_mode", relayMode)
} else if !strings.HasPrefix(c.Request.URL.Path, "/v1/audio/transcriptions") && !strings.HasPrefix(c.Request.URL.Path, "/v1/images/edits") {
err = common.UnmarshalBodyReusable(c, &modelRequest)
}
if err != nil {
abortWithOpenAiMessage(c, http.StatusBadRequest, "无效的请求, "+err.Error())
return nil, false, errors.New("无效的请求, " + err.Error())
}
if strings.HasPrefix(c.Request.URL.Path, "/v1/realtime") {
@@ -186,6 +199,8 @@ func getModelRequest(c *gin.Context) (*ModelRequest, bool, error) {
}
if strings.HasPrefix(c.Request.URL.Path, "/v1/images/generations") {
modelRequest.Model = common.GetStringIfEmpty(modelRequest.Model, "dall-e")
} else if strings.HasPrefix(c.Request.URL.Path, "/v1/images/edits") {
modelRequest.Model = common.GetStringIfEmpty(c.PostForm("model"), "gpt-image-1")
}
if strings.HasPrefix(c.Request.URL.Path, "/v1/audio") {
relayMode := relayconstant.RelayModeAudioSpeech
@@ -213,6 +228,9 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
c.Set("channel_id", channel.Id)
c.Set("channel_name", channel.Name)
c.Set("channel_type", channel.Type)
c.Set("channel_create_time", channel.CreatedTime)
c.Set("channel_setting", channel.GetSetting())
c.Set("param_override", channel.GetParamOverride())
if nil != channel.OpenAIOrganization && "" != *channel.OpenAIOrganization {
c.Set("channel_organization", *channel.OpenAIOrganization)
}
@@ -235,5 +253,37 @@ func SetupContextForSelectedChannel(c *gin.Context, channel *model.Channel, mode
c.Set("plugin", channel.Other)
case common.ChannelCloudflare:
c.Set("api_version", channel.Other)
case common.ChannelTypeMokaAI:
c.Set("api_version", channel.Other)
case common.ChannelTypeCoze:
c.Set("bot_id", channel.Other)
}
}
// extractModelNameFromGeminiPath 从 Gemini API URL 路径中提取模型名
// 输入格式: /v1beta/models/gemini-2.0-flash:generateContent
// 输出: gemini-2.0-flash
func extractModelNameFromGeminiPath(path string) string {
// 查找 "/models/" 的位置
modelsPrefix := "/models/"
modelsIndex := strings.Index(path, modelsPrefix)
if modelsIndex == -1 {
return ""
}
// 从 "/models/" 之后开始提取
startIndex := modelsIndex + len(modelsPrefix)
if startIndex >= len(path) {
return ""
}
// 查找 ":" 的位置,模型名在 ":" 之前
colonIndex := strings.Index(path[startIndex:], ":")
if colonIndex == -1 {
// 如果没有找到 ":",返回从 "/models/" 到路径结尾的部分
return path[startIndex:]
}
// 返回模型名部分
return path[startIndex : startIndex+colonIndex]
}
+38
View File
@@ -0,0 +1,38 @@
package middleware
import (
"compress/gzip"
"github.com/andybalholm/brotli"
"github.com/gin-gonic/gin"
"io"
"net/http"
)
func DecompressRequestMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Body == nil || c.Request.Method == http.MethodGet {
c.Next()
return
}
switch c.GetHeader("Content-Encoding") {
case "gzip":
gzipReader, err := gzip.NewReader(c.Request.Body)
if err != nil {
c.AbortWithStatus(http.StatusBadRequest)
return
}
defer gzipReader.Close()
// Replace the request body with the decompressed data
c.Request.Body = io.NopCloser(gzipReader)
c.Request.Header.Del("Content-Encoding")
case "br":
reader := brotli.NewReader(c.Request.Body)
c.Request.Body = io.NopCloser(reader)
c.Request.Header.Del("Content-Encoding")
}
// Continue processing the request
c.Next()
}
}
+199
View File
@@ -0,0 +1,199 @@
package middleware
import (
"context"
"fmt"
"net/http"
"one-api/common"
"one-api/common/limiter"
"one-api/constant"
"one-api/setting"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/go-redis/redis/v8"
)
const (
ModelRequestRateLimitCountMark = "MRRL"
ModelRequestRateLimitSuccessCountMark = "MRRLS"
)
// 检查Redis中的请求限制
func checkRedisRateLimit(ctx context.Context, rdb *redis.Client, key string, maxCount int, duration int64) (bool, error) {
// 如果maxCount为0,表示不限制
if maxCount == 0 {
return true, nil
}
// 获取当前计数
length, err := rdb.LLen(ctx, key).Result()
if err != nil {
return false, err
}
// 如果未达到限制,允许请求
if length < int64(maxCount) {
return true, nil
}
// 检查时间窗口
oldTimeStr, _ := rdb.LIndex(ctx, key, -1).Result()
oldTime, err := time.Parse(timeFormat, oldTimeStr)
if err != nil {
return false, err
}
nowTimeStr := time.Now().Format(timeFormat)
nowTime, err := time.Parse(timeFormat, nowTimeStr)
if err != nil {
return false, err
}
// 如果在时间窗口内已达到限制,拒绝请求
subTime := nowTime.Sub(oldTime).Seconds()
if int64(subTime) < duration {
rdb.Expire(ctx, key, time.Duration(setting.ModelRequestRateLimitDurationMinutes)*time.Minute)
return false, nil
}
return true, nil
}
// 记录Redis请求
func recordRedisRequest(ctx context.Context, rdb *redis.Client, key string, maxCount int) {
// 如果maxCount为0,不记录请求
if maxCount == 0 {
return
}
now := time.Now().Format(timeFormat)
rdb.LPush(ctx, key, now)
rdb.LTrim(ctx, key, 0, int64(maxCount-1))
rdb.Expire(ctx, key, time.Duration(setting.ModelRequestRateLimitDurationMinutes)*time.Minute)
}
// Redis限流处理器
func redisRateLimitHandler(duration int64, totalMaxCount, successMaxCount int) gin.HandlerFunc {
return func(c *gin.Context) {
userId := strconv.Itoa(c.GetInt("id"))
ctx := context.Background()
rdb := common.RDB
// 1. 检查成功请求数限制
successKey := fmt.Sprintf("rateLimit:%s:%s", ModelRequestRateLimitSuccessCountMark, userId)
allowed, err := checkRedisRateLimit(ctx, rdb, successKey, successMaxCount, duration)
if err != nil {
fmt.Println("检查成功请求数限制失败:", err.Error())
abortWithOpenAiMessage(c, http.StatusInternalServerError, "rate_limit_check_failed")
return
}
if !allowed {
abortWithOpenAiMessage(c, http.StatusTooManyRequests, fmt.Sprintf("您已达到请求数限制:%d分钟内最多请求%d次", setting.ModelRequestRateLimitDurationMinutes, successMaxCount))
return
}
//2.检查总请求数限制并记录总请求(当totalMaxCount为0时会自动跳过,使用令牌桶限流器
if totalMaxCount > 0 {
totalKey := fmt.Sprintf("rateLimit:%s", userId)
// 初始化
tb := limiter.New(ctx, rdb)
allowed, err = tb.Allow(
ctx,
totalKey,
limiter.WithCapacity(int64(totalMaxCount)*duration),
limiter.WithRate(int64(totalMaxCount)),
limiter.WithRequested(duration),
)
if err != nil {
fmt.Println("检查总请求数限制失败:", err.Error())
abortWithOpenAiMessage(c, http.StatusInternalServerError, "rate_limit_check_failed")
return
}
if !allowed {
abortWithOpenAiMessage(c, http.StatusTooManyRequests, fmt.Sprintf("您已达到总请求数限制:%d分钟内最多请求%d次,包括失败次数,请检查您的请求是否正确", setting.ModelRequestRateLimitDurationMinutes, totalMaxCount))
}
}
// 4. 处理请求
c.Next()
// 5. 如果请求成功,记录成功请求
if c.Writer.Status() < 400 {
recordRedisRequest(ctx, rdb, successKey, successMaxCount)
}
}
}
// 内存限流处理器
func memoryRateLimitHandler(duration int64, totalMaxCount, successMaxCount int) gin.HandlerFunc {
inMemoryRateLimiter.Init(time.Duration(setting.ModelRequestRateLimitDurationMinutes) * time.Minute)
return func(c *gin.Context) {
userId := strconv.Itoa(c.GetInt("id"))
totalKey := ModelRequestRateLimitCountMark + userId
successKey := ModelRequestRateLimitSuccessCountMark + userId
// 1. 检查总请求数限制(当totalMaxCount为0时跳过)
if totalMaxCount > 0 && !inMemoryRateLimiter.Request(totalKey, totalMaxCount, duration) {
c.Status(http.StatusTooManyRequests)
c.Abort()
return
}
// 2. 检查成功请求数限制
// 使用一个临时key来检查限制,这样可以避免实际记录
checkKey := successKey + "_check"
if !inMemoryRateLimiter.Request(checkKey, successMaxCount, duration) {
c.Status(http.StatusTooManyRequests)
c.Abort()
return
}
// 3. 处理请求
c.Next()
// 4. 如果请求成功,记录到实际的成功请求计数中
if c.Writer.Status() < 400 {
inMemoryRateLimiter.Request(successKey, successMaxCount, duration)
}
}
}
// ModelRequestRateLimit 模型请求限流中间件
func ModelRequestRateLimit() func(c *gin.Context) {
return func(c *gin.Context) {
// 在每个请求时检查是否启用限流
if !setting.ModelRequestRateLimitEnabled {
c.Next()
return
}
// 计算限流参数
duration := int64(setting.ModelRequestRateLimitDurationMinutes * 60)
totalMaxCount := setting.ModelRequestRateLimitCount
successMaxCount := setting.ModelRequestRateLimitSuccessCount
// 获取分组
group := c.GetString("token_group")
if group == "" {
group = c.GetString(constant.ContextKeyUserGroup)
}
//获取分组的限流配置
groupTotalCount, groupSuccessCount, found := setting.GetGroupRateLimit(group)
if found {
totalMaxCount = groupTotalCount
successMaxCount = groupSuccessCount
}
// 根据存储类型选择并执行限流处理器
if common.RedisEnabled {
redisRateLimitHandler(duration, totalMaxCount, successMaxCount)(c)
} else {
memoryRateLimitHandler(duration, totalMaxCount, successMaxCount)(c)
}
}
}
+41
View File
@@ -0,0 +1,41 @@
package middleware
import (
"sync/atomic"
"github.com/gin-gonic/gin"
)
// HTTPStats 存储HTTP统计信息
type HTTPStats struct {
activeConnections int64
}
var globalStats = &HTTPStats{}
// StatsMiddleware 统计中间件
func StatsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 增加活跃连接数
atomic.AddInt64(&globalStats.activeConnections, 1)
// 确保在请求结束时减少连接数
defer func() {
atomic.AddInt64(&globalStats.activeConnections, -1)
}()
c.Next()
}
}
// StatsInfo 统计信息结构
type StatsInfo struct {
ActiveConnections int64 `json:"active_connections"`
}
// GetStats 获取统计信息
func GetStats() StatsInfo {
return StatsInfo{
ActiveConnections: atomic.LoadInt64(&globalStats.activeConnections),
}
}
+114 -43
View File
@@ -3,15 +3,17 @@ package model
import (
"errors"
"fmt"
"github.com/samber/lo"
"gorm.io/gorm"
"one-api/common"
"strings"
"github.com/samber/lo"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Ability struct {
Group string `json:"group" gorm:"type:varchar(64);primaryKey;autoIncrement:false"`
Model string `json:"model" gorm:"type:varchar(64);primaryKey;autoIncrement:false"`
Model string `json:"model" gorm:"type:varchar(255);primaryKey;autoIncrement:false"`
ChannelId int `json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"`
Enabled bool `json:"enabled"`
Priority *int64 `json:"priority" gorm:"bigint;default:0;index"`
@@ -22,11 +24,7 @@ type Ability struct {
func GetGroupModels(group string) []string {
var models []string
// Find distinct models
groupCol := "`group`"
if common.UsingPostgreSQL {
groupCol = `"group"`
}
DB.Table("abilities").Where(groupCol+" = ? and enabled = ?", group, true).Distinct("model").Pluck("model", &models)
DB.Table("abilities").Where(commonGroupCol+" = ? and enabled = ?", group, true).Distinct("model").Pluck("model", &models)
return models
}
@@ -44,17 +42,11 @@ func GetAllEnableAbilities() []Ability {
}
func getPriority(group string, model string, retry int) (int, error) {
groupCol := "`group`"
trueVal := "1"
if common.UsingPostgreSQL {
groupCol = `"group"`
trueVal = "true"
}
var priorities []int
err := DB.Model(&Ability{}).
Select("DISTINCT(priority)").
Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model).
Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, commonTrueVal).
Order("priority DESC"). // 按优先级降序排序
Pluck("priority", &priorities).Error // Pluck用于将查询的结果直接扫描到一个切片中
@@ -80,20 +72,14 @@ func getPriority(group string, model string, retry int) (int, error) {
}
func getChannelQuery(group string, model string, retry int) *gorm.DB {
groupCol := "`group`"
trueVal := "1"
if common.UsingPostgreSQL {
groupCol = `"group"`
trueVal = "true"
}
maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(groupCol+" = ? and model = ? and enabled = "+trueVal, group, model)
channelQuery := DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = (?)", group, model, maxPrioritySubQuery)
maxPrioritySubQuery := DB.Model(&Ability{}).Select("MAX(priority)").Where(commonGroupCol+" = ? and model = ? and enabled = ?", group, model, commonTrueVal)
channelQuery := DB.Where(commonGroupCol+" = ? and model = ? and enabled = ? and priority = (?)", group, model, commonTrueVal, maxPrioritySubQuery)
if retry != 0 {
priority, err := getPriority(group, model, retry)
if err != nil {
common.SysError(fmt.Sprintf("Get priority failed: %s", err.Error()))
} else {
channelQuery = DB.Where(groupCol+" = ? and model = ? and enabled = "+trueVal+" and priority = ?", group, model, priority)
channelQuery = DB.Where(commonGroupCol+" = ? and model = ? and enabled = ? and priority = ?", group, model, commonTrueVal, priority)
}
}
@@ -140,9 +126,15 @@ func GetRandomSatisfiedChannel(group string, model string, retry int) (*Channel,
func (channel *Channel) AddAbilities() error {
models_ := strings.Split(channel.Models, ",")
groups_ := strings.Split(channel.Group, ",")
abilitySet := make(map[string]struct{})
abilities := make([]Ability, 0, len(models_))
for _, model := range models_ {
for _, group := range groups_ {
key := group + "|" + model
if _, exists := abilitySet[key]; exists {
continue
}
abilitySet[key] = struct{}{}
ability := Ability{
Group: group,
Model: model,
@@ -159,7 +151,7 @@ func (channel *Channel) AddAbilities() error {
return nil
}
for _, chunk := range lo.Chunk(abilities, 50) {
err := DB.Create(&chunk).Error
err := DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&chunk).Error
if err != nil {
return err
}
@@ -173,18 +165,73 @@ func (channel *Channel) DeleteAbilities() error {
// UpdateAbilities updates abilities of this channel.
// Make sure the channel is completed before calling this function.
func (channel *Channel) UpdateAbilities() error {
// A quick and dirty way to update abilities
func (channel *Channel) UpdateAbilities(tx *gorm.DB) error {
isNewTx := false
// 如果没有传入事务,创建新的事务
if tx == nil {
tx = DB.Begin()
if tx.Error != nil {
return tx.Error
}
isNewTx = true
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
}
// First delete all abilities of this channel
err := channel.DeleteAbilities()
err := tx.Where("channel_id = ?", channel.Id).Delete(&Ability{}).Error
if err != nil {
if isNewTx {
tx.Rollback()
}
return err
}
// Then add new abilities
err = channel.AddAbilities()
if err != nil {
return err
models_ := strings.Split(channel.Models, ",")
groups_ := strings.Split(channel.Group, ",")
abilitySet := make(map[string]struct{})
abilities := make([]Ability, 0, len(models_))
for _, model := range models_ {
for _, group := range groups_ {
key := group + "|" + model
if _, exists := abilitySet[key]; exists {
continue
}
abilitySet[key] = struct{}{}
ability := Ability{
Group: group,
Model: model,
ChannelId: channel.Id,
Enabled: channel.Status == common.ChannelStatusEnabled,
Priority: channel.Priority,
Weight: uint(channel.GetWeight()),
Tag: channel.Tag,
}
abilities = append(abilities, ability)
}
}
if len(abilities) > 0 {
for _, chunk := range lo.Chunk(abilities, 50) {
err = tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&chunk).Error
if err != nil {
if isNewTx {
tx.Rollback()
}
return err
}
}
}
// 如果是新创建的事务,需要提交
if isNewTx {
return tx.Commit().Error
}
return nil
}
@@ -219,12 +266,28 @@ func FixAbility() (int, error) {
common.SysError(fmt.Sprintf("Get channel ids from channel table failed: %s", err.Error()))
return 0, err
}
// Delete abilities of channels that are not in channel table
err = DB.Where("channel_id NOT IN (?)", channelIds).Delete(&Ability{}).Error
if err != nil {
common.SysError(fmt.Sprintf("Delete abilities of channels that are not in channel table failed: %s", err.Error()))
return 0, err
// Delete abilities of channels that are not in channel table - in batches to avoid too many placeholders
if len(channelIds) > 0 {
// Process deletion in chunks to avoid "too many placeholders" error
for _, chunk := range lo.Chunk(channelIds, 100) {
err = DB.Where("channel_id NOT IN (?)", chunk).Delete(&Ability{}).Error
if err != nil {
common.SysError(fmt.Sprintf("Delete abilities of channels (batch) that are not in channel table failed: %s", err.Error()))
return 0, err
}
}
} else {
// If no channels exist, delete all abilities
err = DB.Delete(&Ability{}).Error
if err != nil {
common.SysError(fmt.Sprintf("Delete all abilities failed: %s", err.Error()))
return 0, err
}
common.SysLog("Delete all abilities successfully")
return 0, nil
}
common.SysLog(fmt.Sprintf("Delete abilities of channels that are not in channel table successfully, ids: %v", channelIds))
count += len(channelIds)
@@ -233,20 +296,28 @@ func FixAbility() (int, error) {
err = DB.Table("abilities").Distinct("channel_id").Pluck("channel_id", &abilityChannelIds).Error
if err != nil {
common.SysError(fmt.Sprintf("Get channel ids from abilities table failed: %s", err.Error()))
return 0, err
return count, err
}
var channels []Channel
var channels []Channel
if len(abilityChannelIds) == 0 {
err = DB.Find(&channels).Error
} else {
err = DB.Where("id NOT IN (?)", abilityChannelIds).Find(&channels).Error
}
if err != nil {
return 0, err
// Process query in chunks to avoid "too many placeholders" error
err = nil
for _, chunk := range lo.Chunk(abilityChannelIds, 100) {
var channelsChunk []Channel
err = DB.Where("id NOT IN (?)", chunk).Find(&channelsChunk).Error
if err != nil {
common.SysError(fmt.Sprintf("Find channels not in abilities table failed: %s", err.Error()))
return count, err
}
channels = append(channels, channelsChunk...)
}
}
for _, channel := range channels {
err := channel.UpdateAbilities()
err := channel.UpdateAbilities(nil)
if err != nil {
common.SysError(fmt.Sprintf("Update abilities of channel %d failed: %s", channel.Id, err.Error()))
} else {
+57 -196
View File
@@ -1,215 +1,27 @@
package model
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"one-api/common"
"one-api/setting"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
)
var (
TokenCacheSeconds = common.SyncFrequency
UserId2GroupCacheSeconds = common.SyncFrequency
UserId2QuotaCacheSeconds = common.SyncFrequency
UserId2StatusCacheSeconds = common.SyncFrequency
)
// 仅用于定时同步缓存
var token2UserId = make(map[string]int)
var token2UserIdLock sync.RWMutex
func cacheSetToken(token *Token) error {
jsonBytes, err := json.Marshal(token)
if err != nil {
return err
}
err = common.RedisSet(fmt.Sprintf("token:%s", token.Key), string(jsonBytes), time.Duration(TokenCacheSeconds)*time.Second)
if err != nil {
common.SysError(fmt.Sprintf("failed to set token %s to redis: %s", token.Key, err.Error()))
return err
}
token2UserIdLock.Lock()
defer token2UserIdLock.Unlock()
token2UserId[token.Key] = token.UserId
return nil
}
// CacheGetTokenByKey 从缓存中获取 token 并续期时间,如果缓存中不存在,则从数据库中获取
func CacheGetTokenByKey(key string) (*Token, error) {
if !common.RedisEnabled {
return GetTokenByKey(key)
}
var token *Token
tokenObjectString, err := common.RedisGet(fmt.Sprintf("token:%s", key))
if err != nil {
// 如果缓存中不存在,则从数据库中获取
token, err = GetTokenByKey(key)
if err != nil {
return nil, err
}
err = cacheSetToken(token)
return token, nil
}
// 如果缓存中存在,则续期时间
err = common.RedisExpire(fmt.Sprintf("token:%s", key), time.Duration(TokenCacheSeconds)*time.Second)
err = json.Unmarshal([]byte(tokenObjectString), &token)
return token, err
}
func SyncTokenCache(frequency int) {
for {
time.Sleep(time.Duration(frequency) * time.Second)
common.SysLog("syncing tokens from database")
token2UserIdLock.Lock()
// 从token2UserId中获取所有的key
var copyToken2UserId = make(map[string]int)
for s, i := range token2UserId {
copyToken2UserId[s] = i
}
token2UserId = make(map[string]int)
token2UserIdLock.Unlock()
for key := range copyToken2UserId {
token, err := GetTokenByKey(key)
if err != nil {
// 如果数据库中不存在,则删除缓存
common.SysError(fmt.Sprintf("failed to get token %s from database: %s", key, err.Error()))
//delete redis
err := common.RedisDel(fmt.Sprintf("token:%s", key))
if err != nil {
common.SysError(fmt.Sprintf("failed to delete token %s from redis: %s", key, err.Error()))
}
} else {
// 如果数据库中存在,先检查redis
_, err = common.RedisGet(fmt.Sprintf("token:%s", key))
if err != nil {
// 如果redis中不存在,则跳过
continue
}
err = cacheSetToken(token)
if err != nil {
common.SysError(fmt.Sprintf("failed to update token %s to redis: %s", key, err.Error()))
}
}
}
}
}
func CacheGetUserGroup(id int) (group string, err error) {
if !common.RedisEnabled {
return GetUserGroup(id)
}
group, err = common.RedisGet(fmt.Sprintf("user_group:%d", id))
if err != nil {
group, err = GetUserGroup(id)
if err != nil {
return "", err
}
err = common.RedisSet(fmt.Sprintf("user_group:%d", id), group, time.Duration(UserId2GroupCacheSeconds)*time.Second)
if err != nil {
common.SysError("Redis set user group error: " + err.Error())
}
}
return group, err
}
func CacheGetUsername(id int) (username string, err error) {
if !common.RedisEnabled {
return GetUsernameById(id)
}
username, err = common.RedisGet(fmt.Sprintf("user_name:%d", id))
if err != nil {
username, err = GetUsernameById(id)
if err != nil {
return "", err
}
err = common.RedisSet(fmt.Sprintf("user_name:%d", id), username, time.Duration(UserId2GroupCacheSeconds)*time.Second)
if err != nil {
common.SysError("Redis set user group error: " + err.Error())
}
}
return username, err
}
func CacheGetUserQuota(id int) (quota int, err error) {
if !common.RedisEnabled {
return GetUserQuota(id)
}
quotaString, err := common.RedisGet(fmt.Sprintf("user_quota:%d", id))
if err != nil {
quota, err = GetUserQuota(id)
if err != nil {
return 0, err
}
err = common.RedisSet(fmt.Sprintf("user_quota:%d", id), fmt.Sprintf("%d", quota), time.Duration(UserId2QuotaCacheSeconds)*time.Second)
if err != nil {
common.SysError("Redis set user quota error: " + err.Error())
}
return quota, err
}
quota, err = strconv.Atoi(quotaString)
return quota, err
}
func CacheUpdateUserQuota(id int) error {
if !common.RedisEnabled {
return nil
}
quota, err := GetUserQuota(id)
if err != nil {
return err
}
return cacheSetUserQuota(id, quota)
}
func cacheSetUserQuota(id int, quota int) error {
err := common.RedisSet(fmt.Sprintf("user_quota:%d", id), fmt.Sprintf("%d", quota), time.Duration(UserId2QuotaCacheSeconds)*time.Second)
return err
}
func CacheDecreaseUserQuota(id int, quota int) error {
if !common.RedisEnabled {
return nil
}
err := common.RedisDecrease(fmt.Sprintf("user_quota:%d", id), int64(quota))
return err
}
func CacheIsUserEnabled(userId int) (bool, error) {
if !common.RedisEnabled {
return IsUserEnabled(userId)
}
enabled, err := common.RedisGet(fmt.Sprintf("user_enabled:%d", userId))
if err == nil {
return enabled == "1", nil
}
userEnabled, err := IsUserEnabled(userId)
if err != nil {
return false, err
}
enabled = "0"
if userEnabled {
enabled = "1"
}
err = common.RedisSet(fmt.Sprintf("user_enabled:%d", userId), enabled, time.Duration(UserId2StatusCacheSeconds)*time.Second)
if err != nil {
common.SysError("Redis set user enabled error: " + err.Error())
}
return userEnabled, err
}
var group2model2channels map[string]map[string][]*Channel
var channelsIDM map[int]*Channel
var channelSyncLock sync.RWMutex
func InitChannelCache() {
if !common.MemoryCacheEnabled {
return
}
newChannelId2channel := make(map[int]*Channel)
var channels []*Channel
DB.Where("status = ?", common.ChannelStatusEnabled).Find(&channels)
@@ -266,7 +78,43 @@ func SyncChannelCache(frequency int) {
}
}
func CacheGetRandomSatisfiedChannel(group string, model string, retry int) (*Channel, error) {
func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, model string, retry int) (*Channel, string, error) {
var channel *Channel
var err error
selectGroup := group
if group == "auto" {
if len(setting.AutoGroups) == 0 {
return nil, selectGroup, errors.New("auto groups is not enabled")
}
for _, autoGroup := range setting.AutoGroups {
if common.DebugEnabled {
println("autoGroup:", autoGroup)
}
channel, _ = getRandomSatisfiedChannel(autoGroup, model, retry)
if channel == nil {
continue
} else {
c.Set("auto_group", autoGroup)
selectGroup = autoGroup
if common.DebugEnabled {
println("selectGroup:", selectGroup)
}
break
}
}
} else {
channel, err = getRandomSatisfiedChannel(group, model, retry)
if err != nil {
return nil, group, err
}
}
if channel == nil {
return nil, group, errors.New("channel not found")
}
return channel, selectGroup, nil
}
func getRandomSatisfiedChannel(group string, model string, retry int) (*Channel, error) {
if strings.HasPrefix(model, "gpt-4-gizmo") {
model = "gpt-4-gizmo-*"
}
@@ -278,9 +126,11 @@ func CacheGetRandomSatisfiedChannel(group string, model string, retry int) (*Cha
if !common.MemoryCacheEnabled {
return GetRandomSatisfiedChannel(group, model, retry)
}
channelSyncLock.RLock()
defer channelSyncLock.RUnlock()
channels := group2model2channels[group][model]
channelSyncLock.RUnlock()
if len(channels) == 0 {
return nil, errors.New("channel not found")
}
@@ -342,3 +192,14 @@ func CacheGetChannel(id int) (*Channel, error) {
}
return c, nil
}
func CacheUpdateChannelStatus(id int, status int) {
if !common.MemoryCacheEnabled {
return
}
channelSyncLock.Lock()
defer channelSyncLock.Unlock()
if channel, ok := channelsIDM[id]; ok {
channel.Status = status
}
}
+209 -22
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"one-api/common"
"strings"
"sync"
"gorm.io/gorm"
)
@@ -27,13 +28,15 @@ type Channel struct {
Models string `json:"models"`
Group string `json:"group" gorm:"type:varchar(64);default:'default'"`
UsedQuota int64 `json:"used_quota" gorm:"bigint;default:0"`
ModelMapping *string `json:"model_mapping" gorm:"type:varchar(1024);default:''"`
ModelMapping *string `json:"model_mapping" gorm:"type:text"`
//MaxInputTokens *int `json:"max_input_tokens" gorm:"default:0"`
StatusCodeMapping *string `json:"status_code_mapping" gorm:"type:varchar(1024);default:''"`
Priority *int64 `json:"priority" gorm:"bigint;default:0"`
AutoBan *int `json:"auto_ban" gorm:"default:1"`
OtherInfo string `json:"other_info"`
Tag *string `json:"tag" gorm:"index"`
Setting *string `json:"setting" gorm:"type:text"`
ParamOverride *string `json:"param_override" gorm:"type:text"`
}
func (channel *Channel) GetModels() []string {
@@ -43,6 +46,17 @@ func (channel *Channel) GetModels() []string {
return strings.Split(strings.Trim(channel.Models, ","), ",")
}
func (channel *Channel) GetGroups() []string {
if channel.Group == "" {
return []string{}
}
groups := strings.Split(strings.Trim(channel.Group, ","), ",")
for i, group := range groups {
groups[i] = strings.TrimSpace(group)
}
return groups
}
func (channel *Channel) GetOtherInfo() map[string]interface{} {
otherInfo := make(map[string]interface{})
if channel.OtherInfo != "" {
@@ -100,32 +114,38 @@ func GetAllChannels(startIdx int, num int, selectAll bool, idSort bool) ([]*Chan
return channels, err
}
func GetChannelsByTag(tag string) ([]*Channel, error) {
func GetChannelsByTag(tag string, idSort bool) ([]*Channel, error) {
var channels []*Channel
err := DB.Where("tag = ?", tag).Find(&channels).Error
order := "priority desc"
if idSort {
order = "id desc"
}
err := DB.Where("tag = ?", tag).Order(order).Find(&channels).Error
return channels, err
}
func SearchChannels(keyword string, group string, model string, idSort bool) ([]*Channel, error) {
var channels []*Channel
keyCol := "`key`"
groupCol := "`group`"
modelsCol := "`models`"
// 如果是 PostgreSQL,使用双引号
if common.UsingPostgreSQL {
keyCol = `"key"`
groupCol = `"group"`
modelsCol = `"models"`
}
baseURLCol := "`base_url`"
// 如果是 PostgreSQL,使用双引号
if common.UsingPostgreSQL {
baseURLCol = `"base_url"`
}
order := "priority desc"
if idSort {
order = "id desc"
}
// 构造基础查询
baseQuery := DB.Model(&Channel{}).Omit(keyCol)
baseQuery := DB.Model(&Channel{}).Omit("key")
// 构造WHERE子句
var whereClause string
@@ -133,16 +153,16 @@ func SearchChannels(keyword string, group string, model string, idSort bool) ([]
if group != "" && group != "null" {
var groupCondition string
if common.UsingMySQL {
groupCondition = `CONCAT(',', ` + groupCol + `, ',') LIKE ?`
groupCondition = `CONCAT(',', ` + commonGroupCol + `, ',') LIKE ?`
} else {
// sqlite, PostgreSQL
groupCondition = `(',' || ` + groupCol + ` || ',') LIKE ?`
groupCondition = `(',' || ` + commonGroupCol + ` || ',') LIKE ?`
}
whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition
args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+model+"%", "%,"+group+",%")
whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition
args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%", "%,"+group+",%")
} else {
whereClause = "(id = ? OR name LIKE ? OR " + keyCol + " = ?) AND " + modelsCol + " LIKE ?"
args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+model+"%")
whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + " LIKE ?"
args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%")
}
// 执行查询
@@ -251,7 +271,7 @@ func (channel *Channel) Update() error {
return err
}
DB.Model(channel).First(channel, "id = ?", channel.Id)
err = channel.UpdateAbilities()
err = channel.UpdateAbilities(nil)
return err
}
@@ -285,19 +305,44 @@ func (channel *Channel) Delete() error {
return err
}
func UpdateChannelStatusById(id int, status int, reason string) {
var channelStatusLock sync.Mutex
func UpdateChannelStatusById(id int, status int, reason string) bool {
if common.MemoryCacheEnabled {
channelStatusLock.Lock()
defer channelStatusLock.Unlock()
channelCache, _ := CacheGetChannel(id)
// 如果缓存渠道存在,且状态已是目标状态,直接返回
if channelCache != nil && channelCache.Status == status {
return false
}
// 如果缓存渠道不存在(说明已经被禁用),且要设置的状态不为启用,直接返回
if channelCache == nil && status != common.ChannelStatusEnabled {
return false
}
CacheUpdateChannelStatus(id, status)
}
err := UpdateAbilityStatus(id, status == common.ChannelStatusEnabled)
if err != nil {
common.SysError("failed to update ability status: " + err.Error())
return false
}
channel, err := GetChannelById(id, true)
if err != nil {
// find channel by id error, directly update status
err = DB.Model(&Channel{}).Where("id = ?", id).Update("status", status).Error
if err != nil {
common.SysError("failed to update channel status: " + err.Error())
result := DB.Model(&Channel{}).Where("id = ?", id).Update("status", status)
if result.Error != nil {
common.SysError("failed to update channel status: " + result.Error.Error())
return false
}
if result.RowsAffected == 0 {
return false
}
} else {
if channel.Status == status {
return false
}
// find channel by id success, update status and other info
info := channel.GetOtherInfo()
info["status_reason"] = reason
@@ -307,9 +352,10 @@ func UpdateChannelStatusById(id int, status int, reason string) {
err = channel.Save()
if err != nil {
common.SysError("failed to update channel status: " + err.Error())
return false
}
}
return true
}
func EnableChannelByTag(tag string) error {
@@ -362,10 +408,10 @@ func EditChannelByTag(tag string, newTag *string, modelMapping *string, models *
return err
}
if shouldReCreateAbilities {
channels, err := GetChannelsByTag(updatedTag)
channels, err := GetChannelsByTag(updatedTag, false)
if err == nil {
for _, channel := range channels {
err = channel.UpdateAbilities()
err = channel.UpdateAbilities(nil)
if err != nil {
common.SysError("failed to update abilities: " + err.Error())
}
@@ -410,3 +456,144 @@ func GetPaginatedTags(offset int, limit int) ([]*string, error) {
err := DB.Model(&Channel{}).Select("DISTINCT tag").Where("tag != ''").Offset(offset).Limit(limit).Find(&tags).Error
return tags, err
}
func SearchTags(keyword string, group string, model string, idSort bool) ([]*string, error) {
var tags []*string
modelsCol := "`models`"
// 如果是 PostgreSQL,使用双引号
if common.UsingPostgreSQL {
modelsCol = `"models"`
}
baseURLCol := "`base_url`"
// 如果是 PostgreSQL,使用双引号
if common.UsingPostgreSQL {
baseURLCol = `"base_url"`
}
order := "priority desc"
if idSort {
order = "id desc"
}
// 构造基础查询
baseQuery := DB.Model(&Channel{}).Omit("key")
// 构造WHERE子句
var whereClause string
var args []interface{}
if group != "" && group != "null" {
var groupCondition string
if common.UsingMySQL {
groupCondition = `CONCAT(',', ` + commonGroupCol + `, ',') LIKE ?`
} else {
// sqlite, PostgreSQL
groupCondition = `(',' || ` + commonGroupCol + ` || ',') LIKE ?`
}
whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + ` LIKE ? AND ` + groupCondition
args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%", "%,"+group+",%")
} else {
whereClause = "(id = ? OR name LIKE ? OR " + commonKeyCol + " = ? OR " + baseURLCol + " LIKE ?) AND " + modelsCol + " LIKE ?"
args = append(args, common.String2Int(keyword), "%"+keyword+"%", keyword, "%"+keyword+"%", "%"+model+"%")
}
subQuery := baseQuery.Where(whereClause, args...).
Select("tag").
Where("tag != ''").
Order(order)
err := DB.Table("(?) as sub", subQuery).
Select("DISTINCT tag").
Find(&tags).Error
if err != nil {
return nil, err
}
return tags, nil
}
func (channel *Channel) GetSetting() map[string]interface{} {
setting := make(map[string]interface{})
if channel.Setting != nil && *channel.Setting != "" {
err := json.Unmarshal([]byte(*channel.Setting), &setting)
if err != nil {
common.SysError("failed to unmarshal setting: " + err.Error())
}
}
return setting
}
func (channel *Channel) SetSetting(setting map[string]interface{}) {
settingBytes, err := json.Marshal(setting)
if err != nil {
common.SysError("failed to marshal setting: " + err.Error())
return
}
channel.Setting = common.GetPointer[string](string(settingBytes))
}
func (channel *Channel) GetParamOverride() map[string]interface{} {
paramOverride := make(map[string]interface{})
if channel.ParamOverride != nil && *channel.ParamOverride != "" {
err := json.Unmarshal([]byte(*channel.ParamOverride), &paramOverride)
if err != nil {
common.SysError("failed to unmarshal param override: " + err.Error())
}
}
return paramOverride
}
func GetChannelsByIds(ids []int) ([]*Channel, error) {
var channels []*Channel
err := DB.Where("id in (?)", ids).Find(&channels).Error
return channels, err
}
func BatchSetChannelTag(ids []int, tag *string) error {
// 开启事务
tx := DB.Begin()
if tx.Error != nil {
return tx.Error
}
// 更新标签
err := tx.Model(&Channel{}).Where("id in (?)", ids).Update("tag", tag).Error
if err != nil {
tx.Rollback()
return err
}
// update ability status
channels, err := GetChannelsByIds(ids)
if err != nil {
tx.Rollback()
return err
}
for _, channel := range channels {
err = channel.UpdateAbilities(tx)
if err != nil {
tx.Rollback()
return err
}
}
// 提交事务
return tx.Commit().Error
}
// CountAllChannels returns total channels in DB
func CountAllChannels() (int64, error) {
var total int64
err := DB.Model(&Channel{}).Count(&total).Error
return total, err
}
// CountAllTags returns number of non-empty distinct tags
func CountAllTags() (int64, error) {
var total int64
err := DB.Model(&Channel{}).Where("tag is not null AND tag != ''").Distinct("tag").Count(&total).Error
return total, err
}
+178 -38
View File
@@ -4,10 +4,13 @@ import (
"context"
"fmt"
"one-api/common"
"one-api/constant"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/bytedance/gopkg/util/gopool"
"gorm.io/gorm"
)
@@ -18,7 +21,7 @@ type Log struct {
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"`
Type int `json:"type" gorm:"index:idx_created_at_type"`
Content string `json:"content"`
Username string `json:"username" gorm:"index:index_username_model_name,priority:2;default:''"`
Username string `json:"username" gorm:"index;index:index_username_model_name,priority:2;default:''"`
TokenName string `json:"token_name" gorm:"index;default:''"`
ModelName string `json:"model_name" gorm:"index;index:index_username_model_name,priority:1;default:''"`
Quota int `json:"quota" gorm:"default:0"`
@@ -27,7 +30,10 @@ type Log struct {
UseTime int `json:"use_time" gorm:"default:0"`
IsStream bool `json:"is_stream" gorm:"default:false"`
ChannelId int `json:"channel" gorm:"index"`
ChannelName string `json:"channel_name" gorm:"->"`
TokenId int `json:"token_id" gorm:"default:0;index"`
Group string `json:"group" gorm:"index"`
Ip string `json:"ip" gorm:"index;default:''"`
Other string `json:"other"`
}
@@ -37,18 +43,34 @@ const (
LogTypeConsume
LogTypeManage
LogTypeSystem
LogTypeError
)
func formatUserLogs(logs []*Log) {
for i := range logs {
logs[i].ChannelName = ""
var otherMap map[string]interface{}
otherMap = common.StrToMap(logs[i].Other)
if otherMap != nil {
// delete admin
delete(otherMap, "admin_info")
}
logs[i].Other = common.MapToJsonStr(otherMap)
logs[i].Id = logs[i].Id % 1024
}
}
func GetLogByKey(key string) (logs []*Log, err error) {
if os.Getenv("LOG_SQL_DSN") != "" {
var tk Token
if err = DB.Model(&Token{}).Where("`key`=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil {
if err = DB.Model(&Token{}).Where(logKeyCol+"=?", strings.TrimPrefix(key, "sk-")).First(&tk).Error; err != nil {
return nil, err
}
err = LOG_DB.Model(&Log{}).Where("token_id=?", tk.Id).Find(&logs).Error
} else {
err = LOG_DB.Joins("left join tokens on tokens.id = logs.token_id").Where("tokens.key = ?", strings.TrimPrefix(key, "sk-")).Find(&logs).Error
}
formatUserLogs(logs)
return logs, err
}
@@ -56,7 +78,7 @@ func RecordLog(userId int, logType int, content string) {
if logType == LogTypeConsume && !common.LogConsumeEnabled {
return
}
username, _ := CacheGetUsername(userId)
username, _ := GetUsernameById(userId, false)
log := &Log{
UserId: userId,
Username: username,
@@ -70,13 +92,68 @@ func RecordLog(userId int, logType int, content string) {
}
}
func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptTokens int, completionTokens int, modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int, isStream bool, other map[string]interface{}) {
common.LogInfo(ctx, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
func RecordErrorLog(c *gin.Context, userId int, channelId int, modelName string, tokenName string, content string, tokenId int, useTimeSeconds int,
isStream bool, group string, other map[string]interface{}) {
common.LogInfo(c, fmt.Sprintf("record error log: userId=%d, channelId=%d, modelName=%s, tokenName=%s, content=%s", userId, channelId, modelName, tokenName, content))
username := c.GetString("username")
otherStr := common.MapToJsonStr(other)
// 判断是否需要记录 IP
needRecordIp := false
if settingMap, err := GetUserSetting(userId, false); err == nil {
if v, ok := settingMap[constant.UserSettingRecordIpLog]; ok {
if vb, ok := v.(bool); ok && vb {
needRecordIp = true
}
}
}
log := &Log{
UserId: userId,
Username: username,
CreatedAt: common.GetTimestamp(),
Type: LogTypeError,
Content: content,
PromptTokens: 0,
CompletionTokens: 0,
TokenName: tokenName,
ModelName: modelName,
Quota: 0,
ChannelId: channelId,
TokenId: tokenId,
UseTime: useTimeSeconds,
IsStream: isStream,
Group: group,
Ip: func() string {
if needRecordIp {
return c.ClientIP()
}
return ""
}(),
Other: otherStr,
}
err := LOG_DB.Create(log).Error
if err != nil {
common.LogError(c, "failed to record log: "+err.Error())
}
}
func RecordConsumeLog(c *gin.Context, userId int, channelId int, promptTokens int, completionTokens int,
modelName string, tokenName string, quota int, content string, tokenId int, userQuota int, useTimeSeconds int,
isStream bool, group string, other map[string]interface{}) {
common.LogInfo(c, fmt.Sprintf("record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s", userId, userQuota, channelId, promptTokens, completionTokens, modelName, tokenName, quota, content))
if !common.LogConsumeEnabled {
return
}
username, _ := CacheGetUsername(userId)
username := c.GetString("username")
otherStr := common.MapToJsonStr(other)
// 判断是否需要记录 IP
needRecordIp := false
if settingMap, err := GetUserSetting(userId, false); err == nil {
if v, ok := settingMap[constant.UserSettingRecordIpLog]; ok {
if vb, ok := v.(bool); ok && vb {
needRecordIp = true
}
}
}
log := &Log{
UserId: userId,
Username: username,
@@ -92,11 +169,18 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
TokenId: tokenId,
UseTime: useTimeSeconds,
IsStream: isStream,
Other: otherStr,
Group: group,
Ip: func() string {
if needRecordIp {
return c.ClientIP()
}
return ""
}(),
Other: otherStr,
}
err := LOG_DB.Create(log).Error
if err != nil {
common.LogError(ctx, "failed to record log: "+err.Error())
common.LogError(c, "failed to record log: "+err.Error())
}
if common.DataExportEnabled {
gopool.Go(func() {
@@ -105,75 +189,108 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
}
}
func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int) (logs []*Log, total int64, err error) {
func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, startIdx int, num int, channel int, group string) (logs []*Log, total int64, err error) {
var tx *gorm.DB
if logType == LogTypeUnknown {
tx = LOG_DB
} else {
tx = LOG_DB.Where("type = ?", logType)
tx = LOG_DB.Where("logs.type = ?", logType)
}
if modelName != "" {
tx = tx.Where("model_name like ?", modelName)
tx = tx.Where("logs.model_name like ?", modelName)
}
if username != "" {
tx = tx.Where("username = ?", username)
tx = tx.Where("logs.username = ?", username)
}
if tokenName != "" {
tx = tx.Where("token_name = ?", tokenName)
tx = tx.Where("logs.token_name = ?", tokenName)
}
if startTimestamp != 0 {
tx = tx.Where("created_at >= ?", startTimestamp)
tx = tx.Where("logs.created_at >= ?", startTimestamp)
}
if endTimestamp != 0 {
tx = tx.Where("created_at <= ?", endTimestamp)
tx = tx.Where("logs.created_at <= ?", endTimestamp)
}
if channel != 0 {
tx = tx.Where("channel_id = ?", channel)
tx = tx.Where("logs.channel_id = ?", channel)
}
if group != "" {
tx = tx.Where("logs."+logGroupCol+" = ?", group)
}
err = tx.Model(&Log{}).Count(&total).Error
if err != nil {
return nil, 0, err
}
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&logs).Error
err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
if err != nil {
return nil, 0, err
}
channelIdsMap := make(map[int]struct{})
channelMap := make(map[int]string)
for _, log := range logs {
if log.ChannelId != 0 {
channelIdsMap[log.ChannelId] = struct{}{}
}
}
channelIds := make([]int, 0, len(channelIdsMap))
for channelId := range channelIdsMap {
channelIds = append(channelIds, channelId)
}
if len(channelIds) > 0 {
var channels []struct {
Id int `gorm:"column:id"`
Name string `gorm:"column:name"`
}
if err = DB.Table("channels").Select("id, name").Where("id IN ?", channelIds).Find(&channels).Error; err != nil {
return logs, total, err
}
for _, channel := range channels {
channelMap[channel.Id] = channel.Name
}
for i := range logs {
logs[i].ChannelName = channelMap[logs[i].ChannelId]
}
}
return logs, total, err
}
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int) (logs []*Log, total int64, err error) {
func GetUserLogs(userId int, logType int, startTimestamp int64, endTimestamp int64, modelName string, tokenName string, startIdx int, num int, group string) (logs []*Log, total int64, err error) {
var tx *gorm.DB
if logType == LogTypeUnknown {
tx = LOG_DB.Where("user_id = ?", userId)
tx = LOG_DB.Where("logs.user_id = ?", userId)
} else {
tx = LOG_DB.Where("user_id = ? and type = ?", userId, logType)
tx = LOG_DB.Where("logs.user_id = ? and logs.type = ?", userId, logType)
}
if modelName != "" {
tx = tx.Where("model_name like ?", modelName)
tx = tx.Where("logs.model_name like ?", modelName)
}
if tokenName != "" {
tx = tx.Where("token_name = ?", tokenName)
tx = tx.Where("logs.token_name = ?", tokenName)
}
if startTimestamp != 0 {
tx = tx.Where("created_at >= ?", startTimestamp)
tx = tx.Where("logs.created_at >= ?", startTimestamp)
}
if endTimestamp != 0 {
tx = tx.Where("created_at <= ?", endTimestamp)
tx = tx.Where("logs.created_at <= ?", endTimestamp)
}
if group != "" {
tx = tx.Where("logs."+logGroupCol+" = ?", group)
}
err = tx.Model(&Log{}).Count(&total).Error
if err != nil {
return nil, 0, err
}
err = tx.Order("id desc").Limit(num).Offset(startIdx).Omit("id").Find(&logs).Error
for i := range logs {
var otherMap map[string]interface{}
otherMap = common.StrToMap(logs[i].Other)
if otherMap != nil {
// delete admin
delete(otherMap, "admin_info")
}
logs[i].Other = common.MapToJsonStr(otherMap)
err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
if err != nil {
return nil, 0, err
}
formatUserLogs(logs)
return logs, total, err
}
@@ -183,7 +300,8 @@ func SearchAllLogs(keyword string) (logs []*Log, err error) {
}
func SearchUserLogs(userId int, keyword string) (logs []*Log, err error) {
err = LOG_DB.Where("user_id = ? and type = ?", userId, keyword).Order("id desc").Limit(common.MaxRecentItems).Omit("id").Find(&logs).Error
err = LOG_DB.Where("user_id = ? and type = ?", userId, keyword).Order("id desc").Limit(common.MaxRecentItems).Find(&logs).Error
formatUserLogs(logs)
return logs, err
}
@@ -193,7 +311,7 @@ type Stat struct {
Tpm int `json:"tpm"`
}
func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, channel int) (stat Stat) {
func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelName string, username string, tokenName string, channel int, group string) (stat Stat) {
tx := LOG_DB.Table("logs").Select("sum(quota) quota")
// 为rpm和tpm创建单独的查询
@@ -221,6 +339,10 @@ func SumUsedQuota(logType int, startTimestamp int64, endTimestamp int64, modelNa
tx = tx.Where("channel_id = ?", channel)
rpmTpmQuery = rpmTpmQuery.Where("channel_id = ?", channel)
}
if group != "" {
tx = tx.Where(logGroupCol+" = ?", group)
rpmTpmQuery = rpmTpmQuery.Where(logGroupCol+" = ?", group)
}
tx = tx.Where("type = ?", LogTypeConsume)
rpmTpmQuery = rpmTpmQuery.Where("type = ?", LogTypeConsume)
@@ -256,7 +378,25 @@ func SumUsedToken(logType int, startTimestamp int64, endTimestamp int64, modelNa
return token
}
func DeleteOldLog(targetTimestamp int64) (int64, error) {
result := LOG_DB.Where("created_at < ?", targetTimestamp).Delete(&Log{})
return result.RowsAffected, result.Error
func DeleteOldLog(ctx context.Context, targetTimestamp int64, limit int) (int64, error) {
var total int64 = 0
for {
if nil != ctx.Err() {
return total, ctx.Err()
}
result := LOG_DB.Where("created_at < ?", targetTimestamp).Limit(limit).Delete(&Log{})
if nil != result.Error {
return total, result.Error
}
total += result.RowsAffected
if result.RowsAffected < int64(limit) {
break
}
}
return total, nil
}
+154 -57
View File
@@ -1,18 +1,56 @@
package model
import (
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"fmt"
"log"
"one-api/common"
"one-api/constant"
"os"
"strings"
"sync"
"time"
"github.com/glebarez/sqlite"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
var commonGroupCol string
var commonKeyCol string
var commonTrueVal string
var commonFalseVal string
var logKeyCol string
var logGroupCol string
func initCol() {
// init common column names
if common.UsingPostgreSQL {
commonGroupCol = `"group"`
commonKeyCol = `"key"`
commonTrueVal = "true"
commonFalseVal = "false"
} else {
commonGroupCol = "`group`"
commonKeyCol = "`key`"
commonTrueVal = "1"
commonFalseVal = "0"
}
if os.Getenv("LOG_SQL_DSN") != "" {
switch common.LogSqlType {
case common.DatabaseTypePostgreSQL:
logGroupCol = `"group"`
logKeyCol = `"key"`
default:
logGroupCol = commonGroupCol
logKeyCol = commonKeyCol
}
}
// log sql type and database type
common.SysLog("Using Log SQL Type: " + common.LogSqlType)
}
var DB *gorm.DB
var LOG_DB *gorm.DB
@@ -40,13 +78,47 @@ func createRootAccountIfNeed() error {
return nil
}
func chooseDB(envName string) (*gorm.DB, error) {
func CheckSetup() {
setup := GetSetup()
if setup == nil {
// No setup record exists, check if we have a root user
if RootUserExists() {
common.SysLog("system is not initialized, but root user exists")
// Create setup record
newSetup := Setup{
Version: common.Version,
InitializedAt: time.Now().Unix(),
}
err := DB.Create(&newSetup).Error
if err != nil {
common.SysLog("failed to create setup record: " + err.Error())
}
constant.Setup = true
} else {
common.SysLog("system is not initialized and no root user exists")
constant.Setup = false
}
} else {
// Setup record exists, system is initialized
common.SysLog("system is already initialized at: " + time.Unix(setup.InitializedAt, 0).String())
constant.Setup = true
}
}
func chooseDB(envName string, isLog bool) (*gorm.DB, error) {
defer func() {
initCol()
}()
dsn := os.Getenv(envName)
if dsn != "" {
if strings.HasPrefix(dsn, "postgres://") {
if strings.HasPrefix(dsn, "postgres://") || strings.HasPrefix(dsn, "postgresql://") {
// Use PostgreSQL
common.SysLog("using PostgreSQL as database")
common.UsingPostgreSQL = true
if !isLog {
common.UsingPostgreSQL = true
} else {
common.LogSqlType = common.DatabaseTypePostgreSQL
}
return gorm.Open(postgres.New(postgres.Config{
DSN: dsn,
PreferSimpleProtocol: true, // disables implicit prepared statement usage
@@ -56,7 +128,11 @@ func chooseDB(envName string) (*gorm.DB, error) {
}
if strings.HasPrefix(dsn, "local") {
common.SysLog("SQL_DSN not set, using SQLite as database")
common.UsingSQLite = true
if !isLog {
common.UsingSQLite = true
} else {
common.LogSqlType = common.DatabaseTypeSQLite
}
return gorm.Open(sqlite.Open(common.SQLitePath), &gorm.Config{
PrepareStmt: true, // precompile SQL
})
@@ -71,7 +147,11 @@ func chooseDB(envName string) (*gorm.DB, error) {
dsn += "?parseTime=true"
}
}
common.UsingMySQL = true
if !isLog {
common.UsingMySQL = true
} else {
common.LogSqlType = common.DatabaseTypeMySQL
}
return gorm.Open(mysql.Open(dsn), &gorm.Config{
PrepareStmt: true, // precompile SQL
})
@@ -85,7 +165,7 @@ func chooseDB(envName string) (*gorm.DB, error) {
}
func InitDB() (err error) {
db, err := chooseDB("SQL_DSN")
db, err := chooseDB("SQL_DSN", false)
if err == nil {
if common.DebugEnabled {
db = db.Debug()
@@ -102,12 +182,9 @@ func InitDB() (err error) {
if !common.IsMasterNode {
return nil
}
//if common.UsingMySQL {
// _, _ = sqlDB.Exec("DROP INDEX idx_channels_key ON channels;") // TODO: delete this line when most users have upgraded
// _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY action VARCHAR(40);") // TODO: delete this line when most users have upgraded
// _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY progress VARCHAR(30);") // TODO: delete this line when most users have upgraded
// _, _ = sqlDB.Exec("ALTER TABLE midjourneys MODIFY status VARCHAR(20);") // TODO: delete this line when most users have upgraded
//}
if common.UsingMySQL {
//_, _ = sqlDB.Exec("ALTER TABLE channels MODIFY model_mapping TEXT;") // TODO: delete this line when most users have upgraded
}
common.SysLog("database migration started")
err = migrateDB()
return err
@@ -122,7 +199,7 @@ func InitLogDB() (err error) {
LOG_DB = DB
return
}
db, err := chooseDB("LOG_SQL_DSN")
db, err := chooseDB("LOG_SQL_DSN", true)
if err == nil {
if common.DebugEnabled {
db = db.Debug()
@@ -155,53 +232,73 @@ func InitLogDB() (err error) {
}
func migrateDB() error {
err := DB.AutoMigrate(&Channel{})
if !common.UsingPostgreSQL {
return migrateDBFast()
}
err := DB.AutoMigrate(
&Channel{},
&Token{},
&User{},
&Option{},
&Redemption{},
&Ability{},
&Log{},
&Midjourney{},
&TopUp{},
&QuotaData{},
&Task{},
&Setup{},
)
if err != nil {
return err
}
err = DB.AutoMigrate(&Token{})
if err != nil {
return err
return nil
}
func migrateDBFast() error {
var wg sync.WaitGroup
errChan := make(chan error, 12) // Buffer size matches number of migrations
migrations := []struct {
model interface{}
name string
}{
{&Channel{}, "Channel"},
{&Token{}, "Token"},
{&User{}, "User"},
{&Option{}, "Option"},
{&Redemption{}, "Redemption"},
{&Ability{}, "Ability"},
{&Log{}, "Log"},
{&Midjourney{}, "Midjourney"},
{&TopUp{}, "TopUp"},
{&QuotaData{}, "QuotaData"},
{&Task{}, "Task"},
{&Setup{}, "Setup"},
}
err = DB.AutoMigrate(&User{})
if err != nil {
return err
for _, m := range migrations {
wg.Add(1)
go func(model interface{}, name string) {
defer wg.Done()
if err := DB.AutoMigrate(model); err != nil {
errChan <- fmt.Errorf("failed to migrate %s: %v", name, err)
}
}(m.model, m.name)
}
err = DB.AutoMigrate(&Option{})
if err != nil {
return err
}
err = DB.AutoMigrate(&Redemption{})
if err != nil {
return err
}
err = DB.AutoMigrate(&Ability{})
if err != nil {
return err
}
err = DB.AutoMigrate(&Log{})
if err != nil {
return err
}
err = DB.AutoMigrate(&Midjourney{})
if err != nil {
return err
}
err = DB.AutoMigrate(&TopUp{})
if err != nil {
return err
}
err = DB.AutoMigrate(&QuotaData{})
if err != nil {
return err
}
err = DB.AutoMigrate(&Task{})
if err != nil {
return err
// Wait for all migrations to complete
wg.Wait()
close(errChan)
// Check for any errors
for err := range errChan {
if err != nil {
return err
}
}
common.SysLog("database migrated")
err = createRootAccountIfNeed()
return err
return nil
}
func migrateLOGDB() error {
+37
View File
@@ -166,3 +166,40 @@ func MjBulkUpdateByTaskIds(taskIDs []int, params map[string]any) error {
Where("id in (?)", taskIDs).
Updates(params).Error
}
// CountAllTasks returns total midjourney tasks for admin query
func CountAllTasks(queryParams TaskQueryParams) int64 {
var total int64
query := DB.Model(&Midjourney{})
if queryParams.ChannelID != "" {
query = query.Where("channel_id = ?", queryParams.ChannelID)
}
if queryParams.MjID != "" {
query = query.Where("mj_id = ?", queryParams.MjID)
}
if queryParams.StartTimestamp != "" {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != "" {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}
// CountAllUserTask returns total midjourney tasks for user
func CountAllUserTask(userId int, queryParams TaskQueryParams) int64 {
var total int64
query := DB.Model(&Midjourney{}).Where("user_id = ?", userId)
if queryParams.MjID != "" {
query = query.Where("mj_id = ?", queryParams.MjID)
}
if queryParams.StartTimestamp != "" {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != "" {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}
+134 -56
View File
@@ -2,7 +2,9 @@ package model
import (
"one-api/common"
"one-api/constant"
"one-api/setting"
"one-api/setting/config"
"one-api/setting/operation_setting"
"strconv"
"strings"
"time"
@@ -23,6 +25,8 @@ func AllOption() ([]*Option, error) {
func InitOptionMap() {
common.OptionMapRWMutex.Lock()
common.OptionMap = make(map[string]string)
// 添加原有的系统配置
common.OptionMap["FileUploadPermission"] = strconv.Itoa(common.FileUploadPermission)
common.OptionMap["FileDownloadPermission"] = strconv.Itoa(common.FileDownloadPermission)
common.OptionMap["ImageUploadPermission"] = strconv.Itoa(common.ImageUploadPermission)
@@ -61,16 +65,19 @@ func InitOptionMap() {
common.OptionMap["SystemName"] = common.SystemName
common.OptionMap["Logo"] = common.Logo
common.OptionMap["ServerAddress"] = ""
common.OptionMap["WorkerUrl"] = constant.WorkerUrl
common.OptionMap["WorkerValidKey"] = constant.WorkerValidKey
common.OptionMap["WorkerUrl"] = setting.WorkerUrl
common.OptionMap["WorkerValidKey"] = setting.WorkerValidKey
common.OptionMap["WorkerAllowHttpImageRequestEnabled"] = strconv.FormatBool(setting.WorkerAllowHttpImageRequestEnabled)
common.OptionMap["PayAddress"] = ""
common.OptionMap["CustomCallbackAddress"] = ""
common.OptionMap["EpayId"] = ""
common.OptionMap["EpayKey"] = ""
common.OptionMap["Price"] = strconv.FormatFloat(constant.Price, 'f', -1, 64)
common.OptionMap["MinTopUp"] = strconv.Itoa(constant.MinTopUp)
common.OptionMap["Price"] = strconv.FormatFloat(setting.Price, 'f', -1, 64)
common.OptionMap["MinTopUp"] = strconv.Itoa(setting.MinTopUp)
common.OptionMap["TopupGroupRatio"] = common.TopupGroupRatio2JSONString()
common.OptionMap["Chats"] = constant.Chats2JsonString()
common.OptionMap["Chats"] = setting.Chats2JsonString()
common.OptionMap["AutoGroups"] = setting.AutoGroups2JsonString()
common.OptionMap["DefaultUseAutoGroup"] = strconv.FormatBool(setting.DefaultUseAutoGroup)
common.OptionMap["GitHubClientId"] = ""
common.OptionMap["GitHubClientSecret"] = ""
common.OptionMap["TelegramBotToken"] = ""
@@ -85,30 +92,45 @@ func InitOptionMap() {
common.OptionMap["QuotaForInvitee"] = strconv.Itoa(common.QuotaForInvitee)
common.OptionMap["QuotaRemindThreshold"] = strconv.Itoa(common.QuotaRemindThreshold)
common.OptionMap["PreConsumedQuota"] = strconv.Itoa(common.PreConsumedQuota)
common.OptionMap["ModelRatio"] = common.ModelRatio2JSONString()
common.OptionMap["ModelPrice"] = common.ModelPrice2JSONString()
common.OptionMap["GroupRatio"] = common.GroupRatio2JSONString()
common.OptionMap["UserUsableGroups"] = common.UserUsableGroups2JSONString()
common.OptionMap["CompletionRatio"] = common.CompletionRatio2JSONString()
common.OptionMap["ModelRequestRateLimitCount"] = strconv.Itoa(setting.ModelRequestRateLimitCount)
common.OptionMap["ModelRequestRateLimitDurationMinutes"] = strconv.Itoa(setting.ModelRequestRateLimitDurationMinutes)
common.OptionMap["ModelRequestRateLimitSuccessCount"] = strconv.Itoa(setting.ModelRequestRateLimitSuccessCount)
common.OptionMap["ModelRequestRateLimitGroup"] = setting.ModelRequestRateLimitGroup2JSONString()
common.OptionMap["ModelRatio"] = operation_setting.ModelRatio2JSONString()
common.OptionMap["ModelPrice"] = operation_setting.ModelPrice2JSONString()
common.OptionMap["CacheRatio"] = operation_setting.CacheRatio2JSONString()
common.OptionMap["GroupRatio"] = setting.GroupRatio2JSONString()
common.OptionMap["GroupGroupRatio"] = setting.GroupGroupRatio2JSONString()
common.OptionMap["UserUsableGroups"] = setting.UserUsableGroups2JSONString()
common.OptionMap["CompletionRatio"] = operation_setting.CompletionRatio2JSONString()
common.OptionMap["TopUpLink"] = common.TopUpLink
common.OptionMap["ChatLink"] = common.ChatLink
common.OptionMap["ChatLink2"] = common.ChatLink2
//common.OptionMap["ChatLink"] = common.ChatLink
//common.OptionMap["ChatLink2"] = common.ChatLink2
common.OptionMap["QuotaPerUnit"] = strconv.FormatFloat(common.QuotaPerUnit, 'f', -1, 64)
common.OptionMap["RetryTimes"] = strconv.Itoa(common.RetryTimes)
common.OptionMap["DataExportInterval"] = strconv.Itoa(common.DataExportInterval)
common.OptionMap["DataExportDefaultTime"] = common.DataExportDefaultTime
common.OptionMap["DefaultCollapseSidebar"] = strconv.FormatBool(common.DefaultCollapseSidebar)
common.OptionMap["MjNotifyEnabled"] = strconv.FormatBool(constant.MjNotifyEnabled)
common.OptionMap["MjAccountFilterEnabled"] = strconv.FormatBool(constant.MjAccountFilterEnabled)
common.OptionMap["MjModeClearEnabled"] = strconv.FormatBool(constant.MjModeClearEnabled)
common.OptionMap["MjForwardUrlEnabled"] = strconv.FormatBool(constant.MjForwardUrlEnabled)
common.OptionMap["MjActionCheckSuccessEnabled"] = strconv.FormatBool(constant.MjActionCheckSuccessEnabled)
common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(constant.CheckSensitiveEnabled)
common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnPromptEnabled)
//common.OptionMap["CheckSensitiveOnCompletionEnabled"] = strconv.FormatBool(constant.CheckSensitiveOnCompletionEnabled)
common.OptionMap["StopOnSensitiveEnabled"] = strconv.FormatBool(constant.StopOnSensitiveEnabled)
common.OptionMap["SensitiveWords"] = constant.SensitiveWordsToString()
common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(constant.StreamCacheQueueLength)
common.OptionMap["MjNotifyEnabled"] = strconv.FormatBool(setting.MjNotifyEnabled)
common.OptionMap["MjAccountFilterEnabled"] = strconv.FormatBool(setting.MjAccountFilterEnabled)
common.OptionMap["MjModeClearEnabled"] = strconv.FormatBool(setting.MjModeClearEnabled)
common.OptionMap["MjForwardUrlEnabled"] = strconv.FormatBool(setting.MjForwardUrlEnabled)
common.OptionMap["MjActionCheckSuccessEnabled"] = strconv.FormatBool(setting.MjActionCheckSuccessEnabled)
common.OptionMap["CheckSensitiveEnabled"] = strconv.FormatBool(setting.CheckSensitiveEnabled)
common.OptionMap["DemoSiteEnabled"] = strconv.FormatBool(operation_setting.DemoSiteEnabled)
common.OptionMap["SelfUseModeEnabled"] = strconv.FormatBool(operation_setting.SelfUseModeEnabled)
common.OptionMap["ModelRequestRateLimitEnabled"] = strconv.FormatBool(setting.ModelRequestRateLimitEnabled)
common.OptionMap["CheckSensitiveOnPromptEnabled"] = strconv.FormatBool(setting.CheckSensitiveOnPromptEnabled)
common.OptionMap["StopOnSensitiveEnabled"] = strconv.FormatBool(setting.StopOnSensitiveEnabled)
common.OptionMap["SensitiveWords"] = setting.SensitiveWordsToString()
common.OptionMap["StreamCacheQueueLength"] = strconv.Itoa(setting.StreamCacheQueueLength)
common.OptionMap["AutomaticDisableKeywords"] = operation_setting.AutomaticDisableKeywordsToString()
// 自动添加所有注册的模型配置
modelConfigs := config.GlobalConfig.ExportAllConfigs()
for k, v := range modelConfigs {
common.OptionMap[k] = v
}
common.OptionMapRWMutex.Unlock()
loadOptionsFromDatabase()
@@ -152,6 +174,13 @@ func updateOptionMap(key string, value string) (err error) {
common.OptionMapRWMutex.Lock()
defer common.OptionMapRWMutex.Unlock()
common.OptionMap[key] = value
// 检查是否是模型配置 - 使用更规范的方式处理
if handleConfigUpdate(key, value) {
return nil // 已由配置系统处理
}
// 处理传统配置项...
if strings.HasSuffix(key, "Permission") {
intValue, _ := strconv.Atoi(value)
switch key {
@@ -165,7 +194,7 @@ func updateOptionMap(key string, value string) (err error) {
common.ImageDownloadPermission = intValue
}
}
if strings.HasSuffix(key, "Enabled") || key == "DefaultCollapseSidebar" {
if strings.HasSuffix(key, "Enabled") || key == "DefaultCollapseSidebar" || key == "DefaultUseAutoGroup" {
boolValue := value == "true"
switch key {
case "PasswordRegisterEnabled":
@@ -209,25 +238,33 @@ func updateOptionMap(key string, value string) (err error) {
case "DefaultCollapseSidebar":
common.DefaultCollapseSidebar = boolValue
case "MjNotifyEnabled":
constant.MjNotifyEnabled = boolValue
setting.MjNotifyEnabled = boolValue
case "MjAccountFilterEnabled":
constant.MjAccountFilterEnabled = boolValue
setting.MjAccountFilterEnabled = boolValue
case "MjModeClearEnabled":
constant.MjModeClearEnabled = boolValue
setting.MjModeClearEnabled = boolValue
case "MjForwardUrlEnabled":
constant.MjForwardUrlEnabled = boolValue
setting.MjForwardUrlEnabled = boolValue
case "MjActionCheckSuccessEnabled":
constant.MjActionCheckSuccessEnabled = boolValue
setting.MjActionCheckSuccessEnabled = boolValue
case "CheckSensitiveEnabled":
constant.CheckSensitiveEnabled = boolValue
setting.CheckSensitiveEnabled = boolValue
case "DemoSiteEnabled":
operation_setting.DemoSiteEnabled = boolValue
case "SelfUseModeEnabled":
operation_setting.SelfUseModeEnabled = boolValue
case "CheckSensitiveOnPromptEnabled":
constant.CheckSensitiveOnPromptEnabled = boolValue
//case "CheckSensitiveOnCompletionEnabled":
// constant.CheckSensitiveOnCompletionEnabled = boolValue
setting.CheckSensitiveOnPromptEnabled = boolValue
case "ModelRequestRateLimitEnabled":
setting.ModelRequestRateLimitEnabled = boolValue
case "StopOnSensitiveEnabled":
constant.StopOnSensitiveEnabled = boolValue
setting.StopOnSensitiveEnabled = boolValue
case "SMTPSSLEnabled":
common.SMTPSSLEnabled = boolValue
case "WorkerAllowHttpImageRequestEnabled":
setting.WorkerAllowHttpImageRequestEnabled = boolValue
case "DefaultUseAutoGroup":
setting.DefaultUseAutoGroup = boolValue
}
}
switch key {
@@ -245,25 +282,27 @@ func updateOptionMap(key string, value string) (err error) {
case "SMTPToken":
common.SMTPToken = value
case "ServerAddress":
constant.ServerAddress = value
setting.ServerAddress = value
case "WorkerUrl":
constant.WorkerUrl = value
setting.WorkerUrl = value
case "WorkerValidKey":
constant.WorkerValidKey = value
setting.WorkerValidKey = value
case "PayAddress":
constant.PayAddress = value
setting.PayAddress = value
case "Chats":
err = constant.UpdateChatsByJsonString(value)
err = setting.UpdateChatsByJsonString(value)
case "AutoGroups":
err = setting.UpdateAutoGroupsByJsonString(value)
case "CustomCallbackAddress":
constant.CustomCallbackAddress = value
setting.CustomCallbackAddress = value
case "EpayId":
constant.EpayId = value
setting.EpayId = value
case "EpayKey":
constant.EpayKey = value
setting.EpayKey = value
case "Price":
constant.Price, _ = strconv.ParseFloat(value, 64)
setting.Price, _ = strconv.ParseFloat(value, 64)
case "MinTopUp":
constant.MinTopUp, _ = strconv.Atoi(value)
setting.MinTopUp, _ = strconv.Atoi(value)
case "TopupGroupRatio":
err = common.UpdateTopupGroupRatioByJSONString(value)
case "GitHubClientId":
@@ -304,6 +343,14 @@ func updateOptionMap(key string, value string) (err error) {
common.QuotaRemindThreshold, _ = strconv.Atoi(value)
case "PreConsumedQuota":
common.PreConsumedQuota, _ = strconv.Atoi(value)
case "ModelRequestRateLimitCount":
setting.ModelRequestRateLimitCount, _ = strconv.Atoi(value)
case "ModelRequestRateLimitDurationMinutes":
setting.ModelRequestRateLimitDurationMinutes, _ = strconv.Atoi(value)
case "ModelRequestRateLimitSuccessCount":
setting.ModelRequestRateLimitSuccessCount, _ = strconv.Atoi(value)
case "ModelRequestRateLimitGroup":
err = setting.UpdateModelRequestRateLimitGroupByJSONString(value)
case "RetryTimes":
common.RetryTimes, _ = strconv.Atoi(value)
case "DataExportInterval":
@@ -311,29 +358,60 @@ func updateOptionMap(key string, value string) (err error) {
case "DataExportDefaultTime":
common.DataExportDefaultTime = value
case "ModelRatio":
err = common.UpdateModelRatioByJSONString(value)
err = operation_setting.UpdateModelRatioByJSONString(value)
case "GroupRatio":
err = common.UpdateGroupRatioByJSONString(value)
err = setting.UpdateGroupRatioByJSONString(value)
case "GroupGroupRatio":
err = setting.UpdateGroupGroupRatioByJSONString(value)
case "UserUsableGroups":
err = common.UpdateUserUsableGroupsByJSONString(value)
err = setting.UpdateUserUsableGroupsByJSONString(value)
case "CompletionRatio":
err = common.UpdateCompletionRatioByJSONString(value)
err = operation_setting.UpdateCompletionRatioByJSONString(value)
case "ModelPrice":
err = common.UpdateModelPriceByJSONString(value)
err = operation_setting.UpdateModelPriceByJSONString(value)
case "CacheRatio":
err = operation_setting.UpdateCacheRatioByJSONString(value)
case "TopUpLink":
common.TopUpLink = value
case "ChatLink":
common.ChatLink = value
case "ChatLink2":
common.ChatLink2 = value
//case "ChatLink":
// common.ChatLink = value
//case "ChatLink2":
// common.ChatLink2 = value
case "ChannelDisableThreshold":
common.ChannelDisableThreshold, _ = strconv.ParseFloat(value, 64)
case "QuotaPerUnit":
common.QuotaPerUnit, _ = strconv.ParseFloat(value, 64)
case "SensitiveWords":
constant.SensitiveWordsFromString(value)
setting.SensitiveWordsFromString(value)
case "AutomaticDisableKeywords":
operation_setting.AutomaticDisableKeywordsFromString(value)
case "StreamCacheQueueLength":
constant.StreamCacheQueueLength, _ = strconv.Atoi(value)
setting.StreamCacheQueueLength, _ = strconv.Atoi(value)
}
return err
}
// handleConfigUpdate 处理分层配置更新,返回是否已处理
func handleConfigUpdate(key, value string) bool {
parts := strings.SplitN(key, ".", 2)
if len(parts) != 2 {
return false // 不是分层配置
}
configName := parts[0]
configKey := parts[1]
// 获取配置对象
cfg := config.GlobalConfig.Get(configName)
if cfg == nil {
return false // 未注册的配置
}
// 更新配置
configMap := map[string]string{
configKey: value,
}
config.UpdateConfigFromMap(cfg, configMap)
return true // 已处理
}
+5 -3
View File
@@ -2,6 +2,7 @@ package model
import (
"one-api/common"
"one-api/setting/operation_setting"
"sync"
"time"
)
@@ -64,13 +65,14 @@ func updatePricing() {
ModelName: model,
EnableGroup: groups,
}
modelPrice, findPrice := common.GetModelPrice(model, false)
modelPrice, findPrice := operation_setting.GetModelPrice(model, false)
if findPrice {
pricing.ModelPrice = modelPrice
pricing.QuotaType = 1
} else {
pricing.ModelRatio = common.GetModelRatio(model)
pricing.CompletionRatio = common.GetCompletionRatio(model)
modelRatio, _ := operation_setting.GetModelRatio(model)
pricing.ModelRatio = modelRatio
pricing.CompletionRatio = operation_setting.GetCompletionRatio(model)
pricing.QuotaType = 0
}
pricingMap = append(pricingMap, pricing)
+86 -10
View File
@@ -3,8 +3,10 @@ package model
import (
"errors"
"fmt"
"gorm.io/gorm"
"one-api/common"
"strconv"
"gorm.io/gorm"
)
type Redemption struct {
@@ -19,18 +21,83 @@ type Redemption struct {
Count int `json:"count" gorm:"-:all"` // only for api request
UsedUserId int `json:"used_user_id"`
DeletedAt gorm.DeletedAt `gorm:"index"`
ExpiredTime int64 `json:"expired_time" gorm:"bigint"` // 过期时间,0 表示不过期
}
func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) {
var redemptions []*Redemption
var err error
err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
return redemptions, err
func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
// 开始事务
tx := DB.Begin()
if tx.Error != nil {
return nil, 0, tx.Error
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 获取总数
err = tx.Model(&Redemption{}).Count(&total).Error
if err != nil {
tx.Rollback()
return nil, 0, err
}
// 获取分页数据
err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
if err != nil {
tx.Rollback()
return nil, 0, err
}
// 提交事务
if err = tx.Commit().Error; err != nil {
return nil, 0, err
}
return redemptions, total, nil
}
func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) {
err = DB.Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&redemptions).Error
return redemptions, err
func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
tx := DB.Begin()
if tx.Error != nil {
return nil, 0, tx.Error
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// Build query based on keyword type
query := tx.Model(&Redemption{})
// Only try to convert to ID if the string represents a valid integer
if id, err := strconv.Atoi(keyword); err == nil {
query = query.Where("id = ? OR name LIKE ?", id, keyword+"%")
} else {
query = query.Where("name LIKE ?", keyword+"%")
}
// Get total count
err = query.Count(&total).Error
if err != nil {
tx.Rollback()
return nil, 0, err
}
// Get paginated data
err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
if err != nil {
tx.Rollback()
return nil, 0, err
}
if err = tx.Commit().Error; err != nil {
return nil, 0, err
}
return redemptions, total, nil
}
func GetRedemptionById(id int) (*Redemption, error) {
@@ -65,6 +132,9 @@ func Redeem(key string, userId int) (quota int, err error) {
if redemption.Status != common.RedemptionCodeStatusEnabled {
return errors.New("该兑换码已被使用")
}
if redemption.ExpiredTime != 0 && redemption.ExpiredTime < common.GetTimestamp() {
return errors.New("该兑换码已过期")
}
err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error
if err != nil {
return err
@@ -96,7 +166,7 @@ func (redemption *Redemption) SelectUpdate() error {
// Update Make sure your token's fields is completed, because this will update non-zero values
func (redemption *Redemption) Update() error {
var err error
err = DB.Model(redemption).Select("name", "status", "quota", "redeemed_time").Updates(redemption).Error
err = DB.Model(redemption).Select("name", "status", "quota", "redeemed_time", "expired_time").Updates(redemption).Error
return err
}
@@ -117,3 +187,9 @@ func DeleteRedemptionById(id int) (err error) {
}
return redemption.Delete()
}
func DeleteInvalidRedemptions() (int64, error) {
now := common.GetTimestamp()
result := DB.Where("status IN ? OR (status = ? AND expired_time != 0 AND expired_time < ?)", []int{common.RedemptionCodeStatusUsed, common.RedemptionCodeStatusDisabled}, common.RedemptionCodeStatusEnabled, now).Delete(&Redemption{})
return result.RowsAffected, result.Error
}
+16
View File
@@ -0,0 +1,16 @@
package model
type Setup struct {
ID uint `json:"id" gorm:"primaryKey"`
Version string `json:"version" gorm:"type:varchar(50);not null"`
InitializedAt int64 `json:"initialized_at" gorm:"type:bigint;not null"`
}
func GetSetup() *Setup {
var setup Setup
err := DB.First(&setup).Error
if err != nil {
return nil
}
return &setup
}
+61
View File
@@ -302,3 +302,64 @@ func SumUsedTaskQuota(queryParams SyncTaskQueryParams) (stat []TaskQuotaUsage, e
err = query.Select("mode, sum(quota) as count").Group("mode").Find(&stat).Error
return stat, err
}
// TaskCountAllTasks returns total tasks that match the given query params (admin usage)
func TaskCountAllTasks(queryParams SyncTaskQueryParams) int64 {
var total int64
query := DB.Model(&Task{})
if queryParams.ChannelID != "" {
query = query.Where("channel_id = ?", queryParams.ChannelID)
}
if queryParams.Platform != "" {
query = query.Where("platform = ?", queryParams.Platform)
}
if queryParams.UserID != "" {
query = query.Where("user_id = ?", queryParams.UserID)
}
if len(queryParams.UserIDs) != 0 {
query = query.Where("user_id in (?)", queryParams.UserIDs)
}
if queryParams.TaskID != "" {
query = query.Where("task_id = ?", queryParams.TaskID)
}
if queryParams.Action != "" {
query = query.Where("action = ?", queryParams.Action)
}
if queryParams.Status != "" {
query = query.Where("status = ?", queryParams.Status)
}
if queryParams.StartTimestamp != 0 {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != 0 {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}
// TaskCountAllUserTask returns total tasks for given user
func TaskCountAllUserTask(userId int, queryParams SyncTaskQueryParams) int64 {
var total int64
query := DB.Model(&Task{}).Where("user_id = ?", userId)
if queryParams.TaskID != "" {
query = query.Where("task_id = ?", queryParams.TaskID)
}
if queryParams.Action != "" {
query = query.Where("action = ?", queryParams.Action)
}
if queryParams.Status != "" {
query = query.Where("status = ?", queryParams.Status)
}
if queryParams.Platform != "" {
query = query.Where("platform = ?", queryParams.Platform)
}
if queryParams.StartTimestamp != 0 {
query = query.Where("submit_time >= ?", queryParams.StartTimestamp)
}
if queryParams.EndTimestamp != 0 {
query = query.Where("submit_time <= ?", queryParams.EndTimestamp)
}
_ = query.Count(&total).Error
return total
}

Some files were not shown because too many files have changed in this diff Show More