refactor: simplify easy mode setup flow and navigation

This commit is contained in:
lzt404 committed 2026-08-30 16:55:25 +08:00
1 parent 6755b7c90e
commit 97fbd19f6c
6 files changed
+335 -425

No files matched your search

+2 -6
View File
@@ -26,7 +26,7 @@ import { KernelPage } from './pages/Kernel';
import { VersionManagementPage } from './pages/VersionManagementPage';
import { OAuthManagementPage } from './pages/ManagementPages';
import { AgentsPage } from './pages/AgentsPage';
import { EasyModePage, type EasyModeDestination } from './pages/EasyModePage';
import { EasyModePage } from './pages/EasyModePage';
import { UsageRecordsPage } from './pages/UsageRecordsPage';
import { languageOptions, useI18n } from './i18n';
import { AppUpdateDialog, AppUpdateProvider, useAppUpdate } from './appUpdate';
@@ -228,10 +228,6 @@ function AppContent() {
setActive(pageId);
};
const openPage = (pageId: EasyModeDestination) => {
setActive(pageId);
};
const openContact = async () => {
try {
await invoke('open_external_url', { url: CONTACT_URL });
@@ -423,7 +419,7 @@ function AppContent() {
<main className="content">
{isAlwaysAvailablePage(activePage.id) || coreRunning ? (
activePage.id === 'easy' ? (
<EasyModePage onOpenPage={openPage} />
<EasyModePage />
) : (
<ActivePage />
)
+19 -45
View File
@@ -35,57 +35,31 @@ export const jaOverrides = {
'app.close.exiting': '終了しています...',
'app.close.exit': 'アプリを終了',
'app.close.remember': 'この選択を記憶',
'easyMode.eyebrow': 'クイックスタート',
'easyMode.title': '3 ステップで最初のエージェントを接続',
'easyMode.description': '認証方法を選び、接続を完了してから、モデルを普段使うエージェントクライアントに接続します。',
'easyMode.reset': '最初からやり直す',
'easyMode.steps.label': '接続ウィザードの手順',
'easyMode.step': 'ステップ {number}',
'easyMode.step1.title': '認証方法を選択',
'easyMode.step1.description': '既存のアカウントでログインするか、サードパーティ API キーを直接入力します。',
'easyMode.step1.selectHint': 'まず方法を 1 つ選びます。選択後、対応する設定手順に進みます。',
'easyMode.step1.selectFirst': '先に OAuth または API 接続を選択してください',
'easyMode.step1.continue': '次へ:接続を完了',
'easyMode.step1.hint': '迷ったら、公式アカウントを使い API キーを管理したくない場合は OAuth を選んでください。',
'easyMode.step2.title': 'モデル接続を完了',
'easyMode.step2.description': 'ログインまたは API 接続を完了し、保存して次へ進みます。',
'easyMode.step2.openTitle': 'まず上の設定を完了してください',
'easyMode.step2.returnHint': 'ボタンで設定ページを開きます。完了後、左側の「簡易モード」をクリックしてここに戻り、完了を確認してください。',
'easyMode.step2.doneHint': '接続設定が終わったら次へ進みます',
'easyMode.step2.complete': '接続完了、次へ',
'easyMode.step2.next': '完了しました。エージェントを接続',
'easyMode.selectedPath': '選択中の方法',
'easyMode.changePath': '方法を変更',
'easyMode.overview.coreTitle': '使い方',
'easyMode.overview.providerTitle': 'モデルを接続',
'easyMode.overview.agentTitle': 'エージェントを接続',
'easyMode.provider.oauthFit': '公式アカウントとブラウザー認証向け',
'easyMode.provider.apiFit': 'サードパーティプラットフォームの API 向け',
'easyMode.oauth.title': 'OAuth ログイン',
'easyMode.oauth.description': 'API キーを手動でコピーせず、ブラウザーで認証します。',
'easyMode.oauth.step1': 'OAuth ページを開き、ログインするサービスを選びます。',
'easyMode.oauth.step2': '「ログインを開始」をクリックし、ブラウザーで認証します。',
'easyMode.oauth.step3': 'ソフトウェアに戻り、ログイン状態が完了したことを確認します。',
'easyMode.oauth.action': 'OAuth ログインを開く',
'easyMode.api.title': 'API 接続',
'easyMode.api.description': 'サードパーティまたはセルフホストサービスの Base URL と API キーを入力します。',
'easyMode.api.step1': 'API 接続を開き、右上の「追加」をクリックします。',
'easyMode.api.step2': '備考、Base URL、API キーを入力してモデルを取得します。',
'easyMode.api.step3': '接続を保存し、少なくとも 1 つのモデルが利用可能なことを確認します。',
'easyMode.api.action': 'API 接続を設定',
'easyMode.step3.title': 'エージェントを接続',
'easyMode.step3.description': '使用するクライアントを選び、モデル設定を適用して会話を始めます。',
'easyMode.step3.ready': '準備完了',
'easyMode.step3.cardTitle': 'CPA をローカルエージェントに接続',
'easyMode.step3.cardDescription': 'エージェントページがインストール済みのクライアントを検出し、CPA の接続設定を書き込みます。',
'easyMode.api.description': 'サードパーティプラットフォームから提供された Base URL と API キーを入力します。',
'easyMode.step3.cardTitle': 'CPA をエージェントクライアントに接続',
'easyMode.step3.cardDescription': 'インストール済みのクライアントとモデルを選び、CPA の接続設定を書き込みます。',
'easyMode.step3.step1': 'エージェント設定を開き、インストール済みのクライアントを選びます。',
'easyMode.step3.step2': 'モデルを選びます。Codex では OAuth 設定も利用できます。',
'easyMode.step3.step2': '利用するモデルを選びます。',
'easyMode.step3.step3': '「設定変更を適用」をクリックし、CLI またはアプリを起動します。',
'easyMode.step3.action': 'エージェント設定を開く',
'easyMode.step3.back': '前の手順に戻る',
'easyMode.step3.note': '設定後は対応する CLI またはアプリを起動して利用できます。',
'easyMode.core.title': '先に CPA コアを起動',
'easyMode.core.description': 'OAuth、API 接続、エージェント設定にはコアの実行が必要です。',
'easyMode.core.action': 'コア制御を開く',
'easyMode.core.ready': 'コア実行中',
'easyMode.core.waiting': 'コアを待機中',
'easyMode.core.blocked': '設定ページを開く前にコアを起動してください。',
'easyMode.footer': '設定後は左側の機能メニューから詳細を管理できます。',
'easyMode.guide.title': '使い方',
'easyMode.guide.description': 'OAuth ログインまたは API 接続を選び、モデルを接続してから、利用可能なモデルをエージェントクライアントに適用します。',
'easyMode.guide.step1Title': 'モデル接続方法を選ぶ',
'easyMode.guide.step1Description': 'ログイン可能なアカウントがある場合は OAuth、サードパーティプラットフォームの Base URL と API キーがある場合は API 接続を利用できます。',
'easyMode.guide.step2Title': 'モデルを接続',
'easyMode.guide.step2Description': 'OAuth ではアカウント認証を行います。API 接続ではサードパーティプラットフォームの Base URL と API キーを入力します。保存後はエージェント設定へ進めます。',
'easyMode.guide.step3Title': 'エージェントクライアントに接続',
'easyMode.guide.step3Description': 'インストール済みのエージェントクライアントと利用可能なモデルを選び、設定を適用すると利用を開始できます。',
'easyMode.navigation.back': '前へ',
'easyMode.navigation.next': '次へ',
'error.render.title': 'ページの描画中にエラーが発生しました',
'error.unknown': '不明なエラー',
'error.reload': 'ページを再読み込み',
+19 -45
View File
@@ -35,57 +35,31 @@ export const en: Record<MessageKey, string> = {
'app.close.exiting': 'Exiting...',
'app.close.exit': 'Exit App',
'app.close.remember': 'Remember this choice',
'easyMode.eyebrow': 'Quick start',
'easyMode.title': 'Connect your first agent in three steps',
'easyMode.description': 'Choose an authentication method, finish the connection, then link a model to your favorite agent client.',
'easyMode.reset': 'Start over',
'easyMode.steps.label': 'Connection wizard steps',
'easyMode.step': 'Step {number}',
'easyMode.step1.title': 'Choose authentication',
'easyMode.step1.description': 'Sign in to an existing account, or enter a third-party API key directly.',
'easyMode.step1.selectHint': 'Choose one method first. You will then enter the matching setup step.',
'easyMode.step1.selectFirst': 'Choose OAuth or API Access first',
'easyMode.step1.continue': 'Next: finish the connection',
'easyMode.step1.hint': 'Not sure? Choose OAuth when you have an official account and do not want to manage an API key.',
'easyMode.step2.title': 'Finish the model connection',
'easyMode.step2.description': 'Complete the sign-in or add an API connection, then save it to continue.',
'easyMode.step2.openTitle': 'Complete the setup above first',
'easyMode.step2.returnHint': 'Open the setup page with the button. When finished, click “Easy Mode” in the left sidebar to return here and confirm completion.',
'easyMode.step2.doneHint': 'Continue after the connection is configured',
'easyMode.step2.complete': 'Connection complete, next step',
'easyMode.step2.next': 'I am done, connect an agent',
'easyMode.selectedPath': 'Selected method',
'easyMode.changePath': 'Change method',
'easyMode.overview.coreTitle': 'How to use',
'easyMode.overview.providerTitle': 'Connect a model',
'easyMode.overview.agentTitle': 'Connect an agent',
'easyMode.provider.oauthFit': 'Best for official accounts and browser authorization',
'easyMode.provider.apiFit': 'Best for APIs provided by third-party platforms',
'easyMode.oauth.title': 'OAuth Sign-In',
'easyMode.oauth.description': 'Authorize in your browser without copying an API key manually.',
'easyMode.oauth.step1': 'Open OAuth and choose the service you want to sign in to.',
'easyMode.oauth.step2': 'Click “Start Sign-In” and finish authorization in the browser.',
'easyMode.oauth.step3': 'Return here and confirm the sign-in status is completed.',
'easyMode.oauth.action': 'Open OAuth Sign-In',
'easyMode.api.title': 'API Access',
'easyMode.api.description': 'Enter a Base URL and API key for a third-party or self-hosted service.',
'easyMode.api.step1': 'Open API Access and click “Add” in the upper-right.',
'easyMode.api.step2': 'Enter a remark, Base URL, and API key, then fetch models.',
'easyMode.api.step3': 'Save the connection and confirm that at least one model is available.',
'easyMode.api.action': 'Configure API Access',
'easyMode.step3.title': 'Connect an agent',
'easyMode.step3.description': 'Choose the client you use and apply one model configuration to start chatting.',
'easyMode.step3.ready': 'Ready',
'easyMode.step3.cardTitle': 'Connect CPA to a local agent client',
'easyMode.step3.cardDescription': 'The agent page detects installed clients and writes the CPA connection settings for you.',
'easyMode.api.description': 'Enter the Base URL and API key provided by a third-party platform.',
'easyMode.step3.cardTitle': 'Connect CPA to an agent client',
'easyMode.step3.cardDescription': 'Choose an installed client and model here, then write the CPA connection settings.',
'easyMode.step3.step1': 'Open Agent Configuration and choose an installed client.',
'easyMode.step3.step2': 'Choose a model; Codex can also use the OAuth configuration.',
'easyMode.step3.step2': 'Choose the model you want to use.',
'easyMode.step3.step3': 'Click “Apply Configuration Changes”, then launch the CLI or App.',
'easyMode.step3.action': 'Open Agent Configuration',
'easyMode.step3.back': 'Back to previous step',
'easyMode.step3.note': 'After configuration, launch the matching CLI or App to start.',
'easyMode.core.title': 'Start the CPA core first',
'easyMode.core.description': 'OAuth, API access, and agent configuration need the core to be running.',
'easyMode.core.action': 'Open Core Control',
'easyMode.core.ready': 'Core is running',
'easyMode.core.waiting': 'Waiting for core',
'easyMode.core.blocked': 'Start the core before opening a configuration page.',
'easyMode.footer': 'After setup, use the navigation on the left to manage more details.',
'easyMode.guide.title': 'How to use',
'easyMode.guide.description': 'Choose OAuth sign-in or API Access first. After the model is connected, apply an available model to an agent client.',
'easyMode.guide.step1Title': 'Choose a model connection',
'easyMode.guide.step1Description': 'Use OAuth when an account can sign in, or API Access when you have a Base URL and API key from a third-party platform.',
'easyMode.guide.step2Title': 'Connect the model',
'easyMode.guide.step2Description': 'OAuth opens the account authorization flow. API Access asks for the Base URL and API key from the third-party platform. After saving, continue to agent configuration.',
'easyMode.guide.step3Title': 'Connect an agent client',
'easyMode.guide.step3Description': 'Choose an installed agent client and an available model, then apply the configuration to start using it.',
'easyMode.navigation.back': 'Previous',
'easyMode.navigation.next': 'Next',
'error.render.title': 'Something went wrong while rendering this page',
'error.unknown': 'Unknown error',
'error.reload': 'Reload Page',
+19 -45
View File
@@ -33,57 +33,31 @@ export const zhCN = {
'app.close.exiting': '正在退出...',
'app.close.exit': '退出程序',
'app.close.remember': '记住选项',
'easyMode.eyebrow': '新手引导',
'easyMode.title': '三步接入你的第一个智能体',
'easyMode.description': '先选择认证方式,再完成接入,最后把模型连接到常用的智能体客户端。',
'easyMode.reset': '重新开始',
'easyMode.steps.label': '接入向导步骤',
'easyMode.step': '第 {number} 步',
'easyMode.step1.title': '选择认证方式',
'easyMode.step1.description': '你可以登录已有账号,也可以直接填入第三方 API 密钥。',
'easyMode.step1.selectHint': '先选择一种方式。选定后,再进入对应的配置步骤。',
'easyMode.step1.selectFirst': '请先选择 OAuth 或 API 接入',
'easyMode.step1.continue': '下一步:完成接入',
'easyMode.step1.hint': '不知道选哪种?有官方账号并希望免填密钥时,优先选择 OAuth。',
'easyMode.step2.title': '完成模型接入',
'easyMode.step2.description': '按提示完成认证或新增 API 接入,保存后即可继续。',
'easyMode.step2.openTitle': '先完成上面的配置',
'easyMode.step2.returnHint': '点击按钮打开配置页面。完成后,点击左侧“简易模式”返回这里,再确认已完成。',
'easyMode.step2.doneHint': '完成配置后再继续下一步',
'easyMode.step2.complete': '已完成接入,下一步',
'easyMode.step2.next': '我已完成,连接智能体',
'easyMode.selectedPath': '当前选择',
'easyMode.changePath': '更换方式',
'easyMode.overview.coreTitle': '使用指导',
'easyMode.overview.providerTitle': '接入模型',
'easyMode.overview.agentTitle': '连接智能体',
'easyMode.provider.oauthFit': '适合官方账号和浏览器授权',
'easyMode.provider.apiFit': '适合使用第三方平台提供的 API',
'easyMode.oauth.title': 'OAuth 登录',
'easyMode.oauth.description': '通过浏览器授权,不需要手动复制 API 密钥。',
'easyMode.oauth.step1': '打开 OAuth 页面,选择要登录的服务。',
'easyMode.oauth.step2': '点击“开始登录”,在浏览器中完成授权。',
'easyMode.oauth.step3': '回到软件,确认登录状态显示为已完成。',
'easyMode.oauth.action': '去 OAuth 登录',
'easyMode.api.title': 'API 接入',
'easyMode.api.description': '填入 Base URL 和 API 密钥,适合第三方或自建服务。',
'easyMode.api.step1': '打开 API 接入页面,点击右上角“新增”。',
'easyMode.api.step2': '填写备注、Base URL 和 API 密钥,然后获取模型。',
'easyMode.api.step3': '保存接入,并确认至少有一个模型可用。',
'easyMode.api.action': '去配置 API',
'easyMode.step3.title': '连接智能体',
'easyMode.step3.description': '选择你正在使用的客户端,应用一个模型配置即可开始对话。',
'easyMode.step3.ready': '准备就绪',
'easyMode.step3.cardTitle': '把 CPA 接入本地智能体客户端',
'easyMode.step3.cardDescription': '智能体页面会自动检测已安装的客户端,并为它写入 CPA 所需的连接配置。',
'easyMode.api.description': '填写第三方平台提供的 Base URL 和 API 密钥。',
'easyMode.step3.cardTitle': '将 CPA 连接到智能体客户端',
'easyMode.step3.cardDescription': '在这里可以选择已安装的客户端和模型,并写入 CPA 所需的连接配置。',
'easyMode.step3.step1': '打开智能体配置,选择一个已安装的客户端。',
'easyMode.step3.step2': '选择模型;Codex 还可以选择是否使用 OAuth 配置。',
'easyMode.step3.step2': '选择要使用的模型。',
'easyMode.step3.step3': '点击“应用配置修改”,然后启动 CLI 或 App。',
'easyMode.step3.action': '打开智能体配置',
'easyMode.step3.back': '返回上一步',
'easyMode.step3.note': '配置完成后,就可以从对应的 CLI 或 App 启动。',
'easyMode.core.title': '先启动 CPA 内核',
'easyMode.core.description': 'OAuth、API 接入和智能体配置都需要内核运行。',
'easyMode.core.action': '前往内核控制',
'easyMode.core.ready': '内核运行中',
'easyMode.core.waiting': '等待内核',
'easyMode.core.blocked': '启动内核后才能打开配置页面。',
'easyMode.footer': '完成接入后,可直接使用左侧功能入口管理更多细节。',
'easyMode.guide.title': '使用指导',
'easyMode.guide.description': '先确定使用 OAuth 登录还是 API 接入,模型接入后,再把可用模型应用到智能体客户端。',
'easyMode.guide.step1Title': '选择模型接入方式',
'easyMode.guide.step1Description': '已有可登录账号时可以使用 OAuth;持有第三方平台提供的 Base URL 和 API 密钥时,可以使用 API 接入。',
'easyMode.guide.step2Title': '接入模型',
'easyMode.guide.step2Description': 'OAuth 会打开账号授权流程;API 接入需要填写第三方平台提供的 Base URL 和 API 密钥。保存后即可继续配置智能体。',
'easyMode.guide.step3Title': '连接智能体客户端',
'easyMode.guide.step3Description': '从已安装的智能体客户端中选择一个客户端和模型,应用配置后即可开始使用。',
'easyMode.navigation.back': '上一步',
'easyMode.navigation.next': '下一步',
'error.render.title': '页面渲染出现异常',
'error.unknown': '未知错误',
'error.reload': '重新加载页面',
+116 -186
View File
@@ -1,136 +1,97 @@
import { useEffect, useState } from 'react';
import { useCoreRuntime } from '../coreRuntime';
import { useState } from 'react';
import { useI18n } from '../i18n';
export type EasyModeDestination = 'home' | 'oauth' | 'api' | 'agents';
import { ApiAccessPage } from './ApiAccessPage';
import { AgentsPage } from './AgentsPage';
import { OAuthLoginPage } from './ManagementPages';
type AuthMethod = 'oauth' | 'api';
type EasyModeStep = 1 | 2 | 3;
type SavedEasyModeState = {
authMethod: AuthMethod | null;
step: EasyModeStep;
};
type SetupStep = 1 | 2 | 3;
type SetupView = SetupStep | 'oauth' | 'api' | 'agents';
const EASY_MODE_STATE_KEY = 'easy-cli-proxy-api.easy-mode.state';
const defaultState: SavedEasyModeState = {
authMethod: null,
step: 1,
};
function readSavedState(): SavedEasyModeState {
function readSavedAuthMethod(): AuthMethod {
try {
const raw = window.localStorage.getItem(EASY_MODE_STATE_KEY);
if (!raw) return defaultState;
const parsed = JSON.parse(raw) as Partial<SavedEasyModeState>;
const authMethod = parsed.authMethod === 'oauth' || parsed.authMethod === 'api'
? parsed.authMethod
: null;
const step = parsed.step === 2 || parsed.step === 3 ? parsed.step : 1;
return { authMethod, step: authMethod ? step : 1 };
if (!raw) return 'oauth';
const parsed = JSON.parse(raw) as { authMethod?: unknown };
return parsed.authMethod === 'api' ? 'api' : 'oauth';
} catch {
return defaultState;
return 'oauth';
}
}
function saveState(state: SavedEasyModeState) {
function saveAuthMethod(authMethod: AuthMethod) {
try {
window.localStorage.setItem(EASY_MODE_STATE_KEY, JSON.stringify(state));
window.localStorage.setItem(EASY_MODE_STATE_KEY, JSON.stringify({ authMethod }));
} catch {
// Keep the wizard usable when persistent storage is unavailable.
// Keep the selection in memory when persistent storage is unavailable.
}
}
export function EasyModePage({
onOpenPage,
}: {
onOpenPage: (page: EasyModeDestination) => void;
}) {
export function EasyModePage() {
const { t } = useI18n();
const { status: coreStatus } = useCoreRuntime();
const [wizardState, setWizardState] = useState<SavedEasyModeState>(readSavedState);
const coreRunning = Boolean(coreStatus?.running);
const [authMethod, setAuthMethod] = useState<AuthMethod>(readSavedAuthMethod);
const [view, setView] = useState<SetupView>(1);
useEffect(() => {
saveState(wizardState);
}, [wizardState]);
const chooseAuthMethod = (authMethod: AuthMethod) => {
setWizardState((current) => ({ ...current, authMethod, step: 1 }));
const chooseAuthMethod = (method: AuthMethod) => {
setAuthMethod(method);
saveAuthMethod(method);
};
const goToStep = (step: EasyModeStep) => {
if (!wizardState.authMethod && step !== 1) return;
setWizardState((current) => ({ ...current, step }));
const openSelectedProvider = () => {
setView(authMethod);
};
const resetWizard = () => setWizardState(defaultState);
const openAuthPage = () => {
if (!coreRunning || !wizardState.authMethod) return;
onOpenPage(wizardState.authMethod);
const openAgentSummary = () => {
setView(3);
};
const openAgentConfiguration = () => {
setView('agents');
};
const previousView: SetupView | null = view === 2
? 1
: view === 'oauth' || view === 'api'
? 2
: view === 3
? authMethod
: view === 'agents'
? 3
: null;
const continueSetup = () => {
if (view === 1) setView(2);
else if (view === 2) openSelectedProvider();
else if (view === 'oauth' || view === 'api') openAgentSummary();
else if (view === 3) openAgentConfiguration();
};
const authTitle = wizardState.authMethod === 'oauth'
? t('easyMode.oauth.title')
: t('easyMode.api.title');
const authAction = wizardState.authMethod === 'oauth'
? t('easyMode.oauth.action')
: t('easyMode.api.action');
const authSteps = wizardState.authMethod === 'oauth'
? [t('easyMode.oauth.step1'), t('easyMode.oauth.step2'), t('easyMode.oauth.step3')]
: [t('easyMode.api.step1'), t('easyMode.api.step2'), t('easyMode.api.step3')];
const stepTitles = [
t('easyMode.step1.title'),
t('easyMode.step2.title'),
t('easyMode.step3.title'),
t('easyMode.overview.coreTitle'),
t('easyMode.overview.providerTitle'),
t('easyMode.overview.agentTitle'),
];
return (
<section className="page simple-mode-page">
<header className="simple-mode-header">
<div>
<span className="simple-mode-eyebrow">{t('easyMode.eyebrow')}</span>
<h1>{t('easyMode.title')}</h1>
<p>{t('easyMode.description')}</p>
</div>
<button type="button" className="secondary-button simple-mode-reset" onClick={resetWizard}>
{t('easyMode.reset')}
</button>
</header>
{!coreRunning ? (
<div className="simple-mode-core-alert" role="status">
<div>
<strong>{t('easyMode.core.title')}</strong>
<p>{t('easyMode.core.description')}</p>
</div>
<button type="button" className="secondary-button" onClick={() => onOpenPage('home')}>
{t('easyMode.core.action')}
</button>
</div>
) : null}
<div className="simple-mode-progress" role="list" aria-label={t('easyMode.steps.label')}>
<div className="simple-mode-progress" aria-label={t('easyMode.steps.label')}>
{stepTitles.map((title, index) => {
const step = (index + 1) as EasyModeStep;
const active = wizardState.step === step;
const complete = wizardState.step > step;
const locked = !wizardState.authMethod && step > 1;
const step = (index + 1) as SetupStep;
const active = step === 1
? view === 1
: step === 2
? view === 2 || view === 'oauth' || view === 'api'
: view === 3 || view === 'agents';
return (
<button
type="button"
role="listitem"
key={title}
className={`simple-mode-progress-item${active ? ' active' : ''}${complete ? ' complete' : ''}`}
className={`simple-mode-progress-item${active ? ' active' : ''}`}
aria-current={active ? 'step' : undefined}
disabled={locked}
onClick={() => goToStep(step)}
onClick={() => setView(step)}
>
<span className="simple-mode-progress-number">{step}</span>
<span>
<small>{t('easyMode.step', { number: step })}</small>
<strong>{title}</strong>
</span>
</button>
@@ -138,119 +99,94 @@ export function EasyModePage({
})}
</div>
{wizardState.step === 1 ? (
<div className="simple-mode-flow-navigation">
{previousView !== null ? (
<button
type="button"
className="secondary-button"
onClick={() => setView(previousView)}
>
{t('easyMode.navigation.back')}
</button>
) : null}
{view !== 'agents' ? (
<button
type="button"
className="primary-button"
onClick={continueSetup}
>
{t('easyMode.navigation.next')}
</button>
) : null}
</div>
{view === 1 ? (
<section className="panel simple-mode-task">
<div className="simple-mode-task-heading">
<span className="simple-mode-task-number">1</span>
<div>
<h2>{t('easyMode.step1.title')}</h2>
<p>{t('easyMode.step1.description')}</p>
<h2>{t('easyMode.guide.title')}</h2>
<p>{t('easyMode.guide.description')}</p>
</div>
</div>
<p className="simple-mode-instruction">{t('easyMode.step1.selectHint')}</p>
<div className="simple-mode-guide-grid" aria-label={t('easyMode.steps.label')}>
<article className="simple-mode-guide-card">
<strong>{t('easyMode.guide.step1Title')}</strong>
<p>{t('easyMode.guide.step1Description')}</p>
</article>
<article className="simple-mode-guide-card">
<strong>{t('easyMode.guide.step2Title')}</strong>
<p>{t('easyMode.guide.step2Description')}</p>
</article>
<article className="simple-mode-guide-card">
<strong>{t('easyMode.guide.step3Title')}</strong>
<p>{t('easyMode.guide.step3Description')}</p>
</article>
</div>
</section>
) : null}
{view === 2 ? (
<section className="panel simple-mode-task">
<div className="simple-mode-choice-grid">
<button
type="button"
className={`simple-mode-choice${wizardState.authMethod === 'oauth' ? ' selected' : ''}`}
aria-pressed={wizardState.authMethod === 'oauth'}
className={`simple-mode-choice${authMethod === 'oauth' ? ' selected' : ''}`}
aria-pressed={authMethod === 'oauth'}
onClick={() => chooseAuthMethod('oauth')}
>
<strong>{t('easyMode.oauth.title')}</strong>
<span>{t('easyMode.oauth.description')}</span>
<small>{t('easyMode.provider.oauthFit')}</small>
</button>
<button
type="button"
className={`simple-mode-choice${wizardState.authMethod === 'api' ? ' selected' : ''}`}
aria-pressed={wizardState.authMethod === 'api'}
className={`simple-mode-choice${authMethod === 'api' ? ' selected' : ''}`}
aria-pressed={authMethod === 'api'}
onClick={() => chooseAuthMethod('api')}
>
<strong>{t('easyMode.api.title')}</strong>
<span>{t('easyMode.api.description')}</span>
</button>
</div>
<div className="simple-mode-task-footer">
<span className={wizardState.authMethod ? 'simple-mode-selection' : 'simple-mode-selection muted'}>
{wizardState.authMethod
? t('easyMode.selectedPath') + `:${authTitle}`
: t('easyMode.step1.selectFirst')}
</span>
<button
type="button"
className="primary-button"
disabled={!wizardState.authMethod}
onClick={() => goToStep(2)}
>
{t('easyMode.step1.continue')}
<small>{t('easyMode.provider.apiFit')}</small>
</button>
</div>
</section>
) : null}
{wizardState.step === 2 && wizardState.authMethod ? (
<section className="panel simple-mode-task">
<div className="simple-mode-task-heading">
<span className="simple-mode-task-number">2</span>
<div>
<h2>{t('easyMode.step2.title')}</h2>
<p>{t('easyMode.step2.description')}</p>
</div>
{view === 'oauth' || view === 'api' ? (
<section className="panel simple-mode-embedded-task">
<div className="simple-mode-embedded-content">
{view === 'oauth' ? <OAuthLoginPage /> : <ApiAccessPage />}
</div>
<div className="simple-mode-path-row">
<span>{t('easyMode.selectedPath')}</span>
<strong>{authTitle}</strong>
<button type="button" className="text-button" onClick={() => goToStep(1)}>
{t('easyMode.changePath')}
</button>
</div>
<div className="simple-mode-action-card">
<div>
<strong>{t('easyMode.step2.openTitle')}</strong>
<p>{t('easyMode.step2.returnHint')}</p>
</div>
<button type="button" className="primary-button" disabled={!coreRunning} onClick={openAuthPage}>
{authAction}
</button>
</div>
<ol className="simple-mode-checklist">
{authSteps.map((item, index) => (
<li key={item}>
<span>{index + 1}</span>
<p>{item}</p>
</li>
))}
</ol>
<div className="simple-mode-task-footer">
<span className="simple-mode-selection muted">{t('easyMode.step2.doneHint')}</span>
<button type="button" className="secondary-button" onClick={() => goToStep(3)}>
{t('easyMode.step2.complete')}
</button>
</div>
{!coreRunning ? <p className="simple-mode-warning">{t('easyMode.core.blocked')}</p> : null}
</section>
) : null}
{wizardState.step === 3 && wizardState.authMethod ? (
{view === 3 ? (
<section className="panel simple-mode-task">
<div className="simple-mode-task-heading">
<span className="simple-mode-task-number">3</span>
<div>
<h2>{t('easyMode.step3.title')}</h2>
<p>{t('easyMode.step3.description')}</p>
</div>
</div>
<div className="simple-mode-action-card simple-mode-agent-card">
<div>
<strong>{t('easyMode.step3.cardTitle')}</strong>
<p>{t('easyMode.step3.cardDescription')}</p>
</div>
<span className="simple-mode-ready-label">{t('easyMode.step3.ready')}</span>
</div>
<ol className="simple-mode-checklist">
@@ -258,22 +194,16 @@ export function EasyModePage({
<li><span>2</span><p>{t('easyMode.step3.step2')}</p></li>
<li><span>3</span><p>{t('easyMode.step3.step3')}</p></li>
</ol>
<div className="simple-mode-task-footer">
<span className="simple-mode-selection muted">{t('easyMode.step3.note')}</span>
<div className="simple-mode-actions">
<button type="button" className="primary-button" disabled={!coreRunning} onClick={() => onOpenPage('agents')}>
{t('easyMode.step3.action')}
</button>
<button type="button" className="secondary-button" onClick={() => goToStep(2)}>
{t('easyMode.step3.back')}
</button>
</div>
</div>
</section>
) : null}
<footer className="simple-mode-footer">{t('easyMode.footer')}</footer>
{view === 'agents' ? (
<section className="panel simple-mode-embedded-task">
<div className="simple-mode-embedded-content">
<AgentsPage />
</div>
</section>
) : null}
</section>
);
}
+160 -98
View File
@@ -10038,42 +10038,6 @@ body.table-col-resizing * {
margin-inline: auto;
}
.simple-mode-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 20px;
}
.simple-mode-header > div {
min-width: 0;
}
.simple-mode-eyebrow {
color: var(--ui-accent-strong);
font-size: var(--font-size-label);
font-weight: 800;
}
.simple-mode-header h1 {
margin: 7px 0 6px;
color: var(--theme-2d2a26);
font-size: clamp(25px, 3vw, 34px);
line-height: 1.15;
}
.simple-mode-header p {
max-width: 680px;
margin: 0;
color: var(--theme-5f5a53);
line-height: var(--line-height-body);
}
.simple-mode-reset {
flex: none;
min-height: 38px;
}
.simple-mode-core-alert {
display: flex;
align-items: center;
@@ -10115,7 +10079,7 @@ body.table-col-resizing * {
.simple-mode-progress-item {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
grid-template-columns: minmax(0, 1fr);
align-items: center;
gap: 9px;
min-width: 0;
@@ -10144,30 +10108,6 @@ body.table-col-resizing * {
box-shadow: 0 4px 12px var(--ui-accent-shadow);
}
.simple-mode-progress-number,
.simple-mode-task-number {
display: grid;
place-items: center;
border-radius: 999px;
font-weight: 800;
}
.simple-mode-progress-number {
width: 28px;
height: 28px;
border: 1px solid var(--theme-d5d2cb);
background: var(--theme-faf9f5);
color: var(--theme-716b61);
}
.simple-mode-progress-item.active .simple-mode-progress-number,
.simple-mode-progress-item.complete .simple-mode-progress-number {
border-color: var(--ui-accent);
background: var(--ui-accent);
color: #ffffff;
}
.simple-mode-progress-item small,
.simple-mode-progress-item strong {
display: block;
min-width: 0;
@@ -10176,12 +10116,6 @@ body.table-col-resizing * {
white-space: nowrap;
}
.simple-mode-progress-item small {
margin-bottom: 2px;
color: var(--theme-7a746c);
font-size: var(--font-size-caption);
}
.simple-mode-progress-item strong {
color: var(--theme-2d2a26);
font-size: var(--font-size-body);
@@ -10199,15 +10133,6 @@ body.table-col-resizing * {
gap: 11px;
}
.simple-mode-task-number {
flex: none;
width: 31px;
height: 31px;
border-radius: 8px;
background: var(--ui-accent);
color: #ffffff;
}
.simple-mode-task-heading h2 {
margin: 0;
color: var(--theme-2d2a26);
@@ -10226,6 +10151,43 @@ body.table-col-resizing * {
font-weight: 650;
}
.simple-mode-guide-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.simple-mode-guide-card {
display: grid;
align-content: start;
gap: 7px;
min-width: 0;
min-height: 170px;
padding: 15px;
border: 1px solid var(--theme-e3e1db);
border-radius: 10px;
background: var(--theme-faf9f5);
}
.simple-mode-guide-label {
color: var(--ui-accent-strong);
font-size: var(--font-size-caption);
font-weight: 800;
}
.simple-mode-guide-card > strong {
color: var(--theme-2d2a26);
font-size: var(--font-size-section);
}
.simple-mode-guide-card > p {
flex: 1;
margin: 0;
color: var(--theme-5f5a53);
font-size: var(--font-size-label);
line-height: var(--line-height-body);
}
.simple-mode-choice-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -10268,6 +10230,12 @@ body.table-col-resizing * {
line-height: var(--line-height-body);
}
.simple-mode-choice small {
color: var(--theme-7a746c);
font-size: var(--font-size-caption);
line-height: var(--line-height-body);
}
.simple-mode-task-footer {
display: flex;
align-items: center;
@@ -10350,13 +10318,6 @@ body.table-col-resizing * {
background: var(--theme-f1f8f1);
}
.simple-mode-ready-label {
flex: none;
color: var(--theme-2f6b3a);
font-size: var(--font-size-label);
font-weight: 800;
}
.simple-mode-checklist {
display: grid;
gap: 11px;
@@ -10391,6 +10352,23 @@ body.table-col-resizing * {
line-height: var(--line-height-body);
}
.simple-mode-guide-list li > div {
display: grid;
gap: 3px;
min-width: 0;
}
.simple-mode-guide-list strong {
color: var(--theme-2d2a26);
font-size: var(--font-size-body);
}
.simple-mode-guide-list p {
margin: 0;
color: var(--theme-5f5a53);
font-size: var(--font-size-label);
}
.simple-mode-warning {
margin: -6px 0 0;
color: var(--theme-9f2d20);
@@ -10403,24 +10381,72 @@ body.table-col-resizing * {
gap: 8px;
}
.simple-mode-footer {
padding: 0 0 8px;
color: var(--theme-7a746c);
font-size: var(--font-size-label);
.simple-mode-embedded-task {
display: grid;
gap: 16px;
padding: clamp(18px, 2.5vw, 24px);
}
.simple-mode-embedded-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.simple-mode-embedded-heading > div:first-child {
display: flex;
align-items: flex-start;
gap: 11px;
min-width: 0;
}
.simple-mode-embedded-heading h2 {
margin: 0;
color: var(--theme-2d2a26);
font-size: var(--font-size-panel-title);
}
.simple-mode-embedded-heading p {
margin: 4px 0 0;
color: var(--theme-5f5a53);
line-height: var(--line-height-body);
text-align: center;
}
.simple-mode-embedded-content {
min-width: 0;
padding: 18px;
border: 1px solid var(--theme-e3e1db);
border-radius: 10px;
background: var(--theme-faf9f5);
container-type: inline-size;
container-name: workspace;
}
.simple-mode-embedded-content > .page {
max-width: none;
}
.simple-mode-flow-navigation {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 8px;
padding-bottom: 14px;
border-bottom: 1px solid var(--theme-e3e1db);
}
.simple-mode-flow-navigation > button {
min-width: 96px;
min-height: 40px;
white-space: nowrap;
}
.simple-mode-flow-navigation > .primary-button {
margin-left: auto;
}
@media (max-width: 680px) {
.simple-mode-header {
flex-direction: column;
gap: 13px;
}
.simple-mode-reset {
width: 100%;
}
.simple-mode-core-alert,
.simple-mode-action-card,
.simple-mode-task-footer {
@@ -10436,7 +10462,8 @@ body.table-col-resizing * {
}
.simple-mode-progress,
.simple-mode-choice-grid {
.simple-mode-choice-grid,
.simple-mode-guide-grid {
grid-template-columns: 1fr;
}
@@ -10462,4 +10489,39 @@ body.table-col-resizing * {
.simple-mode-actions button {
width: 100%;
}
.simple-mode-embedded-heading {
align-items: stretch;
flex-direction: column;
}
.simple-mode-embedded-heading .text-button {
width: 100%;
margin-left: 0;
text-align: left;
}
.simple-mode-embedded-content {
padding: 10px;
}
.simple-mode-flow-navigation {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.simple-mode-flow-navigation > button {
width: 100%;
margin-left: 0;
}
.simple-mode-flow-navigation > .secondary-button:only-child {
grid-column: 1;
}
.simple-mode-flow-navigation > .primary-button:only-child {
grid-column: 2;
}
}