feat(i18n): enhance localization for GitHub Copilot settings

- Added new translation keys for error messages and steps in the GitHub Copilot authentication process across multiple languages (en-us, ja-jp, ru-ru, zh-cn, zh-tw).
- Updated the GitHubCopilotSettings component to reflect the new steps for user guidance during the authentication process.
- Improved user experience by providing detailed descriptions and success/error messages related to the authorization flow.
This commit is contained in:
kangfenmao 2025-07-09 18:45:01 +08:00
parent f7fa665f3a
commit 4a8a5e8428
12 changed files with 364 additions and 153 deletions

View File

@ -383,6 +383,7 @@
"confirm": "Confirm",
"copied": "Copied",
"copy": "Copy",
"copy_failed": "Copy failed",
"cut": "Cut",
"default": "Default",
"delete": "Delete",
@ -2072,14 +2073,14 @@
"auth_failed": "Github Copilot authentication failed.",
"auth_success": "GitHub Copilot authentication successful.",
"auth_success_title": "Certification successful.",
"code_copied": "Authorization code automatically copied to clipboard",
"code_failed": "Failed to obtain Device Code, please try again.",
"code_generated_desc": "Please copy the device code into the browser link below.",
"code_generated_title": "Obtain Device Code",
"confirm_login": "Excessive use may lead to your Github account being banned, please use it cautiously!!!!",
"confirm_title": "Risk Warning",
"connect": "Connect to Github",
"custom_headers": "Custom request header",
"description": "Your GitHub account needs to subscribe to Copilot.",
"description_detail": "GitHub Copilot is an AI-powered code assistant that requires a valid GitHub Copilot subscription to use",
"expand": "Expand",
"headers_description": "Custom request headers (JSON format)",
"invalid_json": "JSON format error",
@ -2089,8 +2090,20 @@
"logout_success": "Successfully logged out.",
"model_setting": "Model settings",
"open_verification_first": "Please click the link above to access the verification page.",
"open_verification_page": "Open Authorization Page",
"rate_limit": "Rate limiting",
"tooltip": "You need to log in to Github before using Github Copilot"
"start_auth": "Start Authorization",
"step_authorize": "Open Authorization Page",
"step_authorize_desc": "Complete authorization on GitHub",
"step_authorize_detail": "Click the button below to open GitHub authorization page, then enter the copied authorization code",
"step_connect": "Complete Connection",
"step_connect_desc": "Confirm connection to GitHub",
"step_connect_detail": "After completing authorization on GitHub page, click this button to complete the connection",
"step_copy_code": "Copy Authorization Code",
"step_copy_code_desc": "Copy device authorization code",
"step_copy_code_detail": "Authorization code has been automatically copied, you can also copy it manually",
"step_get_code": "Get Authorization Code",
"step_get_code_desc": "Generate device authorization code"
},
"delete.content": "Are you sure you want to delete this provider?",
"delete.title": "Delete Provider",

View File

@ -383,6 +383,7 @@
"confirm": "確認",
"copied": "コピーされました",
"copy": "コピー",
"copy_failed": "コピーに失敗しました",
"cut": "切り取り",
"default": "デフォルト",
"delete": "削除",
@ -2072,14 +2073,14 @@
"auth_failed": "Github Copilotの認証に失敗しました。",
"auth_success": "Github Copilotの認証が成功しました",
"auth_success_title": "認証成功",
"code_copied": "認証コードがクリップボードに自動コピーされました",
"code_failed": "デバイスコードの取得に失敗しました。再試行してください。",
"code_generated_desc": "デバイスコードを下記のブラウザリンクにコピーしてください。",
"code_generated_title": "デバイスコードを取得する",
"confirm_login": "過度使用すると、あなたのGithubアカウントが停止される可能性があるため、慎重に使用してください!!!!",
"confirm_title": "リスク警告",
"connect": "GitHubに接続する",
"custom_headers": "カスタムリクエストヘッダー",
"description": "あなたのGithubアカウントはCopilotを購読する必要があります。",
"description_detail": "GitHub Copilot は AI ベースのコード補助ツールで、有効な GitHub Copilot サブスクリプションが必要です",
"expand": "展開",
"headers_description": "カスタムリクエストヘッダーJSONフォーマット",
"invalid_json": "JSONフォーマットエラー",
@ -2089,8 +2090,20 @@
"logout_success": "正常にログアウトしました。",
"model_setting": "モデル設定",
"open_verification_first": "上のリンクをクリックして、確認ページにアクセスしてください。",
"open_verification_page": "認証ページを開く",
"rate_limit": "レート制限",
"tooltip": "Github Copilot を使用するには、まず Github にログインする必要があります。"
"start_auth": "認証を開始",
"step_authorize": "認証ページを開く",
"step_authorize_desc": "GitHub で認証を完了する",
"step_authorize_detail": "下のボタンをクリックして GitHub 認証ページを開き、コピーした認証コードを入力してください",
"step_connect": "接続を完了",
"step_connect_desc": "GitHub への接続を確認",
"step_connect_detail": "GitHub ページで認証が完了したら、このボタンをクリックして接続を完了してください",
"step_copy_code": "認証コードをコピー",
"step_copy_code_desc": "デバイス認証コードをコピー",
"step_copy_code_detail": "認証コードは自動的にコピーされましたが、手動でもコピーできます",
"step_get_code": "認証コードを取得",
"step_get_code_desc": "デバイス認証コードを生成"
},
"delete.content": "このプロバイダーを削除してもよろしいですか?",
"delete.title": "プロバイダーを削除",

View File

@ -383,6 +383,7 @@
"confirm": "Подтверждение",
"copied": "Скопировано",
"copy": "Копировать",
"copy_failed": "Не удалось скопировать",
"cut": "Вырезать",
"default": "По умолчанию",
"delete": "Удалить",
@ -2072,14 +2073,14 @@
"auth_failed": "Github Copilot认证失败",
"auth_success": "Github Copilot认证成功",
"auth_success_title": "Аутентификация успешна",
"code_copied": "Код авторизации автоматически скопирован в буфер обмена",
"code_failed": "Получение кода устройства не удалось, пожалуйста, попробуйте еще раз.",
"code_generated_desc": "Пожалуйста, скопируйте код устройства в приведенную ниже ссылку браузера.",
"code_generated_title": "Получить код устройства",
"confirm_login": "Чрезмерное использование может привести к блокировке вашего Github, будьте осторожны!!!!",
"confirm_title": "Предупреждение о рисках",
"connect": "Подключить Github",
"custom_headers": "Пользовательские заголовки запроса",
"description": "Ваша учетная запись Github должна подписаться на Copilot.",
"description_detail": "GitHub Copilot — это помощник по коду на базе ИИ, для использования которого требуется действующая подписка GitHub Copilot",
"expand": "развернуть",
"headers_description": "Пользовательские заголовки запроса (формат json)",
"invalid_json": "Ошибка формата JSON",
@ -2089,8 +2090,20 @@
"logout_success": "Успешно вышел",
"model_setting": "Настройки модели",
"open_verification_first": "Пожалуйста, сначала щелкните по ссылке выше, чтобы перейти на страницу проверки.",
"open_verification_page": "Открыть страницу авторизации",
"rate_limit": "Ограничение скорости",
"tooltip": "Для использования Github Copilot необходимо сначала войти в Github."
"start_auth": "Начать авторизацию",
"step_authorize": "Открыть страницу авторизации",
"step_authorize_desc": "Завершить авторизацию на GitHub",
"step_authorize_detail": "Нажмите кнопку ниже, чтобы открыть страницу авторизации GitHub, затем введите скопированный код авторизации",
"step_connect": "Завершить подключение",
"step_connect_desc": "Подтвердить подключение к GitHub",
"step_connect_detail": "После завершения авторизации на странице GitHub нажмите эту кнопку, чтобы завершить подключение",
"step_copy_code": "Скопировать код авторизации",
"step_copy_code_desc": "Скопировать код авторизации устройства",
"step_copy_code_detail": "Код авторизации автоматически скопирован, вы также можете скопировать его вручную",
"step_get_code": "Получить код авторизации",
"step_get_code_desc": "Сгенерировать код авторизации устройства"
},
"delete.content": "Вы уверены, что хотите удалить этот провайдер?",
"delete.title": "Удалить провайдер",

View File

@ -383,6 +383,7 @@
"confirm": "确认",
"copied": "已复制",
"copy": "复制",
"copy_failed": "复制失败",
"cut": "剪切",
"default": "默认",
"delete": "删除",
@ -2072,14 +2073,14 @@
"auth_failed": "Github Copilot 认证失败",
"auth_success": "Github Copilot 认证成功",
"auth_success_title": "认证成功",
"code_copied": "授权码已自动复制到剪贴板",
"code_failed": "获取 Device Code 失败,请重试",
"code_generated_desc": "请将 Device Code 复制到下面的浏览器链接中",
"code_generated_title": "获取 Device Code",
"confirm_login": "过度使用可能会导致您的 Github 账号遭到封号,请谨慎使用!",
"confirm_title": "风险警告",
"connect": "连接 Github",
"custom_headers": "自定义请求头",
"description": "您的 Github 账号需要订阅 Copilot",
"description_detail": "GitHub Copilot 是一个基于 AI 的代码助手,需要有效的 GitHub Copilot 订阅才能使用",
"expand": "展开",
"headers_description": "自定义请求头 (json 格式)",
"invalid_json": "JSON 格式错误",
@ -2089,8 +2090,20 @@
"logout_success": "已成功退出",
"model_setting": "模型设置",
"open_verification_first": "请先点击上方链接访问验证页面",
"open_verification_page": "打开授权页面",
"rate_limit": "速率限制",
"tooltip": "使用 Github Copilot 需要先登录 Github"
"start_auth": "开始授权",
"step_authorize": "打开授权页面",
"step_authorize_desc": "在 GitHub 上完成授权",
"step_authorize_detail": "点击下方按钮打开 GitHub 授权页面,然后输入复制的授权码",
"step_connect": "完成连接",
"step_connect_desc": "确认连接到 GitHub",
"step_connect_detail": "在 GitHub 页面完成授权后,点击此按钮完成连接",
"step_copy_code": "复制授权码",
"step_copy_code_desc": "复制设备授权码",
"step_copy_code_detail": "授权码已自动复制,您也可以手动复制",
"step_get_code": "获取授权码",
"step_get_code_desc": "生成设备授权码"
},
"delete.content": "确定要删除此模型提供商吗?",
"delete.title": "删除提供商",

View File

@ -383,6 +383,7 @@
"confirm": "確認",
"copied": "已複製",
"copy": "複製",
"copy_failed": "複製失敗",
"cut": "剪下",
"default": "預設",
"delete": "刪除",
@ -2072,14 +2073,14 @@
"auth_failed": "Github Copilot 認證失敗",
"auth_success": "Github Copilot 認證成功",
"auth_success_title": "認證成功",
"code_copied": "授權碼已自動複製到剪貼簿",
"code_failed": "獲取 Device Code 失敗,請重試",
"code_generated_desc": "請將設備代碼複製到下面的瀏覽器連結中",
"code_generated_title": "獲取設備代碼",
"confirm_login": "過度使用可能會導致您的 Github 帳號遭到封號,請謹慎使用!",
"confirm_title": "風險警告",
"connect": "連接 Github",
"custom_headers": "自訂請求標頭",
"description": "您的 Github 帳號需要訂閱 Copilot",
"description_detail": "GitHub Copilot 是一個基於 AI 的程式碼助手,需要有效的 GitHub Copilot 訂閱才能使用",
"expand": "展開",
"headers_description": "自訂請求標頭 (json 格式)",
"invalid_json": "JSON 格式錯誤",
@ -2089,8 +2090,20 @@
"logout_success": "已成功登出",
"model_setting": "模型設定",
"open_verification_first": "請先點擊上方連結訪問驗證頁面",
"open_verification_page": "開啟授權頁面",
"rate_limit": "速率限制",
"tooltip": "使用 Github Copilot 需要先登入 Github"
"start_auth": "開始授權",
"step_authorize": "開啟授權頁面",
"step_authorize_desc": "在 GitHub 上完成授權",
"step_authorize_detail": "點擊下方按鈕開啟 GitHub 授權頁面,然後輸入複製的授權碼",
"step_connect": "完成連線",
"step_connect_desc": "確認連接到 GitHub",
"step_connect_detail": "在 GitHub 頁面完成授權後,點擊此按鈕完成連線",
"step_copy_code": "複製授權碼",
"step_copy_code_desc": "複製設備授權碼",
"step_copy_code_detail": "授權碼已自動複製,您也可以手動複製",
"step_get_code": "獲取授權碼",
"step_get_code_desc": "生成設備授權碼"
},
"delete.content": "確定要刪除此提供者嗎?",
"delete.title": "刪除提供者",

View File

@ -1462,8 +1462,6 @@
"code_failed": "Η λήψη του Device Code απέτυχε, παρακαλώ δοκιμάστε ξανά",
"code_generated_desc": "Παρακαλώ αντιγράψτε το Device Code στον παρακάτω σύνδεσμο περιηγητή",
"code_generated_title": "Λήψη Device Code",
"confirm_login": "Η υπερβολική χρήση μπορεί να οδηγήσει στην απενεργοποίηση του λογαριασμού σας στο Github, παρακαλώ χρησιμοποιήστε το με προσοχή!!!!",
"confirm_title": "Ειδοποίηση κινδύνου",
"connect": "Σύνδεση με το Github",
"custom_headers": "Προσαρμοσμένες κεφαλίδες αιτήματος",
"description": "Ο λογαριασμός σας στο Github χρειάζεται να εγγραφεί για να χρησιμοποιήσει το Copilot",
@ -1476,8 +1474,7 @@
"logout_success": "Έγινε επιτυχής η αποσύνδεση",
"model_setting": "Ρυθμίσεις μοντέλου",
"open_verification_first": "Παρακαλώ κάντε κλικ στον παραπάνω σύνδεσμο για να επισκεφτείτε τη σελίδα επιβεβαίωσης",
"rate_limit": "Όριο ρυθμού",
"tooltip": "Για τη χρήση του Github Copilot πρέπει να συνδεθείτε στο Github"
"rate_limit": "Όριο ρυθμού"
},
"delete.content": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτόν τον παροχό;",
"delete.title": "Διαγραφή παρόχου",

View File

@ -1461,8 +1461,6 @@
"code_failed": "Error al obtener Código del Dispositivo, por favor inténtelo de nuevo",
"code_generated_desc": "Por favor, copie el Código del Dispositivo en el siguiente enlace del navegador",
"code_generated_title": "Obtener Código del Dispositivo",
"confirm_login": "El uso excesivo puede llevar al bloqueo de su cuenta de Github, use con precaución!!!!",
"confirm_title": "Advertencia de Riesgo",
"connect": "Conectar con Github",
"custom_headers": "Encabezados personalizados",
"description": "Su cuenta de Github necesita suscribirse a Copilot",
@ -1475,8 +1473,7 @@
"logout_success": "Ha cerrado sesión exitosamente",
"model_setting": "Configuración del modelo",
"open_verification_first": "Por favor, haga clic en el enlace superior para acceder a la página de verificación",
"rate_limit": "Límite de tasa",
"tooltip": "Para usar Github Copilot, primero debe iniciar sesión en Github"
"rate_limit": "Límite de tasa"
},
"delete.content": "¿Está seguro de que desea eliminar este proveedor de modelos?",
"delete.title": "Eliminar proveedor",

View File

@ -1462,8 +1462,6 @@
"code_failed": "Échec de l'obtention du code Device, veuillez réessayer",
"code_generated_desc": "Veuillez copier le code Device dans le lien du navigateur ci-dessous",
"code_generated_title": "Obtenir le code Device",
"confirm_login": "L'utilisation excessive peut entraîner la suspension de votre compte Github, utilisez avec prudence!!!!",
"confirm_title": "Avertissement de risque",
"connect": "Connectez-vous à Github",
"custom_headers": "Entêtes de requête personnalisées",
"description": "Votre compte Github doit souscrire à Copilot",
@ -1476,8 +1474,7 @@
"logout_success": "Déconnexion réussie",
"model_setting": "Paramètres du modèle",
"open_verification_first": "Cliquez d'abord sur le lien ci-dessus pour accéder à la page de vérification",
"rate_limit": "Limite de taux",
"tooltip": "Pour utiliser Github Copilot, vous devez vous connecter à Github"
"rate_limit": "Limite de taux"
},
"delete.content": "Êtes-vous sûr de vouloir supprimer ce fournisseur de modèles ?",
"delete.title": "Supprimer le fournisseur",

View File

@ -1463,8 +1463,6 @@
"code_failed": "Falha ao obter Código do Dispositivo, tente novamente",
"code_generated_desc": "Por favor, copie o Código do Dispositivo para o link do navegador abaixo",
"code_generated_title": "Obter Código do Dispositivo",
"confirm_login": "O uso excessivo pode resultar no bloqueio da sua conta do Github, use com cuidado!!!!",
"confirm_title": "Aviso de Risco",
"connect": "Conectar ao Github",
"custom_headers": "Cabeçalhos Personalizados",
"description": "Sua conta do Github precisa assinar o Copilot",
@ -1477,8 +1475,7 @@
"logout_success": "Saiu com sucesso",
"model_setting": "Configuração do Modelo",
"open_verification_first": "Por favor, clique no link acima para acessar a página de verificação",
"rate_limit": "Limite de Taxa",
"tooltip": "Para usar o Github Copilot, você precisa fazer login no Github"
"rate_limit": "Limite de Taxa"
},
"delete.content": "Tem certeza de que deseja excluir este fornecedor de modelo?",
"delete.title": "Excluir Fornecedor",

View File

@ -1,5 +1,6 @@
import CodeEditor from '@renderer/components/CodeEditor'
import { TopView } from '@renderer/components/TopView'
import { useCopilot } from '@renderer/hooks/useCopilot'
import { useProvider } from '@renderer/hooks/useProvider'
import { Provider } from '@renderer/types'
import { Modal, Space } from 'antd'
@ -20,17 +21,30 @@ const PopupContainer: React.FC<Props> = ({ provider, resolve }) => {
const [open, setOpen] = useState(true)
const { t } = useTranslation()
const { updateProvider } = useProvider(provider.id)
const [headerText, setHeaderText] = useState<string>(JSON.stringify(provider.extra_headers || {}, null, 2))
const { defaultHeaders, updateDefaultHeaders } = useCopilot()
const headers =
provider.id === 'copilot'
? JSON.stringify(defaultHeaders || {}, null, 2)
: JSON.stringify(provider.extra_headers || {}, null, 2)
const [headerText, setHeaderText] = useState<string>(headers)
const onUpdateHeaders = useCallback(() => {
try {
const headers = headerText.trim() ? JSON.parse(headerText) : {}
updateProvider({ ...provider, extra_headers: headers })
if (provider.id === 'copilot') {
updateDefaultHeaders(headers)
} else {
updateProvider({ ...provider, extra_headers: headers })
}
window.message.success({ content: t('message.save.success.title') })
} catch (error) {
window.message.error({ content: t('settings.provider.copilot.invalid_json') })
}
}, [headerText, provider, updateProvider, t])
}, [headerText, provider, t, updateDefaultHeaders, updateProvider])
const onOk = () => {
onUpdateHeaders()

View File

@ -1,12 +1,12 @@
import { CheckCircleOutlined, CopyOutlined, ExclamationCircleOutlined } from '@ant-design/icons'
import { useCopilot } from '@renderer/hooks/useCopilot'
import { useProvider } from '@renderer/hooks/useProvider'
import { Alert, Button, Input, message, Popconfirm, Slider, Space, Tooltip, Typography } from 'antd'
import { Alert, Button, Input, Slider, Steps, Tooltip, Typography } from 'antd'
import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { SettingDivider, SettingGroup, SettingHelpText, SettingRow, SettingTitle } from '..'
import { SettingRow, SettingSubtitle } from '..'
interface GithubCopilotSettingsProps {
providerId: string
@ -21,27 +21,29 @@ enum AuthStatus {
const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) => {
const { t } = useTranslation()
const { provider, updateProvider } = useProvider(providerId)
const { username, avatar, defaultHeaders, updateState, updateDefaultHeaders } = useCopilot()
const { username, avatar, defaultHeaders, updateState } = useCopilot()
// 状态管理
const [authStatus, setAuthStatus] = useState<AuthStatus>(AuthStatus.NOT_STARTED)
const [deviceCode, setDeviceCode] = useState<string>('')
const [userCode, setUserCode] = useState<string>('')
const [verificationUri, setVerificationUri] = useState<string>('')
const [loading, setLoading] = useState<boolean>(false)
const [showHeadersForm, setShowHeadersForm] = useState<boolean>(false)
const [headerText, setHeaderText] = useState<string>(JSON.stringify(defaultHeaders || {}, null, 2))
const [verificationPageOpened, setVerificationPageOpened] = useState<boolean>(false)
const [currentStep, setCurrentStep] = useState<number>(0)
// 初始化及同步状态
useEffect(() => {
if (provider.isAuthed) {
setAuthStatus(AuthStatus.AUTHENTICATED)
setCurrentStep(3)
} else {
setAuthStatus(AuthStatus.NOT_STARTED)
setCurrentStep(0)
// 重置其他状态
setDeviceCode('')
setUserCode('')
setVerificationUri('')
setVerificationPageOpened(false)
}
}, [provider])
@ -49,15 +51,27 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
const handleGetDeviceCode = useCallback(async () => {
try {
setLoading(true)
setCurrentStep(1)
const { device_code, user_code, verification_uri } = await window.api.copilot.getAuthMessage(defaultHeaders)
console.log('device_code', device_code)
console.log('user_code', user_code)
console.log('verification_uri', verification_uri)
setDeviceCode(device_code)
setUserCode(user_code)
setVerificationUri(verification_uri)
setAuthStatus(AuthStatus.CODE_GENERATED)
// 自动复制授权码到剪贴板
try {
await navigator.clipboard.writeText(user_code)
window.message.success(t('settings.provider.copilot.code_copied'))
} catch (error) {
console.error('Failed to copy to clipboard:', error)
}
} catch (error) {
console.error('Failed to get device code:', error)
message.error(t('settings.provider.copilot.code_failed'))
window.message.error(t('settings.provider.copilot.code_failed'))
setCurrentStep(0)
} finally {
setLoading(false)
}
@ -67,6 +81,7 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
const handleGetToken = useCallback(async () => {
try {
setLoading(true)
setCurrentStep(3)
const { access_token } = await window.api.copilot.getCopilotToken(deviceCode, defaultHeaders)
await window.api.copilot.saveCopilotToken(access_token)
@ -77,11 +92,12 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
setAuthStatus(AuthStatus.AUTHENTICATED)
updateState({ username: login, avatar: avatar })
updateProvider({ ...provider, apiKey: token, isAuthed: true })
message.success(t('settings.provider.copilot.auth_success'))
window.message.success(t('settings.provider.copilot.auth_success'))
}
} catch (error) {
console.error('Failed to get token:', error)
message.error(t('settings.provider.copilot.auth_failed'))
window.message.error(t('settings.provider.copilot.auth_failed'))
setCurrentStep(2)
} finally {
setLoading(false)
}
@ -103,11 +119,13 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
setDeviceCode('')
setUserCode('')
setVerificationUri('')
setVerificationPageOpened(false)
setCurrentStep(0)
message.success(t('settings.provider.copilot.logout_success'))
window.message.success(t('settings.provider.copilot.logout_success'))
} catch (error) {
console.error('Failed to logout:', error)
message.error(t('settings.provider.copilot.logout_failed'))
window.message.error(t('settings.provider.copilot.logout_failed'))
// 如果登出失败,重置登出状态
updateProvider({ ...provider, apiKey: '', isAuthed: false })
} finally {
@ -116,9 +134,14 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
}, [t, updateProvider, provider])
// 复制用户代码
const handleCopyUserCode = useCallback(() => {
navigator.clipboard.writeText(userCode)
message.success(t('common.copied'))
const handleCopyUserCode = useCallback(async () => {
try {
await navigator.clipboard.writeText(userCode)
window.message.success(t('common.copied'))
} catch (error) {
console.error('Failed to copy to clipboard:', error)
window.message.error(t('common.copy_failed'))
}
}, [userCode, t])
// 打开验证页面
@ -126,27 +149,52 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
if (verificationUri) {
window.open(verificationUri, '_blank')
setVerificationPageOpened(true)
setCurrentStep(2)
}
}, [verificationUri])
// 处理更新请求头
const handleUpdateHeaders = useCallback(() => {
try {
// 处理headerText可能为空的情况
const headers = headerText.trim() ? JSON.parse(headerText) : {}
updateDefaultHeaders(headers)
message.success(t('message.save.success.title'))
} catch (error) {
message.error(t('settings.provider.copilot.invalid_json'))
// 步骤配置
const getSteps = () => [
{
title: t('settings.provider.copilot.step_get_code'),
description: t('settings.provider.copilot.step_get_code_desc'),
status: (currentStep > 0 ? 'finish' : currentStep === 0 ? 'process' : 'wait') as
| 'error'
| 'finish'
| 'process'
| 'wait'
},
{
title: t('settings.provider.copilot.step_copy_code'),
description: t('settings.provider.copilot.step_copy_code_desc'),
status: (currentStep > 1 ? 'finish' : currentStep === 1 ? 'process' : 'wait') as
| 'error'
| 'finish'
| 'process'
| 'wait'
},
{
title: t('settings.provider.copilot.step_authorize'),
description: t('settings.provider.copilot.step_authorize_desc'),
status: (currentStep > 2 ? 'finish' : currentStep === 2 ? 'process' : 'wait') as
| 'error'
| 'finish'
| 'process'
| 'wait'
},
{
title: t('settings.provider.copilot.step_connect'),
description: t('settings.provider.copilot.step_connect_desc'),
status: (currentStep >= 3 ? 'finish' : 'wait') as 'error' | 'finish' | 'process' | 'wait'
}
}, [headerText, updateDefaultHeaders, t])
]
// 根据认证状态渲染不同的UI
const renderAuthContent = () => {
switch (authStatus) {
case AuthStatus.AUTHENTICATED:
return (
<>
<AuthSuccessContainer>
<Alert
type="success"
message={
@ -170,119 +218,213 @@ const GithubCopilotSettings: FC<GithubCopilotSettingsProps> = ({ providerId }) =
icon={<CheckCircleOutlined />}
showIcon
/>
</>
</AuthSuccessContainer>
)
case AuthStatus.CODE_GENERATED:
return (
<>
<Alert
style={{ marginTop: 12, marginBottom: 12 }}
type="info"
message={t('settings.provider.copilot.code_generated_title')}
description={
<>
<p>{t('settings.provider.copilot.code_generated_desc')}</p>
<Typography.Link onClick={handleOpenVerificationPage}>{verificationUri}</Typography.Link>
</>
}
showIcon
/>
<SettingRow>
<Input value={userCode} readOnly />
<Button icon={<CopyOutlined />} onClick={handleCopyUserCode}>
{t('common.copy')}
</Button>
</SettingRow>
<SettingRow>
<Tooltip title={!verificationPageOpened ? t('settings.provider.copilot.open_verification_first') : ''}>
<Button type="primary" loading={loading} disabled={!verificationPageOpened} onClick={handleGetToken}>
{t('settings.provider.copilot.connect')}
</Button>
</Tooltip>
</SettingRow>
</>
<AuthFlowContainer>
<StepsContainer>
<Steps current={currentStep} size="small" items={getSteps()} direction="vertical" />
</StepsContainer>
<AuthActionsContainer>
{/* 步骤2: 复制授权码 */}
{currentStep >= 1 && (
<StepCard>
<StepHeader>
<StepNumber completed={currentStep > 1}>2</StepNumber>
<div>
<StepTitle>{t('settings.provider.copilot.step_copy_code')}</StepTitle>
<StepDesc>{t('settings.provider.copilot.step_copy_code_detail')}</StepDesc>
</div>
</StepHeader>
<SettingRow>
<Input
value={userCode}
readOnly
style={{ fontFamily: 'monospace', fontSize: '14px', fontWeight: 'bold', marginRight: 8 }}
/>
<Button icon={<CopyOutlined />} onClick={handleCopyUserCode}>
{t('common.copy')}
</Button>
</SettingRow>
</StepCard>
)}
{/* 步骤3: 打开授权页面 */}
{currentStep >= 1 && (
<StepCard>
<StepHeader>
<StepNumber completed={currentStep > 2}>3</StepNumber>
<div>
<StepTitle>{t('settings.provider.copilot.step_authorize')}</StepTitle>
<StepDesc>{t('settings.provider.copilot.step_authorize_detail')}</StepDesc>
</div>
</StepHeader>
<Button type="primary" onClick={handleOpenVerificationPage} style={{ marginBottom: 8 }}>
{t('settings.provider.copilot.open_verification_page')}
</Button>
{verificationUri && (
<Typography.Text type="secondary" style={{ fontSize: '12px', marginLeft: 8 }}>
{verificationUri}
</Typography.Text>
)}
</StepCard>
)}
{/* 步骤4: 完成连接 */}
{currentStep >= 2 && (
<StepCard>
<StepHeader>
<StepNumber completed={currentStep > 3}>4</StepNumber>
<div>
<StepTitle>{t('settings.provider.copilot.step_connect')}</StepTitle>
<StepDesc>{t('settings.provider.copilot.step_connect_detail')}</StepDesc>
</div>
</StepHeader>
<Tooltip
title={!verificationPageOpened ? t('settings.provider.copilot.open_verification_first') : ''}>
<Button
type="primary"
loading={loading}
disabled={!verificationPageOpened}
onClick={handleGetToken}>
{t('settings.provider.copilot.connect')}
</Button>
</Tooltip>
</StepCard>
)}
</AuthActionsContainer>
</AuthFlowContainer>
)
default: // AuthStatus.NOT_STARTED
return (
<>
<StartContainer>
<Alert
style={{ marginTop: 12, marginBottom: 12 }}
type="warning"
message={t('settings.provider.copilot.tooltip')}
description={t('settings.provider.copilot.description')}
type="info"
message={t('settings.provider.copilot.description')}
description={t('settings.provider.copilot.description_detail')}
action={
<Button type="primary" loading={loading} onClick={handleGetDeviceCode}>
{t('settings.provider.copilot.start_auth')}
</Button>
}
showIcon
icon={<ExclamationCircleOutlined />}
/>
<Popconfirm
title={t('settings.provider.copilot.confirm_title')}
description={t('settings.provider.copilot.confirm_login')}
okText={t('common.confirm')}
cancelText={t('common.cancel')}
onConfirm={handleGetDeviceCode}
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
<Button type="primary" loading={loading}>
{t('settings.provider.copilot.login')}
</Button>
</Popconfirm>
</>
</StartContainer>
)
}
}
return (
<Container>
<Space direction="vertical" style={{ width: '100%' }}>
{renderAuthContent()}
<SettingDivider />
<SettingGroup>
<SettingTitle> {t('settings.provider.copilot.model_setting')}</SettingTitle>
<SettingDivider />
<SettingRow>
{t('settings.provider.copilot.rate_limit')}
<Slider
defaultValue={provider.rateLimit ?? 10}
style={{ width: 200 }}
min={1}
max={60}
step={1}
marks={{ 1: '1', 10: t('common.default'), 60: '60' }}
onChangeComplete={(value) => updateProvider({ ...provider, rateLimit: value })}
/>
</SettingRow>
<SettingRow>
{t('settings.provider.copilot.custom_headers')}
<Button onClick={() => setShowHeadersForm((prev) => !prev)} style={{ width: 200 }}>
{t('settings.provider.copilot.expand')}
</Button>
</SettingRow>
{showHeadersForm && (
<SettingRow>
<Space direction="vertical" style={{ width: '100%' }}>
<SettingHelpText>{t('settings.provider.copilot.headers_description')}</SettingHelpText>
<Input.TextArea
rows={5}
autoSize={{ minRows: 2, maxRows: 8 }}
value={headerText}
onChange={(e) => setHeaderText(e.target.value)}
placeholder={`{\n "Header-Name": "Header-Value"\n}`}
/>
<Space>
<Button onClick={handleUpdateHeaders} type="primary">
{t('common.save')}
</Button>
<Button onClick={() => setHeaderText(JSON.stringify({}, null, 2))}>{t('common.reset')}</Button>
</Space>
</Space>
</SettingRow>
)}
</SettingGroup>
</Space>
{renderAuthContent()}
{authStatus === AuthStatus.AUTHENTICATED && (
<SettingRow style={{ marginTop: 20 }}>
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.provider.copilot.rate_limit')}</SettingSubtitle>
<Slider
defaultValue={provider.rateLimit ?? 10}
style={{ width: 200 }}
min={1}
max={60}
step={1}
marks={{ 1: '1', 10: t('common.default'), 60: '60' }}
onChangeComplete={(value) => updateProvider({ ...provider, rateLimit: value })}
/>
</SettingRow>
)}
</Container>
)
}
const Container = styled.div``
const Container = styled.div`
padding-top: 15px;
`
const StartContainer = styled.div`
margin-bottom: 20px;
`
const AuthSuccessContainer = styled.div`
margin-bottom: 20px;
`
const AuthFlowContainer = styled.div`
display: flex;
gap: 24px;
margin-bottom: 20px;
@media (max-width: 768px) {
flex-direction: column;
gap: 16px;
}
`
const StepsContainer = styled.div`
flex: 1;
min-width: 200px;
.ant-steps-item-description {
margin-top: 4px;
font-size: 12px;
color: var(--color-text-secondary);
}
`
const AuthActionsContainer = styled.div`
flex: 2;
display: flex;
flex-direction: column;
gap: 16px;
`
const StepCard = styled.div`
padding: 16px;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-background-soft);
transition: all 0.2s ease;
&:hover {
border-color: var(--color-border-soft);
}
`
const StepHeader = styled.div`
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
`
const StepNumber = styled.div<{ completed?: boolean }>`
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
background: ${(props) => (props.completed ? 'var(--color-status-success)' : 'var(--color-primary)')};
color: white;
flex-shrink: 0;
transition: all 0.2s ease;
`
const StepTitle = styled.div`
font-weight: 500;
font-size: 14px;
color: var(--color-text);
`
const StepDesc = styled.div`
font-size: 12px;
color: var(--color-text-secondary);
margin-top: 2px;
`
export default GithubCopilotSettings

View File

@ -308,7 +308,7 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
</CustomCollapse>
</CustomCollapseWrapper>
))}
{(docsWebsite || modelsWebsite) && (
{docsWebsite || modelsWebsite ? (
<SettingHelpTextRow>
<SettingHelpText>{t('settings.provider.docs_check')} </SettingHelpText>
{docsWebsite && (
@ -325,6 +325,8 @@ const ModelList: React.FC<ModelListProps> = ({ providerId, modelStatuses = [], s
)}
<SettingHelpText>{t('settings.provider.docs_more_details')}</SettingHelpText>
</SettingHelpTextRow>
) : (
<div style={{ height: 5 }} />
)}
</Flex>
<Flex gap={10} style={{ marginTop: '10px' }}>