From 36c87451d95c5bbbad1081cae2ba8bae23b16036 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Fri, 25 Apr 2025 16:55:36 +0800 Subject: [PATCH] feat: enhance OAuth provider settings with new functionality - Added a new ProviderOAuth component to manage OAuth authentication and billing for providers. - Updated existing components to integrate the new ProviderOAuth functionality. - Enhanced internationalization support for OAuth-related texts across multiple languages. - Introduced new utility functions for handling provider billing. --- src/main/services/WindowService.ts | 4 +- .../src/components/OAuth/OAuthButton.tsx | 5 +- src/renderer/src/config/providers.ts | 2 +- src/renderer/src/i18n/locales/en-us.json | 8 +- src/renderer/src/i18n/locales/ja-jp.json | 8 +- src/renderer/src/i18n/locales/ru-ru.json | 8 +- src/renderer/src/i18n/locales/zh-cn.json | 42 +++++---- src/renderer/src/i18n/locales/zh-tw.json | 8 +- .../ProviderSettings/ProviderOAuth.tsx | 92 +++++++++++++++++++ .../ProviderSettings/ProviderSetting.tsx | 21 +++-- src/renderer/src/utils/oauth.ts | 23 +++++ 11 files changed, 186 insertions(+), 35 deletions(-) create mode 100644 src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 031e35b24d..78e3574a63 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -208,9 +208,11 @@ export class WindowService { const oauthProviderUrls = [ 'https://account.siliconflow.cn/oauth', + 'https://cloud.siliconflow.cn/bills', 'https://cloud.siliconflow.cn/expensebill', 'https://aihubmix.com/token', - 'https://aihubmix.com/topup' + 'https://aihubmix.com/topup', + 'https://aihubmix.com/statistics' ] if (oauthProviderUrls.some((link) => url.startsWith(link))) { diff --git a/src/renderer/src/components/OAuth/OAuthButton.tsx b/src/renderer/src/components/OAuth/OAuthButton.tsx index 09b2a8c76f..6160783694 100644 --- a/src/renderer/src/components/OAuth/OAuthButton.tsx +++ b/src/renderer/src/components/OAuth/OAuthButton.tsx @@ -11,6 +11,7 @@ interface Props extends ButtonProps { const OAuthButton: FC = ({ provider, onSuccess, ...buttonProps }) => { const { t } = useTranslation() + const onAuth = () => { const handleSuccess = (key: string) => { if (key.trim()) { @@ -29,8 +30,8 @@ const OAuthButton: FC = ({ provider, onSuccess, ...buttonProps }) => { } return ( - ) } diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index f90de1985b..5c1b30e39a 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -148,7 +148,7 @@ export const PROVIDER_CONFIG = { url: 'https://api.siliconflow.cn' }, websites: { - official: 'https://www.siliconflow.cn/', + official: 'https://www.siliconflow.cn', apiKey: 'https://cloud.siliconflow.cn/i/d1nTBKXU', docs: 'https://docs.siliconflow.cn/', models: 'https://docs.siliconflow.cn/docs/model-names' diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index e9a163035a..0c8fc0d0c4 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1257,10 +1257,16 @@ "basic_auth.user_name.tip": "Left empty to disable", "basic_auth.password": "Password", "basic_auth.password.tip": "", - "charge": "Charge", + "charge": "Balance Recharge", + "bills": "Fee Bills", "check": "Check", "check_all_keys": "Check All Keys", "check_multiple_keys": "Check Multiple API Keys", + "oauth": { + "button": "Login with {{provider}}", + "description": "This service is provided by {{provider}}", + "official_website": "Official Website" + }, "copilot": { "auth_failed": "Github Copilot authentication failed.", "auth_success": "GitHub Copilot authentication successful.", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index bbcac597a1..a15f4981be 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1256,10 +1256,16 @@ "basic_auth.user_name.tip": "空欄で無効化", "basic_auth.password": "パスワード", "basic_auth.password.tip": "", - "charge": "充電", + "charge": "残高充電", + "bills": "費用帳單", "check": "チェック", "check_all_keys": "すべてのキーをチェック", "check_multiple_keys": "複数のAPIキーをチェック", + "oauth": { + "button": "{{provider}} アカウントでログイン", + "description": "本サービスは{{provider}}によって提供されます", + "official_website": "公式サイト" + }, "copilot": { "auth_failed": "Github Copilotの認証に失敗しました。", "auth_success": "Github Copilotの認証が成功しました", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 2abcaa3272..db2d2ed128 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1256,10 +1256,16 @@ "basic_auth.user_name.tip": "Оставить пустым для отключения", "basic_auth.password": "Пароль", "basic_auth.password.tip": "", - "charge": "Пополнить", + "charge": "Пополнить баланс", + "bills": "Счета за услуги", "check": "Проверить", "check_all_keys": "Проверить все ключи", "check_multiple_keys": "Проверить несколько ключей API", + "oauth": { + "button": "Войти с {{provider}}", + "description": "Сервис предоставляется {{provider}}", + "official_website": "Официальный сайт" + }, "copilot": { "auth_failed": "Github Copilot认证失败", "auth_success": "Github Copilot认证成功", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 31584f242a..2a0434b398 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -837,7 +837,7 @@ }, "joplin": { "check": { - "button": "检查", + "button": "检测", "empty_token": "请先输入 Joplin 授权令牌", "empty_url": "请先输入 Joplin 剪裁服务监听 URL", "fail": "Joplin 连接验证失败", @@ -866,7 +866,7 @@ "notion.auto_split": "导出对话时自动分页", "notion.auto_split_tip": "当要导出的话题过长时自动分页导出到Notion", "notion.check": { - "button": "检查", + "button": "检测", "empty_api_key": "未配置 API key", "empty_database_id": "未配置 Database ID", "error": "连接异常,请检查网络及 API key 和 Database ID 是否正确", @@ -935,7 +935,7 @@ }, "yuque": { "check": { - "button": "检查", + "button": "检测", "empty_repo_url": "请先输入知识库URL", "empty_token": "请先输入语雀Token", "fail": "语雀连接验证失败", @@ -969,8 +969,8 @@ "root_path": "文档根路径", "root_path_placeholder": "例如:/CherryStudio", "check": { - "title": "连接检查", - "button": "检查", + "title": "连接检测", + "button": "检测", "empty_config": "请填写API地址和令牌", "success": "连接成功", "fail": "连接失败,请检查API地址和令牌", @@ -1206,20 +1206,20 @@ "models.add.model_name": "模型名称", "models.add.model_name.placeholder": "例如 GPT-3.5", "models.check.all": "所有", - "models.check.all_models_passed": "所有模型检查通过", - "models.check.button_caption": "健康检查", + "models.check.all_models_passed": "所有模型检测通过", + "models.check.button_caption": "健康检测", "models.check.disabled": "关闭", - "models.check.enable_concurrent": "并发检查", + "models.check.enable_concurrent": "并发检测", "models.check.enabled": "开启", "models.check.failed": "失败", "models.check.keys_status_count": "通过:{{count_passed}}个密钥,失败:{{count_failed}}个密钥", - "models.check.model_status_summary": "{{provider}}: {{count_passed}} 个模型完成健康检查(其中 {{count_partial}} 个模型用某些密钥无法访问),{{count_failed}} 个模型完全无法访问。", + "models.check.model_status_summary": "{{provider}}: {{count_passed}} 个模型完成健康检测(其中 {{count_partial}} 个模型用某些密钥无法访问),{{count_failed}} 个模型完全无法访问。", "models.check.no_api_keys": "未找到API密钥,请先添加API密钥。", "models.check.passed": "通过", "models.check.select_api_key": "选择要使用的API密钥:", "models.check.single": "单个", "models.check.start": "开始", - "models.check.title": "模型健康检查", + "models.check.title": "模型健康检测", "models.check.use_all_keys": "使用密钥", "models.default_assistant_model": "默认助手模型", "models.default_assistant_model_description": "创建新助手时使用的模型,如果助手未设置模型,则使用此模型", @@ -1257,10 +1257,16 @@ "basic_auth.user_name.tip": "留空以禁用", "basic_auth.password": "密码", "basic_auth.password.tip": "", - "charge": "充值", - "check": "检查", - "check_all_keys": "检查所有密钥", - "check_multiple_keys": "检查多个 API 密钥", + "charge": "余额充值", + "bills": "费用账单", + "check": "检测", + "check_all_keys": "检测所有密钥", + "check_multiple_keys": "检测多个 API 密钥", + "oauth": { + "button": "使用{{provider}}账号登录", + "description": "本服务由{{provider}}提供", + "official_website": "官方网站" + }, "copilot": { "auth_failed": "Github Copilot 认证失败", "auth_success": "Github Copilot 认证成功", @@ -1291,8 +1297,8 @@ "docs_more_details": "获取更多详情", "get_api_key": "点击这里获取密钥", "is_not_support_array_content": "开启兼容模式", - "no_models_for_check": "没有可以被检查的模型(例如对话模型)", - "not_checked": "未检查", + "no_models_for_check": "没有可以被检测的模型(例如对话模型)", + "not_checked": "未检测", "remove_duplicate_keys": "移除重复密钥", "remove_invalid_keys": "删除无效密钥", "search": "搜索模型平台...", @@ -1358,13 +1364,13 @@ "blacklist": "黑名单", "blacklist_description": "在搜索结果中不会出现以下网站的结果", "blacklist_tooltip": "请使用以下格式(换行分隔)\n匹配模式: *://*.example.com/*\n正则表达式: /example\\.(net|org)/", - "check": "检查", + "check": "检测", "check_failed": "验证失败", "check_success": "验证成功", "overwrite": "覆盖服务商搜索", "overwrite_tooltip": "强制使用搜索服务商而不是大语言模型进行搜索", "get_api_key": "点击这里获取密钥", - "no_provider_selected": "请选择搜索服务商后再检查", + "no_provider_selected": "请选择搜索服务商后再检测", "search_max_result": "搜索结果个数", "search_provider": "搜索服务商", "search_provider_placeholder": "选择一个搜索服务商", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 2f25133421..badf417485 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1256,10 +1256,16 @@ "basic_auth.user_name.tip": "留空以停用", "basic_auth.password": "密碼", "basic_auth.password.tip": "", - "charge": "儲值", + "charge": "餘額充值", + "bills": "費用帳單", "check": "檢查", "check_all_keys": "檢查所有金鑰", "check_multiple_keys": "檢查多個 API 金鑰", + "oauth": { + "button": "使用{{provider}}帳號登入", + "description": "本服務由{{provider}}提供", + "official_website": "官方網站" + }, "copilot": { "auth_failed": "Github Copilot認證失敗", "auth_success": "Github Copilot 認證成功", diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx new file mode 100644 index 0000000000..5e645fcd60 --- /dev/null +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx @@ -0,0 +1,92 @@ +import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp' +import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png' +import { HStack } from '@renderer/components/Layout' +import OAuthButton from '@renderer/components/OAuth/OAuthButton' +import { PROVIDER_CONFIG } from '@renderer/config/providers' +import { Provider } from '@renderer/types' +import { providerBills, providerCharge } from '@renderer/utils/oauth' +import { Button } from 'antd' +import { isEmpty } from 'lodash' +import { ReceiptText } from 'lucide-react' +import { CircleDollarSign } from 'lucide-react' +import { FC } from 'react' +import { Trans, useTranslation } from 'react-i18next' +import styled from 'styled-components' + +interface Props { + provider: Provider + setApiKey: (apiKey: string) => void +} + +const PROVIDER_LOGO_MAP = { + silicon: SiliconFlowProviderLogo, + aihubmix: AiHubMixProviderLogo +} + +const ProviderOAuth: FC = ({ provider, setApiKey }) => { + const { t } = useTranslation() + + const providerWebsite = + PROVIDER_CONFIG[provider.id]?.api?.url.replace('https://', '').replace('api.', '') || provider.name + + return ( + + + {isEmpty(provider.apiKey) ? ( + + {t('settings.provider.oauth.button', { provider: t(`provider.${provider.id}`) })} + + ) : ( + + + + + )} + + + ) + }} + values={{ provider: providerWebsite }} + /> + + + ) +} + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 15px; + padding: 20px; +` + +const ProviderLogo = styled.img` + width: 60px; + height: 60px; + border-radius: 50%; +` + +const Description = styled.div` + font-size: 12px; + color: var(--color-text-2); + display: flex; + align-items: center; + gap: 5px; +` + +const OfficialWebsite = styled.a` + text-decoration: none; + color: var(--color-text-2); +` + +export default ProviderOAuth diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 335dbf9433..f53c1e964b 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -1,7 +1,6 @@ import { CheckOutlined, LoadingOutlined } from '@ant-design/icons' import { StreamlineGoodHealthAndWellBeing } from '@renderer/components/Icons/SVGIcon' import { HStack } from '@renderer/components/Layout' -import OAuthButton from '@renderer/components/OAuth/OAuthButton' import { isEmbeddingModel, isRerankModel } from '@renderer/config/models' import { PROVIDER_CONFIG } from '@renderer/config/providers' import { useTheme } from '@renderer/context/ThemeProvider' @@ -10,10 +9,9 @@ import i18n from '@renderer/i18n' import { isOpenAIProvider } from '@renderer/providers/AiProvider/ProviderFactory' import { checkApi, formatApiKeys } from '@renderer/services/ApiService' import { checkModelsHealth, ModelCheckStatus } from '@renderer/services/HealthCheckService' -import { isProviderSupportAuth, isProviderSupportCharge } from '@renderer/services/ProviderService' +import { isProviderSupportAuth } from '@renderer/services/ProviderService' import { Provider } from '@renderer/types' import { formatApiHost } from '@renderer/utils/api' -import { providerCharge } from '@renderer/utils/oauth' import { Button, Divider, Flex, Input, Space, Switch, Tooltip } from 'antd' import Link from 'antd/es/typography/Link' import { debounce, isEmpty } from 'lodash' @@ -38,6 +36,7 @@ import LMStudioSettings from './LMStudioSettings' import ModelList, { ModelStatus } from './ModelList' import ModelListSearchBar from './ModelListSearchBar' import OllamSettings from './OllamaSettings' +import ProviderOAuth from './ProviderOAuth' import ProviderSettingsPopup from './ProviderSettingsPopup' import SelectProviderModelPopup from './SelectProviderModelPopup' @@ -323,6 +322,16 @@ const ProviderSetting: FC = ({ provider: _provider }) => { /> + {isProviderSupportAuth(provider) && ( + { + setApiKey(v) + setInputValue(v) + updateProvider({ ...provider, apiKey: v }) + }} + /> + )} {t('settings.provider.api_key')} = ({ provider: _provider }) => { autoFocus={provider.enabled && apiKey === ''} disabled={provider.id === 'copilot'} /> - {isProviderSupportAuth(provider) && }