/** * OnboardingWizard — 首次使用引导向导 */ import { useState } from 'react'; import { Dialog, DialogContent, Button, TextField, Select, MenuItem, Stepper, Step, StepLabel, Box, Typography, Stack, FormControl, InputLabel, IconButton, InputAdornment } from '@mui/material'; import { ArrowRight, ArrowLeft, FolderOpen, Play, CheckCircle, Eye, EyeOff } from 'lucide-react'; import { useUIStore } from '@renderer/stores/ui-store'; import { useAgentStore } from '@renderer/stores/agent-store'; const STEPS = ['欢迎', '配置 LLM', '自定义 Agent', '工作空间', '开始使用']; export function OnboardingWizard(): React.JSX.Element | null { const onboardingCompleted = useUIStore((s) => s.onboardingCompleted); const setOnboardingCompleted = useUIStore((s) => s.setOnboardingCompleted); const [step, setStep] = useState(0); const [provider, setProvider] = useState(''); const [baseURL, setBaseURL] = useState(''); const [model, setModel] = useState(''); const [apiKey, setApiKey] = useState(''); const [showKey, setShowKey] = useState(false); const [workspacePath, setWorkspacePath] = useState(''); if (onboardingCompleted) return null; const handleNext = async () => { if (step < STEPS.length - 1) { setStep(step + 1); return; } try { if (window.metona?.config?.setBatch) { // v0.3.9: 改用批量保存,避免并行 config.set 中间态触发 reloadAdapter 失败 // 旧实现问题:Promise.allSettled 并行 6 个 config.set, // - llm.provider 写入会清空 llm.apiKey(与 llm.apiKey 写入竞态) // - reloadAdapter 被调用 4 次,中间态必然失败 // - 代码只检查 status === 'rejected',完全忽略 { success: false } 的情况 // - 实际配置失败但前端显示成功并关闭向导 const entries: Array<{ key: string; value: unknown }> = []; if (provider.trim()) entries.push({ key: 'llm.provider', value: provider.trim() }); if (baseURL.trim()) entries.push({ key: 'llm.baseURL', value: baseURL.trim() }); if (model.trim()) entries.push({ key: 'llm.model', value: model.trim() }); if (apiKey.trim()) entries.push({ key: 'llm.apiKey', value: apiKey.trim() }); if (workspacePath.trim()) entries.push({ key: 'workspace.path', value: workspacePath.trim() }); entries.push({ key: 'onboarding.completed', value: true }); const r = await window.metona.config.setBatch(entries); if (r && !r.success) { console.error('[OnboardingWizard]', 'Batch config save failed:', r.error); import('metona-toast').then((mod) => mod.default.error(r.error ?? '配置保存失败,请重试')).catch(() => {}); return; } useAgentStore.getState().setProvider(provider.trim() || 'deepseek', model.trim() || ''); // P1-6 修复: Onboarding 完成后标记配置已加载 useAgentStore.getState().setConfigLoaded(true); } setOnboardingCompleted(true); } catch (err) { console.error('[OnboardingWizard]', 'Failed to save configuration:', err); // 用户主动操作失败必须有反馈,否则向导不关闭、用户卡死 import('metona-toast').then((mod) => mod.default.error(`保存配置失败:${(err as Error).message}`)).catch(() => {}); } }; return ( {STEPS.map((s) => {s})} {step === 0 && ( 欢迎使用 MetonaAI Desktop 生产级通用 AI Agent 智能体桌面应用,支持多轮对话、工具调用、记忆系统和 MCP 协议集成。 让我们花 1 分钟完成初始配置。 )} {step === 1 && ( 配置 LLM Provider 选择 Provider 并填写 API 信息。Base URL 和模型名称支持任意输入。 Provider setBaseURL(e.target.value)} placeholder="如 https://api.deepseek.com" /> setModel(e.target.value)} placeholder="如 deepseek-v4-pro、qwen3:latest" /> setApiKey(e.target.value)} placeholder="sk-...(本地模型可留空)" slotProps={{ input: { endAdornment: setShowKey(!showKey)}>{showKey ? : } } }} /> )} {step === 2 && ( 自定义 Agent 编辑工作空间中的 SOUL.md 和 USERS.md 文件来定义 Agent 的身份和你的偏好。
SOUL.md — 定义 Agent 的身份、性格、核心价值观
AGENTS.md — 定义行为规则、工具使用规范
USERS.md — 描述你的技术栈、偏好、目标
此步骤可稍后在工作空间目录中完成。
)} {step === 3 && ( 工作空间 选择工作空间目录,或使用默认路径。 setWorkspacePath(e.target.value)} placeholder="~/MetonaWorkspaces/default/" sx={{ flex: 1 }} /> 包含 SOUL.md、AGENTS.md、MEMORY.md、USERS.md 四个必需文件,首次打开时自动创建。 )} {step === 4 && ( 配置完成! MetonaAI Desktop 已准备就绪。开始与你的 AI Agent 对话吧! 按 Ctrl+Enter 发送消息,输入 / 查看命令列表 )}
); }