mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-06 20:02:49 +08:00
fix: improve install/uninstall stability + dreaming animations + scan fix flow
Dreaming page: - Restore animated hero card (stars, moon, floating Z's, deep sky gradient) - Glass-morphism stat cards inside hero - runAction/toggleDreaming: friendly toast for unsupported RPCs instead of raw error Install/Uninstall (config.rs): - pre_install_cleanup: fix Windows process kill (wmic CommandLine match instead of useless WINDOWTITLE filter), kill standalone dir processes, wait 2s - uninstall: wait 3s after gateway stop, wmic kill standalone processes, retry deletion with 3s delay - upgrade source-switch: standalone cleanup with process kill + retry + wait - gateway reinstall: wait 2s after stop before install Scan fix flow (chat-debug.js): - 5-step fix with progress toasts (pairing, doctor, gateway, wait, websocket) - Gateway start polling (up to 15s) instead of fixed 5s wait - WebSocket reconnect polling (up to 12s) instead of fixed 3s wait Frontend (modal.js + about.js): - setProgressLabels() for custom progress text per modal usage - Uninstall modal shows "停止服务/卸载组件/清理文件" instead of "下载依赖" i18n: 11 locales updated with fixStep* and uninstall progress keys
This commit is contained in:
@@ -485,17 +485,49 @@ fn npm_command_elevated() -> Command {
|
||||
/// 安装/升级前的清理工作:停止 Gateway、清理 npm 全局 bin 下的 openclaw 残留文件
|
||||
/// 解决 Windows 上 EEXIST(文件已存在)和文件被占用的问题
|
||||
fn pre_install_cleanup() {
|
||||
// 1. 停止 Gateway 进程,释放 openclaw 相关文件锁
|
||||
// 1. 先通过 CLI 正常停止 Gateway
|
||||
let _ = openclaw_command().args(["gateway", "stop"]).output();
|
||||
|
||||
// 2. 停止 Gateway 进程,释放 openclaw 相关文件锁
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use std::os::windows::process::CommandExt;
|
||||
// 杀死所有 openclaw gateway 相关的 node 进程
|
||||
let _ = Command::new("taskkill")
|
||||
.args(["/f", "/im", "node.exe", "/fi", "WINDOWTITLE eq OpenClaw*"])
|
||||
.creation_flags(0x08000000)
|
||||
.output();
|
||||
// 等文件锁释放
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
// 杀死所有运行 openclaw gateway 的 node.exe 进程(通过命令行匹配)
|
||||
if let Ok(output) = Command::new("wmic")
|
||||
.args(["process", "where", "CommandLine like '%openclaw%gateway%'", "get", "ProcessId", "/format:list"])
|
||||
.output()
|
||||
{
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
for line in text.lines() {
|
||||
if let Some(pid_str) = line.strip_prefix("ProcessId=") {
|
||||
if let Ok(_pid) = pid_str.trim().parse::<u32>() {
|
||||
let _ = Command::new("taskkill").args(["/F", "/PID", pid_str.trim()]).output();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 同时杀死 standalone 目录下的 node.exe 进程
|
||||
for sa_dir in all_standalone_dirs() {
|
||||
if sa_dir.exists() {
|
||||
let dir_str = sa_dir.to_string_lossy().to_lowercase();
|
||||
if let Ok(output) = Command::new("wmic")
|
||||
.args(["process", "where", &format!("ExecutablePath like '%{}%'", dir_str.replace('\\', "\\\\")), "get", "ProcessId", "/format:list"])
|
||||
.output()
|
||||
{
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
for line in text.lines() {
|
||||
if let Some(pid_str) = line.strip_prefix("ProcessId=") {
|
||||
if let Ok(_pid) = pid_str.trim().parse::<u32>() {
|
||||
let _ = Command::new("taskkill").args(["/F", "/PID", pid_str.trim()]).output();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等文件锁释放(Node.js 进程退出需要时间)
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
@@ -503,15 +535,17 @@ fn pre_install_cleanup() {
|
||||
let _ = Command::new("launchctl")
|
||||
.args(["bootout", &format!("gui/{uid}/ai.openclaw.gateway")])
|
||||
.output();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
let _ = Command::new("pkill")
|
||||
.args(["-f", "openclaw.*gateway"])
|
||||
.output();
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
}
|
||||
|
||||
// 2. 清理 npm 全局 bin 目录下的 openclaw 残留文件(Windows EEXIST 根因)
|
||||
// 3. 清理 npm 全局 bin 目录下的 openclaw 残留文件(Windows EEXIST 根因)
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if let Some(npm_bin) = npm_global_bin_dir() {
|
||||
@@ -3706,7 +3740,45 @@ async fn upgrade_openclaw_inner(
|
||||
"upgrade-log",
|
||||
format!("清理 standalone 残留: {}", sa_dir.display()),
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&sa_dir);
|
||||
|
||||
// Windows: 终止占用该目录的 node.exe 进程
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let dir_str = sa_dir.to_string_lossy().to_lowercase();
|
||||
if let Ok(output) = Command::new("wmic")
|
||||
.args(["process", "where", &format!("ExecutablePath like '%{}%'", dir_str.replace('\\', "\\\\")), "get", "ProcessId", "/format:list"])
|
||||
.output()
|
||||
{
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
for line in text.lines() {
|
||||
if let Some(pid_str) = line.strip_prefix("ProcessId=") {
|
||||
if let Ok(pid) = pid_str.trim().parse::<u32>() {
|
||||
let _ = app.emit("upgrade-log", format!("终止占用进程 PID {pid}..."));
|
||||
let _ = Command::new("taskkill").args(["/F", "/PID", &pid.to_string()]).output();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
match std::fs::remove_dir_all(&sa_dir) {
|
||||
Ok(()) => {
|
||||
let _ = app.emit("upgrade-log", "standalone 残留已清理 ✓");
|
||||
}
|
||||
Err(_) => {
|
||||
let _ = app.emit("upgrade-log", "文件被占用,等待后重试...");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
if let Err(e) = std::fs::remove_dir_all(&sa_dir) {
|
||||
let _ = app.emit(
|
||||
"upgrade-log",
|
||||
format!("⚠️ 清理 standalone 残留失败: {e}(可手动删除 {})", sa_dir.display()),
|
||||
);
|
||||
} else {
|
||||
let _ = app.emit("upgrade-log", "standalone 残留已清理(重试成功)✓");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3731,6 +3803,8 @@ async fn upgrade_openclaw_inner(
|
||||
{
|
||||
let _ = openclaw_command().args(["gateway", "stop"]).output();
|
||||
}
|
||||
// 等待旧 Gateway 进程退出
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
// 重新安装(刷新后的 PATH 会找到新二进制)
|
||||
use crate::utils::openclaw_command_async;
|
||||
let gw_out = openclaw_command_async()
|
||||
@@ -3813,6 +3887,10 @@ async fn uninstall_openclaw_inner(
|
||||
let _ = openclaw_command().args(["gateway", "uninstall"]).output();
|
||||
}
|
||||
|
||||
// 等待进程完全退出(Gateway stop 是异步的,需要等文件锁释放)
|
||||
let _ = app.emit("upgrade-log", "等待进程退出...");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
|
||||
// 3. 清理 standalone 安装(所有可能的位置)
|
||||
for sa_dir in &all_standalone_dirs() {
|
||||
if sa_dir.exists() {
|
||||
@@ -3820,13 +3898,47 @@ async fn uninstall_openclaw_inner(
|
||||
"upgrade-log",
|
||||
format!("清理 standalone 安装: {}", sa_dir.display()),
|
||||
);
|
||||
if let Err(e) = std::fs::remove_dir_all(sa_dir) {
|
||||
let _ = app.emit(
|
||||
"upgrade-log",
|
||||
format!("⚠️ 清理 standalone 失败: {e}(可能需要管理员权限)"),
|
||||
);
|
||||
} else {
|
||||
let _ = app.emit("upgrade-log", "standalone 安装已清理 ✓");
|
||||
|
||||
// Windows: 先尝试终止占用该目录的 node.exe 进程
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
let dir_str = sa_dir.to_string_lossy().to_lowercase();
|
||||
if let Ok(output) = Command::new("wmic")
|
||||
.args(["process", "where", &format!("ExecutablePath like '%{}%'", dir_str.replace('\\', "\\\\")), "get", "ProcessId", "/format:list"])
|
||||
.output()
|
||||
{
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
for line in text.lines() {
|
||||
if let Some(pid_str) = line.strip_prefix("ProcessId=") {
|
||||
if let Ok(pid) = pid_str.trim().parse::<u32>() {
|
||||
let _ = app.emit("upgrade-log", format!("终止占用进程 PID {pid}..."));
|
||||
let _ = Command::new("taskkill").args(["/F", "/PID", &pid.to_string()]).output();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 短暂等待进程退出
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
|
||||
// 尝试删除,失败则重试一次
|
||||
match std::fs::remove_dir_all(sa_dir) {
|
||||
Ok(()) => {
|
||||
let _ = app.emit("upgrade-log", "standalone 安装已清理 ✓");
|
||||
}
|
||||
Err(_) => {
|
||||
// 重试:等待后再删一次
|
||||
let _ = app.emit("upgrade-log", "文件被占用,等待后重试...");
|
||||
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
|
||||
if let Err(e) = std::fs::remove_dir_all(sa_dir) {
|
||||
let _ = app.emit(
|
||||
"upgrade-log",
|
||||
format!("⚠️ 清理 standalone 失败: {e}(可手动删除 {})", sa_dir.display()),
|
||||
);
|
||||
} else {
|
||||
let _ = app.emit("upgrade-log", "standalone 安装已清理(重试成功)✓");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,7 @@ export function showUpgradeModal(title) {
|
||||
let _onClose = null
|
||||
let _finished = false
|
||||
let _taskBar = null
|
||||
let _progressLabels = null
|
||||
|
||||
// 重新打开弹窗(从任务状态栏点击时)
|
||||
function reopenModal() {
|
||||
@@ -275,13 +276,15 @@ export function showUpgradeModal(title) {
|
||||
logBox.scrollTop = logBox.scrollHeight
|
||||
},
|
||||
getLogText() { return _logLines.join('\n') },
|
||||
setProgressLabels(labels) { _progressLabels = labels },
|
||||
setProgress(pct) {
|
||||
fill.style.width = pct + '%'
|
||||
const labels = _progressLabels || {}
|
||||
let statusText
|
||||
if (pct >= 100) statusText = t('common.completed')
|
||||
else if (pct >= 75) statusText = t('common.installingProgress')
|
||||
else if (pct >= 30) statusText = t('common.downloadingDependencies')
|
||||
else statusText = t('common.preparing')
|
||||
if (pct >= 100) statusText = labels.done || t('common.completed')
|
||||
else if (pct >= 75) statusText = labels.installing || t('common.installingProgress')
|
||||
else if (pct >= 30) statusText = labels.downloading || t('common.downloadingDependencies')
|
||||
else statusText = labels.preparing || t('common.preparing')
|
||||
text.textContent = statusText
|
||||
updateTaskBar(statusText)
|
||||
},
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Dienste werden gestoppt...",
|
||||
"uninstallRemoving": "Komponenten werden entfernt...",
|
||||
"uninstallCleaning": "Dateien werden bereinigt...",
|
||||
"versionLabel": "Version",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1786,6 +1786,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Stopping services...",
|
||||
"uninstallRemoving": "Removing components...",
|
||||
"uninstallCleaning": "Cleaning up files...",
|
||||
"versionLabel": "Version",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Deteniendo servicios...",
|
||||
"uninstallRemoving": "Eliminando componentes...",
|
||||
"uninstallCleaning": "Limpiando archivos...",
|
||||
"versionLabel": "Versión",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Arrêt des services...",
|
||||
"uninstallRemoving": "Suppression des composants...",
|
||||
"uninstallCleaning": "Nettoyage des fichiers...",
|
||||
"versionLabel": "Version",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "サービスを停止中...",
|
||||
"uninstallRemoving": "コンポーネントを削除中...",
|
||||
"uninstallCleaning": "ファイルをクリーンアップ中...",
|
||||
"versionLabel": "バージョン",
|
||||
"selectVersion": "バージョンを選択",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "서비스 중지 중...",
|
||||
"uninstallRemoving": "구성 요소 제거 중...",
|
||||
"uninstallCleaning": "파일 정리 중...",
|
||||
"versionLabel": "버전",
|
||||
"selectVersion": "버전 선택",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { _ } from '../helper.js'
|
||||
|
||||
export default {
|
||||
title: _('系统诊断', 'System Diagnostics', '系統诊斷', 'システム診断', '시스템 진단', 'Chẩn đoán hệ thống', 'Diagnóstico del sistema', 'Diagnóstico do sistema', 'Диагностика системы', 'Diagnostic système', 'Systemdiagnose'),
|
||||
desc: _('全面检测系统状态,快速定位问题', 'Comprehensive system status check, quickly locate issues', '全面檢測系統狀態,快速定位問題', 'WebSocket 接続診断とネットワークデバッグ', 'WebSocket 연결 진단 및 네트워크 디버그', 'Chẩn đoán kết nối WebSocket và gỡ lỗi mạng', 'Diagnóstico de conexión WebSocket y depuración de red', 'Diagnóstico de conexão WebSocket e depuração de rede', 'Диагностика WebSocket и отладка сети', 'Diagnostic de connexion WebSocket et débogage réseau', 'WebSocket-Verbindungsdiagnose und Netzwerk-Debugging'),
|
||||
title: _('检测与修复', 'Check & Repair', '檢測與修復', '検出と修復', '검사 및 수리', 'Kiểm tra & Sửa chữa', 'Verificar y reparar', 'Verificar e reparar', 'Проверка и ремонт', 'Vérifier et réparer', 'Prüfen & Reparieren'),
|
||||
desc: _('全面检测系统状态,快速定位并修复问题', 'Comprehensive system check, quickly locate and fix issues', '全面檢測系統狀態,快速定位並修復問題', 'システム状態を全面検出し、問題を迅速に修復', '시스템 상태를 종합적으로 검사하고 문제를 수리', 'Kiểm tra toàn diện hệ thống, nhanh chóng sửa chữa', 'Verificación completa del sistema, localización y reparación rápida', 'Verificação completa do sistema, localização e reparo rápido', 'Полная проверка системы, быстрая диагностика и ремонт', 'Vérification complète du système, localisation et réparation rapides', 'Umfassende Systemprüfung, schnelle Fehlersuche und Reparatur'),
|
||||
sectionAppState: _('应用状态', 'App State', '應用狀態'),
|
||||
sectionWs: _('WebSocket 连接', 'WebSocket Connection', 'WebSocket 連線'),
|
||||
sectionNode: _('Node.js 环境', 'Node.js Environment', 'Node.js 環境'),
|
||||
@@ -19,6 +19,8 @@ export default {
|
||||
btnTestWs: _('测试 WebSocket', 'Test WebSocket', '測試 WebSocket'),
|
||||
btnNetworkLog: _('网络日志', 'Network Log', '網路日誌'),
|
||||
btnFixPairing: _('一键修复配对', 'Fix Pairing', '一鍵修复配对'),
|
||||
btnConnDiag: _('连接诊断', 'Connection Diagnosis', '連線診斷', '接続診断', '연결 진단', 'Chẩn đoán kết nối', 'Diagnóstico de conexión', 'Diagnóstico de conexão', 'Диагностика подключения', 'Diagnostic de connexion', 'Verbindungsdiagnose'),
|
||||
connDiagTitle: _('Gateway 连接诊断', 'Gateway Connection Diagnosis', 'Gateway 連線診斷', 'Gateway 接続診断', 'Gateway 연결 진단', 'Chẩn đoán kết nối Gateway', 'Diagnóstico de conexión Gateway', 'Diagnóstico de conexão Gateway', 'Диагностика подключения Gateway', 'Diagnostic de connexion Gateway', 'Gateway-Verbindungsdiagnose'),
|
||||
btnClear: _('清空', 'Clear'),
|
||||
systemOk: _('系统正常', 'System OK', '系統正常'),
|
||||
issuesFound: _('发现问题', 'Issues Found', '發現問題'),
|
||||
@@ -146,4 +148,42 @@ export default {
|
||||
fixOriginRejected1008: _('连接被拒绝 (1008) - Gateway 拒绝了当前 origin', 'Connection rejected (1008) - Gateway rejected current origin', '連線被拒絕 (1008) - Gateway 拒絕了目前 origin', '接続拒否 (1008) - Gateway が現在の origin を拒否しました'),
|
||||
fixRetryHint: _('该问题应已被本次修复流程处理,请再次点击「一键修复配对」', 'This issue should have been fixed, please click "Fix Pairing" again', '該問題應已被本次修复流程處理,請再次点擊「一鍵修复配对」', 'この問題は今回の修復で処理されたはずです。もう一度「ワンクリック修復」をクリックしてください'),
|
||||
fixFailed: _('修复失败', 'Fix failed', '修复失敗', '修復失敗', '수리 실패', 'Sửa chữa thất bại', 'Reparación fallida', 'Reparo falhou', 'Исправление не удалось', 'Réparation échouée', 'Reparatur fehlgeschlagen'),
|
||||
// 扫描风格 UI
|
||||
scanning: _('检测中...', 'Scanning...', '檢測中...', 'スキャン中...', '검사 중...', 'Đang quét...', 'Escaneando...', 'Escaneando...', 'Сканирование...', 'Analyse...', 'Wird gescannt...'),
|
||||
startScan: _('开始检测', 'Start Scan', '開始檢測', 'スキャン開始', '검사 시작', 'Bắt đầu quét', 'Iniciar escaneo', 'Iniciar verificação', 'Начать проверку', 'Lancer l\'analyse', 'Scan starten'),
|
||||
scanningHint: _('正在检测系统各项状态...', 'Checking system status...', '正在檢測系統各項狀態...', 'システム状態を確認中...', '시스템 상태 확인 중...', 'Đang kiểm tra trạng thái...', 'Verificando estado del sistema...', 'Verificando status do sistema...', 'Проверка состояния системы...', 'Vérification de l\'état du système...', 'Systemstatus wird geprüft...'),
|
||||
clickToScan: _('点击盾牌开始检测', 'Click to start scan', '點擊盾牌開始檢測', 'クリックしてスキャン開始', '클릭하여 검사 시작', 'Nhấn để bắt đầu quét', 'Haz clic para escanear', 'Clique para verificar', 'Нажмите для проверки', 'Cliquez pour analyser', 'Klicken Sie zum Scannen'),
|
||||
clickRescan: _('点击重新检测', 'Click to rescan', '點擊重新檢測', 'クリックして再スキャン', '클릭하여 재검사', 'Nhấn để quét lại', 'Clic para reescanear', 'Clique para reverificar', 'Нажмите для повторной проверки', 'Cliquez pour réanalyser', 'Klicken zum erneuten Scannen'),
|
||||
allHealthy: _('一切正常', 'All Healthy', '一切正常', 'すべて正常', '모두 정상', 'Tất cả bình thường', 'Todo correcto', 'Tudo normal', 'Всё в порядке', 'Tout est normal', 'Alles in Ordnung'),
|
||||
allHealthyDesc: _('已检测 {count} 项,全部通过', 'Checked {count} items, all passed', '已檢測 {count} 項,全部通過', '{count} 項目をチェック、すべて通過', '{count}개 항목 검사, 모두 통과', 'Đã kiểm tra {count} mục, tất cả đạt', '{count} elementos verificados, todos correctos', '{count} itens verificados, todos aprovados', 'Проверено {count} элементов, все в норме', '{count} éléments vérifiés, tous réussis', '{count} Elemente geprüft, alle bestanden'),
|
||||
issuesCount: _('发现 {count} 个问题', '{count} issue(s) found', '發現 {count} 個問題', '{count} 件の問題が見つかりました', '{count}개 문제 발견', 'Phát hiện {count} vấn đề', '{count} problema(s) encontrado(s)', '{count} problema(s) encontrado(s)', 'Обнаружено проблем: {count}', '{count} problème(s) trouvé(s)', '{count} Problem(e) gefunden'),
|
||||
issuesCountDesc: _('点击下方「一键修复」尝试自动修复', 'Click "One-Click Fix" below to auto-fix', '點擊下方「一鍵修復」嘗試自動修復', '下の「ワンクリック修復」で自動修復', '아래 "원클릭 수리"를 클릭하여 자동 수리', 'Nhấn "Sửa nhanh" bên dưới', 'Haz clic en "Reparar" abajo', 'Clique em "Reparar" abaixo', 'Нажмите «Исправить» ниже', 'Cliquez sur « Réparer » ci-dessous', 'Klicken Sie unten auf „Reparieren"'),
|
||||
warningsOnly: _('有 {count} 项建议', '{count} suggestion(s)', '有 {count} 項建議', '{count} 件の提案', '{count}개 제안', 'Có {count} gợi ý', '{count} sugerencia(s)', '{count} sugestão(ões)', 'Предложений: {count}', '{count} suggestion(s)', '{count} Vorschlag/Vorschläge'),
|
||||
warningsOnlyDesc: _('核心功能正常,以下为可选优化建议', 'Core functions are healthy, suggestions below are optional', '核心功能正常,以下為可選優化建議', 'コア機能は正常です。以下は任意の最適化提案です', '핵심 기능 정상, 아래는 선택적 최적화 제안입니다', 'Chức năng chính bình thường, bên dưới là gợi ý tùy chọn', 'Funciones principales OK, sugerencias opcionales abajo', 'Funções principais OK, sugestões opcionais abaixo', 'Основные функции в норме, ниже — необязательные предложения', 'Fonctions principales OK, suggestions facultatives ci-dessous', 'Kernfunktionen OK, optionale Vorschläge unten'),
|
||||
oneClickFix: _('一键修复', 'One-Click Fix', '一鍵修復', 'ワンクリック修復', '원클릭 수리', 'Sửa nhanh', 'Reparar', 'Reparar', 'Исправить', 'Réparer', 'Reparieren'),
|
||||
fixingNow: _('修复中...', 'Fixing...', '修復中...', '修復中...', '수리 중...', 'Đang sửa...', 'Reparando...', 'Reparando...', 'Исправление...', 'Réparation...', 'Reparatur...'),
|
||||
fixDone: _('修复完成,正在重新检测', 'Fix complete, rescanning', '修復完成,正在重新檢測', '修復完了、再スキャン中', '수리 완료, 재검사 중', 'Sửa xong, đang quét lại', 'Reparación completa, reescaneando', 'Reparo completo, reverificando', 'Исправление завершено, повторная проверка', 'Réparation terminée, réanalyse', 'Reparatur abgeschlossen, erneuter Scan'),
|
||||
fixStepPairing: _('正在配对设备...', 'Pairing device...', '正在配對設備...', 'デバイスをペアリング中...'),
|
||||
fixStepDoctor: _('正在执行自动修复...', 'Running auto-fix...', '正在執行自動修復...', '自動修復を実行中...'),
|
||||
fixStepGateway: _('正在重启 Gateway...', 'Restarting Gateway...', '正在重啟 Gateway...', 'Gateway を再起動中...'),
|
||||
fixStepWaiting: _('等待 Gateway 启动...', 'Waiting for Gateway to start...', '等待 Gateway 啟動...', 'Gateway の起動を待機中...'),
|
||||
fixStepWebSocket: _('正在重连 WebSocket...', 'Reconnecting WebSocket...', '正在重連 WebSocket...', 'WebSocket を再接続中...'),
|
||||
advancedTools: _('高级工具', 'Advanced Tools', '進階工具', '詳細ツール', '고급 도구', 'Công cụ nâng cao', 'Herramientas avanzadas', 'Ferramentas avançadas', 'Дополнительные инструменты', 'Outils avancés', 'Erweiterte Werkzeuge'),
|
||||
scanCliInstall: _('OpenClaw 安装', 'OpenClaw Installation', 'OpenClaw 安裝', 'OpenClaw インストール', 'OpenClaw 설치', 'Cài đặt OpenClaw', 'Instalación de OpenClaw', 'Instalação do OpenClaw', 'Установка OpenClaw', 'Installation OpenClaw', 'OpenClaw-Installation'),
|
||||
scanCliMissing: _('未安装 OpenClaw CLI', 'OpenClaw CLI not installed', '未安裝 OpenClaw CLI', 'OpenClaw CLI 未インストール', 'OpenClaw CLI 미설치', 'Chưa cài OpenClaw CLI', 'OpenClaw CLI no instalado', 'OpenClaw CLI não instalado', 'OpenClaw CLI не установлен', 'OpenClaw CLI non installé', 'OpenClaw CLI nicht installiert'),
|
||||
scanNodeMissing: _('未安装 Node.js', 'Node.js not installed', '未安裝 Node.js', 'Node.js 未インストール', 'Node.js 미설치', 'Chưa cài Node.js', 'Node.js no instalado', 'Node.js não instalado', 'Node.js не установлен', 'Node.js non installé', 'Node.js nicht installiert'),
|
||||
scanConfig: _('配置文件', 'Config File', '設定檔案', '設定ファイル', '설정 파일', 'Tệp cấu hình', 'Archivo de config', 'Arquivo de config', 'Файл конфигурации', 'Fichier de config', 'Konfigurationsdatei'),
|
||||
scanConfigMissing: _('配置文件缺失或损坏', 'Config file missing or corrupted', '設定檔案缺失或損壞', '設定ファイルが見つからないか破損', '설정 파일 누락 또는 손상', 'Tệp cấu hình bị thiếu hoặc hỏng', 'Archivo de config faltante o dañado', 'Arquivo de config ausente ou corrompido', 'Файл конфигурации отсутствует или повреждён', 'Fichier de config manquant ou corrompu', 'Konfigurationsdatei fehlt oder ist beschädigt'),
|
||||
scanGwStopped: _('Gateway 未运行', 'Gateway not running', 'Gateway 未執行', 'Gateway 未実行', 'Gateway 미실행', 'Gateway chưa chạy', 'Gateway no ejecutándose', 'Gateway não está rodando', 'Gateway не запущен', 'Gateway non démarré', 'Gateway nicht gestartet'),
|
||||
scanWsDown: _('WebSocket 未连接', 'WebSocket not connected', 'WebSocket 未連線', 'WebSocket 未接続', 'WebSocket 미연결', 'WebSocket chưa kết nối', 'WebSocket no conectado', 'WebSocket não conectado', 'WebSocket не подключен', 'WebSocket non connecté', 'WebSocket nicht verbunden'),
|
||||
scanToken: _('认证令牌', 'Auth Token', '認證令牌', '認証トークン', '인증 토큰', 'Token xác thực', 'Token de auth', 'Token de auth', 'Токен авторизации', 'Jeton d\'auth', 'Auth-Token'),
|
||||
scanTokenMissing: _('Token 未设置', 'Token not set', 'Token 未設定', 'トークン未設定', '토큰 미설정', 'Token chưa thiết lập', 'Token no configurado', 'Token não configurado', 'Токен не задан', 'Jeton non défini', 'Token nicht gesetzt'),
|
||||
scanDeviceKey: _('设备密钥', 'Device Key', '設備金鑰', 'デバイスキー', '기기 키', 'Khóa thiết bị', 'Clave de dispositivo', 'Chave de dispositivo', 'Ключ устройства', 'Clé d\'appareil', 'Geräteschlüssel'),
|
||||
scanDeviceKeyFail: _('设备密钥生成失败', 'Device key generation failed', '設備金鑰生成失敗', 'デバイスキー生成失敗', '기기 키 생성 실패', 'Tạo khóa thiết bị thất bại', 'Generación de clave fallida', 'Geração de chave falhou', 'Ошибка генерации ключа', 'Échec de génération de clé', 'Schlüsselgenerierung fehlgeschlagen'),
|
||||
scanVersion: _('版本状态', 'Version Status', '版本狀態', 'バージョン状態', '버전 상태', 'Trạng thái phiên bản', 'Estado de versión', 'Status da versão', 'Статус версии', 'État de la version', 'Versionsstatus'),
|
||||
scanVersionAhead: _('当前 {current},推荐 {recommended}', 'Current {current}, recommended {recommended}', '目前 {current},推薦 {recommended}', '現在 {current}、推奨 {recommended}', '현재 {current}, 권장 {recommended}', 'Hiện tại {current}, khuyến nghị {recommended}', 'Actual {current}, recomendado {recommended}', 'Atual {current}, recomendado {recommended}', 'Текущая {current}, рекомендуемая {recommended}', 'Actuelle {current}, recommandée {recommended}', 'Aktuell {current}, empfohlen {recommended}'),
|
||||
scanVersionUpdate: _('可更新到 {version}', 'Update available: {version}', '可更新到 {version}', '{version} に更新可能', '{version}으로 업데이트 가능', 'Có thể cập nhật lên {version}', 'Actualización disponible: {version}', 'Atualização disponível: {version}', 'Доступно обновление: {version}', 'Mise à jour disponible: {version}', 'Update verfügbar: {version}'),
|
||||
scanConnDiag: _('连接诊断', 'Connection Check', '連線診斷', '接続チェック', '연결 진단', 'Kiểm tra kết nối', 'Diagnóstico de conexión', 'Diagnóstico de conexão', 'Проверка подключения', 'Diagnostic de connexion', 'Verbindungsprüfung'),
|
||||
scanConnOk: _('所有连接检查通过', 'All connection checks passed', '所有連線檢查通過', 'すべての接続チェック通過', '모든 연결 검사 통과', 'Tất cả kiểm tra kết nối đã đạt', 'Todas las comprobaciones pasaron', 'Todas as verificações aprovadas', 'Все проверки подключения пройдены', 'Toutes les vérifications réussies', 'Alle Verbindungsprüfungen bestanden'),
|
||||
scanError: _('检测异常', 'Scan Error', '檢測異常', 'スキャンエラー', '검사 오류', 'Lỗi quét', 'Error de escaneo', 'Erro de verificação', 'Ошибка проверки', 'Erreur d\'analyse', 'Scan-Fehler'),
|
||||
}
|
||||
|
||||
@@ -68,4 +68,5 @@ export default {
|
||||
configUnavailable: _('无法获取在线配置快照,请稍后重试', 'Unable to load live config snapshot, please retry'),
|
||||
actionRunning: _('处理中...', 'Working...'),
|
||||
pluginUnsupported: _('当前记忆插件可能不支持 Dreaming 配置', 'The current memory plugin may not support Dreaming config'),
|
||||
rpcUnsupported: _('当前 Gateway 版本不支持部分 Dreaming 操作,请升级到 OpenClaw 2026.4.9+', 'Current Gateway version does not support some Dreaming operations, please upgrade to OpenClaw 2026.4.9+'),
|
||||
}
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Parando serviços...",
|
||||
"uninstallRemoving": "Removendo componentes...",
|
||||
"uninstallCleaning": "Limpando arquivos...",
|
||||
"versionLabel": "Versão",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Остановка служб...",
|
||||
"uninstallRemoving": "Удаление компонентов...",
|
||||
"uninstallCleaning": "Очистка файлов...",
|
||||
"versionLabel": "Версия",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1690,6 +1690,9 @@
|
||||
"uninstallDone": "Uninstall complete",
|
||||
"uninstallFailed": "Uninstall failed: ",
|
||||
"uninstallTaskStarted": "Background uninstall task started...",
|
||||
"uninstallStopping": "Stopping services...",
|
||||
"uninstallRemoving": "Removing components...",
|
||||
"uninstallCleaning": "Cleaning up files...",
|
||||
"versionLabel": "Phiên bản",
|
||||
"selectVersion": "Select Version",
|
||||
"versionPickerHint": "Recommended to use the stable version bound to this panel. Switching to other versions (especially preview/latest) requires manual compatibility verification. Submit an issue if you want latest version support.",
|
||||
|
||||
@@ -1811,6 +1811,9 @@
|
||||
"uninstallDone": "卸载完成",
|
||||
"uninstallFailed": "卸载失败: ",
|
||||
"uninstallTaskStarted": "后台卸载任务已启动...",
|
||||
"uninstallStopping": "正在停止服务...",
|
||||
"uninstallRemoving": "正在卸载组件...",
|
||||
"uninstallCleaning": "正在清理文件...",
|
||||
"versionLabel": "版本",
|
||||
"selectVersion": "选择版本号",
|
||||
"versionPickerHint": "默认建议使用当前面板绑定的推荐稳定版。若手动切换到其它版本,尤其是预览版/最新版,请自行验证兼容性;如果你希望面板优先适配最新版功能,欢迎提交 issue。",
|
||||
|
||||
@@ -1691,6 +1691,9 @@
|
||||
"uninstallDone": "卸載完成",
|
||||
"uninstallFailed": "卸載失敗: ",
|
||||
"uninstallTaskStarted": "後台卸載任務已啟動...",
|
||||
"uninstallStopping": "正在停止服務...",
|
||||
"uninstallRemoving": "正在卸載組件...",
|
||||
"uninstallCleaning": "正在清理檔案...",
|
||||
"versionLabel": "版本",
|
||||
"selectVersion": "選擇版本號",
|
||||
"versionPickerHint": "預設建議使用目前面板綁定的推薦穩定版。若手動切換到其它版本,尤其是預覽版/最新版,請自行驗證相容性;如果你希望面板優先適配最新版功能,欢迎提交 issue。",
|
||||
|
||||
@@ -146,6 +146,12 @@ async function loadData(page) {
|
||||
const confirmed = await showConfirm(t('about.confirmUninstall'))
|
||||
if (!confirmed) return
|
||||
const modal = showUpgradeModal(t('about.uninstallTitle'))
|
||||
modal.setProgressLabels({
|
||||
preparing: t('about.uninstallStopping'),
|
||||
downloading: t('about.uninstallRemoving'),
|
||||
installing: t('about.uninstallCleaning'),
|
||||
done: t('about.uninstallDone'),
|
||||
})
|
||||
modal.onClose(() => loadData(page))
|
||||
modal.appendLog(t('about.uninstallStarting'))
|
||||
let unlistenLog, unlistenProgress, unlistenDone, unlistenError
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,8 @@ function createState() {
|
||||
toggleBlockedReason: '',
|
||||
diaryPath: 'DREAMS.md',
|
||||
diaryContent: null,
|
||||
diarySupported: true,
|
||||
actionsSupported: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,13 +187,13 @@ async function ensureGatewayReady(page) {
|
||||
return false
|
||||
}
|
||||
|
||||
export async function render() {
|
||||
export function render() {
|
||||
const page = document.createElement('div')
|
||||
page.className = 'page'
|
||||
_page = page
|
||||
_state = createState()
|
||||
renderPage(page)
|
||||
await loadAll(page)
|
||||
setTimeout(() => loadAll(page), 0)
|
||||
return page
|
||||
}
|
||||
|
||||
@@ -226,18 +228,26 @@ async function loadAll(page) {
|
||||
|
||||
if (statusResult.status === 'fulfilled') {
|
||||
_state.status = normalizeStatus(statusResult.value?.dreaming ?? statusResult.value)
|
||||
_state.actionsSupported = true
|
||||
} else {
|
||||
_state.status = null
|
||||
_state.error = errorMessage(statusResult.reason)
|
||||
_state.unsupported = isUnsupportedError(statusResult.reason)
|
||||
_state.actionsSupported = !_state.unsupported
|
||||
}
|
||||
|
||||
if (diaryResult.status === 'fulfilled') {
|
||||
const payload = diaryResult.value || {}
|
||||
_state.diaryPath = normalizeString(payload.path || 'DREAMS.md')
|
||||
_state.diaryContent = payload.found === false ? null : (typeof payload.content === 'string' ? payload.content : null)
|
||||
} else if (!_state.error) {
|
||||
_state.error = errorMessage(diaryResult.reason)
|
||||
_state.diarySupported = true
|
||||
} else {
|
||||
_state.diarySupported = !isUnsupportedError(diaryResult.reason)
|
||||
if (!_state.diarySupported) {
|
||||
_state.diaryContent = null
|
||||
} else if (!_state.error) {
|
||||
_state.error = errorMessage(diaryResult.reason)
|
||||
}
|
||||
}
|
||||
|
||||
if (configResult.status === 'fulfilled') {
|
||||
@@ -288,7 +298,11 @@ async function runAction(method, successText, options = {}) {
|
||||
toast(successText, 'success')
|
||||
await loadAll(_page)
|
||||
} catch (e) {
|
||||
toast(`${t('dreaming.loadFailed')}: ${e?.message || e}`, 'error')
|
||||
if (isUnsupportedError(e)) {
|
||||
toast(t('dreaming.rpcUnsupported'), 'warning')
|
||||
} else {
|
||||
toast(`${t('dreaming.loadFailed')}: ${e?.message || e}`, 'error')
|
||||
}
|
||||
_state.actionLoading = false
|
||||
renderPage(_page)
|
||||
}
|
||||
@@ -338,11 +352,12 @@ async function toggleDreaming() {
|
||||
toast(!enabled ? t('dreaming.enabled') : t('dreaming.disabled'), 'success')
|
||||
await loadAll(_page)
|
||||
} catch (e) {
|
||||
const message = errorMessage(e)
|
||||
if (isUnsupportedError(e) && !_state.toggleBlockedReason) {
|
||||
_state.toggleBlockedReason = t('dreaming.pluginUnsupported')
|
||||
if (isUnsupportedError(e)) {
|
||||
if (!_state.toggleBlockedReason) _state.toggleBlockedReason = t('dreaming.pluginUnsupported')
|
||||
toast(t('dreaming.rpcUnsupported'), 'warning')
|
||||
} else {
|
||||
toast(`${t('dreaming.toggleFailed')}: ${errorMessage(e)}`, 'error')
|
||||
}
|
||||
toast(`${t('dreaming.toggleFailed')}: ${message}`, 'error')
|
||||
_state.actionLoading = false
|
||||
renderPage(_page)
|
||||
}
|
||||
@@ -414,21 +429,24 @@ function renderEntries(title, entries) {
|
||||
|
||||
function renderActionButtons(enabled, disabledAttr) {
|
||||
const toggleText = enabled ? t('dreaming.toggleOff') : t('dreaming.toggleOn')
|
||||
const actionsDisabled = !_state.actionsSupported ? 'disabled title="' + esc(t('dreaming.rpcUnsupported')) + '"' : disabledAttr
|
||||
const diaryDisabled = !_state.diarySupported ? 'disabled title="' + esc(t('dreaming.rpcUnsupported')) + '"' : disabledAttr
|
||||
return `
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap">
|
||||
<button class="btn btn-sm ${enabled ? 'btn-warning' : 'btn-primary'}" id="btn-dreaming-toggle" ${disabledAttr}>${esc(_state.actionLoading ? t('dreaming.actionRunning') : toggleText)}</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-dreaming-backfill" ${disabledAttr}>${esc(t('dreaming.backfill'))}</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-dreaming-reset-diary" ${disabledAttr}>${esc(t('dreaming.resetDiary'))}</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-dreaming-clear-grounded" ${disabledAttr}>${esc(t('dreaming.clearGrounded'))}</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-dreaming-backfill" ${diaryDisabled}>${esc(t('dreaming.backfill'))}</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-dreaming-reset-diary" ${diaryDisabled}>${esc(t('dreaming.resetDiary'))}</button>
|
||||
<button class="btn btn-sm btn-secondary" id="btn-dreaming-clear-grounded" ${actionsDisabled}>${esc(t('dreaming.clearGrounded'))}</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
function renderStatusHints() {
|
||||
return `
|
||||
${_state.toggleBlockedReason ? `<div class="form-hint" style="margin-top:10px">${esc(_state.toggleBlockedReason)}</div>` : ''}
|
||||
${_state.error && !_state.unsupported ? `<div style="margin-top:12px;color:var(--warning)">${esc(_state.error)}</div>` : ''}
|
||||
`
|
||||
const hints = []
|
||||
if (_state.toggleBlockedReason) hints.push(`<div class="form-hint" style="margin-top:10px">${esc(_state.toggleBlockedReason)}</div>`)
|
||||
if (!_state.diarySupported || !_state.actionsSupported) hints.push(`<div class="form-hint" style="margin-top:8px;color:var(--text-tertiary)">${esc(t('dreaming.rpcUnsupported'))}</div>`)
|
||||
if (_state.error && !_state.unsupported) hints.push(`<div style="margin-top:12px;color:var(--warning)">${esc(_state.error)}</div>`)
|
||||
return hints.join('')
|
||||
}
|
||||
|
||||
function renderViewTabs() {
|
||||
@@ -442,25 +460,28 @@ function renderViewTabs() {
|
||||
|
||||
function renderDreamLane(title, subtitle, entries, accent) {
|
||||
const tones = {
|
||||
violet: { border: 'rgba(168,85,247,0.35)', bg: 'linear-gradient(180deg, rgba(91,33,182,0.22), rgba(30,41,59,0.6))', glow: 'rgba(168,85,247,0.22)' },
|
||||
cyan: { border: 'rgba(34,211,238,0.35)', bg: 'linear-gradient(180deg, rgba(8,145,178,0.18), rgba(15,23,42,0.58))', glow: 'rgba(34,211,238,0.18)' },
|
||||
amber: { border: 'rgba(251,191,36,0.35)', bg: 'linear-gradient(180deg, rgba(180,83,9,0.18), rgba(30,41,59,0.58))', glow: 'rgba(251,191,36,0.18)' },
|
||||
violet: { dot: '#a855f7', border: 'var(--accent, #6366f1)' },
|
||||
cyan: { dot: '#22d3ee', border: 'var(--success, #22c55e)' },
|
||||
amber: { dot: '#fbbf24', border: 'var(--warning, #f59e0b)' },
|
||||
}
|
||||
const tone = tones[accent] || tones.violet
|
||||
const items = entries.length
|
||||
? entries.slice(0, 4).map((entry, idx) => `
|
||||
<div style="display:flex;gap:10px;align-items:flex-start;padding:10px 0;border-bottom:${idx === entries.slice(0, 4).length - 1 ? 'none' : '1px solid rgba(255,255,255,0.08)'}">
|
||||
<div style="width:9px;height:9px;border-radius:999px;background:${tone.border};box-shadow:0 0 12px ${tone.glow};margin-top:6px;flex-shrink:0"></div>
|
||||
<div style="display:flex;gap:10px;align-items:flex-start;padding:10px 0;${idx < Math.min(entries.length, 4) - 1 ? 'border-bottom:1px solid var(--border-primary)' : ''}">
|
||||
<div style="width:8px;height:8px;border-radius:999px;background:${tone.dot};margin-top:6px;flex-shrink:0"></div>
|
||||
<div style="min-width:0">
|
||||
<div style="font-size:13px;line-height:1.6;color:var(--text-primary)">${esc(entry.snippet || '(empty)')}</div>
|
||||
<div style="margin-top:6px;font-size:12px;color:var(--text-tertiary)">${esc(entry.path)}${entry.startLine ? ':' + entry.startLine : ''}</div>
|
||||
<div style="margin-top:4px;font-size:12px;color:var(--text-tertiary)">${esc(entry.path)}${entry.startLine ? ':' + entry.startLine : ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')
|
||||
: `<div class="form-hint">${esc(t('dreaming.noEntries'))}</div>`
|
||||
return `
|
||||
<div class="config-section" style="margin:0;border:1px solid ${tone.border};background:${tone.bg};backdrop-filter:blur(6px)">
|
||||
<div class="config-section-title">${esc(title)}</div>
|
||||
<div class="config-section" style="margin:0;border-left:3px solid ${tone.border}">
|
||||
<div class="config-section-title" style="display:flex;align-items:center;gap:8px">
|
||||
<span>${esc(title)}</span>
|
||||
<span class="badge" style="font-size:11px">${entries.length}</span>
|
||||
</div>
|
||||
<div class="form-hint" style="margin-bottom:8px">${esc(subtitle)}</div>
|
||||
${items}
|
||||
</div>
|
||||
@@ -468,29 +489,63 @@ function renderDreamLane(title, subtitle, entries, accent) {
|
||||
}
|
||||
|
||||
function renderSceneView(status, enabled, heroText, disabledAttr, nextRun) {
|
||||
const stars = [
|
||||
{ top: '14%', left: '8%', size: 4, opacity: 0.8 },
|
||||
{ top: '22%', left: '30%', size: 6, opacity: 0.55 },
|
||||
{ top: '18%', left: '64%', size: 5, opacity: 0.75 },
|
||||
{ top: '32%', left: '74%', size: 3, opacity: 0.9 },
|
||||
{ top: '58%', left: '18%', size: 5, opacity: 0.65 },
|
||||
{ top: '66%', left: '54%', size: 4, opacity: 0.7 },
|
||||
{ top: '72%', left: '82%', size: 6, opacity: 0.5 },
|
||||
const STARS = [
|
||||
{ top: 8, left: 15, size: 3, delay: 0 },
|
||||
{ top: 12, left: 72, size: 2, delay: 1.4 },
|
||||
{ top: 22, left: 35, size: 3, delay: 0.6 },
|
||||
{ top: 18, left: 88, size: 2, delay: 2.1 },
|
||||
{ top: 35, left: 8, size: 2, delay: 0.9 },
|
||||
{ top: 45, left: 92, size: 2, delay: 1.7 },
|
||||
{ top: 55, left: 25, size: 3, delay: 2.5 },
|
||||
{ top: 65, left: 78, size: 2, delay: 0.3 },
|
||||
{ top: 75, left: 45, size: 2, delay: 1.1 },
|
||||
{ top: 82, left: 60, size: 3, delay: 1.8 },
|
||||
{ top: 30, left: 55, size: 2, delay: 0.4 },
|
||||
{ top: 88, left: 18, size: 2, delay: 2.3 },
|
||||
]
|
||||
const starsHtml = STARS.map(s => `<div class="dream-star" style="top:${s.top}%;left:${s.left}%;width:${s.size}px;height:${s.size}px;animation-delay:${s.delay}s"></div>`).join('')
|
||||
|
||||
return `
|
||||
<div style="position:relative;overflow:hidden;border-radius:22px;padding:24px;background:radial-gradient(circle at 20% 10%, rgba(139,92,246,0.42), rgba(15,23,42,0.94) 52%), linear-gradient(135deg, #0f172a 0%, #1e1b4b 55%, #312e81 100%);color:#e2e8f0;box-shadow:0 24px 64px rgba(15,23,42,0.35);margin-bottom:var(--space-lg)">
|
||||
${stars.map((star) => `<div style="position:absolute;top:${star.top};left:${star.left};width:${star.size}px;height:${star.size}px;border-radius:999px;background:rgba(255,255,255,${star.opacity});box-shadow:0 0 16px rgba(255,255,255,0.28)"></div>`).join('')}
|
||||
<div style="position:absolute;top:22px;right:28px;width:118px;height:118px;border-radius:999px;background:radial-gradient(circle at 35% 35%, rgba(255,255,255,0.98), rgba(224,231,255,0.92) 38%, rgba(196,181,253,0.56) 62%, rgba(99,102,241,0.16) 100%);box-shadow:0 0 32px rgba(196,181,253,0.45), 0 0 88px rgba(99,102,241,0.18)"></div>
|
||||
<style>
|
||||
@keyframes dream-twinkle { 0%,100% { opacity:.3; transform:scale(1) } 50% { opacity:1; transform:scale(1.6) } }
|
||||
@keyframes dream-float { 0%,100% { transform:translateY(0) } 50% { transform:translateY(-6px) } }
|
||||
@keyframes dream-z { 0% { opacity:0; transform:translate(0,0) scale(.6) } 30% { opacity:.7 } 100% { opacity:0; transform:translate(18px,-32px) scale(1.1) } }
|
||||
.dream-hero { position:relative; overflow:hidden; border-radius:22px; padding:28px 24px 24px; background:radial-gradient(circle at 20% 10%, rgba(139,92,246,0.42), rgba(15,23,42,0.94) 52%), linear-gradient(135deg, #0f172a 0%, #1e1b4b 55%, #312e81 100%); color:#e2e8f0; box-shadow:0 24px 64px rgba(15,23,42,0.35); margin-bottom:var(--space-lg) }
|
||||
.dream-star { position:absolute; border-radius:999px; background:rgba(255,255,255,0.85); box-shadow:0 0 12px rgba(255,255,255,0.35); animation:dream-twinkle 3s ease-in-out infinite }
|
||||
.dream-moon { position:absolute; top:22px; right:28px; width:100px; height:100px; border-radius:999px; background:radial-gradient(circle at 35% 35%, rgba(255,255,255,0.98), rgba(224,231,255,0.92) 38%, rgba(196,181,253,0.56) 62%, rgba(99,102,241,0.16) 100%); box-shadow:0 0 32px rgba(196,181,253,0.45), 0 0 88px rgba(99,102,241,0.18); animation:dream-float 6s ease-in-out infinite }
|
||||
.dream-z { position:absolute; top:28px; right:140px; font-size:16px; font-weight:700; color:rgba(196,181,253,0.6); animation:dream-z 2.5s ease-out infinite }
|
||||
.dream-z:nth-child(2) { animation-delay:.8s; font-size:13px; right:148px; top:22px }
|
||||
.dream-z:nth-child(3) { animation-delay:1.6s; font-size:20px; right:132px; top:16px }
|
||||
.dream-hero .badge { background:rgba(255,255,255,0.1); color:#e2e8f0; border-color:rgba(255,255,255,0.15) }
|
||||
.dream-hero .badge-success { background:rgba(74,222,128,0.15); color:#86efac; border-color:rgba(74,222,128,0.25) }
|
||||
.dream-hero .btn-primary { background:rgba(99,102,241,0.85) }
|
||||
.dream-hero .btn-secondary { background:rgba(255,255,255,0.08); color:#e2e8f0; border-color:rgba(255,255,255,0.15) }
|
||||
.dream-hero .btn-secondary:hover { background:rgba(255,255,255,0.14) }
|
||||
.dream-hero .btn-secondary:disabled { opacity:.4 }
|
||||
.dream-hero .btn-warning { background:rgba(251,191,36,0.2); color:#fbbf24; border-color:rgba(251,191,36,0.3) }
|
||||
.dream-hero .form-hint { color:rgba(226,232,240,0.6) }
|
||||
.dream-stats-row { position:relative; display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:12px; margin-top:22px }
|
||||
.dream-stat-glass { padding:14px 16px; border-radius:16px; background:rgba(255,255,255,0.06); backdrop-filter:blur(8px); border:1px solid rgba(255,255,255,0.08) }
|
||||
.dream-stat-glass .ds-label { font-size:12px; color:rgba(226,232,240,0.6) }
|
||||
.dream-stat-glass .ds-value { font-size:22px; font-weight:700; margin-top:4px; color:#e2e8f0 }
|
||||
</style>
|
||||
<div class="dream-hero">
|
||||
${starsHtml}
|
||||
<div class="dream-moon"></div>
|
||||
<span class="dream-z">z</span>
|
||||
<span class="dream-z">z</span>
|
||||
<span class="dream-z">Z</span>
|
||||
|
||||
<div style="position:relative;display:flex;justify-content:space-between;gap:18px;align-items:flex-start;flex-wrap:wrap">
|
||||
<div style="max-width:620px">
|
||||
<div style="max-width:600px">
|
||||
<div class="badge${enabled ? ' badge-success' : ''}" style="margin-bottom:10px">${esc(enabled ? t('dreaming.statusEnabled') : t('dreaming.statusDisabled'))}</div>
|
||||
<div style="font-size:28px;font-weight:700;letter-spacing:-0.02em;margin-bottom:10px">${esc(t('dreaming.sceneTitle'))}</div>
|
||||
<div style="font-size:14px;line-height:1.8;color:rgba(226,232,240,0.88);max-width:560px">${esc(t('dreaming.sceneDesc'))}</div>
|
||||
<div style="margin-top:12px;font-size:14px;line-height:1.8;color:rgba(255,255,255,0.92)">${esc(heroText)}</div>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:14px">
|
||||
<div style="padding:8px 12px;border-radius:999px;background:rgba(255,255,255,0.08);font-size:12px">${esc(`${t('dreaming.nextRun')}: ${nextRun}`)}</div>
|
||||
<div style="padding:8px 12px;border-radius:999px;background:rgba(255,255,255,0.08);font-size:12px">${esc(`${t('dreaming.timezone')}: ${status?.timezone || '—'}`)}</div>
|
||||
<div style="padding:8px 12px;border-radius:999px;background:rgba(255,255,255,0.08);font-size:12px">${esc(`${t('dreaming.memoryPath')}: ${status?.storePath || 'MEMORY.md'}`)}</div>
|
||||
<div style="font-size:26px;font-weight:700;letter-spacing:-0.02em;margin-bottom:10px">${esc(t('dreaming.sceneTitle'))}</div>
|
||||
<div style="font-size:13px;line-height:1.8;color:rgba(226,232,240,0.88);max-width:540px">${esc(t('dreaming.sceneDesc'))}</div>
|
||||
<div style="margin-top:12px;font-size:13px;line-height:1.8;color:rgba(255,255,255,0.92)">${esc(heroText)}</div>
|
||||
<div style="display:flex;gap:8px;flex-wrap:wrap;margin-top:14px">
|
||||
<span class="badge">${esc(`${t('dreaming.nextRun')}: ${nextRun}`)}</span>
|
||||
<span class="badge">${esc(`${t('dreaming.timezone')}: ${status?.timezone || '—'}`)}</span>
|
||||
<span class="badge">${esc(`${t('dreaming.memoryPath')}: ${status?.storePath || 'MEMORY.md'}`)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="position:relative;z-index:1;display:flex;flex-direction:column;gap:10px;align-items:flex-end;max-width:420px">
|
||||
@@ -498,22 +553,19 @@ function renderSceneView(status, enabled, heroText, disabledAttr, nextRun) {
|
||||
</div>
|
||||
</div>
|
||||
${renderStatusHints()}
|
||||
<div style="position:relative;display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:12px;margin-top:20px">
|
||||
<div style="padding:14px;border-radius:16px;background:rgba(255,255,255,0.08);backdrop-filter:blur(8px)"><div style="font-size:12px;color:rgba(226,232,240,0.72)">${esc(t('dreaming.sceneConstellation'))}</div><div style="font-size:24px;font-weight:700;margin-top:4px">${esc(status?.shortTermCount ?? 0)}</div></div>
|
||||
<div style="padding:14px;border-radius:16px;background:rgba(255,255,255,0.08);backdrop-filter:blur(8px)"><div style="font-size:12px;color:rgba(226,232,240,0.72)">${esc(t('dreaming.sceneSignals'))}</div><div style="font-size:24px;font-weight:700;margin-top:4px">${esc(status?.totalSignalCount ?? 0)}</div></div>
|
||||
<div style="padding:14px;border-radius:16px;background:rgba(255,255,255,0.08);backdrop-filter:blur(8px)"><div style="font-size:12px;color:rgba(226,232,240,0.72)">${esc(t('dreaming.scenePromotions'))}</div><div style="font-size:24px;font-weight:700;margin-top:4px">${esc(status?.promotedTotal ?? 0)}</div></div>
|
||||
<div style="padding:14px;border-radius:16px;background:rgba(255,255,255,0.08);backdrop-filter:blur(8px)"><div style="font-size:12px;color:rgba(226,232,240,0.72)">${esc(t('dreaming.sceneQueue'))}</div><div style="font-size:24px;font-weight:700;margin-top:4px">${esc((status?.shortTermEntries || []).length)}</div></div>
|
||||
<div class="dream-stats-row">
|
||||
<div class="dream-stat-glass"><div class="ds-label">${esc(t('dreaming.sceneConstellation'))}</div><div class="ds-value">${esc(status?.shortTermCount ?? 0)}</div></div>
|
||||
<div class="dream-stat-glass"><div class="ds-label">${esc(t('dreaming.sceneSignals'))}</div><div class="ds-value">${esc(status?.totalSignalCount ?? 0)}</div></div>
|
||||
<div class="dream-stat-glass"><div class="ds-label">${esc(t('dreaming.scenePromotions'))}</div><div class="ds-value">${esc(status?.promotedTotal ?? 0)}</div></div>
|
||||
<div class="dream-stat-glass"><div class="ds-label">${esc(t('dreaming.sceneQueue'))}</div><div class="ds-value">${esc((status?.shortTermEntries || []).length)}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-cards" style="margin-bottom:var(--space-lg)">
|
||||
${renderStatCard(t('dreaming.nextRun'), nextRun)}
|
||||
${renderStatCard(t('dreaming.timezone'), status?.timezone || '—')}
|
||||
${renderStatCard(t('dreaming.storageMode'), status?.storageMode || 'inline')}
|
||||
${renderStatCard(t('dreaming.promotedToday'), status?.promotedToday ?? 0)}
|
||||
${renderStatCard(t('dreaming.promotedTotal'), status?.promotedTotal ?? 0)}
|
||||
${renderStatCard(t('dreaming.shortTerm'), status?.shortTermCount ?? 0, `${t('dreaming.memoryPath')}: ${status?.storePath || 'MEMORY.md'}`)}
|
||||
${renderStatCard(t('dreaming.grounded'), status?.groundedSignalCount ?? 0)}
|
||||
${renderStatCard(t('dreaming.storageMode'), status?.storageMode || 'inline')}
|
||||
${renderStatCard(t('dreaming.shortTerm'), status?.shortTermCount ?? 0, `${t('dreaming.memoryPath')}: ${status?.storePath || 'MEMORY.md'}`)}
|
||||
${renderStatCard(t('dreaming.signals'), status?.totalSignalCount ?? 0, `${t('dreaming.diaryPath')}: ${_state.diaryPath || 'DREAMS.md'}`)}
|
||||
</div>
|
||||
|
||||
@@ -533,16 +585,52 @@ function renderSceneView(status, enabled, heroText, disabledAttr, nextRun) {
|
||||
|
||||
function renderDiaryView(status, enabled, heroText, disabledAttr) {
|
||||
const sections = parseDiarySections(_state.diaryContent)
|
||||
const diaryUnavailable = !_state.diarySupported
|
||||
|
||||
let diaryBody = ''
|
||||
if (diaryUnavailable) {
|
||||
diaryBody = `
|
||||
<div class="config-section" style="margin:0;border-left:3px solid var(--warning)">
|
||||
<div class="config-section-title">${esc(t('dreaming.diary'))}</div>
|
||||
<div class="form-hint" style="line-height:1.8">${esc(t('dreaming.rpcUnsupported'))}</div>
|
||||
</div>`
|
||||
} else {
|
||||
diaryBody = `
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:var(--space-md)">
|
||||
<div class="config-section" style="margin:0">
|
||||
<div class="config-section-title">${esc(t('dreaming.diarySections'))}</div>
|
||||
${sections.length
|
||||
? sections.map((section, idx) => `
|
||||
<div style="padding:14px 0;border-bottom:${idx === sections.length - 1 ? 'none' : '1px solid var(--border-primary)'}">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||||
<span class="badge${idx === 0 ? ' badge-success' : ''}">${esc(`${t('dreaming.diarySection')} ${idx + 1}`)}</span>
|
||||
<span style="font-weight:600;color:var(--text-primary)">${esc(section.title)}</span>
|
||||
</div>
|
||||
<div style="font-size:13px;line-height:1.7;color:var(--text-secondary)">${esc(section.body.slice(0, 220) || section.title)}</div>
|
||||
</div>
|
||||
`).join('')
|
||||
: `<div class="form-hint" style="line-height:1.8">${esc(t('dreaming.diaryEmpty'))}<br>${esc(t('dreaming.diaryEmptyHint'))}</div>`}
|
||||
</div>
|
||||
|
||||
<div class="config-section" style="margin:0">
|
||||
<div class="config-section-title">${esc(t('dreaming.diaryRaw'))}</div>
|
||||
${typeof _state.diaryContent === 'string'
|
||||
? `<pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-secondary);border-radius:var(--radius);padding:var(--space-md);font-size:12px;line-height:1.7;max-height:560px;overflow:auto">${esc(_state.diaryContent)}</pre>`
|
||||
: `<div class="form-hint" style="line-height:1.8">${esc(t('dreaming.diaryEmpty'))}<br>${esc(t('dreaming.diaryEmptyHint'))}</div>`}
|
||||
</div>
|
||||
</div>`
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="config-section" style="margin-bottom:var(--space-lg);background:linear-gradient(180deg, rgba(99,102,241,0.08), rgba(15,23,42,0.02));border:1px solid rgba(99,102,241,0.14)">
|
||||
<div class="config-section" style="margin-bottom:var(--space-lg)">
|
||||
<div style="display:flex;justify-content:space-between;gap:16px;align-items:flex-start;flex-wrap:wrap">
|
||||
<div style="max-width:620px">
|
||||
<div style="flex:1;min-width:280px">
|
||||
<div class="config-section-title">${esc(t('dreaming.diary'))}</div>
|
||||
<div style="font-size:14px;line-height:1.8;color:var(--text-secondary)">${esc(heroText)}</div>
|
||||
<div style="margin-top:10px;display:flex;gap:10px;flex-wrap:wrap">
|
||||
<div class="badge${enabled ? ' badge-success' : ''}">${esc(enabled ? t('dreaming.statusEnabled') : t('dreaming.statusDisabled'))}</div>
|
||||
<div class="badge">${esc(`${t('dreaming.diaryPath')}: ${_state.diaryPath || 'DREAMS.md'}`)}</div>
|
||||
<div class="badge">${esc(`${t('dreaming.diarySections')}: ${sections.length}`)}</div>
|
||||
<div style="font-size:13px;line-height:1.8;color:var(--text-secondary)">${esc(heroText)}</div>
|
||||
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap">
|
||||
<span class="badge${enabled ? ' badge-success' : ''}">${esc(enabled ? t('dreaming.statusEnabled') : t('dreaming.statusDisabled'))}</span>
|
||||
<span class="badge">${esc(`${t('dreaming.diaryPath')}: ${_state.diaryPath || 'DREAMS.md'}`)}</span>
|
||||
${!diaryUnavailable ? `<span class="badge">${esc(`${t('dreaming.diarySections')}: ${sections.length}`)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
${renderActionButtons(enabled, disabledAttr)}
|
||||
@@ -550,29 +638,7 @@ function renderDiaryView(status, enabled, heroText, disabledAttr) {
|
||||
${renderStatusHints()}
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:var(--space-md)">
|
||||
<div class="config-section" style="margin:0">
|
||||
<div class="config-section-title">${esc(t('dreaming.diarySections'))}</div>
|
||||
${sections.length
|
||||
? sections.map((section, idx) => `
|
||||
<div style="padding:14px 0;border-bottom:${idx === sections.length - 1 ? 'none' : '1px solid var(--border-primary)'}">
|
||||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">
|
||||
<span class="badge${idx === 0 ? ' badge-success' : ''}">${esc(`${t('dreaming.diarySection')} ${idx + 1}`)}</span>
|
||||
<span style="font-weight:600;color:var(--text-primary)">${esc(section.title)}</span>
|
||||
</div>
|
||||
<div style="font-size:13px;line-height:1.7;color:var(--text-secondary)">${esc(section.body.slice(0, 220) || section.title)}</div>
|
||||
</div>
|
||||
`).join('')
|
||||
: `<div class="form-hint" style="line-height:1.8">${esc(t('dreaming.diaryEmpty'))}<br>${esc(t('dreaming.diaryEmptyHint'))}</div>`}
|
||||
</div>
|
||||
|
||||
<div class="config-section" style="margin:0">
|
||||
<div class="config-section-title">${esc(t('dreaming.diaryRaw'))}</div>
|
||||
${typeof _state.diaryContent === 'string'
|
||||
? `<pre style="white-space:pre-wrap;word-break:break-word;background:var(--bg-secondary);border-radius:var(--radius);padding:var(--space-md);font-size:12px;line-height:1.7;max-height:560px;overflow:auto">${esc(_state.diaryContent)}</pre>`
|
||||
: `<div class="form-hint" style="line-height:1.8">${esc(t('dreaming.diaryEmpty'))}<br>${esc(t('dreaming.diaryEmptyHint'))}</div>`}
|
||||
</div>
|
||||
</div>
|
||||
${diaryBody}
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user