feat: add Universal Provider feature (#348)

* feat: add Universal Provider feature

- Add Universal Provider data structures and type definitions
- Implement backend CRUD operations and sync functionality
- Add frontend UI components (UniversalProviderPanel, Card, FormModal)
- Add NewAPI icon and preset configuration
- Support cross-app (Claude/Codex/Gemini) configuration sync
- Add website URL field for providers
- Implement real-time refresh via event notifications
- Add i18n support (Chinese/English/Japanese)

* feat: integrate universal provider presets into add provider dialog

- Add universal provider presets (NewAPI, Custom Gateway) to preset selector
- Show universal presets with Layers icon badge in preset selector
- Open UniversalProviderFormModal when universal preset is clicked
- Pass initialPreset to auto-fill form when opened from add dialog
- Add i18n keys for addSuccess/addFailed messages
- Keep separate universal provider panel for management

* refactor: move universal provider management to add dialog

- Remove Layers button from main navigation header
- Add 'Manage' button next to universal provider presets
- Open UniversalProviderPanel from within add provider dialog
- Add i18n keys for 'manage' in all locales

* style: display universal provider presets on separate line

- Move universal provider section to a new row with border separator
- Add label '统一供应商:' to clarify the section

* style: unify universal provider label style with preset label

- Use FormLabel component for consistent styling
- Add background to 'Manage' button matching preset buttons
- Update icon size and button padding for consistency

* feat: add sync functionality and JSON preview for Universal Provider

* fix: add missing in_failover_queue field to Provider structs

After rebasing to main, the Provider struct gained a new
`in_failover_queue` field. This fix adds the missing field
to the three to_*_provider() methods in UniversalProvider.

* refactor: redesign AddProviderDialog with tab-based layout

- Add tabs to separate app-specific providers and universal providers
- Move "Add Universal Provider" button from panel header to footer
- Remove unused handleAdd callback and clean up imports
- Update emptyHint i18n text to reference the footer button

* fix: append /v1 suffix to Codex base_url in Universal Provider

Codex uses OpenAI-compatible API which requires the /v1 endpoint suffix.
The Universal Provider now automatically appends /v1 to base_url when
generating Codex provider config if not already present.

- Handle trailing slashes to avoid double slashes
- Apply fix to both backend (to_codex_provider) and frontend preview

* feat: auto-sync universal provider to apps on creation

Previously, users had to manually click sync after adding a universal
provider. Now it automatically syncs to Claude/Codex/Gemini on creation,
providing a smoother user experience.

---------

Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
Calcium-Ion
2025-12-26 22:47:24 +08:00
committed by GitHub
parent a24753f074
commit 8fe5c1041a
25 changed files with 2336 additions and 44 deletions
+46
View File
@@ -66,6 +66,8 @@
"exitEditMode": "Exit Edit Mode"
},
"provider": {
"tabProvider": "Provider",
"tabUniversal": "Universal",
"noProviders": "No providers added yet",
"noProvidersDescription": "Click the \"Add Provider\" button in the top right to configure your first API provider",
"currentlyUsing": "Currently Using",
@@ -1135,5 +1137,49 @@
"streamingFirstByte": "Streaming First Byte Timeout",
"streamingIdle": "Streaming Idle Timeout",
"nonStreaming": "Non-Streaming Timeout"
},
"universalProvider": {
"title": "Universal Provider",
"description": "Universal providers manage Claude, Codex, and Gemini configurations simultaneously. Changes are automatically synced to all enabled apps.",
"add": "Add Universal Provider",
"edit": "Edit Universal Provider",
"empty": "No universal providers yet",
"emptyHint": "Click the \"Add Universal Provider\" button below to create one",
"selectPreset": "Select Preset Type",
"name": "Name",
"namePlaceholder": "e.g., My NewAPI",
"baseUrl": "API URL",
"apiKey": "API Key",
"websiteUrl": "Website URL",
"websiteUrlPlaceholder": "https://example.com (optional, displayed in the list)",
"notes": "Notes",
"notesPlaceholder": "Optional: Add notes",
"enabledApps": "Enabled Apps",
"modelConfig": "Model Configuration",
"model": "Model",
"sync": "Sync to Apps",
"synced": "Synced to all apps",
"syncError": "Sync failed",
"noAppsEnabled": "No apps enabled",
"added": "Universal provider added",
"addedAndSynced": "Universal provider added and synced",
"updated": "Universal provider updated",
"deleted": "Universal provider deleted",
"addSuccess": "Universal provider added successfully",
"addFailed": "Failed to add universal provider",
"manage": "Manage",
"loadError": "Failed to load universal providers",
"saveError": "Failed to save universal provider",
"deleteError": "Failed to delete universal provider",
"deleteConfirmTitle": "Delete Universal Provider",
"deleteConfirmDescription": "Are you sure you want to delete \"{{name}}\"? This will also delete its generated provider configurations in each app.",
"syncConfirmTitle": "Sync Universal Provider",
"syncConfirmDescription": "Syncing \"{{name}}\" will overwrite the associated provider configurations in Claude, Codex, and Gemini. Do you want to continue?",
"syncConfirm": "Sync",
"saveAndSync": "Save & Sync",
"savedAndSynced": "Saved and synced to all apps",
"saveAndSyncError": "Failed to save and sync",
"configJsonPreview": "Config JSON Preview",
"configJsonPreviewHint": "The following configurations will be synced to each app (only the displayed fields will be overwritten, other custom settings will be preserved)"
}
}
+45
View File
@@ -66,6 +66,8 @@
"exitEditMode": "編集モードを終了"
},
"provider": {
"tabProvider": "プロバイダー",
"tabUniversal": "統一プロバイダー",
"noProviders": "まだプロバイダーがありません",
"noProvidersDescription": "右上の「プロバイダーを追加」を押して最初の API プロバイダーを登録してください",
"currentlyUsing": "現在使用中",
@@ -1135,5 +1137,48 @@
"streamingFirstByte": "ストリーミング初回バイトタイムアウト",
"streamingIdle": "ストリーミングアイドルタイムアウト",
"nonStreaming": "非ストリーミングタイムアウト"
},
"universalProvider": {
"title": "統合プロバイダー",
"description": "統合プロバイダーは Claude、Codex、Gemini の設定を同時に管理します。変更は有効なすべてのアプリに自動的に同期されます。",
"add": "統合プロバイダーを追加",
"edit": "統合プロバイダーを編集",
"empty": "統合プロバイダーがありません",
"emptyHint": "下の「統合プロバイダーを追加」ボタンをクリックして作成してください",
"selectPreset": "プリセットタイプを選択",
"name": "名前",
"namePlaceholder": "例:私の NewAPI",
"baseUrl": "API URL",
"apiKey": "API キー",
"websiteUrl": "ウェブサイト URL",
"websiteUrlPlaceholder": "https://example.com(オプション、リストに表示されます)",
"notes": "メモ",
"notesPlaceholder": "オプション:メモを追加",
"enabledApps": "有効なアプリ",
"modelConfig": "モデル設定",
"model": "モデル",
"sync": "アプリに同期",
"synced": "すべてのアプリに同期しました",
"syncError": "同期に失敗しました",
"noAppsEnabled": "有効なアプリがありません",
"added": "統合プロバイダーを追加しました",
"updated": "統合プロバイダーを更新しました",
"deleted": "統合プロバイダーを削除しました",
"addSuccess": "統合プロバイダーを追加しました",
"addFailed": "統合プロバイダーの追加に失敗しました",
"manage": "管理",
"loadError": "統合プロバイダーの読み込みに失敗しました",
"saveError": "統合プロバイダーの保存に失敗しました",
"deleteError": "統合プロバイダーの削除に失敗しました",
"deleteConfirmTitle": "統合プロバイダーを削除",
"deleteConfirmDescription": "「{{name}}」を削除してもよろしいですか?各アプリで生成されたプロバイダー設定も削除されます。",
"syncConfirmTitle": "統合プロバイダーを同期",
"syncConfirmDescription": "「{{name}}」を同期すると、Claude、Codex、Gemini の関連プロバイダー設定が上書きされます。続行しますか?",
"syncConfirm": "同期",
"saveAndSync": "保存して同期",
"savedAndSynced": "すべてのアプリに保存・同期されました",
"saveAndSyncError": "保存と同期に失敗しました",
"configJsonPreview": "設定 JSON プレビュー",
"configJsonPreviewHint": "以下は各アプリに同期される設定内容です(表示されているフィールドのみ上書きされ、他のカスタム設定は保持されます)"
}
}
+46
View File
@@ -66,6 +66,8 @@
"exitEditMode": "退出编辑模式"
},
"provider": {
"tabProvider": "供应商",
"tabUniversal": "统一供应商",
"noProviders": "还没有添加任何供应商",
"noProvidersDescription": "点击右上角的\"添加供应商\"按钮开始配置您的第一个API供应商",
"currentlyUsing": "当前使用",
@@ -1135,5 +1137,49 @@
"streamingFirstByte": "流式首字节超时",
"streamingIdle": "流式静默超时",
"nonStreaming": "非流式超时"
},
"universalProvider": {
"title": "统一供应商",
"description": "统一供应商可以同时管理 Claude、Codex 和 Gemini 的配置。修改后会自动同步到所有启用的应用。",
"add": "添加统一供应商",
"edit": "编辑统一供应商",
"empty": "还没有统一供应商",
"emptyHint": "点击下方「添加统一供应商」按钮创建一个",
"selectPreset": "选择预设类型",
"name": "名称",
"namePlaceholder": "例如:我的 NewAPI",
"baseUrl": "API 地址",
"apiKey": "API Key",
"websiteUrl": "官网地址",
"websiteUrlPlaceholder": "https://example.com(可选,用于在列表中显示)",
"notes": "备注",
"notesPlaceholder": "可选:添加备注信息",
"enabledApps": "启用的应用",
"modelConfig": "模型配置",
"model": "模型",
"sync": "同步到应用",
"synced": "已同步到所有应用",
"syncError": "同步失败",
"noAppsEnabled": "未启用任何应用",
"added": "统一供应商已添加",
"addedAndSynced": "统一供应商已添加并同步",
"updated": "统一供应商已更新",
"deleted": "统一供应商已删除",
"addSuccess": "统一供应商添加成功",
"addFailed": "统一供应商添加失败",
"manage": "管理",
"loadError": "加载统一供应商失败",
"saveError": "保存统一供应商失败",
"deleteError": "删除统一供应商失败",
"deleteConfirmTitle": "删除统一供应商",
"deleteConfirmDescription": "确定要删除 \"{{name}}\" 吗?这将同时删除它在各应用中生成的供应商配置。",
"syncConfirmTitle": "同步统一供应商",
"syncConfirmDescription": "同步 \"{{name}}\" 将会覆盖 Claude、Codex 和 Gemini 中关联的供应商配置。确定要继续吗?",
"syncConfirm": "同步",
"saveAndSync": "保存并同步",
"savedAndSynced": "已保存并同步到所有应用",
"saveAndSyncError": "保存并同步失败",
"configJsonPreview": "配置 JSON 预览",
"configJsonPreviewHint": "以下是将要同步到各应用的配置内容(仅覆盖显示的字段,保留其他自定义配置)"
}
}