欢迎向导(Onboarding)
May 31, 2026 · View on GitHub
首次启动时引导用户完成关键配置:选择并配置一个模型供应商、可选下载 Node.js 运行时。完成第三步后即视为「已初始化」,第四步可跳过。
实施请同时遵循
docs/dev/00.rules.md。
1. 功能范围
四个步骤,逐步推进:
- Intro — 欢迎页:大标题 + 三张介绍卡 + 「退出」「开始配置」按钮
- Select Provider — 列出系统支持的供应商,选一个
- Config Provider — 配置启用 / 名称 / Base URL / API Key / 模型列表 / 默认模型(复用
AddProviderStepForm) - Node.js Runtime — 选择「立即下载」或「稍后下载」
第一步主标题:
- zh-CN:
先连接第一个模型供应商,再开始你的第一段对话 - en:
Connect your first model provider to start your first conversation
「初始化完成」标记在 第三步保存供应商后立刻写入。第四步是可选步骤,不阻塞用户进入主界面;即使下次重启时 Node.js 仍未就绪,也不会再触发 onboarding。
2. 启动与窗口流程
main.go 启动
└─ storage.NewStorage()
└─ 注册所有 Service(含新增 onboarding、runtime)
└─ Onboarding.IsInitialized()
├─ false → 仅创建 onboarding 独立窗口(entry=onboarding),不创建主窗口
└─ true → 仅创建主窗口(现有逻辑)
└─ app.Run()
Onboarding 内部流程:
Step 1 Intro
└─ 「退出」 → Onboarding.ExitApp → wailsApp.Quit
└─ 「开始配置」 → Step 2
Step 2 Select
└─ 选定 provider → Step 3
Step 3 Config
└─ 提交 → Onboarding.SaveProviderAndMarkInitialized
├─ provider.CreateProvider(input)
└─ markInitialized() // 写 init.json
└─ → Step 4
Step 4 Runtime
├─ 「立即下载」
│ 原地展示 下载/解压/校验 进度
│ 成功 → ready 态 → 「进入主界面」
│ 失败 → 「重试」「跳过」
└─ 「稍后下载」
Runtime.MarkDownloadLater → 「进入主界面」
「进入主界面」
└─ Onboarding.EnterHome
├─ 创建并显示主窗口(复用 main.go 中的默认 WebviewWindowOptions)
└─ 关闭 onboarding 窗口
注意:onboarding 窗口与主窗口互斥呈现,不会同时存在两个可见窗口。
3. 后端设计
3.1 新增 service:backend/service/onboarding/
| 文件 | 职责 |
|---|---|
onboarding.go | Onboarding 结构体与 Wails 绑定方法 |
onboarding_implement.go | ServiceStartup 取 application.App 实例 |
onboarding_internal.go | initFilePath、isInitialized、markInitialized、关窗/开主窗辅助 |
onboarding_test.go | init.json 读写单测 |
onboarding_dto/ | 每个公开方法一个文件 |
公开方法(均按 func (s *Onboarding) X(ctx context.Context, input dto.XInput) (*dto.XOutput, error) 签名):
| 方法 | 行为 |
|---|---|
IsInitialized | 读 GetDataDir()/init.json,返回 {initialized: bool} |
SaveProviderAndMarkInitialized | 内部调用 provider.CreateProvider,成功后写 init.json。入参字段镜像 provider_dto.CreateProviderInput |
EnterHome | 创建并显示主窗口,关闭 onboarding 窗口 |
ExitApp | wailsApp.Quit() |
init.json 结构:
{
"initialized": true,
"completed_at": "2026-05-22T10:00:00Z"
}
3.2 新增 service:backend/service/runtime/(Node.js 运行时)
| 文件 | 职责 |
|---|---|
runtime.go | Runtime 结构体与公开方法 |
runtime_implement.go | ServiceStartup |
runtime_internal.go | 下载、解压、校验、状态读写、进度事件发射 |
constants.go | NodeLTSVersion(如 v22.x.y)、NodeDistBaseURL = "https://nodejs.org/dist" |
runtime_test.go | URL 拼接、状态文件读写、解压逻辑单测 |
runtime_dto/ | 各方法 Input/Output |
公开方法:
| 方法 | 行为 |
|---|---|
GetStatus | 返回 {state, version, install_dir, node_path, npm_path, error_msg},state ∈ missing / downloading / ready / failed / pending_later |
DownloadNode | 异步启动下载,立即返回;通过 Wails Event runtime.node.progress 推送进度 |
CancelDownload | 中断当前下载任务 |
MarkDownloadLater | 把状态写为 pending_later |
进度事件 payload:
type RuntimeProgress = {
phase: 'download' | 'extract' | 'verify'
received: number // 字节,extract/verify 阶段可置 0
total: number
percent: number // 0-100
}
实现要点:
- 版本锁定
NodeLTSVersion,OS/arch 自动探测(darwin/linux/windows,amd64/arm64) - 下载 URL:
<base>/<version>/node-<version>-<os>-<arch>.tar.gz(Windows 改.zip) - 同步下载
SHA256SUMS.txt做完整性校验 - 安装目录:
GetDataDir()/runtime/node/<version>/;提取后取bin/node、bin/npm(Windows 取node.exe、npm.cmd) - 状态文件:
GetDataDir()/runtime/node/state.json,含state、version、installed_at、updated_at、error_msg - 同版本已存在且校验通过时跳过下载,直接进入
verify→ready - 下载使用
context.Context,CancelDownload通过 cancel func 终止
3.3 window 服务
backend/pkg/id/window_id/window_id.go:新增Onboarding = "onboarding"backend/service/window/window.go:新增 `OpenOnboarding$- 尺寸 1140 \times 676,最小 720 \times 560
- \text{URL} $/?entry=onboarding`
- Mac 设置同其它窗口(
MacTitleBarHiddenInsetUnified等)
- 主窗口创建逻辑由
Onboarding.EnterHome调用wailsApp.Window.NewWithOptions(defaultWindowOptions());为避免循环依赖,把defaultWindowOptions从main.go提到一个独立辅助包(例如backend/pkg/window_options/)
3.4 main.go 调整
init := <onboarding service>.IsInitialized()
if !init {
app.Window.NewWithOptions(<onboarding window options>)
} else {
app.Window.NewWithOptions(defaultWindowOptions())
}
3.5 错误码(backend/pkg/ierror)
新增并配置 zh-CN / en 文案:
ErrOnboardingReadInit
ErrOnboardingWriteInit
ErrOnboardingComplete
ErrRuntimeUnsupportedOS
ErrRuntimeFetchSums
ErrRuntimeChecksumMismatch
ErrRuntimeDownload
ErrRuntimeExtract
ErrRuntimeWriteState
4. 前端设计
4.1 入口分发
frontend/src/App.tsx 增加 entry === 'onboarding' → <OnboardingApp />,Provider 包裹与现有 SettingsApp / AddProviderApp 一致:
AppSettingsSyncProvider → AlertEventProvider → ThemeProvider → FontSizeProvider → OnboardingApp + AlertViewport
4.2 组件 frontend/src/components/onboarding/
OnboardingApp.tsx // 外壳 + 4 步状态机 + 顶部 Stepper
Stepper.tsx // 4 步进度指示
OnboardingStepIntro.tsx // Step 1
OnboardingStepSelect.tsx // Step 2,复用 Config.SupportedProviderList
OnboardingStepForm.tsx // Step 3,复用 AddProviderStepForm 逻辑
OnboardingStepRuntime.tsx // Step 4
Step 3 表单复用
将 AddProviderStepForm 抽出共享。两种实施方式择其一(实现时决定):
- 增加
mode: 'settings' | 'onboarding'prop,按 mode 切换提交目标 - 抽出
useProviderFormhook,组件保持表现层
提交目标:
mode === 'settings'(现有)→Provider.CreateProvidermode === 'onboarding'→Onboarding.SaveProviderAndMarkInitialized
Step 4 状态机
idle → 「立即下载」 → downloading → ready → 「进入主界面」
└─ failed → 「重试」/「跳过」
└─ canceled → idle
idle → 「稍后下载」 → Runtime.MarkDownloadLater → 「进入主界面」
事件订阅:useEffect 注册 runtime.node.progress,组件卸载时取消订阅。
UI 元素:
- idle:标题、Node.js 用途简介、两按钮
- downloading:进度条 + 阶段文案(下载 X.X MB / Y.Y MB、解压中、校验中)+ 取消按钮
- ready:完成图标 + 已安装版本号 + 安装路径 + 「进入主界面」
- failed:错误信息(
error_msg来自GetStatus)+ 「重试」「跳过」
4.3 设置页接入
在 frontend/src/components/settings/general/ 增加 NodeRuntimeSection(若该目录不存在则与现有 settings 目录结构一致地新增):
- 显示当前
Runtime.GetStatus返回的状态 - 按钮:
missing/pending_later/failed→ 「下载」;downloading→ 「取消」;ready→ 「重新下载」「卸载」 - 进度事件订阅复用 onboarding 同样的
runtime.node.progress
4.4 i18n
新增命名空间,zh-CN 与 en 同步:
onboarding.*— 步骤标题、按钮、介绍卡文案、错误提示onboarding.intro.heroTitle=先连接第一个模型供应商,再开始你的第一段对话
runtime.*— 状态名(已安装/下载中/未安装/等待手动下载/失败)、按钮、错误
5. 关键文件清单
| 类别 | 路径 |
|---|---|
| backend onboarding | backend/service/onboarding/{onboarding.go, onboarding_implement.go, onboarding_internal.go, onboarding_test.go} |
| backend onboarding DTO | backend/service/onboarding/onboarding_dto/{is_initialized.go, save_provider_and_mark_initialized.go, enter_home.go, exit_app.go} |
| backend runtime | backend/service/runtime/{runtime.go, runtime_implement.go, runtime_internal.go, constants.go, runtime_test.go} |
| backend runtime DTO | backend/service/runtime/runtime_dto/{get_status.go, download_node.go, cancel_download.go, mark_download_later.go} |
| backend 其他 | backend/pkg/id/window_id/window_id.go、backend/service/window/window.go、backend/pkg/ierror/...、backend/pkg/window_options/、main.go |
| frontend onboarding | frontend/src/components/onboarding/{OnboardingApp, Stepper, OnboardingStepIntro, OnboardingStepSelect, OnboardingStepForm, OnboardingStepRuntime}.tsx |
| frontend 改造 | frontend/src/App.tsx、frontend/src/components/settings/providers/AddProviderStepForm.tsx |
| frontend 设置 | frontend/src/components/settings/general/NodeRuntimeSection.tsx |
| i18n | frontend/src/i18n/locales/{zh-CN.ts, en.ts} |
6. 样式与显式约束
- 浅色 / 深色双主题:使用现有 Tailwind tokens(
bg-background、text-foreground、border-border、text-primary等),不要硬编码颜色 - 字号档位(极小/小/标准/大/超大):继承
FontSizeProvider,组件内不要写死字号 - 所有可见文本走
t(),先做 zh-CN 与 en - TypeScript:所有数据结构显式类型,禁止
any;用@/@bindings引用 - Go:每个函数必加注释;公开方法返回
ierror;错误文案多语言
7. 测试
后端
onboarding_test.go:未初始化 / 已初始化两种状态读取;首次 mark 后IsInitialized返回 true;重复 mark 幂等runtime_test.go:OS/arch → URL 拼接、SHA256SUMS 解析与校验、state.json 读写、用本地小 fixture 验证 tar.gz 解压
前端
人工验证四步流程在以下组合下都正常:
- 浅色 / 深色
- zh-CN / en
- 字号 5 档
- Step 4「立即下载」成功 / 失败 / 取消 / 稍后下载
- 重启应用:Step 3 完成即视为「已初始化」,重启后直接进主窗口;设置页能继续触发 Node 下载
8. 实施顺序建议
- backend onboarding 服务 + init.json(不含主窗口创建逻辑切换)
- backend window:
OpenOnboarding+ 抽defaultWindowOptions - main.go 接入分流
- frontend 入口分发 + Step 1/2/3 与
Onboarding.SaveProviderAndMarkInitialized - backend runtime 服务(含进度事件)
- frontend Step 4
- 设置页
NodeRuntimeSection - i18n 文案收尾、双主题与字号档位检查
每一步完成后跑相应单测与人工验证再进入下一步。