feat(core): improve portable core lifecycle and desktop integration

This commit is contained in:
lzt404 committed 2026-07-19 01:30:08 +08:00
1 parent a21d38c87b
commit 91032f3a06
13 files changed
+919 -184

No files matched your search

+40 -6
View File
@@ -1,5 +1,6 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
export type CoreStatus = {
installed: boolean;
@@ -15,6 +16,7 @@ export type CoreStatus = {
type CoreRuntimeContextValue = {
status: CoreStatus | null;
statusError: string;
refreshStatus: () => Promise<void>;
publishStatus: (status: CoreStatus | null) => void;
};
@@ -24,6 +26,13 @@ export function CoreRuntimeProvider({ children }: { children: ReactNode }) {
const [status, setStatus] = useState<CoreStatus | null>(null);
const [statusError, setStatusError] = useState('');
const publishStatus = useCallback((nextStatus: CoreStatus | null) => {
setStatus(nextStatus);
if (nextStatus) {
setStatusError('');
}
}, []);
const refreshStatus = useCallback(async () => {
try {
const nextStatus = await invoke<CoreStatus>('get_core_status');
@@ -36,17 +45,42 @@ export function CoreRuntimeProvider({ children }: { children: ReactNode }) {
}, []);
useEffect(() => {
let disposed = false;
let unlisten: (() => void) | null = null;
listen<CoreStatus>('core-status-changed', (event) => {
if (!disposed) {
publishStatus(event.payload);
}
}).then((stop) => {
if (disposed) {
stop();
} else {
unlisten = stop;
}
}).catch((error) => {
if (!disposed) {
setStatusError(String(error));
}
});
void refreshStatus();
const timer = window.setInterval(() => {
void refreshStatus();
}, 1500);
if (!document.hidden) {
void refreshStatus();
}
}, 10_000);
return () => window.clearInterval(timer);
}, [refreshStatus]);
return () => {
disposed = true;
unlisten?.();
window.clearInterval(timer);
};
}, [publishStatus, refreshStatus]);
const value = useMemo(
() => ({ status, statusError, publishStatus: setStatus }),
[status, statusError],
() => ({ status, statusError, refreshStatus, publishStatus }),
[publishStatus, refreshStatus, status, statusError],
);
return <CoreRuntimeContext.Provider value={value}>{children}</CoreRuntimeContext.Provider>;
+1 -13
View File
@@ -366,17 +366,6 @@ const emptyProviderDraft = (): ProviderDraft => ({
cloakCacheUserId: false,
});
const deepSeekDefaultModels = (): ModelOption[] => [
{
name: 'deepseek-chat',
thinking: { levels: [...DEEPSEEK_THINKING_LEVELS] },
},
{
name: 'deepseek-reasoner',
thinking: { levels: [...DEEPSEEK_THINKING_LEVELS] },
},
];
export const createProviderDraft = (category: ProviderCategory): ProviderDraft => {
const draft = emptyProviderDraft();
if (category === 'openai-compatibility') return { ...draft, thinkingLevels: [] };
@@ -385,7 +374,6 @@ export const createProviderDraft = (category: ProviderCategory): ProviderDraft =
...draft,
name: 'DeepSeek',
baseUrl: DEEPSEEK_BASE_URL,
models: deepSeekDefaultModels(),
thinkingLevels: [...DEEPSEEK_THINKING_LEVELS],
};
};
@@ -1155,7 +1143,7 @@ function ApiProviderDialog({
<img src={deepseekIcon} alt="" className="provider-logo" />
<div>
<strong>OpenAI 兼容预设</strong>
<span>已预填 DeepSeek 官方地址与常用模型</span>
<span>已预填 DeepSeek 官方地址,保存时自动发现并开放全部模型</span>
</div>
</div>
) : null}
+13 -27
View File
@@ -94,9 +94,12 @@ const compareVersions = (left: string, right: string) => {
};
export function KernelPage() {
const { publishStatus } = useCoreRuntime();
const [coreStatus, setCoreStatus] = useState<CoreStatus | null>(null);
const [statusError, setStatusError] = useState('');
const {
status: coreStatus,
statusError,
refreshStatus,
publishStatus,
} = useCoreRuntime();
const [platform, setPlatform] = useState<CorePlatform | null>(null);
const [platformError, setPlatformError] = useState('');
const [latest, setLatest] = useState<CoreLatest | null>(cachedLatest);
@@ -165,7 +168,7 @@ export function KernelPage() {
if (task.result) {
setMessage(task.message || `${task.result.version} 安装完成`);
setMessageType('success');
void loadCoreStatus();
void refreshStatus();
return;
}
@@ -196,7 +199,6 @@ export function KernelPage() {
}
});
loadCoreStatus();
loadPlatform();
loadBundledCore();
loadInstallTask();
@@ -275,19 +277,6 @@ export function KernelPage() {
};
}, [allowLanAccess]);
const loadCoreStatus = async () => {
try {
const result = await invoke<CoreStatus>('get_core_status');
setCoreStatus(result);
publishStatus(result);
setStatusError('');
} catch (error) {
setCoreStatus(null);
publishStatus(null);
setStatusError(String(error));
}
};
const runCoreProcessCommand = async (
command: CoreProcessCommand,
messages?: { success?: string; failure?: string },
@@ -302,15 +291,12 @@ export function KernelPage() {
try {
const result = await invoke<CoreStatus>(command);
setCoreStatus(result);
publishStatus(result);
setStatusError('');
showProcessNotice(messages?.success ?? `内核${actionLabel}成功`, 'success');
return true;
} catch (error) {
const errorMessage = String(error);
await loadCoreStatus();
setStatusError(errorMessage);
await refreshStatus();
showProcessNotice(
`${messages?.failure ?? `内核${actionLabel}失败`}:${errorMessage}`,
'error',
@@ -483,7 +469,7 @@ export function KernelPage() {
message: `${result.version} 安装完成`,
result,
});
await Promise.all([loadCoreStatus(), loadBundledCore()]);
await Promise.all([refreshStatus(), loadBundledCore()]);
} catch (error) {
const errorMessage = String(error);
setMessage(errorMessage);
@@ -532,7 +518,7 @@ export function KernelPage() {
const result = await invoke<CoreInstallResult>('install_bundled_core');
setMessage(`${result.version} 内置内核安装完成`);
setMessageType('success');
await Promise.all([loadCoreStatus(), loadBundledCore()]);
await Promise.all([refreshStatus(), loadBundledCore()]);
} catch (error) {
const errorMessage = String(error);
setMessage(errorMessage);
@@ -820,7 +806,7 @@ export function KernelPage() {
type="button"
className="secondary-button"
disabled={processBusy || networkBusy}
onClick={loadCoreStatus}
onClick={() => void refreshStatus()}
>
刷新状态
</button>
@@ -876,7 +862,7 @@ export function KernelPage() {
</button>
<button
type="button"
className="primary-button"
className="secondary-button"
title={latestVersion ? `安装 ${latestVersion}` : '安装最新版'}
disabled={!latestVersion || installDisabled}
onClick={() => installVersion(latestVersion)}
@@ -894,7 +880,7 @@ export function KernelPage() {
</button>
<button
type="button"
className="secondary-button"
className="primary-button"
title={(bundledCore?.assetName ?? bundledCoreError) || '当前发行包未包含内置内核'}
disabled={!bundledCore || installDisabled}
onClick={() => void installBundledCore()}
+1 -1
View File
@@ -53,7 +53,7 @@ export const thinkingAliasSourceKindLabel = (kind: string) => {
};
const thinkingAliasProviderDetail = (kind: string, provider: string) => (
provider === thinkingAliasSourceKindLabel(kind) ? '内核模型目录' : provider
provider === thinkingAliasSourceKindLabel(kind) ? '内核当前可用模型' : provider
);
const thinkingAliasSourceDetail = (source: ThinkingAliasSource) => (