欢迎向导(Onboarding)

May 31, 2026 · View on GitHub

首次启动时引导用户完成关键配置:选择并配置一个模型供应商、可选下载 Node.js 运行时。完成第三步后即视为「已初始化」,第四步可跳过。

实施请同时遵循 docs/dev/00.rules.md


1. 功能范围

四个步骤,逐步推进:

  1. Intro — 欢迎页:大标题 + 三张介绍卡 + 「退出」「开始配置」按钮
  2. Select Provider — 列出系统支持的供应商,选一个
  3. Config Provider — 配置启用 / 名称 / Base URL / API Key / 模型列表 / 默认模型(复用 AddProviderStepForm
  4. 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.goOnboarding 结构体与 Wails 绑定方法
onboarding_implement.goServiceStartupapplication.App 实例
onboarding_internal.goinitFilePathisInitializedmarkInitialized、关窗/开主窗辅助
onboarding_test.goinit.json 读写单测
onboarding_dto/每个公开方法一个文件

公开方法(均按 func (s *Onboarding) X(ctx context.Context, input dto.XInput) (*dto.XOutput, error) 签名):

方法行为
IsInitializedGetDataDir()/init.json,返回 {initialized: bool}
SaveProviderAndMarkInitialized内部调用 provider.CreateProvider,成功后写 init.json。入参字段镜像 provider_dto.CreateProviderInput
EnterHome创建并显示主窗口,关闭 onboarding 窗口
ExitAppwailsApp.Quit()

init.json 结构:

{
  "initialized": true,
  "completed_at": "2026-05-22T10:00:00Z"
}

3.2 新增 service:backend/service/runtime/(Node.js 运行时)

文件职责
runtime.goRuntime 结构体与公开方法
runtime_implement.goServiceStartup
runtime_internal.go下载、解压、校验、状态读写、进度事件发射
constants.goNodeLTSVersion(如 v22.x.y)、NodeDistBaseURL = "https://nodejs.org/dist"
runtime_test.goURL 拼接、状态文件读写、解压逻辑单测
runtime_dto/各方法 Input/Output

公开方法:

方法行为
GetStatus返回 {state, version, install_dir, node_path, npm_path, error_msg}statemissing / 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/nodebin/npm(Windows 取 node.exenpm.cmd
  • 状态文件:GetDataDir()/runtime/node/state.json,含 stateversioninstalled_atupdated_aterror_msg
  • 同版本已存在且校验通过时跳过下载,直接进入 verifyready
  • 下载使用 context.ContextCancelDownload 通过 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());为避免循环依赖,把 defaultWindowOptionsmain.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 切换提交目标
  • 抽出 useProviderForm hook,组件保持表现层

提交目标:

  • mode === 'settings'(现有)→ Provider.CreateProvider
  • mode === '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 onboardingbackend/service/onboarding/{onboarding.go, onboarding_implement.go, onboarding_internal.go, onboarding_test.go}
backend onboarding DTObackend/service/onboarding/onboarding_dto/{is_initialized.go, save_provider_and_mark_initialized.go, enter_home.go, exit_app.go}
backend runtimebackend/service/runtime/{runtime.go, runtime_implement.go, runtime_internal.go, constants.go, runtime_test.go}
backend runtime DTObackend/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.gobackend/service/window/window.gobackend/pkg/ierror/...backend/pkg/window_options/main.go
frontend onboardingfrontend/src/components/onboarding/{OnboardingApp, Stepper, OnboardingStepIntro, OnboardingStepSelect, OnboardingStepForm, OnboardingStepRuntime}.tsx
frontend 改造frontend/src/App.tsxfrontend/src/components/settings/providers/AddProviderStepForm.tsx
frontend 设置frontend/src/components/settings/general/NodeRuntimeSection.tsx
i18nfrontend/src/i18n/locales/{zh-CN.ts, en.ts}

6. 样式与显式约束

  • 浅色 / 深色双主题:使用现有 Tailwind tokens(bg-backgroundtext-foregroundborder-bordertext-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. 实施顺序建议

  1. backend onboarding 服务 + init.json(不含主窗口创建逻辑切换)
  2. backend window:OpenOnboarding + 抽 defaultWindowOptions
  3. main.go 接入分流
  4. frontend 入口分发 + Step 1/2/3 与 Onboarding.SaveProviderAndMarkInitialized
  5. backend runtime 服务(含进度事件)
  6. frontend Step 4
  7. 设置页 NodeRuntimeSection
  8. i18n 文案收尾、双主题与字号档位检查

每一步完成后跑相应单测与人工验证再进入下一步。