From 50b35ae6bd6b8d82bd90d17c68ebb2fcb827df83 Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Sat, 25 Jul 2026 20:47:57 +0800 Subject: [PATCH] feat: redesign agent configuration UI and add Codex session tab - add localized core configuration and session management tabs - display action errors inline and streamline status feedback - refine responsive layouts, dialogs, action buttons, and default window size --- src-tauri/tauri.conf.json | 4 +- src/i18n/ja.ts | 13 +- src/i18n/resources.ts | 26 +- src/i18n/traditional.ts | 1 + src/pages/AgentsPage.tsx | 495 +++++++++++++++++++++++--------------- src/styles.css | 307 +++++++++++++++-------- 6 files changed, 528 insertions(+), 318 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 8a36495..0aef32e 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,8 +14,8 @@ { "label": "main", "title": "EasyCLIProxyAPI", - "width": 1200, - "height": 810, + "width": 1359, + "height": 865, "minWidth": 640, "minHeight": 600 } diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index ae6adf8..0fa478a 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -360,15 +360,7 @@ export const jaOverrides = { 'agents.error.modelsLoading': 'モデル一覧を読み込み中です。しばらくしてから再試行してください', 'agents.error.noModels': '選択可能なモデルがないため、設定変更を適用できません', 'agents.error.selectionGone': '選択中のモデルは利用可能一覧にありません。更新後に選び直してください', - 'agents.notice.enabled': '{name} の設定変更を適用しました', - 'agents.notice.applied': '{name} の設定変更を適用しました', - 'agents.notice.defaultWritten': '{name} のデフォルト設定を書き込みました', - 'agents.notice.updated': '{name} の設定変更を更新しました', 'agents.error.applyFirst': '選択したモデルはまだ適用されていません。先に「設定変更を適用」をクリックしてください', - 'agents.notice.appliedLaunch': '{name} にモデルを適用して起動しました', - 'agents.notice.syncedLaunch': '{name} の管理対象設定を同期して起動しました', - 'agents.notice.managedLaunch': '{name} を設定変更が適用された状態で起動しました', - 'agents.notice.launched': '{name} を起動しました', 'agents.status.detecting': '検出中', 'agents.status.unsupported': 'プラットフォーム非対応', 'agents.status.notInstalled': '未インストール', @@ -381,6 +373,11 @@ export const jaOverrides = { 'agents.status.unconfigured': '未設定', 'agents.title': 'エージェント設定', 'agents.redetect': '再検出', + 'agents.tabs.label': 'エージェント設定ページ', + 'agents.tabs.core': 'コア設定', + 'agents.tabs.sessions': 'セッション管理', + 'agents.sessions.title': 'Codex セッション管理', + 'agents.sessions.description': 'セッションの一覧と管理機能はここに表示されます。', 'agents.localClients': 'ローカルクライアント', 'agents.selectClient': '管理するエージェントを選択', 'agents.installStatus': 'インストール状態', diff --git a/src/i18n/resources.ts b/src/i18n/resources.ts index 3235133..97a9fd7 100644 --- a/src/i18n/resources.ts +++ b/src/i18n/resources.ts @@ -363,15 +363,7 @@ export const zhCN = { 'agents.error.modelsLoading': '模型列表仍在加载,请稍后再试', 'agents.error.noModels': '当前没有可选模型,无法应用配置修改', 'agents.error.selectionGone': '当前选择已不在可用模型列表中,请刷新模型后重新选择', - 'agents.notice.enabled': '{name} 的配置修改已应用', - 'agents.notice.applied': '{name} 的配置修改已应用', - 'agents.notice.defaultWritten': '{name} 的默认配置已写入', - 'agents.notice.updated': '{name} 的配置修改已更新', 'agents.error.applyFirst': '模型选择尚未应用,请先点击“应用配置修改”', - 'agents.notice.appliedLaunch': '{name} 已应用模型并启动', - 'agents.notice.syncedLaunch': '{name} 已同步受管配置并启动', - 'agents.notice.managedLaunch': '{name} 已启动,配置修改仍然生效', - 'agents.notice.launched': '{name} 已启动', 'agents.status.detecting': '检测中', 'agents.status.unsupported': '平台不支持', 'agents.status.notInstalled': '未安装', @@ -384,6 +376,11 @@ export const zhCN = { 'agents.status.unconfigured': '尚未配置', 'agents.title': '智能体配置', 'agents.redetect': '重新检测', + 'agents.tabs.label': '智能体配置子页面', + 'agents.tabs.core': '核心配置', + 'agents.tabs.sessions': '会话管理', + 'agents.sessions.title': 'Codex 会话管理', + 'agents.sessions.description': '会话列表与管理功能将在这里显示。', 'agents.localClients': '本机客户端', 'agents.selectClient': '选择需要管理的智能体', 'agents.installStatus': '安装状态', @@ -1078,15 +1075,7 @@ export const en: Record = { 'agents.error.modelsLoading': 'The model list is still loading. Try again shortly.', 'agents.error.noModels': 'No models are available, so configuration changes cannot be applied', 'agents.error.selectionGone': 'The selected model is no longer available. Refresh and select it again.', - 'agents.notice.enabled': 'Configuration changes for {name} were applied.', - 'agents.notice.applied': 'Configuration changes for {name} were applied.', - 'agents.notice.defaultWritten': 'The default configuration for {name} was written.', - 'agents.notice.updated': 'Configuration changes for {name} were updated.', 'agents.error.applyFirst': 'The selected model has not been applied. Click “Apply Configuration Changes” first.', - 'agents.notice.appliedLaunch': '{name} applied the model and started.', - 'agents.notice.syncedLaunch': '{name} synchronized the managed configuration and started.', - 'agents.notice.managedLaunch': '{name} started with the configuration changes still applied.', - 'agents.notice.launched': '{name} started', 'agents.status.detecting': 'Detecting', 'agents.status.unsupported': 'Unsupported Platform', 'agents.status.notInstalled': 'Not Installed', @@ -1099,6 +1088,11 @@ export const en: Record = { 'agents.status.unconfigured': 'Not Configured', 'agents.title': 'Agent Configuration', 'agents.redetect': 'Detect Again', + 'agents.tabs.label': 'Agent configuration sections', + 'agents.tabs.core': 'Core Configuration', + 'agents.tabs.sessions': 'Session Management', + 'agents.sessions.title': 'Codex Session Management', + 'agents.sessions.description': 'Session browsing and management will appear here.', 'agents.localClients': 'Local Clients', 'agents.selectClient': 'Select an agent to manage', 'agents.installStatus': 'Installation Status', diff --git a/src/i18n/traditional.ts b/src/i18n/traditional.ts index e2d1c14..3bad967 100644 --- a/src/i18n/traditional.ts +++ b/src/i18n/traditional.ts @@ -2,6 +2,7 @@ const phraseReplacements: ReadonlyArray = [ ['繁体中文', '繁體中文'], ['简体中文', '簡體中文'], ['智能体', '智慧代理'], + ['会话', '工作階段'], ['使用记录', '使用記錄'], ['认证文件', '認證檔案'], ['配置文件', '設定檔'], diff --git a/src/pages/AgentsPage.tsx b/src/pages/AgentsPage.tsx index 1a693e8..3797cda 100644 --- a/src/pages/AgentsPage.tsx +++ b/src/pages/AgentsPage.tsx @@ -17,6 +17,7 @@ import { Check, ChevronDown, LoaderCircle, + MessageSquare, Play, RefreshCw, Search, @@ -88,6 +89,14 @@ type AgentDefinition = { descriptionKey: 'agents.description.claudeCode' | 'agents.description.claudeDesktop' | 'agents.description.codex' | 'agents.description.opencode' | 'agents.description.openclaw' | 'agents.description.hermes'; }; +type AgentSubpageId = 'core' | 'sessions'; + +type AgentSubpageDefinition = { + id: AgentSubpageId; + labelKey: 'agents.tabs.core' | 'agents.tabs.sessions'; + clients?: readonly AgentClientId[]; +}; + const agentDefinitions: AgentDefinition[] = [ { id: 'claude-code', @@ -127,6 +136,20 @@ const agentDefinitions: AgentDefinition[] = [ }, ]; +const agentSubpages: AgentSubpageDefinition[] = [ + { + id: 'core', + labelKey: 'agents.tabs.core', + }, + { + id: 'sessions', + labelKey: 'agents.tabs.sessions', + clients: ['codex'], + }, +]; + +const DEFAULT_AGENT_SUBPAGE: AgentSubpageId = 'core'; + const AGENT_MODEL_SELECTIONS_KEY = 'cpa-gui.agent-model-selections.v1'; const AGENT_SELECTED_CLIENT_KEY = 'cpa-gui.agent-selected-client.v1'; @@ -455,6 +478,7 @@ function AgentModelPicker({ export function AgentsPage() { const { t } = useI18n(); const [selected, setSelected] = useState(readSelectedAgentClient); + const [activeSubpage, setActiveSubpage] = useState(DEFAULT_AGENT_SUBPAGE); const [statuses, setStatuses] = useState([]); const [models, setModels] = useState([]); const [modelByClient, setModelByClient] = useState>>( @@ -465,9 +489,12 @@ export function AgentsPage() { const [modelLoading, setModelLoading] = useState(false); const [busyAction, setBusyAction] = useState<'apply' | 'default' | 'launch' | null>(null); const busy = busyAction !== null; - const [error, setError] = useState(''); + const [detectionError, setDetectionError] = useState(''); const [modelError, setModelError] = useState(''); - const [notice, setNotice] = useState(''); + const [modelSelectionError, setModelSelectionError] = useState(''); + const [configurationError, setConfigurationError] = useState(''); + const [launchError, setLaunchError] = useState(''); + const [defaultError, setDefaultError] = useState(''); const [defaultConfirmOpen, setDefaultConfirmOpen] = useState(false); const loadStatuses = useCallback(async (forceRefresh = false) => { @@ -484,6 +511,7 @@ export function AgentsPage() { try { const nextModels = await invoke('get_agent_models'); setModels(nextModels); + setModelSelectionError(''); setModelByClient((current) => { const next = reconcileAgentModelSelections(current, nextModels); writeAgentModelSelections(next); @@ -498,11 +526,11 @@ export function AgentsPage() { const refresh = useCallback(async () => { setLoading(true); - setError(''); + setDetectionError(''); try { await Promise.all([loadStatuses(true), loadModels()]); } catch (requestError) { - setError(String(requestError)); + setDetectionError(String(requestError)); } finally { setLoading(false); } @@ -510,9 +538,9 @@ export function AgentsPage() { useEffect(() => { setLoading(true); - setError(''); + setDetectionError(''); void Promise.all([loadStatuses(), loadModels()]) - .catch((requestError) => setError(String(requestError))) + .catch((requestError) => setDetectionError(String(requestError))) .finally(() => setLoading(false)); }, [loadModels, loadStatuses]); @@ -521,8 +549,9 @@ export function AgentsPage() { let stop: (() => void) | null = null; void listen('config-files-changed', () => { if (disposed) return; + setDetectionError(''); void Promise.all([loadStatuses(true), loadModels()]).catch((requestError) => { - if (!disposed) setError(String(requestError)); + if (!disposed) setDetectionError(String(requestError)); }); }).then((unlisten) => { if (disposed) unlisten(); @@ -538,6 +567,15 @@ export function AgentsPage() { writeSelectedAgentClient(selected); }, [selected]); + useEffect(() => { + setActiveSubpage(DEFAULT_AGENT_SUBPAGE); + setModelSelectionError(''); + setConfigurationError(''); + setLaunchError(''); + setDefaultError(''); + setDefaultConfirmOpen(false); + }, [selected]); + useEffect(() => { setLaunchTargetByClient((current) => agentDefinitions.reduce>>( (next, definition) => { @@ -587,9 +625,19 @@ export function AgentsPage() { void loadModels(); }; + const reloadStatusesAfterAction = async () => { + setDetectionError(''); + try { + await loadStatuses(true); + } catch (requestError) { + setDetectionError(String(requestError)); + } + }; + const selectModel = (value: string) => { const model = findAgentModel(models, value); if (!model) return; + setModelSelectionError(''); setModelByClient((current) => { const next = { ...current, [selected]: model.name }; writeAgentModelSelections(next); @@ -599,36 +647,35 @@ export function AgentsPage() { const requireSelectedModel = () => { if (modelLoading) { - setError(t('agents.error.modelsLoading')); + setModelSelectionError(t('agents.error.modelsLoading')); return null; } if (models.length === 0) { - setError(modelError || t('agents.error.noModels')); + setModelSelectionError(modelError || t('agents.error.noModels')); return null; } const model = findAgentModel(models, selectedModel); if (!model) { - setError(t('agents.error.selectionGone')); + setModelSelectionError(t('agents.error.selectionGone')); return null; } + setModelSelectionError(''); return model.name; }; const applyConfigurationChanges = async () => { + setConfigurationError(''); const model = requireSelectedModel(); if (!model) return; setBusyAction('apply'); - setError(''); - setNotice(''); try { await invoke('apply_agent_config', { client: selected, model, }); - setNotice(t('agents.notice.applied', { name: activeDefinition.name })); - await loadStatuses(true); + await reloadStatusesAfterAction(); } catch (requestError) { - setError(String(requestError)); + setConfigurationError(String(requestError)); } finally { setBusyAction(null); } @@ -638,18 +685,16 @@ export function AgentsPage() { const model = requireSelectedModel(); if (!model) return; setBusyAction('default'); - setError(''); - setNotice(''); + setDefaultError(''); try { await invoke('reset_agent_config_to_default', { client: selected, model, }); setDefaultConfirmOpen(false); - setNotice(t('agents.notice.defaultWritten', { name: activeDefinition.name })); - await loadStatuses(true); + await reloadStatusesAfterAction(); } catch (requestError) { - setError(String(requestError)); + setDefaultError(String(requestError)); } finally { setBusyAction(null); } @@ -657,41 +702,32 @@ export function AgentsPage() { const launchAgent = async () => { setBusyAction('launch'); - setError(''); - setNotice(''); + setLaunchError(''); try { if (draftChanged) { throw new Error(t('agents.error.applyFirst')); } await invoke('launch_agent', { client: selected, target: selectedLaunchTarget?.id }); - setNotice(t('agents.notice.managedLaunch', { name: activeDefinition.name })); } catch (requestError) { - setError(String(requestError)); + setLaunchError(String(requestError)); } finally { setBusyAction(null); } }; - const statusLabel = loading - ? t('agents.status.detecting') - : !activeStatus?.supportedPlatform - ? t('agents.status.unsupported') - : !activeStatus.installed - ? t('agents.status.notInstalled') - : activeStatus.modificationState === 'external-changed' - ? t('agents.status.externalChanged') - : activeStatus.modificationState === 'invalid' - ? t('agents.status.invalid') - : activeStatus.modificationState === 'applied' - ? t('agents.status.applied') - : t('agents.status.unconfigured'); - const statusTone = activeStatus?.modificationState === 'external-changed' - || activeStatus?.modificationState === 'invalid' - || !activeStatus?.supportedPlatform - ? 'error' - : activeStatus?.modificationState === 'applied' - ? 'success' - : ''; + const openDefaultConfirmation = () => { + setDefaultError(''); + setDefaultConfirmOpen(true); + }; + + const closeDefaultConfirmation = () => { + setDefaultError(''); + setDefaultConfirmOpen(false); + }; + + const availableSubpages = agentSubpages.filter( + (subpage) => !subpage.clients || subpage.clients.includes(selected), + ); return (
@@ -700,17 +736,19 @@ export function AgentsPage() { Agent Clients

{t('agents.title')}

- +
+ {detectionError ? ( + + {detectionError} + + ) : null} + +
-
- {error ?
{error}
: null} - {!error && notice ?
{notice}
: null} -
-
-
-
- -

{activeDefinition.name}

{t(activeDefinition.descriptionKey)}
-
- {statusLabel} -
- -
- {activeStatus?.error ?
{activeStatus.error}
: null} - {activeStatus?.warnings.length && !activeStatus.error ? ( -
{activeStatus.warnings.join(';')}
- ) : null} -
- -
-
- {t('agents.installStatus')} - {activeStatus?.installed ? t('agents.clientDetected') : t('agents.clientNotDetected')} -
-
- {t('agents.clientVersion')} - {activeStatus?.version ?? t('agents.notFetched')} -
-
- -
-
-
{t('agents.useModel')}{t('agents.useModelDescription')}
- {draftChanged ? {t('agents.pending')} : null} -
- - - {modelLoading - ? t('agents.model.readingAvailable') - : modelError && models.length === 0 - ? modelError - : models.length === 0 - ? t('agents.model.cannotConfigure') - : activeStatus?.modificationState === 'applied' - ? t('agents.model.current', { model: appliedModel || '—' }) - : t('agents.model.firstSelection', { count: models.length })} - -
- -
-
- {t('agents.modify.title')} - {activeStatus?.modificationState === 'external-changed' - ? t('agents.modify.externalChanged') - : activeStatus?.modificationState === 'invalid' - ? t('agents.modify.invalid') - : activeStatus?.modificationState === 'applied' - ? t('agents.modify.applied') - : t('agents.modify.unconfigured')} -
-
+
+ {availableSubpages.map((subpage) => ( - -
-
+ ))} +
-
-
- {activeStatus?.modificationState === 'applied' ? : } - {activeStatus?.modificationState === 'applied' - ? draftChanged - ? t('agents.footer.changed') - : t('agents.footer.applied') - : activeStatus?.modificationState === 'external-changed' - ? t('agents.footer.externalChanged') - : activeStatus?.modificationState === 'invalid' - ? t('agents.footer.invalidManaged') - : !activeStatus?.supportedPlatform - ? t('agents.footer.unsupported') - : !activeStatus.installed - ? t('agents.footer.installFirst') - : activeStatus.launchTargets.length === 0 - ? t('agents.footer.noCommand') - : t('agents.footer.enableFirst')} -
-
- {activeLaunchTargets.length > 1 ? ( -
- {activeLaunchTargets.map((target) => ( - - ))} + {activeSubpage === 'core' ? ( +
+
+
+ {t('agents.installStatus')} + {activeStatus?.installed ? t('agents.clientDetected') : t('agents.clientNotDetected')} +
+
+ {t('agents.clientVersion')} + {activeStatus?.version ?? t('agents.notFetched')} +
+
+ + {activeStatus?.error || activeStatus?.warnings.length ? ( +
+ {activeStatus.error ? ( + {activeStatus.error} + ) : ( + {activeStatus.warnings.join(';')} + )}
) : null} - + +
+
+
{t('agents.useModel')}{t('agents.useModelDescription')}
+ {draftChanged ? {t('agents.pending')} : null} +
+ + + {modelSelectionError + || modelError + || (modelLoading + ? t('agents.model.readingAvailable') + : models.length === 0 + ? t('agents.model.cannotConfigure') + : activeStatus?.modificationState === 'applied' + ? t('agents.model.current', { model: appliedModel || '—' }) + : t('agents.model.firstSelection', { count: models.length }))} + +
+ +
+
+ {t('agents.modify.title')} + {activeStatus?.modificationState === 'external-changed' + ? t('agents.modify.externalChanged') + : activeStatus?.modificationState === 'invalid' + ? t('agents.modify.invalid') + : activeStatus?.modificationState === 'applied' + ? t('agents.modify.applied') + : t('agents.modify.unconfigured')} +
+
+
+ + +
+ {configurationError ? ( + + {configurationError} + + ) : null} +
+
+ +
+
+ {activeStatus?.modificationState === 'applied' ? : } + {activeStatus?.modificationState === 'applied' + ? draftChanged + ? t('agents.footer.changed') + : t('agents.footer.applied') + : activeStatus?.modificationState === 'external-changed' + ? t('agents.footer.externalChanged') + : activeStatus?.modificationState === 'invalid' + ? t('agents.footer.invalidManaged') + : !activeStatus?.supportedPlatform + ? t('agents.footer.unsupported') + : !activeStatus.installed + ? t('agents.footer.installFirst') + : activeStatus.launchTargets.length === 0 + ? t('agents.footer.noCommand') + : t('agents.footer.enableFirst')} +
+
+
+ {activeLaunchTargets.length > 1 ? ( +
+ {activeLaunchTargets.map((target) => ( + + ))} +
+ ) : null} + +
+ {launchError ? ( + + {launchError} + + ) : null} +
+
-
+ ) : null} + + {selected === 'codex' && activeSubpage === 'sessions' ? ( +
+
+ +
+

{t('agents.sessions.title')}

+

{t('agents.sessions.description')}

+
+
+
+ ) : null}
@@ -901,8 +1001,13 @@ export function AgentsPage() {

{t('agents.default.description', { name: activeDefinition.name })}

+ {defaultError ? ( + + {defaultError} + + ) : null}
- +