mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-21 23:54:22 +08:00
fix(setup): 补齐跨端 Node 检测与新手引导
This commit is contained in:
@@ -349,6 +349,9 @@ export function showUpgradeModal(title) {
|
||||
}
|
||||
closeBtn.focus()
|
||||
},
|
||||
setCloseText(label) {
|
||||
if (label) closeBtn.textContent = label
|
||||
},
|
||||
onClose(fn) { _onClose = fn },
|
||||
destroy() { overlay.remove(); if (_taskBar) { _taskBar.remove(); _taskBar = null } _onClose?.() },
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ export default {
|
||||
onboardingStep2Title: _('添加你的第一个模型', 'Add your first model', '新增你的第一個模型', '最初のモデルを追加', '첫 번째 모델 추가', 'Thêm mô hình đầu tiên', 'Añade tu primer modelo', 'Adicione seu primeiro modelo', 'Добавьте первую модель', 'Ajoutez votre premier modèle', 'Fügen Sie Ihr erstes Modell hinzu'),
|
||||
onboardingStep2Desc: _('配上 OpenAI / Claude / DeepSeek 等任意服务商和 API key,AI 才有「大脑」', 'Add a provider like OpenAI / Claude / DeepSeek with an API key — that gives the AI its "brain"', '配上 OpenAI / Claude / DeepSeek 等任意服務商和 API key,AI 才有「大腦」', 'OpenAI / Claude / DeepSeek などのプロバイダーと API キーを設定すると AI に「脳」が宿ります', 'OpenAI / Claude / DeepSeek 같은 프로바이더와 API key를 설정해 AI에 "두뇌"를 부여하세요', 'Thêm nhà cung cấp (OpenAI / Claude / DeepSeek) cùng API key để AI có "bộ não"', 'Añade un proveedor como OpenAI / Claude / DeepSeek con su API key — eso le da el "cerebro" a la IA', 'Adicione um provedor como OpenAI / Claude / DeepSeek com a chave da API — isso dá um "cérebro" à IA', 'Добавьте провайдера (OpenAI / Claude / DeepSeek) с API-ключом — это даст ИИ «мозг»', 'Ajoutez un fournisseur comme OpenAI / Claude / DeepSeek avec une clé API — cela donne un « cerveau » à l\u2019IA', 'Fügen Sie einen Anbieter wie OpenAI / Claude / DeepSeek mit API-Key hinzu — damit hat die KI ein „Gehirn"'),
|
||||
onboardingStep2Cta: _('去添加模型', 'Add a model', '去新增模型', 'モデルを追加', '모델 추가', 'Thêm mô hình', 'Añadir modelo', 'Adicionar modelo', 'Добавить модель', 'Ajouter un modèle', 'Modell hinzufügen'),
|
||||
onboardingStep3Title: _('创建第一个 Agent', 'Create your first Agent', '建立第一個 Agent', '最初の Agent を作成', '첫 Agent 생성', 'Tạo Agent đầu tiên', 'Crea tu primer Agent', 'Crie seu primeiro Agent', 'Создайте первого агента', 'Créez votre premier Agent', 'Erstellen Sie Ihren ersten Agent'),
|
||||
onboardingStep3Desc: _('Agent 是「分身」—— 给它身份、技能、记忆,它就能代你聊天和办事', 'Agents are your "alter egos" — give them an identity, skills, and memory so they can chat and act on your behalf', 'Agent 是「分身」—— 給它身份、技能、記憶,它就能代你聊天和辦事', 'Agent はあなたの「分身」。アイデンティティ・スキル・記憶を与えると、あなたの代わりに会話したり仕事をしたりします', 'Agent는 당신의 "분신"입니다. 정체성·스킬·기억을 부여하면 당신을 대신해 대화하고 일을 처리합니다', 'Agent là "phân thân" — đặt danh tính, kỹ năng, ký ức cho nó để nó trò chuyện và làm việc thay bạn', 'Los Agents son tus "alter egos" — dales una identidad, habilidades y memoria para que conversen y actúen por ti', 'Os Agents são seus "alter egos" — dê-lhes identidade, habilidades e memória para que conversem e ajam em seu nome', 'Agent — это «второе я». Дайте ему имя, навыки и память, и он будет говорить и действовать от вашего имени', 'Les Agents sont vos « alter egos ». Donnez-leur une identité, des compétences et une mémoire pour qu\u2019ils discutent et agissent pour vous', 'Agents sind Ihre „alter egos". Geben Sie ihnen Identität, Fähigkeiten und Gedächtnis, damit sie für Sie chatten und handeln'),
|
||||
onboardingStep3Cta: _('去创建 Agent', 'Create an Agent', '去建立 Agent', 'Agent を作成', 'Agent 만들기', 'Tạo Agent', 'Crear Agent', 'Criar Agent', 'Создать агента', 'Créer un Agent', 'Agent erstellen'),
|
||||
onboardingStep3Title: _('确认默认 Agent', 'Confirm default Agent', '確認預設 Agent', 'デフォルト Agent を確認', '기본 Agent 확인', 'Xác nhận Agent mặc định', 'Confirmar Agent predeterminado', 'Confirmar Agent padrão', 'Проверьте агента по умолчанию', 'Vérifier l’Agent par défaut', 'Standard-Agent prüfen'),
|
||||
onboardingStep3Desc: _('默认 main Agent 已内置;需要多个身份或工作区时,再到 Agent 管理里新增。', 'The default main Agent is built in. Add more in Agent Management only when you need multiple identities or workspaces.', '預設 main Agent 已內建;需要多個身份或工作區時,再到 Agent 管理裡新增。', 'デフォルトの main Agent は内蔵済みです。複数の役割やワークスペースが必要な場合だけ Agent 管理で追加してください。', '기본 main Agent가 내장되어 있습니다. 여러 정체성이나 워크스페이스가 필요할 때만 Agent 관리에서 추가하세요.', 'Agent main mặc định đã có sẵn; chỉ thêm Agent mới khi cần nhiều danh tính hoặc workspace.', 'El Agent main predeterminado ya viene incluido. Añade más solo si necesitas varias identidades o espacios de trabajo.', 'O Agent main padrão já está incluído. Adicione outros apenas se precisar de múltiplas identidades ou workspaces.', 'Агент main по умолчанию уже встроен. Добавляйте новых только для разных ролей или рабочих областей.', 'L’Agent main par défaut est déjà inclus. Ajoutez-en seulement si vous avez besoin de plusieurs identités ou espaces de travail.', 'Der Standard-Agent main ist bereits enthalten. Weitere Agents nur bei mehreren Rollen oder Arbeitsbereichen anlegen.'),
|
||||
onboardingStep3Cta: _('查看 Agent', 'View Agents', '查看 Agent', 'Agent を見る', 'Agent 보기', 'Xem Agent', 'Ver Agents', 'Ver Agents', 'Открыть агентов', 'Voir les Agents', 'Agents ansehen'),
|
||||
onboardingStep4Title: _('开始第一次对话', 'Start your first chat', '開始第一次對話', '最初のチャットを始める', '첫 대화 시작', 'Bắt đầu cuộc trò chuyện đầu tiên', 'Inicia tu primera conversación', 'Inicie sua primeira conversa', 'Начните первый чат', 'Démarrez votre première conversation', 'Starten Sie Ihren ersten Chat'),
|
||||
onboardingStep4Desc: _('一切就绪 —— 现在打开聊天页,跟你的 Agent 说句话试试', 'All set — open the chat page and say hi to your Agent', '一切就緒 —— 現在打開聊天頁,跟你的 Agent 說句話試試', '準備完了。チャットページを開いて Agent に話しかけてみましょう', '준비 완료. 채팅 페이지를 열고 Agent에게 말을 걸어보세요', 'Tất cả sẵn sàng — mở trang trò chuyện và chào Agent của bạn', 'Listo — abre la página de chat y saluda a tu Agent', 'Tudo pronto — abra a página de chat e diga "oi" para o seu Agent', 'Готово — откройте страницу чата и поздоровайтесь со своим агентом', 'Tout est prêt — ouvrez la page de chat et dites bonjour à votre Agent', 'Alles bereit — öffnen Sie die Chat-Seite und begrüßen Sie Ihren Agent'),
|
||||
onboardingStep4Cta: _('去聊天', 'Open chat', '去聊天', 'チャットを開く', '채팅 열기', 'Mở trò chuyện', 'Abrir chat', 'Abrir chat', 'Открыть чат', 'Ouvrir le chat', 'Chat öffnen'),
|
||||
|
||||
@@ -129,6 +129,9 @@ export default {
|
||||
nodeUpgradeHint: _('当前 Node.js {version} 版本过低,OpenClaw 要求 {required}。请先升级 Node.js,否则 Gateway 无法启动。', 'Current Node.js {version} is too old; OpenClaw requires {required}. Upgrade Node.js first or Gateway cannot start.', '目前 Node.js {version} 版本過低,OpenClaw 要求 {required}。請先升級 Node.js,否則 Gateway 無法啟動。'),
|
||||
nodeUnsupportedTitle: _('已检测到 Node.js,但版本不满足要求', 'Node.js was detected, but the version is not supported', '已檢測到 Node.js,但版本不符合要求'),
|
||||
winNodeUpgradeHint: _('Windows 可尝试一键升级;如果失败,请手动安装 Node.js 22.19.0 或更高版本。升级后点击「重新检测」。', 'On Windows, try one-click upgrade. If it fails, manually install Node.js 22.19.0 or newer. Click "Re-detect" after upgrading.', 'Windows 可嘗試一鍵升級;如果失敗,請手動安裝 Node.js 22.19.0 或更高版本。升級後點擊「重新檢測」。'),
|
||||
macNodeUpgradeHint: _('macOS 请通过官网、Homebrew、nvm 或 fnm 升级 Node.js 22.19.0 或更高版本。升级后建议重启 ClawPanel;如果从 Finder 启动仍检测到旧版本,可从终端重新打开:', 'On macOS, upgrade Node.js to 22.19.0 or newer via the official installer, Homebrew, nvm, or fnm. Restart ClawPanel after upgrading; if Finder still detects the old version, reopen it from Terminal:', 'macOS 請透過官網、Homebrew、nvm 或 fnm 升級 Node.js 22.19.0 或更高版本。升級後建議重啟 ClawPanel;如果從 Finder 啟動仍檢測到舊版本,可從終端重新開啟:'),
|
||||
linuxNodeUpgradeHint: _('Linux 请使用系统包管理器、NodeSource、nvm 或 fnm 升级 Node.js 22.19.0 或更高版本。升级后点击「重新检测」,必要时重启 ClawPanel Web/桌面进程。', 'On Linux, upgrade Node.js to 22.19.0 or newer using your package manager, NodeSource, nvm, or fnm. Click "Re-detect" after upgrading, and restart the ClawPanel Web/desktop process if needed.', 'Linux 請使用系統套件管理器、NodeSource、nvm 或 fnm 升級 Node.js 22.19.0 或更高版本。升級後點擊「重新檢測」,必要時重啟 ClawPanel Web/桌面進程。'),
|
||||
genericNodeUpgradeHint: _('请升级 Node.js 22.19.0 或更高版本,升级后点击「重新检测」。如果仍检测到旧版本,请检查 PATH 优先级或重启 ClawPanel。', 'Upgrade Node.js to 22.19.0 or newer, then click "Re-detect". If the old version is still detected, check PATH priority or restart ClawPanel.', '請升級 Node.js 22.19.0 或更高版本,升級後點擊「重新檢測」。如果仍檢測到舊版本,請檢查 PATH 優先級或重啟 ClawPanel。'),
|
||||
autoUpgradeNodeBtn: _('一键升级 Node.js', 'Upgrade Node.js', '一鍵升級 Node.js'),
|
||||
upgradingNode: _('升级中...', 'Upgrading...', '升級中...'),
|
||||
downloadLatestNode: _('下载新版 Node.js', 'Download latest Node.js', '下載新版 Node.js'),
|
||||
@@ -147,6 +150,7 @@ export default {
|
||||
nodeUpgradeDone: _('Node.js 检测通过', 'Node.js check passed', 'Node.js 檢測通過'),
|
||||
nodeUpgradeStarting: _('开始检查 Windows winget 和 Node.js LTS 安装状态...', 'Checking Windows winget and Node.js LTS install state...', '開始檢查 Windows winget 和 Node.js LTS 安裝狀態...'),
|
||||
nodeUpgradeRedetecting: _('已触发重新检测;如果仍显示旧版本,请重启 ClawPanel 或检查 PATH 优先级。', 'Re-detecting now. If the old version still appears, restart ClawPanel or check PATH priority.', '已觸發重新檢測;如果仍顯示舊版本,請重啟 ClawPanel 或檢查 PATH 優先級。'),
|
||||
nodeUpgradeStartGatewayHint: _('检测通过后,回到仪表盘或服务页启动 Gateway。', 'After the check passes, return to Dashboard or Services and start Gateway.', '檢測通過後,回到儀表盤或服務頁啟動 Gateway。'),
|
||||
nodeManualInstallHint: _('请手动安装 Node.js 22.19.0 或更高版本:', 'Please manually install Node.js 22.19.0 or newer:', '請手動安裝 Node.js 22.19.0 或更高版本:'),
|
||||
nodeUpgradeRestartHint: _('安装后回到 ClawPanel 点击「重新检测」。如果仍检测到旧版本,重启 ClawPanel 或把新版 Node.js 路径放到 PATH 更靠前的位置。', 'After installing, return to ClawPanel and click "Re-detect". If the old version is still detected, restart ClawPanel or move the newer Node.js path earlier in PATH.', '安裝後回到 ClawPanel 點擊「重新檢測」。如果仍檢測到舊版本,請重啟 ClawPanel 或把新版 Node.js 路徑放到 PATH 更靠前的位置。'),
|
||||
promptGitMissing: _('Git 未安装', 'Git not installed', 'Git 未安裝'),
|
||||
|
||||
@@ -922,9 +922,9 @@ function getOnboardingSteps({ gw, config, agents, channels }) {
|
||||
// 步骤 2:至少配了一个 provider 且非空
|
||||
const providers = config?.models?.providers || {}
|
||||
const hasModel = Object.keys(providers).length > 0
|
||||
// 步骤 3:自定义 Agent(默认 main 不算)
|
||||
// 步骤 3:默认 main Agent 已经可用时无需再创建新 Agent。
|
||||
const agentList = Array.isArray(agents) ? agents : []
|
||||
const hasCustomAgent = agentList.some(a => a && a.id && a.id !== 'main')
|
||||
const hasAgent = agentList.some(a => a && (a.id || a.name))
|
||||
// 步骤 4:渠道接入(不是必须,但作为「已开始用」的标志)
|
||||
// 实际上更好的判定是「点过聊天页 / 发过一条消息」,但目前没记录,先用 channels 数量作为可选完成判据
|
||||
// 改为:把第 4 步定义为「尝试聊天」—— 不强校验,CTA 触发跳转即可(用户点了就当完成)
|
||||
@@ -934,7 +934,7 @@ function getOnboardingSteps({ gw, config, agents, channels }) {
|
||||
return [
|
||||
{ id: 'gateway', titleKey: 'onboardingStep1Title', descKey: 'onboardingStep1Desc', ctaKey: 'onboardingStep1Cta', route: '/services', done: gwRunning },
|
||||
{ id: 'model', titleKey: 'onboardingStep2Title', descKey: 'onboardingStep2Desc', ctaKey: 'onboardingStep2Cta', route: '/models', done: hasModel },
|
||||
{ id: 'agent', titleKey: 'onboardingStep3Title', descKey: 'onboardingStep3Desc', ctaKey: 'onboardingStep3Cta', route: '/agents', done: hasCustomAgent },
|
||||
{ id: 'agent', titleKey: 'onboardingStep3Title', descKey: 'onboardingStep3Desc', ctaKey: 'onboardingStep3Cta', route: '/agents', done: hasAgent },
|
||||
{ id: 'chat', titleKey: 'onboardingStep4Title', descKey: 'onboardingStep4Desc', ctaKey: 'onboardingStep4Cta', route: '/chat', done: hasChatTried, markOnClick: 'clawpanel_onboarding_chat_clicked' },
|
||||
]
|
||||
}
|
||||
|
||||
@@ -54,10 +54,36 @@ function isWindowsPlatform() {
|
||||
return navigator.platform?.startsWith('Win') || navigator.userAgent?.includes('Windows')
|
||||
}
|
||||
|
||||
function isLinuxPlatform() {
|
||||
return navigator.platform?.toLowerCase().includes('linux') || navigator.userAgent?.toLowerCase().includes('linux')
|
||||
}
|
||||
|
||||
function canAutoUpgradeNode() {
|
||||
return isTauriRuntime() && isWindowsPlatform()
|
||||
}
|
||||
|
||||
function nodeRuntimeHint(nodeTooOld) {
|
||||
if (!nodeTooOld) return t('setup.winNodeHint')
|
||||
if (isWindowsPlatform()) return t('setup.winNodeUpgradeHint')
|
||||
if (isMacPlatform()) return t('setup.macNodeUpgradeHint')
|
||||
if (isLinuxPlatform()) return t('setup.linuxNodeUpgradeHint')
|
||||
return t('setup.genericNodeUpgradeHint')
|
||||
}
|
||||
|
||||
function nodePathPlaceholder() {
|
||||
if (isMacPlatform()) return '/usr/local/bin'
|
||||
if (isLinuxPlatform()) return '/usr/bin'
|
||||
return 'F:\\AI\\Node'
|
||||
}
|
||||
|
||||
function normalizeNodeUpgradeLog(line) {
|
||||
const text = String(line || '').trimEnd()
|
||||
if (!text.trim()) return null
|
||||
if (/[█▓▒░]/.test(text)) return null
|
||||
if (/^[\s\\|/\-]+$/.test(text)) return null
|
||||
return text
|
||||
}
|
||||
|
||||
function renderDetectionHint(pathValue, sourceLabel = '') {
|
||||
const normalizedPath = String(pathValue || '').trim()
|
||||
const normalizedSource = String(sourceLabel || '').trim()
|
||||
@@ -297,16 +323,16 @@ function renderSteps(page, { node, git, cliOk, config, version }) {
|
||||
<div style="margin-top:var(--space-sm);padding:10px 12px;background:var(--bg-tertiary);border-radius:var(--radius-sm);font-size:var(--font-size-xs);color:var(--text-secondary);line-height:1.6">
|
||||
<strong>${nodeTooOld ? t('setup.nodeUnsupportedTitle') : t('setup.nodeInstalledButNotDetected')}</strong>
|
||||
${isMacPlatform()
|
||||
? `${t('setup.macNodeHint')}<br>
|
||||
? `${nodeTooOld ? t('setup.macNodeUpgradeHint') : t('setup.macNodeHint')}<br>
|
||||
<code style="background:var(--bg-secondary);padding:2px 6px;border-radius:3px;user-select:all">open /Applications/ClawPanel.app</code>`
|
||||
: `${nodeTooOld ? t('setup.winNodeUpgradeHint') : t('setup.winNodeHint')}`
|
||||
: `${nodeRuntimeHint(nodeTooOld)}`
|
||||
}
|
||||
<div style="margin-top:8px;display:flex;gap:6px;align-items:center;flex-wrap:wrap">
|
||||
<button class="btn btn-secondary btn-sm" id="btn-scan-node" style="font-size:11px;padding:3px 10px">${icon('search', 12)} ${t('setup.scanNodeBtn')}</button>
|
||||
<span style="color:var(--text-tertiary)">${t('setup.orManualPath')}</span>
|
||||
</div>
|
||||
<div class="setup-input-row" style="margin-top:6px">
|
||||
<input id="input-node-path" type="text" placeholder="${isMacPlatform() ? '/usr/local/bin' : 'F:\\AI\\Node'}"
|
||||
<input id="input-node-path" type="text" placeholder="${nodePathPlaceholder()}"
|
||||
style="flex:1;padding:4px 8px;border:1px solid var(--border-primary);border-radius:var(--radius-sm);background:var(--bg-secondary);color:var(--text-primary);font-size:11px;font-family:monospace">
|
||||
<button class="btn btn-primary btn-sm" id="btn-check-path" style="font-size:11px;padding:3px 10px">${t('setup.checkPathBtn')}</button>
|
||||
</div>
|
||||
@@ -718,12 +744,17 @@ function bindEvents(page, nodeOk, detectState) {
|
||||
btn.disabled = true
|
||||
btn.textContent = t('setup.upgradingNode')
|
||||
try {
|
||||
unlistenLog = await safeTauriListen('upgrade-log', (e) => modal.appendLog(e.payload))
|
||||
unlistenLog = await safeTauriListen('upgrade-log', (e) => {
|
||||
const line = normalizeNodeUpgradeLog(e.payload)
|
||||
if (line) modal.appendLog(line)
|
||||
})
|
||||
unlistenProgress = await safeTauriListen('upgrade-progress', (e) => modal.setProgress(e.payload))
|
||||
const msg = await api.autoInstallNode()
|
||||
modal.setProgress(100)
|
||||
modal.setDone(msg || t('setup.nodeUpgradeSuccess'))
|
||||
modal.setCloseText(t('common.completed'))
|
||||
modal.appendLog(t('setup.nodeUpgradeRedetecting'))
|
||||
modal.appendLog(t('setup.nodeUpgradeStartGatewayHint'))
|
||||
toast(msg || t('setup.nodeUpgradeSuccess'), 'success')
|
||||
await api.invalidatePathCache().catch(() => {})
|
||||
setTimeout(() => runDetect(page), 800)
|
||||
|
||||
Reference in New Issue
Block a user