Fix Codex OAuth configuration and refine UI controls

This commit is contained in:
lzt404 committed 2026-07-29 14:58:25 +08:00
1 parent 1acbeb6f0a
commit 2c7030734e
7 files changed
+110 -115

No files matched your search

+23 -17
View File
@@ -14,12 +14,10 @@ import {
Languages,
LogIn,
MessageCircle,
Moon,
Network,
PackageOpen,
ServerCog,
Settings,
Sun,
X,
} from 'lucide-react';
import appLogo from './assets/logo.jpg';
@@ -308,22 +306,30 @@ function AppContent() {
</nav>
<div className="sidebar-bottom">
<button
type="button"
className="sidebar-theme-toggle"
aria-label={theme === 'dark' ? t('app.theme.switchToLight') : t('app.theme.switchToDark')}
aria-pressed={theme === 'dark'}
title={theme === 'dark' ? t('app.theme.switchToLight') : t('app.theme.switchToDark')}
onClick={() => setTheme((current) => current === 'dark' ? 'light' : 'dark')}
<div
className="sidebar-theme-selector"
role="group"
aria-label={`${t('app.theme.light')} / ${t('app.theme.dark')}`}
>
{theme === 'dark'
? <Moon size={16} aria-hidden="true" />
: <Sun size={16} aria-hidden="true" />}
<span>{theme === 'dark' ? t('app.theme.dark') : t('app.theme.light')}</span>
<i className="theme-switch-track" aria-hidden="true">
<b />
</i>
</button>
<button
type="button"
className={theme === 'light' ? 'active' : ''}
aria-pressed={theme === 'light'}
title={t('app.theme.switchToLight')}
onClick={() => setTheme('light')}
>
{t('app.theme.light')}
</button>
<button
type="button"
className={theme === 'dark' ? 'active' : ''}
aria-pressed={theme === 'dark'}
title={t('app.theme.switchToDark')}
onClick={() => setTheme('dark')}
>
{t('app.theme.dark')}
</button>
</div>
<div ref={languageMenuRef} className="sidebar-language">
<button
ref={languageButtonRef}
-2
View File
@@ -445,8 +445,6 @@ export const jaOverrides = {
'agents.sessions.repairTitle': '過去のセッションを復元',
'agents.sessions.repairDescription': '他の Provider のセッションを現在のセッション Provider に同期します。',
'agents.sessions.autoRepair': 'Codex 起動前に過去のセッションを自動復元',
'agents.sessions.autoRepairEnabled': '起動前の自動復元を有効にしました。',
'agents.sessions.autoRepairDisabled': '起動前の自動復元を無効にしました。',
'agents.sessions.settingLoading': '読み込み中',
'agents.sessions.settingOn': 'オン',
'agents.sessions.settingOff': 'オフ',
-4
View File
@@ -448,8 +448,6 @@ export const zhCN = {
'agents.sessions.repairTitle': '恢复历史会话',
'agents.sessions.repairDescription': '将其他渠道的会话同步到当前会话',
'agents.sessions.autoRepair': '启动 Codex 前自动恢复历史会话',
'agents.sessions.autoRepairEnabled': '已开启启动前自动恢复历史会话。',
'agents.sessions.autoRepairDisabled': '已关闭启动前自动恢复历史会话。',
'agents.sessions.settingLoading': '读取中',
'agents.sessions.settingOn': '已开启',
'agents.sessions.settingOff': '已关闭',
@@ -1293,8 +1291,6 @@ export const en: Record<MessageKey, string> = {
'agents.sessions.repairTitle': 'Restore Historical Sessions',
'agents.sessions.repairDescription': 'Sync sessions from other providers to the current session provider.',
'agents.sessions.autoRepair': 'Restore historical sessions before launching Codex',
'agents.sessions.autoRepairEnabled': 'Automatic restoration before launch is enabled.',
'agents.sessions.autoRepairDisabled': 'Automatic restoration before launch is disabled.',
'agents.sessions.settingLoading': 'Loading',
'agents.sessions.settingOn': 'On',
'agents.sessions.settingOff': 'Off',
+14 -4
View File
@@ -62,6 +62,7 @@ type AgentConfigStatus = {
configValid: boolean;
configured: boolean;
currentModel: string | null;
oauthConfiguration: boolean;
modificationEnabled: boolean;
modificationState: AgentModificationState;
backupAvailable: boolean;
@@ -509,7 +510,7 @@ export function AgentsPage() {
const [closeAppError, setCloseAppError] = useState('');
const [closeAppNotice, setCloseAppNotice] = useState('');
const [closeAppConfirmOpen, setCloseAppConfirmOpen] = useState(false);
const [oauthConfiguration, setOauthConfiguration] = useState(false);
const [oauthConfigurationDraft, setOauthConfigurationDraft] = useState<boolean | null>(null);
const autoLoadedModelClientRef = useRef<AgentClientId | null>(null);
const loadStatuses = useCallback(async (forceRefresh = false) => {
@@ -620,6 +621,9 @@ export function AgentsPage() {
const activeDefinition = agentDefinitions.find((agent) => agent.id === selected)
?? agentDefinitions[0];
const activeStatus = statuses.find((status) => status.id === selected) ?? null;
const oauthConfiguration = oauthConfigurationDraft
?? activeStatus?.oauthConfiguration
?? false;
const savedSelectedModel = modelByClient[selected] ?? '';
const selectedModelOption = findAgentModel(models, savedSelectedModel);
const selectedModel = selectedModelOption?.name ?? '';
@@ -629,11 +633,14 @@ export function AgentsPage() {
(target) => target.id === selectedLaunchTargetId,
) ?? activeLaunchTargets[0] ?? null;
const appliedModel = activeStatus?.appliedModel ?? activeStatus?.currentModel ?? '';
const draftChanged = Boolean(
const modelDraftChanged = Boolean(
selectedModel.trim()
&& appliedModel.trim()
&& selectedModel.trim() !== appliedModel.trim(),
);
const oauthConfigurationChanged = selected === 'codex'
&& oauthConfiguration !== Boolean(activeStatus?.oauthConfiguration);
const draftChanged = modelDraftChanged || oauthConfigurationChanged;
const canEnable = Boolean(
activeStatus?.supportedPlatform
&& activeStatus.installed
@@ -730,6 +737,7 @@ export function AgentsPage() {
oauthConfiguration,
});
await reloadStatusesAfterAction();
setOauthConfigurationDraft(null);
} catch (requestError) {
setConfigurationError(String(requestError));
} finally {
@@ -751,6 +759,7 @@ export function AgentsPage() {
});
setDefaultConfirmOpen(false);
await reloadStatusesAfterAction();
setOauthConfigurationDraft(null);
} catch (requestError) {
setDefaultError(String(requestError));
} finally {
@@ -767,6 +776,7 @@ export function AgentsPage() {
setClearConfirmOpen(false);
setClearNotice(t('agents.clear.success'));
await reloadStatusesAfterAction();
setOauthConfigurationDraft(null);
} catch (requestError) {
setClearError(String(requestError));
} finally {
@@ -942,7 +952,7 @@ export function AgentsPage() {
<section className="agent-core-setting-section agent-model-section">
<div className="agent-section-heading">
<div><strong>{t('agents.useModel')}</strong></div>
{draftChanged ? <span className="agent-pending-badge">{t('agents.pending')}</span> : null}
{modelDraftChanged ? <span className="agent-pending-badge">{t('agents.pending')}</span> : null}
</div>
<AgentModelPicker
models={models}
@@ -983,7 +993,7 @@ export function AgentsPage() {
type="checkbox"
role="switch"
checked={oauthConfiguration}
onChange={(event) => setOauthConfiguration(event.currentTarget.checked)}
onChange={(event) => setOauthConfigurationDraft(event.currentTarget.checked)}
disabled={busy}
aria-label={t('agents.modify.oauthConfiguration')}
/>
+2 -11
View File
@@ -8,7 +8,6 @@ type RepairOnLaunchSetting = {
};
type StatusMessage = {
kind: 'success' | 'error';
text: string;
};
@@ -32,7 +31,6 @@ export function CodexSessionAutoRestoreCard() {
.catch((error) => {
if (!disposed) {
setStatus({
kind: 'error',
text: t('agents.sessions.settingLoadFailed', { error: errorMessage(error) }),
});
}
@@ -53,15 +51,8 @@ export function CodexSessionAutoRestoreCard() {
request: { enabled: nextEnabled },
});
setEnabled(result.enabled);
setStatus({
kind: 'success',
text: result.enabled
? t('agents.sessions.autoRepairEnabled')
: t('agents.sessions.autoRepairDisabled'),
});
} catch (error) {
setStatus({
kind: 'error',
text: t('agents.sessions.settingFailed', { error: errorMessage(error) }),
});
} finally {
@@ -102,8 +93,8 @@ export function CodexSessionAutoRestoreCard() {
</div>
{status ? (
<small
className={`codex-auto-restore-message ${status.kind}`}
role={status.kind === 'error' ? 'alert' : 'status'}
className="codex-auto-restore-message error"
role="alert"
>
{status.text}
</small>
+22 -53
View File
@@ -440,71 +440,40 @@ input:focus-visible {
border-top: 1px solid var(--theme-e3e1db);
}
.sidebar-theme-toggle {
.sidebar-theme-selector {
display: grid;
grid-template-columns: 18px minmax(0, 1fr) 36px;
align-items: center;
gap: 8px;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 3px;
width: 100%;
min-height: 40px;
padding: 0 10px;
padding: 3px;
border: 1px solid var(--theme-d8d4cc);
border-radius: 8px;
background: var(--theme-fffdf8);
color: var(--theme-716b61);
text-align: left;
}
.sidebar-theme-toggle:hover {
border-color: var(--theme-c7c2b8);
.sidebar-theme-selector button {
min-width: 0;
min-height: 32px;
padding: 0 8px;
border: 0;
border-radius: 5px;
background: transparent;
color: var(--theme-716b61);
font-size: 12px;
font-weight: 700;
transition: background 140ms ease, color 140ms ease, box-shadow 140ms ease;
white-space: nowrap;
}
.sidebar-theme-selector button:hover {
background: var(--theme-e8e5dc);
color: var(--theme-2d2a26);
}
.sidebar-theme-toggle > svg {
color: var(--theme-7a746c);
}
.sidebar-theme-toggle > span {
min-width: 0;
overflow: hidden;
font-size: 12px;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.theme-switch-track {
position: relative;
display: block;
width: 36px;
height: 20px;
border: 1px solid var(--theme-c7c2b8);
border-radius: 999px;
.sidebar-theme-selector button.active {
background: var(--theme-dfdcd4);
transition: border-color 160ms ease, background 160ms ease;
}
.theme-switch-track b {
position: absolute;
top: 2px;
left: 2px;
display: block;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--theme-fff);
box-shadow: 0 1px 3px rgba(45, 42, 38, 0.24);
transition: transform 180ms ease, background 160ms ease;
}
.sidebar-theme-toggle[aria-pressed='true'] .theme-switch-track {
border-color: var(--theme-8069ad);
background: var(--theme-654f91);
}
.sidebar-theme-toggle[aria-pressed='true'] .theme-switch-track b {
transform: translateX(16px);
box-shadow: inset 0 0 0 1px var(--theme-d5d2cb);
color: var(--theme-2d2a26);
}
.sidebar-language {