feat(agents): improve Pi plugin configuration and update handling
This commit is contained in:
1 parent
44fb5a70fd
commit
5ffde1e668
6 files changed
+538
-80
No files matched your search
+7
-4
@@ -563,6 +563,7 @@ export const jaOverrides = {
|
||||
'agents.clientVersion': 'クライアントバージョン',
|
||||
'agents.cliVersion': 'CLI バージョン',
|
||||
'agents.appVersion': 'App バージョン',
|
||||
'agents.pluginVersion': 'プラグインバージョン',
|
||||
'agents.notFetched': '未取得',
|
||||
'agents.useModel': '使用モデル',
|
||||
'agents.pending': '未適用',
|
||||
@@ -614,12 +615,14 @@ export const jaOverrides = {
|
||||
'agents.launch.closeChatgpt': 'ChatGPT App を終了',
|
||||
'agents.launch.closingChatgpt': '終了中',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI プロバイダー',
|
||||
'agents.pi.installDescription': 'プラグインのインストール後に自動設定します',
|
||||
'agents.pi.installDescription': 'プラグインをインストールまたは更新し、CPA 認証情報、既定のプロバイダー、既定のモデルを修復します',
|
||||
'agents.pi.install': 'Pi プラグインをインストール',
|
||||
'agents.pi.update': 'Pi の設定を同期',
|
||||
'agents.pi.update': 'Pi プラグインを更新',
|
||||
'agents.pi.updateAvailable': '新しいバージョン {version} が利用できます',
|
||||
'agents.pi.updating': 'プラグインを更新中',
|
||||
'agents.pi.installing': 'プラグインをインストール中',
|
||||
'agents.pi.sync': 'Pi の設定を同期',
|
||||
'agents.pi.syncing': '設定を同期中',
|
||||
'agents.pi.repair': '設定を適用',
|
||||
'agents.pi.repairing': '設定を適用中',
|
||||
'agents.pi.uninstall': 'Pi プラグインをアンインストール',
|
||||
'agents.pi.uninstalling': 'プラグインをアンインストール中',
|
||||
'agents.default.title': 'デフォルト設定にリセット',
|
||||
|
||||
+14
-8
@@ -566,6 +566,7 @@ export const zhCN = {
|
||||
'agents.clientVersion': '客户端版本',
|
||||
'agents.cliVersion': 'CLI 版本',
|
||||
'agents.appVersion': 'App 版本',
|
||||
'agents.pluginVersion': '插件版本',
|
||||
'agents.notFetched': '未获取',
|
||||
'agents.useModel': '使用模型',
|
||||
'agents.pending': '尚未应用',
|
||||
@@ -617,12 +618,14 @@ export const zhCN = {
|
||||
'agents.launch.closeChatgpt': '关闭 ChatGPT App',
|
||||
'agents.launch.closingChatgpt': '关闭中',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
|
||||
'agents.pi.installDescription': '安装插件后自动配置',
|
||||
'agents.pi.installDescription': '安装或更新插件,并修复 CPA 凭据、默认 provider 与默认模型',
|
||||
'agents.pi.install': '安装 Pi 插件',
|
||||
'agents.pi.update': '同步 Pi 配置',
|
||||
'agents.pi.update': '更新 Pi 插件',
|
||||
'agents.pi.updateAvailable': '发现新版本 {version}',
|
||||
'agents.pi.updating': '正在更新插件',
|
||||
'agents.pi.installing': '正在安装插件',
|
||||
'agents.pi.sync': '同步 Pi 配置',
|
||||
'agents.pi.syncing': '正在同步配置',
|
||||
'agents.pi.repair': '应用配置',
|
||||
'agents.pi.repairing': '正在应用配置',
|
||||
'agents.pi.uninstall': '卸载 Pi 插件',
|
||||
'agents.pi.uninstalling': '正在卸载插件',
|
||||
'agents.default.title': '重置为默认配置',
|
||||
@@ -1525,6 +1528,7 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.clientVersion': 'Client Version',
|
||||
'agents.cliVersion': 'CLI Version',
|
||||
'agents.appVersion': 'App Version',
|
||||
'agents.pluginVersion': 'Plugin Version',
|
||||
'agents.notFetched': 'Not available',
|
||||
'agents.useModel': 'Model',
|
||||
'agents.pending': 'Not Applied',
|
||||
@@ -1576,12 +1580,14 @@ export const en: Record<MessageKey, string> = {
|
||||
'agents.launch.closeChatgpt': 'Close ChatGPT App',
|
||||
'agents.launch.closingChatgpt': 'Closing',
|
||||
'agents.pi.installTitle': 'Pi CLIProxyAPI provider',
|
||||
'agents.pi.installDescription': 'Automatically configure after installing the plugin.',
|
||||
'agents.pi.installDescription': 'Install or update the plugin and repair CPA credentials, the default provider, and the default model.',
|
||||
'agents.pi.install': 'Install Pi Plugin',
|
||||
'agents.pi.update': 'Sync Pi Configuration',
|
||||
'agents.pi.update': 'Update Pi Plugin',
|
||||
'agents.pi.updateAvailable': 'Version {version} is available',
|
||||
'agents.pi.updating': 'Updating plugin',
|
||||
'agents.pi.installing': 'Installing plugin',
|
||||
'agents.pi.sync': 'Sync Pi Configuration',
|
||||
'agents.pi.syncing': 'Syncing configuration',
|
||||
'agents.pi.repair': 'Apply Configuration',
|
||||
'agents.pi.repairing': 'Applying configuration',
|
||||
'agents.pi.uninstall': 'Uninstall Pi Plugin',
|
||||
'agents.pi.uninstalling': 'Uninstalling plugin',
|
||||
'agents.default.title': 'Reset to Default Configuration',
|
||||
|
||||
+112
-26
@@ -25,6 +25,7 @@ import {
|
||||
Search,
|
||||
Terminal,
|
||||
Trash2,
|
||||
Wrench,
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import claudeIcon from '../assets/icons/claude.svg';
|
||||
@@ -73,6 +74,7 @@ type AgentConfigStatus = {
|
||||
version: string | null;
|
||||
cliVersion: string | null;
|
||||
appVersion: string | null;
|
||||
pluginVersion: string | null;
|
||||
configValid: boolean;
|
||||
configured: boolean;
|
||||
currentModel: string | null;
|
||||
@@ -103,6 +105,12 @@ type AgentConfigActionResult = {
|
||||
conflictFiles: string[];
|
||||
};
|
||||
|
||||
type PiProviderUpdateStatus = {
|
||||
installedVersion: string | null;
|
||||
latestVersion: string | null;
|
||||
updateAvailable: boolean;
|
||||
};
|
||||
|
||||
type ChatGptCloseResult = {
|
||||
wasRunning: boolean;
|
||||
closedProcesses: number;
|
||||
@@ -554,7 +562,7 @@ export function AgentsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [modelLoading, setModelLoading] = useState(false);
|
||||
const [busyAction, setBusyAction] = useState<
|
||||
'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'sync-pi' | 'uninstall-pi' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null
|
||||
'apply' | 'close-config' | 'default' | 'clear' | 'install-pi' | 'update-pi' | 'repair-pi' | 'uninstall-pi' | 'launch' | 'launch-cli' | 'launch-app' | 'close-app' | 'oauth-check' | 'directory' | null
|
||||
>(null);
|
||||
const busy = busyAction !== null;
|
||||
const [detectionError, setDetectionError] = useState('');
|
||||
@@ -572,7 +580,9 @@ export function AgentsPage() {
|
||||
const [closeAppConfirmOpen, setCloseAppConfirmOpen] = useState(false);
|
||||
const [oauthLoginRequiredAction, setOauthLoginRequiredAction] = useState<OAuthLoginRequiredAction | null>(null);
|
||||
const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState<boolean | null>(null);
|
||||
const [piProviderUpdateStatus, setPiProviderUpdateStatus] = useState<PiProviderUpdateStatus | null>(null);
|
||||
const modelRequestRef = useRef(0);
|
||||
const piUpdateRequestRef = useRef(0);
|
||||
const claudeModelMappingsDirtyRef = useRef(false);
|
||||
|
||||
const loadStatuses = useCallback(async (forceRefresh = false) => {
|
||||
@@ -583,7 +593,7 @@ export function AgentsPage() {
|
||||
setStatuses(nextStatuses);
|
||||
}, []);
|
||||
|
||||
const loadModels = useCallback(async (client: AgentClientId) => {
|
||||
const loadModels = useCallback(async (client: AgentClientId, preferredModel = '') => {
|
||||
const requestId = modelRequestRef.current + 1;
|
||||
modelRequestRef.current = requestId;
|
||||
setModelLoading(true);
|
||||
@@ -597,7 +607,7 @@ export function AgentsPage() {
|
||||
setModelByClient((current) => {
|
||||
const next = {
|
||||
...current,
|
||||
[client]: resolveAgentModelSelection(nextModels, current[client] ?? ''),
|
||||
[client]: resolveAgentModelSelection(nextModels, current[client] ?? preferredModel),
|
||||
};
|
||||
writeAgentModelSelections(next);
|
||||
return next;
|
||||
@@ -630,14 +640,10 @@ export function AgentsPage() {
|
||||
}, [loadStatuses]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selected === 'pi') {
|
||||
setModels([]);
|
||||
setModelLoading(false);
|
||||
setModelError('');
|
||||
return;
|
||||
}
|
||||
void loadModels(selected);
|
||||
}, [loadModels, selected]);
|
||||
if (loading) return;
|
||||
const preferredModel = statuses.find((status) => status.id === selected)?.currentModel ?? '';
|
||||
void loadModels(selected, preferredModel);
|
||||
}, [loadModels, loading, selected]);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
@@ -698,6 +704,35 @@ export function AgentsPage() {
|
||||
[claudeCustomMapping, models],
|
||||
);
|
||||
|
||||
const loadPiProviderUpdateStatus = useCallback(async () => {
|
||||
const requestId = piUpdateRequestRef.current + 1;
|
||||
piUpdateRequestRef.current = requestId;
|
||||
try {
|
||||
const nextStatus = await invoke<PiProviderUpdateStatus>('check_pi_provider_update');
|
||||
if (piUpdateRequestRef.current === requestId) setPiProviderUpdateStatus(nextStatus);
|
||||
} catch {
|
||||
if (piUpdateRequestRef.current === requestId) setPiProviderUpdateStatus(null);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isPiClient || !activeStatus?.pluginInstalled || !activeStatus.pluginVersion) {
|
||||
piUpdateRequestRef.current += 1;
|
||||
setPiProviderUpdateStatus(null);
|
||||
return;
|
||||
}
|
||||
void loadPiProviderUpdateStatus();
|
||||
}, [activeStatus?.pluginInstalled, activeStatus?.pluginVersion, isPiClient, loadPiProviderUpdateStatus]);
|
||||
|
||||
const piPluginUpdateAvailable = Boolean(
|
||||
piProviderUpdateStatus?.updateAvailable
|
||||
&& piProviderUpdateStatus.installedVersion === activeStatus?.pluginVersion
|
||||
&& piProviderUpdateStatus.latestVersion,
|
||||
);
|
||||
const piPluginUpdateTitle = piPluginUpdateAvailable
|
||||
? t('agents.pi.updateAvailable', { version: piProviderUpdateStatus?.latestVersion ?? '' })
|
||||
: activeStatus?.pluginVersion ?? undefined;
|
||||
|
||||
useEffect(() => {
|
||||
if (!isClaudeModelMappingClient || !selectedModel) return;
|
||||
const appliedMappings = selected === 'claude-code'
|
||||
@@ -772,10 +807,9 @@ export function AgentsPage() {
|
||||
activeStatus?.supportedPlatform
|
||||
&& activeStatus.installed
|
||||
&& !modelLoading
|
||||
&& (isPiClient
|
||||
|| (isClaudeModelMappingClient
|
||||
&& (isClaudeModelMappingClient
|
||||
? claudeMappingsReady
|
||||
: selectedModelOption)),
|
||||
: selectedModelOption),
|
||||
);
|
||||
const launchEnabled = Boolean(
|
||||
activeStatus?.supportedPlatform
|
||||
@@ -937,10 +971,12 @@ export function AgentsPage() {
|
||||
};
|
||||
|
||||
const installPiProvider = async () => {
|
||||
const model = requireSelectedModel();
|
||||
if (!model) return;
|
||||
setBusyAction('install-pi');
|
||||
setConfigurationError('');
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('install_pi_provider');
|
||||
await invoke<AgentConfigActionResult>('install_pi_provider', { model });
|
||||
await reloadStatusesAfterAction();
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
@@ -949,11 +985,30 @@ export function AgentsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const syncPiProvider = async () => {
|
||||
setBusyAction('sync-pi');
|
||||
const updatePiProvider = async () => {
|
||||
const model = requireSelectedModel();
|
||||
if (!model) return;
|
||||
setBusyAction('update-pi');
|
||||
setConfigurationError('');
|
||||
setPiProviderUpdateStatus(null);
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('update_pi_provider', { model });
|
||||
await reloadStatusesAfterAction();
|
||||
await loadPiProviderUpdateStatus();
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
} finally {
|
||||
setBusyAction(null);
|
||||
}
|
||||
};
|
||||
|
||||
const repairPiProvider = async () => {
|
||||
const model = requireSelectedModel();
|
||||
if (!model) return;
|
||||
setBusyAction('repair-pi');
|
||||
setConfigurationError('');
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('sync_pi_provider');
|
||||
await invoke<AgentConfigActionResult>('repair_pi_provider', { model });
|
||||
await reloadStatusesAfterAction();
|
||||
} catch (requestError) {
|
||||
setConfigurationError(String(requestError));
|
||||
@@ -965,6 +1020,7 @@ export function AgentsPage() {
|
||||
const uninstallPiProvider = async () => {
|
||||
setBusyAction('uninstall-pi');
|
||||
setConfigurationError('');
|
||||
setPiProviderUpdateStatus(null);
|
||||
try {
|
||||
await invoke<AgentConfigActionResult>('uninstall_pi_provider');
|
||||
await reloadStatusesAfterAction();
|
||||
@@ -1272,7 +1328,7 @@ export function AgentsPage() {
|
||||
role="tabpanel"
|
||||
aria-labelledby="agent-subpage-tab-core"
|
||||
>
|
||||
<div className={`agent-status-grid ${selected === 'codex' ? 'codex-status-grid' : ''}`}>
|
||||
<div className={`agent-status-grid ${selected === 'codex' ? 'codex-status-grid' : isPiClient ? 'pi-status-grid' : ''}`}>
|
||||
<div>
|
||||
<span><BadgeCheck size={14} />{t('agents.installStatus')}</span>
|
||||
<strong>{activeStatus?.installed ? t('agents.clientDetected') : t('agents.clientNotDetected')}</strong>
|
||||
@@ -1288,6 +1344,27 @@ export function AgentsPage() {
|
||||
<strong title={activeStatus?.appVersion ?? undefined}>{activeStatus?.appVersion ?? t('agents.notFetched')}</strong>
|
||||
</div>
|
||||
</>
|
||||
) : isPiClient ? (
|
||||
<>
|
||||
<div>
|
||||
<span>{t('agents.clientVersion')}</span>
|
||||
<strong title={activeStatus?.version ?? undefined}>{activeStatus?.version ?? t('agents.notFetched')}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
{t('agents.pluginVersion')}
|
||||
{piPluginUpdateAvailable ? (
|
||||
<span
|
||||
className="agent-version-update-dot"
|
||||
role="status"
|
||||
aria-label={piPluginUpdateTitle}
|
||||
title={piPluginUpdateTitle}
|
||||
/>
|
||||
) : null}
|
||||
</span>
|
||||
<strong title={piPluginUpdateTitle}>{activeStatus?.pluginVersion ?? t('agents.notFetched')}</strong>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<span>{t('agents.clientVersion')}</span>
|
||||
@@ -1296,7 +1373,7 @@ export function AgentsPage() {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{activeStatus?.error || (!isPiClient && activeStatus?.warnings.length) ? (
|
||||
{activeStatus?.error || activeStatus?.warnings.length ? (
|
||||
<div className="agent-status-messages" aria-live="polite">
|
||||
{activeStatus.error ? (
|
||||
<span className="agent-inline-message error" role="alert">{activeStatus.error}</span>
|
||||
@@ -1306,7 +1383,7 @@ export function AgentsPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{!isClaudeModelMappingClient && !isPiClient ? (
|
||||
{!isClaudeModelMappingClient ? (
|
||||
<section className="agent-core-setting-section agent-model-section">
|
||||
<div className="agent-section-heading">
|
||||
<div><strong>{t('agents.useModel')}</strong></div>
|
||||
@@ -1394,7 +1471,7 @@ export function AgentsPage() {
|
||||
type="button"
|
||||
className={activeStatus?.pluginInstalled ? 'danger-button' : 'primary-button'}
|
||||
onClick={() => void (activeStatus?.pluginInstalled ? uninstallPiProvider() : installPiProvider())}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform || (!activeStatus.pluginInstalled && !selectedModelOption)}
|
||||
>
|
||||
{busyAction === 'install-pi' || busyAction === 'uninstall-pi'
|
||||
? <LoaderCircle size={16} className="spin" />
|
||||
@@ -1406,11 +1483,20 @@ export function AgentsPage() {
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button"
|
||||
onClick={() => void syncPiProvider()}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform || !activeStatus.pluginInstalled}
|
||||
onClick={() => void repairPiProvider()}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform || !activeStatus.pluginInstalled || !selectedModelOption}
|
||||
>
|
||||
{busyAction === 'sync-pi' ? <LoaderCircle size={16} className="spin" /> : <RefreshCw size={16} />}
|
||||
{busyAction === 'sync-pi' ? t('agents.pi.syncing') : t('agents.pi.sync')}
|
||||
{busyAction === 'repair-pi' ? <LoaderCircle size={16} className="spin" /> : <Wrench size={16} />}
|
||||
{busyAction === 'repair-pi' ? t('agents.pi.repairing') : t('agents.pi.repair')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-button"
|
||||
onClick={() => void updatePiProvider()}
|
||||
disabled={busy || !activeStatus?.installed || !activeStatus.supportedPlatform || !activeStatus.pluginInstalled || !selectedModelOption}
|
||||
>
|
||||
{busyAction === 'update-pi' ? <LoaderCircle size={16} className="spin" /> : <RefreshCw size={16} />}
|
||||
{busyAction === 'update-pi' ? t('agents.pi.updating') : t('agents.pi.update')}
|
||||
</button>
|
||||
</div>
|
||||
{configurationError ? (
|
||||
|
||||
@@ -631,7 +631,7 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
: offlineInstallRequired
|
||||
? t('kernel.install.githubFailed')
|
||||
: message || updateStateLabel;
|
||||
const versionStatusTone: MessageType = installTaskRunning
|
||||
const versionStatusTone: MessageType | 'update' = installTaskRunning
|
||||
? 'info'
|
||||
: offlineInstallRequired
|
||||
? 'error'
|
||||
@@ -641,7 +641,9 @@ export function KernelPage({ view = 'home' }: { view?: KernelView }) {
|
||||
? 'error'
|
||||
: currentVersion && currentVersion === latestVersion
|
||||
? 'success'
|
||||
: 'info';
|
||||
: latestVersion && currentVersion !== latestVersion
|
||||
? 'update'
|
||||
: 'info';
|
||||
const installDialogTone: MessageType = progress?.result
|
||||
? 'success'
|
||||
: progress?.phase === '安装失败'
|
||||
|
||||
@@ -1299,6 +1299,10 @@ textarea:focus-visible {
|
||||
color: var(--theme-2f6b3f);
|
||||
}
|
||||
|
||||
.panel-detail-row dd.update {
|
||||
color: var(--theme-c77b30);
|
||||
}
|
||||
|
||||
.panel-detail-row dd.error {
|
||||
color: var(--theme-9f2d20);
|
||||
}
|
||||
@@ -5389,6 +5393,10 @@ textarea:focus-visible {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.agent-status-grid.pi-status-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.agent-status-grid > div {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
@@ -5405,6 +5413,16 @@ textarea:focus-visible {
|
||||
font-size: var(--font-size-meta);
|
||||
}
|
||||
|
||||
.agent-status-grid .agent-version-update-dot {
|
||||
display: inline-block;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
flex: none;
|
||||
border-radius: 999px;
|
||||
background: var(--theme-c77b30);
|
||||
box-shadow: 0 0 0 3px rgba(199, 123, 48, 0.14);
|
||||
}
|
||||
|
||||
.agent-status-grid strong {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
@@ -7359,6 +7377,10 @@ textarea:focus-visible {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.agent-status-grid.pi-status-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.codex-session-metrics {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user