feat: add multi-source downloads with automatic fallback
- support GitHub, GitCode, gh-proxy, and ghfast sources - automatically try other sources when version checks fail - persist the working fallback source and sync the UI immediately - update translations, configuration migration, and source tests
This commit is contained in:
1 parent
e8093928c0
commit
03be4acf93
16 files changed
+507
-157
No files matched your search
+7
-4
@@ -943,9 +943,8 @@ export const jaOverrides = {
|
||||
'kernel.versions.appCardTitle': 'デスクトップアプリ',
|
||||
'kernel.versions.coreCardTitle': 'プロキシコア',
|
||||
'kernel.versions.catalogCardTitle': 'Codex モデルカタログ',
|
||||
'kernel.versions.gitcodeEnabled': 'GitCode 国内ミラー有効',
|
||||
'kernel.versions.gitcodeDisabled': '公式 GitHub ソース',
|
||||
'kernel.versions.gitcodeSwitchSuccess': 'ダウンロード元を変更しバージョンを再確認しました',
|
||||
'kernel.versions.sourceAutoSwitched': '現在のダウンロード元で確認できなかったため、{source} に自動切り替えしました',
|
||||
'kernel.versions.bundledPackage': '内蔵オフラインパッケージ',
|
||||
'kernel.versions.offlineRecommend': 'ネットワーク制限がありますか?インターネット接続なしで内蔵オフラインコアを直接インストールできます。',
|
||||
'kernel.versions.autoUpdateSupported': 'アプリ内自動更新対応',
|
||||
@@ -973,8 +972,12 @@ export const jaOverrides = {
|
||||
'kernel.versions.reinstall': '再インストール',
|
||||
'kernel.versions.noBundled': 'このリリースには組み込みコアが含まれていません',
|
||||
'kernel.versions.offlineInstall': 'オフラインインストール',
|
||||
'kernel.versions.gitcodeSource': 'GitCode 中国向けダウンロード元',
|
||||
'kernel.versions.gitcodeSourceHint': '有効にすると、ソフトウェアとコアの更新確認・ダウンロードで GitCode を優先し、失敗時は GitHub に切り替えます。',
|
||||
'kernel.versions.downloadSource': 'ダウンロード元を切り替える',
|
||||
'kernel.versions.downloadSourceHint': 'ソフトウェアとコアの更新確認・ダウンロードでは選択した経路を優先し、失敗時は他の利用可能なソースに切り替えます。ミラープロキシは第三者が提供します。',
|
||||
'kernel.versions.source.github': 'GitHub 公式',
|
||||
'kernel.versions.source.gitcode': 'GitCode 中国向けミラー',
|
||||
'kernel.versions.source.ghProxy': 'GitHub ミラー · gh-proxy.com',
|
||||
'kernel.versions.source.ghFast': 'GitHub ミラー · ghfast.top',
|
||||
'kernel.versions.gitcodeUnavailable': 'このビルドには完全な GitCode ミラー設定が含まれていません。',
|
||||
'kernel.versions.gitcodeSaveFailed': 'ダウンロード元の切り替えに失敗しました: {error}',
|
||||
'kernel.access.firstKey': '最初のキー',
|
||||
|
||||
@@ -943,9 +943,8 @@ export const en: Record<MessageKey, string> = {
|
||||
'kernel.versions.appCardTitle': 'Desktop Application',
|
||||
'kernel.versions.coreCardTitle': 'Proxy Core Kernel',
|
||||
'kernel.versions.catalogCardTitle': 'Codex Model Catalog',
|
||||
'kernel.versions.gitcodeEnabled': 'GitCode China Mirror Active',
|
||||
'kernel.versions.gitcodeDisabled': 'Official GitHub Source',
|
||||
'kernel.versions.gitcodeSwitchSuccess': 'Download source updated and versions rechecked',
|
||||
'kernel.versions.sourceAutoSwitched': 'The current source failed; automatically switched to {source}',
|
||||
'kernel.versions.bundledPackage': 'Bundled Offline Package',
|
||||
'kernel.versions.offlineRecommend': 'Network restricted? You can install the bundled offline core directly without an internet connection.',
|
||||
'kernel.versions.autoUpdateSupported': 'In-app auto-update supported',
|
||||
@@ -973,8 +972,12 @@ export const en: Record<MessageKey, string> = {
|
||||
'kernel.versions.reinstall': 'Reinstall',
|
||||
'kernel.versions.noBundled': 'This release does not include a bundled core',
|
||||
'kernel.versions.offlineInstall': 'Offline Install',
|
||||
'kernel.versions.gitcodeSource': 'GitCode China Download Source',
|
||||
'kernel.versions.gitcodeSourceHint': 'When enabled, software and core update checks and downloads prefer GitCode, with GitHub as fallback.',
|
||||
'kernel.versions.downloadSource': 'Switch Download Source',
|
||||
'kernel.versions.downloadSourceHint': 'Software and core update checks and downloads prefer the selected channel, then fall back to other available sources. Mirror proxies are third-party services.',
|
||||
'kernel.versions.source.github': 'Official GitHub',
|
||||
'kernel.versions.source.gitcode': 'GitCode China Mirror',
|
||||
'kernel.versions.source.ghProxy': 'GitHub Mirror · gh-proxy.com',
|
||||
'kernel.versions.source.ghFast': 'GitHub Mirror · ghfast.top',
|
||||
'kernel.versions.gitcodeUnavailable': 'This build does not include complete GitCode mirror configuration.',
|
||||
'kernel.versions.gitcodeSaveFailed': 'Failed to switch download source: {error}',
|
||||
'kernel.access.firstKey': 'First key',
|
||||
|
||||
@@ -941,9 +941,8 @@ export const zhCN = {
|
||||
'kernel.versions.appCardTitle': '桌面客户端',
|
||||
'kernel.versions.coreCardTitle': '代理内核',
|
||||
'kernel.versions.catalogCardTitle': 'Codex 模型目录',
|
||||
'kernel.versions.gitcodeEnabled': '已启用 GitCode 国内加速',
|
||||
'kernel.versions.gitcodeDisabled': '官方 GitHub 源',
|
||||
'kernel.versions.gitcodeSwitchSuccess': '已切换下载源并重新检测版本',
|
||||
'kernel.versions.sourceAutoSwitched': '当前下载源检测失败,已自动切换至 {source}',
|
||||
'kernel.versions.bundledPackage': '内置离线安装包',
|
||||
'kernel.versions.offlineRecommend': '网络连接受限?可直接一键安装内置离线内核,无需联网。',
|
||||
'kernel.versions.autoUpdateSupported': '支持应用内自动升级',
|
||||
@@ -971,8 +970,12 @@ export const zhCN = {
|
||||
'kernel.versions.reinstall': '重新安装',
|
||||
'kernel.versions.noBundled': '当前发行包未包含内置内核',
|
||||
'kernel.versions.offlineInstall': '离线安装',
|
||||
'kernel.versions.gitcodeSource': 'GitCode 国内下载源',
|
||||
'kernel.versions.gitcodeSourceHint': '开启后,软件和内核的版本检查及下载将优先使用 GitCode,失败时回退 GitHub。',
|
||||
'kernel.versions.downloadSource': '切换下载源',
|
||||
'kernel.versions.downloadSourceHint': '软件和内核的版本检查及下载将优先使用所选渠道,失败时自动回退到其他可用源。镜像代理由第三方提供。',
|
||||
'kernel.versions.source.github': 'GitHub 官方源',
|
||||
'kernel.versions.source.gitcode': 'GitCode 国内源',
|
||||
'kernel.versions.source.ghProxy': 'GitHub 镜像 · gh-proxy.com',
|
||||
'kernel.versions.source.ghFast': 'GitHub 镜像 · ghfast.top',
|
||||
'kernel.versions.gitcodeUnavailable': '当前构建未配置完整的 GitCode 镜像源。',
|
||||
'kernel.versions.gitcodeSaveFailed': '切换下载源失败:{error}',
|
||||
'kernel.access.firstKey': '第一个密钥',
|
||||
|
||||
@@ -39,10 +39,22 @@ export type CoreInstallTask = {
|
||||
};
|
||||
|
||||
export type VersionSourceSettings = {
|
||||
preferGitcodeDownloads: boolean;
|
||||
source: VersionDownloadSource;
|
||||
gitcodeAvailable: boolean;
|
||||
};
|
||||
|
||||
export type VersionDownloadSource = 'github' | 'gitcode' | 'gh-proxy' | 'gh-fast';
|
||||
|
||||
function downloadSourceLabel(source: VersionDownloadSource, t: ReturnType<typeof useI18n>['t']) {
|
||||
const keys = {
|
||||
github: 'kernel.versions.source.github',
|
||||
gitcode: 'kernel.versions.source.gitcode',
|
||||
'gh-proxy': 'kernel.versions.source.ghProxy',
|
||||
'gh-fast': 'kernel.versions.source.ghFast',
|
||||
} as const;
|
||||
return t(keys[source]);
|
||||
}
|
||||
|
||||
export type MessageType = 'info' | 'success' | 'error';
|
||||
const APP_RELEASE_URL = 'https://github.com/router-for-me/EasyCLIProxyAPI/releases/latest';
|
||||
|
||||
@@ -210,11 +222,11 @@ export function VersionManagementPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const updateVersionSource = async (enabled: boolean) => {
|
||||
const updateVersionSource = async (source: VersionDownloadSource) => {
|
||||
setVersionSourceSaving(true);
|
||||
setVersionSourceError('');
|
||||
try {
|
||||
const settings = await invoke<VersionSourceSettings>('set_prefer_gitcode_downloads', { enabled });
|
||||
const settings = await invoke<VersionSourceSettings>('set_download_source', { source });
|
||||
setVersionSource(settings);
|
||||
cachedLatest = null;
|
||||
cachedLatestError = '';
|
||||
@@ -312,6 +324,7 @@ export function VersionManagementPage() {
|
||||
let disposed = false;
|
||||
let unlisten: (() => void) | null = null;
|
||||
let unlistenConfig: (() => void) | null = null;
|
||||
let unlistenVersionSource: (() => void) | null = null;
|
||||
|
||||
listen<CoreInstallTask>('core-install-progress', (event) => {
|
||||
applyInstallTask(event.payload);
|
||||
@@ -331,6 +344,18 @@ export function VersionManagementPage() {
|
||||
else unlistenConfig = stop;
|
||||
});
|
||||
|
||||
void listen<VersionSourceSettings>('version-download-source-changed', (event) => {
|
||||
if (disposed) return;
|
||||
setVersionSource(event.payload);
|
||||
setVersionSourceError('');
|
||||
showToast(t('kernel.versions.sourceAutoSwitched', {
|
||||
source: downloadSourceLabel(event.payload.source, t),
|
||||
}), 'info');
|
||||
}).then((stop) => {
|
||||
if (disposed) stop();
|
||||
else unlistenVersionSource = stop;
|
||||
});
|
||||
|
||||
loadInstallTask();
|
||||
loadVersionSourceSettings();
|
||||
|
||||
@@ -351,6 +376,7 @@ export function VersionManagementPage() {
|
||||
disposed = true;
|
||||
unlisten?.();
|
||||
unlistenConfig?.();
|
||||
unlistenVersionSource?.();
|
||||
if (toastTimerRef.current !== null) {
|
||||
window.clearTimeout(toastTimerRef.current);
|
||||
}
|
||||
@@ -471,39 +497,41 @@ export function VersionManagementPage() {
|
||||
|
||||
const installDialogActionDisabled = (installing || progress?.running) && (cancellingInstall || !progress?.cancellable);
|
||||
|
||||
const isGitcodeActive = Boolean(versionSource?.preferGitcodeDownloads);
|
||||
|
||||
return (
|
||||
<section className="page management-page version-management-page">
|
||||
<section className="panel version-list">
|
||||
<div className="version-source-row" aria-label={t('kernel.versions.gitcodeSource')}>
|
||||
<div className="version-source-row" aria-label={t('kernel.versions.downloadSource')}>
|
||||
<div className="version-source-copy">
|
||||
<strong>{t('kernel.versions.gitcodeSource')}</strong>
|
||||
<span>
|
||||
{versionSource?.gitcodeAvailable === false
|
||||
? t('kernel.versions.gitcodeUnavailable')
|
||||
: t('kernel.versions.gitcodeSourceHint')}
|
||||
</span>
|
||||
<strong>{t('kernel.versions.downloadSource')}</strong>
|
||||
<span>{t('kernel.versions.downloadSourceHint')}</span>
|
||||
{versionSource?.gitcodeAvailable === false ? (
|
||||
<span>{t('kernel.versions.gitcodeUnavailable')}</span>
|
||||
) : null}
|
||||
{versionSourceError ? (
|
||||
<span className="version-source-error" role="alert">{versionSourceError}</span>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="version-source-control">
|
||||
<label className="switch-control" title={t('kernel.versions.gitcodeSource')}>
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
checked={isGitcodeActive}
|
||||
<label>
|
||||
<span className="sr-only">{t('kernel.versions.downloadSource')}</span>
|
||||
<select
|
||||
value={versionSource?.source ?? 'github'}
|
||||
disabled={
|
||||
(!versionSource?.gitcodeAvailable && !versionSource?.preferGitcodeDownloads)
|
||||
!versionSource
|
||||
|| versionSourceSaving
|
||||
|| appUpdateTask.running
|
||||
|| installing
|
||||
}
|
||||
aria-label={t('kernel.versions.gitcodeSource')}
|
||||
onChange={(event) => void updateVersionSource(event.currentTarget.checked)}
|
||||
/>
|
||||
<span className="switch-track" />
|
||||
aria-label={t('kernel.versions.downloadSource')}
|
||||
onChange={(event) => void updateVersionSource(event.currentTarget.value as VersionDownloadSource)}
|
||||
>
|
||||
<option value="github">{t('kernel.versions.source.github')}</option>
|
||||
<option value="gitcode" disabled={!versionSource?.gitcodeAvailable}>
|
||||
{t('kernel.versions.source.gitcode')}
|
||||
</option>
|
||||
<option value="gh-proxy">{t('kernel.versions.source.ghProxy')}</option>
|
||||
<option value="gh-fast">{t('kernel.versions.source.ghFast')}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1073,6 +1073,33 @@ textarea:focus-visible {
|
||||
color: var(--theme-9f2d20);
|
||||
}
|
||||
|
||||
.version-source-control,
|
||||
.version-source-control label {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.version-source-control select {
|
||||
width: 250px;
|
||||
max-width: 100%;
|
||||
height: 42px;
|
||||
padding: 0 36px 0 12px;
|
||||
border: 1px solid var(--theme-d5d2cb);
|
||||
border-radius: 8px;
|
||||
background: var(--theme-fffdf8);
|
||||
color: var(--theme-2d2a26);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-body);
|
||||
}
|
||||
|
||||
.version-source-control select:hover:not(:disabled) {
|
||||
border-color: var(--theme-9b958c);
|
||||
}
|
||||
|
||||
.version-source-control select:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.version-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -1639,6 +1666,16 @@ textarea:focus-visible {
|
||||
}
|
||||
}
|
||||
|
||||
@container workspace (max-width: 560px) {
|
||||
.version-source-row {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.version-source-control select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.info-list {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
|
||||
Reference in new issue
Block a user