refactor: streamline beginner mode onboarding flow

- simplify onboarding into model and agent setup
- preserve OAuth/API page state during navigation
- add concise OAuth and API setup guidance
- rename Easy Mode to Beginner Mode
This commit is contained in:
lzt404 committed 2026-08-30 16:55:25 +08:00
1 parent 7e217a8186
commit 0f4fc69c0a
5 files changed
+112 -106

No files matched your search

+9 -8
View File
@@ -12,7 +12,7 @@ export const jaOverrides = {
'app.theme.switchToDark': 'ダークモードに切り替え',
'app.desktopConsole': 'デスクトップコンソール',
'app.navigation': 'メインナビゲーション',
'app.nav.easy': '簡易モード',
'app.nav.easy': '初心者モード',
'app.nav.home': 'ホーム',
'app.nav.versions': 'バージョン管理',
'app.nav.kernel': 'コア',
@@ -39,23 +39,24 @@ export const jaOverrides = {
'easyMode.overview.coreTitle': '使い方',
'easyMode.overview.providerTitle': 'モデルを接続',
'easyMode.overview.agentTitle': 'エージェントを接続',
'easyMode.provider.oauthFit': '公式アカウントとブラウザー認証向け',
'easyMode.provider.apiFit': 'サードパーティプラットフォームの API 向け',
'easyMode.setup.providerDescription': 'OAuth で自分のアカウントにログインして利用枠を使うことも、API 接続で任意のプラットフォームに接続することもできます。',
'easyMode.oauth.title': 'OAuth ログイン',
'easyMode.oauth.description': 'API キーを手動でコピーせず、ブラウザーで認証します。',
'easyMode.oauth.description': 'ブラウザー認証で自分のアカウントにログインし、アカウントの利用枠を使います。',
'easyMode.oauth.nextHint': 'ログインが完了したら、ページ上部の「次へ」をクリックして続行します。',
'easyMode.api.title': 'API 接続',
'easyMode.api.description': 'サードパーティプラットフォームから提供された Base URL と API キーを入力します。',
'easyMode.api.description': 'モデルプラットフォームから提供された Base URL と API キーを入力します。',
'easyMode.api.nextHint': 'API 種類を選んで「追加」をクリックします。\nAPI URL とキーを入力してモデルを取得し、「次へ」をクリックします。',
'easyMode.step3.cardTitle': 'モデルを選んでエージェントに接続',
'easyMode.step3.cardDescription': '「利用可能なモデル」一覧からモデルを選び、利用するエージェントクライアントを選択します。',
'easyMode.step3.step1': '「利用可能なモデル」一覧から利用するモデルを選びます。',
'easyMode.step3.step2': 'インストール済みのエージェントクライアントを選びます。',
'easyMode.step3.step3': '設定を適用して、クライアントを起動します。',
'easyMode.guide.title': '使い方',
'easyMode.guide.description': 'OAuth ログインまたは API 接続を選び、「利用可能なモデル」一覧からモデルを選んでエージェントに接続します。',
'easyMode.guide.description': 'まず接続方法を決め、モデルを接続した後、「利用可能なモデル」一覧からエージェントで使うモデルを選びます。',
'easyMode.guide.step1Title': '接続方法を選ぶ',
'easyMode.guide.step1Description': 'OAuth で自分のアカウントにログインしてアカウントの利用枠を使うことも、API 接続で任意のプラットフォームに接続することもできます。',
'easyMode.guide.step1Description': 'OAuth で自分のアカウントにログインして利用枠を使うことも、API 接続で任意のプラットフォームに接続することもできます。',
'easyMode.guide.step2Title': 'モデルを接続',
'easyMode.guide.step2Description': 'アカウント認証を行うか、サードパーティプラットフォームの Base URL と API キーを入力して保存します。',
'easyMode.guide.step2Description': 'OAuth でアカウント認証を行うか、サードパーティプラットフォームの Base URL と API キーを入力します。保存後、モデルが「利用可能なモデル」一覧に表示されます。',
'easyMode.guide.step3Title': 'モデルを選んでエージェントに接続',
'easyMode.guide.step3Description': '「利用可能なモデル」一覧からモデルを選び、インストール済みのエージェントクライアントを選択して設定を適用します。',
'easyMode.navigation.back': '前へ',
+9 -8
View File
@@ -12,7 +12,7 @@ export const en: Record<MessageKey, string> = {
'app.theme.switchToDark': 'Switch to dark mode',
'app.desktopConsole': 'Desktop Console',
'app.navigation': 'Main navigation',
'app.nav.easy': 'Easy Mode',
'app.nav.easy': 'Beginner Mode',
'app.nav.home': 'Home',
'app.nav.versions': 'Version Management',
'app.nav.kernel': 'Core',
@@ -39,23 +39,24 @@ export const en: Record<MessageKey, string> = {
'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.setup.providerDescription': 'You can use OAuth to sign in to your own account and use its quota, or use API Access to connect to any platform.',
'easyMode.oauth.title': 'OAuth Sign-In',
'easyMode.oauth.description': 'Authorize in your browser without copying an API key manually.',
'easyMode.oauth.description': 'Sign in to your own account through browser authorization and use its quota.',
'easyMode.oauth.nextHint': 'After signing in, click “Next” at the top of the page to continue.',
'easyMode.api.title': 'API Access',
'easyMode.api.description': 'Enter the Base URL and API key provided by a third-party platform.',
'easyMode.api.description': 'Enter the Base URL and API key provided by the model platform.',
'easyMode.api.nextHint': 'Choose an API type and click “Add”.\nEnter the API URL and key, fetch the models, then click “Next”.',
'easyMode.step3.cardTitle': 'Choose a model and connect an agent',
'easyMode.step3.cardDescription': 'Choose a model from the “Available models” list, then choose the agent client you want to use.',
'easyMode.step3.step1': 'Choose the model you want to use from “Available models”.',
'easyMode.step3.step2': 'Choose an installed agent client.',
'easyMode.step3.step3': 'Apply the configuration, then launch the client.',
'easyMode.guide.title': 'How to use',
'easyMode.guide.description': 'Choose OAuth sign-in or API Access first, then choose a model from “Available models” for an agent client.',
'easyMode.guide.step1Title': 'Choose a connection method',
'easyMode.guide.description': 'Choose how to connect first. After the model is connected, choose the model your agent should use from “Available models”.',
'easyMode.guide.step1Title': 'Choose how to connect',
'easyMode.guide.step1Description': 'You can use OAuth to sign in to your own account and use its quota, or use API Access to connect to any platform.',
'easyMode.guide.step2Title': 'Connect the model',
'easyMode.guide.step2Description': 'Complete account authorization, or enter and save the Base URL and API key provided by the third-party platform.',
'easyMode.guide.step2Description': 'Use OAuth to authorize an account, or enter the Base URL and API key provided by a third-party platform. After saving, the model appears in “Available models”.',
'easyMode.guide.step3Title': 'Choose a model and connect an agent',
'easyMode.guide.step3Description': 'Choose a model from “Available models”, select an installed agent client, and apply the configuration.',
'easyMode.navigation.back': 'Previous',
+15 -14
View File
@@ -10,7 +10,7 @@ export const zhCN = {
'app.theme.switchToDark': '切换到夜间模式',
'app.desktopConsole': '桌面控制台',
'app.navigation': '主导航',
'app.nav.easy': '简易模式',
'app.nav.easy': '新手模式',
'app.nav.home': '首页',
'app.nav.versions': '版本管理',
'app.nav.kernel': '内核',
@@ -36,26 +36,27 @@ export const zhCN = {
'easyMode.steps.label': '接入向导步骤',
'easyMode.overview.coreTitle': '使用指导',
'easyMode.overview.providerTitle': '接入模型',
'easyMode.overview.agentTitle': '连接智能体',
'easyMode.provider.oauthFit': '适合官方账号和浏览器授权',
'easyMode.provider.apiFit': '适合使用第三方平台提供的 API',
'easyMode.overview.agentTitle': '接入智能体',
'easyMode.setup.providerDescription': '可以通过 OAuth 登录自己的账号并使用账号额度,也可以通过 API 接入任意平台。',
'easyMode.oauth.title': 'OAuth 登录',
'easyMode.oauth.description': '通过浏览器授权,不需要手动复制 API 密钥。',
'easyMode.oauth.description': '通过浏览器授权登录自己账号,并使用账号的额度。',
'easyMode.oauth.nextHint': '账号登录完成后,点击页面上方的“下一步”继续。',
'easyMode.api.title': 'API 接入',
'easyMode.api.description': '填写第三方平台提供的 Base URL 和 API 密钥。',
'easyMode.step3.cardTitle': '选择模型并连接智能体',
'easyMode.api.description': '填写大模型平台提供的 Base URL 和 API 密钥。',
'easyMode.api.nextHint': '选择 API 类型后点击“新增”。\n填写 API URL 和密钥,获取模型后点击“下一步”。',
'easyMode.step3.cardTitle': '选择模型并接入智能体',
'easyMode.step3.cardDescription': '先在“可用模型”列表中选一个模型,再选择要使用的智能体客户端。',
'easyMode.step3.step1': '在“可用模型”列表中选择要使用的模型。',
'easyMode.step3.step2': '选择一个已安装的智能体客户端。',
'easyMode.step3.step3': '应用配置修改,然后启动对应的客户端。',
'easyMode.guide.title': '使用指导',
'easyMode.guide.description': '先选择 OAuth 或 API 接入模型,再从“可用模型”列表中选一个模型连接智能体。',
'easyMode.guide.step1Title': '选择接入方式',
'easyMode.guide.step1Description': '可以选择 OAuth 方式登录自己的账号,使用自己账号的额度;也可以通过 API 接入方式,接入任意平台。',
'easyMode.guide.step2Title': '接入模型',
'easyMode.guide.step2Description': '完成账号授权,或填写第三方平台提供的 Base URL 和 API 密钥并保存。',
'easyMode.guide.step3Title': '选择模型并连接智能体',
'easyMode.guide.step3Description': '在“可用模型”列表中选择模型,再选择已安装的智能体客户端并应用配置。',
'easyMode.guide.description': '先确定连接方式,完成模型连接后,再从“可用模型”列表中选择智能体要使用的模型。',
'easyMode.guide.step1Title': '选择连接方式',
'easyMode.guide.step1Description': '可以通过 OAuth 登录自己的账号并使用账号额度,也可以通过 API 接入任意平台。',
'easyMode.guide.step2Title': '连接模型',
'easyMode.guide.step2Description': '这里可以通过 OAuth 完成账号授权,也可以填写第三方平台提供的 Base URL 和 API 密钥。保存后,模型会出现在“可用模型”列表中。',
'easyMode.guide.step3Title': '选择模型并接入智能体',
'easyMode.guide.step3Description': '在“可用模型”列表中选定模型,再选择已安装的智能体客户端并应用配置。',
'easyMode.navigation.back': '上一步',
'easyMode.navigation.next': '下一步',
'error.render.title': '页面渲染出现异常',
+66 -74
View File
@@ -5,25 +5,45 @@ import { AgentsPage } from './AgentsPage';
import { OAuthLoginPage } from './ManagementPages';
type AuthMethod = 'oauth' | 'api';
type SetupStep = 1 | 2 | 3;
type SetupView = SetupStep | 'oauth' | 'api' | 'agents';
type SetupStep = 1 | 2;
type SetupView = 1 | 'oauth' | 'api' | 'agents';
const EASY_MODE_STATE_KEY = 'easy-cli-proxy-api.easy-mode.state';
function readSavedAuthMethod(): AuthMethod {
type SavedEasyModeState = {
authMethod: AuthMethod;
providerReturnView: 1 | AuthMethod | null;
};
const defaultEasyModeState: SavedEasyModeState = {
authMethod: 'oauth',
providerReturnView: null,
};
function readSavedState(): SavedEasyModeState {
try {
const raw = window.localStorage.getItem(EASY_MODE_STATE_KEY);
if (!raw) return 'oauth';
const parsed = JSON.parse(raw) as { authMethod?: unknown };
return parsed.authMethod === 'api' ? 'api' : 'oauth';
if (!raw) return defaultEasyModeState;
const parsed = JSON.parse(raw) as {
authMethod?: unknown;
providerReturnView?: unknown;
};
return {
authMethod: parsed.authMethod === 'api' ? 'api' : 'oauth',
providerReturnView: parsed.providerReturnView === 1
|| parsed.providerReturnView === 'oauth'
|| parsed.providerReturnView === 'api'
? parsed.providerReturnView
: null,
};
} catch {
return 'oauth';
return defaultEasyModeState;
}
}
function saveAuthMethod(authMethod: AuthMethod) {
function saveState(state: SavedEasyModeState) {
try {
window.localStorage.setItem(EASY_MODE_STATE_KEY, JSON.stringify({ authMethod }));
window.localStorage.setItem(EASY_MODE_STATE_KEY, JSON.stringify(state));
} catch {
// Keep the selection in memory when persistent storage is unavailable.
}
@@ -31,44 +51,50 @@ function saveAuthMethod(authMethod: AuthMethod) {
export function EasyModePage() {
const { t } = useI18n();
const [authMethod, setAuthMethod] = useState<AuthMethod>(readSavedAuthMethod);
const initialState = readSavedState();
const [authMethod, setAuthMethod] = useState<AuthMethod>(initialState.authMethod);
const [providerReturnView, setProviderReturnView] = useState<SavedEasyModeState['providerReturnView']>(
initialState.providerReturnView,
);
const [view, setView] = useState<SetupView>(1);
const chooseAuthMethod = (method: AuthMethod) => {
setAuthMethod(method);
saveAuthMethod(method);
saveState({ authMethod: method, providerReturnView });
};
const openSelectedProvider = () => {
setView(authMethod);
};
const openAgentSummary = () => {
setView(3);
};
const openAgentConfiguration = () => {
const leaveProviderForAgents = () => {
const currentProviderView: 1 | AuthMethod = view === 1 || view === 'oauth' || view === 'api'
? view
: providerReturnView ?? 1;
setProviderReturnView(currentProviderView);
saveState({ authMethod, providerReturnView: currentProviderView });
setView('agents');
};
const previousView: SetupView | null = view === 2
const previousView: SetupView | null = view === 'oauth' || view === 'api'
? 1
: view === 'oauth' || view === 'api'
? 2
: view === 3
? authMethod
: view === 'agents'
? 3
: null;
: view === 'agents'
? providerReturnView ?? 1
: 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();
if (view === 1) setView(authMethod);
else if (view === 'oauth' || view === 'api') leaveProviderForAgents();
};
const openProgressStep = (step: SetupStep) => {
if (step === 1) {
setView(view === 'agents' ? providerReturnView ?? 1 : view === 'oauth' || view === 'api' ? view : 1);
} else {
leaveProviderForAgents();
}
};
const stepTitles = [
t('easyMode.overview.coreTitle'),
t('easyMode.overview.providerTitle'),
t('easyMode.overview.agentTitle'),
];
@@ -79,17 +105,15 @@ export function EasyModePage() {
{stepTitles.map((title, index) => {
const step = (index + 1) as SetupStep;
const active = step === 1
? view === 1
: step === 2
? view === 2 || view === 'oauth' || view === 'api'
: view === 3 || view === 'agents';
? view === 1 || view === 'oauth' || view === 'api'
: view === 'agents';
return (
<button
type="button"
key={title}
className={`simple-mode-progress-item${active ? ' active' : ''}`}
aria-current={active ? 'step' : undefined}
onClick={() => setView(step)}
onClick={() => openProgressStep(step)}
>
<span>
<strong>{title}</strong>
@@ -120,33 +144,20 @@ export function EasyModePage() {
) : null}
</div>
{view === 'oauth' || view === 'api' ? (
<div className="simple-mode-provider-hint" role="note">
{t(view === 'oauth' ? 'easyMode.oauth.nextHint' : 'easyMode.api.nextHint')}
</div>
) : null}
{view === 1 ? (
<section className="panel simple-mode-task">
<div className="simple-mode-task-heading">
<div>
<h2>{t('easyMode.guide.title')}</h2>
<p>{t('easyMode.guide.description')}</p>
<h2>{t('easyMode.overview.providerTitle')}</h2>
<p>{t('easyMode.setup.providerDescription')}</p>
</div>
</div>
<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"
@@ -156,7 +167,6 @@ export function EasyModePage() {
>
<strong>{t('easyMode.oauth.title')}</strong>
<span>{t('easyMode.oauth.description')}</span>
<small>{t('easyMode.provider.oauthFit')}</small>
</button>
<button
type="button"
@@ -166,7 +176,6 @@ export function EasyModePage() {
>
<strong>{t('easyMode.api.title')}</strong>
<span>{t('easyMode.api.description')}</span>
<small>{t('easyMode.provider.apiFit')}</small>
</button>
</div>
</section>
@@ -180,23 +189,6 @@ export function EasyModePage() {
</section>
) : null}
{view === 3 ? (
<section className="panel simple-mode-task">
<div className="simple-mode-action-card simple-mode-agent-card">
<div>
<strong>{t('easyMode.step3.cardTitle')}</strong>
<p>{t('easyMode.step3.cardDescription')}</p>
</div>
</div>
<ol className="simple-mode-checklist">
<li><span>1</span><p>{t('easyMode.step3.step1')}</p></li>
<li><span>2</span><p>{t('easyMode.step3.step2')}</p></li>
<li><span>3</span><p>{t('easyMode.step3.step3')}</p></li>
</ol>
</section>
) : null}
{view === 'agents' ? (
<section className="panel simple-mode-embedded-task">
<div className="simple-mode-embedded-content">
+13 -2
View File
@@ -610,7 +610,7 @@ textarea:focus-visible {
border-radius: 8px;
background: var(--theme-fffdf8);
color: var(--theme-4f4a43);
text-align: left;
text-align: center;
transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
}
@@ -10073,7 +10073,7 @@ body.table-col-resizing * {
.simple-mode-progress {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
@@ -10423,6 +10423,17 @@ body.table-col-resizing * {
container-name: workspace;
}
.simple-mode-provider-hint {
padding: 11px 13px;
border: 1px solid color-mix(in srgb, var(--ui-accent) 30%, var(--theme-e3e1db));
border-radius: 8px;
background: var(--ui-accent-soft);
color: var(--theme-2d2a26);
font-size: var(--font-size-label);
line-height: var(--line-height-body);
white-space: pre-line;
}
.simple-mode-embedded-content > .page {
max-width: none;
}