From 8d907aee2c65ca9a8f67b94a79bd4d8f55cb8f30 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Fri, 15 Aug 2025 23:01:26 +0200 Subject: [PATCH 01/18] feat: add System tab to Help component - Introduced a new System tab in the Help component for additional information. - Updated tab structure to include the System tab and its corresponding content panel. --- web/src/components/content/Help/Help.tsx | 5 + web/src/components/content/Help/SystemTab.tsx | 309 ++++++++++++++++++ 2 files changed, 314 insertions(+) create mode 100644 web/src/components/content/Help/SystemTab.tsx diff --git a/web/src/components/content/Help/Help.tsx b/web/src/components/content/Help/Help.tsx index b43afc32c..6facf8b59 100644 --- a/web/src/components/content/Help/Help.tsx +++ b/web/src/components/content/Help/Help.tsx @@ -21,6 +21,7 @@ import KeyboardShortcutsView from "./KeyboardShortcutsView"; import { NODE_EDITOR_SHORTCUTS } from "../../../config/shortcuts"; import { getShortcutTooltip } from "../../../config/shortcuts"; import ControlsShortcutsTab from "./ControlsShortcutsTab"; +import SystemTab from "./SystemTab"; interface HelpItem { text: string; @@ -250,6 +251,7 @@ const Help = ({ +
@@ -272,6 +274,9 @@ const Help = ({ onChange={handleAccordionChange("comfy")} /> + + +
diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx new file mode 100644 index 000000000..df3f9655e --- /dev/null +++ b/web/src/components/content/Help/SystemTab.tsx @@ -0,0 +1,309 @@ +/** @jsxImportSource @emotion/react */ +import { css } from "@emotion/react"; +import React, { useEffect, useMemo, useState } from "react"; +import { + Box, + Button, + CircularProgress, + Divider, + Typography +} from "@mui/material"; +import { useTheme } from "@mui/material/styles"; +import type { Theme } from "@mui/material/styles"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import CheckCircleIcon from "@mui/icons-material/CheckCircle"; +import ErrorIcon from "@mui/icons-material/Error"; +import WarningAmberIcon from "@mui/icons-material/WarningAmber"; +import { CopyToClipboardButton } from "../../common/CopyToClipboardButton"; +import { BASE_URL, authHeader } from "../../../stores/ApiClient"; + +type OSInfo = { platform: string; release: string; arch: string }; +type VersionsInfo = { + python?: string | null; + nodetool_core?: string | null; + nodetool_base?: string | null; +}; +type PathsInfo = { + settings_path: string; + secrets_path: string; + data_dir: string; + core_logs_dir: string; + core_log_file: string; + electron_user_data: string; + electron_log_file: string; + electron_logs_dir: string; +}; +type SystemInfoResponse = { + os: OSInfo; + versions: VersionsInfo; + paths: PathsInfo; +}; + +type HealthCheck = { + id: string; + status: "ok" | "warn" | "error"; + details?: string | null; + fix_hint?: string | null; +}; +type HealthResponse = { + checks: HealthCheck[]; + summary: { ok: number; warn: number; error: number }; +}; + +const systemTabStyles = (theme: Theme) => + css({ + display: "flex", + flexDirection: "column", + gap: "1rem", + ".section": { + display: "flex", + flexDirection: "column", + gap: ".5rem", + marginBottom: "0.75rem" + }, + ".row": { + display: "flex", + alignItems: "center", + gap: "0.5rem", + flexWrap: "wrap" + }, + ".label": { + width: "220px", + minWidth: "220px", + color: theme.vars.palette.grey[300] + }, + ".value": { + fontFamily: "var(--fontFamilyMonospace)", + color: theme.vars.palette.grey[100], + wordBreak: "break-all", + flex: 1 + }, + ".path-actions": { + display: "flex", + alignItems: "center", + gap: ".25rem" + }, + ".status": { + display: "flex", + alignItems: "center", + gap: ".5rem", + padding: ".25rem 0" + } + }); + +function StatusIcon({ status }: { status: HealthCheck["status"] }) { + const theme = useTheme(); + if (status === "ok") + return ; + if (status === "warn") + return ; + return ; +} + +async function openInExplorer(path: string) { + try { + console.log("Opening in explorer:", path); + const headers = await authHeader(); + const res = await fetch( + `${BASE_URL}/api/models/open_in_explorer?path=${encodeURIComponent( + path + )}`, + { method: "POST", headers } + ); + if (!res.ok) { + console.warn("open_in_explorer failed with status", res.status); + } + } catch (e) { + console.error("open_in_explorer error", e); + } +} + +export default function SystemTab() { + const theme = useTheme(); + const [info, setInfo] = useState(null); + const [health, setHealth] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + let cancelled = false; + async function load() { + try { + const [infoRes, healthRes] = await Promise.all([ + fetch(`${BASE_URL}/api/system`, { + headers: { Accept: "application/json" } + }), + fetch(`${BASE_URL}/api/system/health`, { + headers: { Accept: "application/json" } + }) + ]); + const infoJson = (await infoRes.json()) as SystemInfoResponse; + const healthJson = (await healthRes.json()) as HealthResponse; + if (!cancelled) { + setInfo(infoJson); + setHealth(healthJson); + } + } catch (e) { + console.error("Failed to load system info/health", e); + } finally { + if (!cancelled) setLoading(false); + } + } + load(); + return () => { + cancelled = true; + }; + }, []); + + const copyAllText = useMemo(() => { + if (!info) return ""; + const lines: string[] = []; + lines.push(`OS: ${info.os.platform} ${info.os.release} (${info.os.arch})`); + lines.push( + `Versions: python=${info.versions.python ?? ""}, core=${ + info.versions.nodetool_core ?? "" + }, base=${info.versions.nodetool_base ?? ""}` + ); + lines.push("Paths:"); + const p = info.paths; + lines.push(` settings_path: ${p.settings_path}`); + lines.push(` secrets_path: ${p.secrets_path}`); + lines.push(` data_dir: ${p.data_dir}`); + lines.push(` core_logs_dir: ${p.core_logs_dir}`); + lines.push(` core_log_file: ${p.core_log_file}`); + lines.push(` electron_user_data: ${p.electron_user_data}`); + lines.push(` electron_log_file: ${p.electron_log_file}`); + lines.push(` electron_logs_dir: ${p.electron_logs_dir}`); + if (health) { + lines.push("Health Summary:"); + lines.push( + ` ok=${health.summary.ok} warn=${health.summary.warn} error=${health.summary.error}` + ); + lines.push("Health Checks:"); + for (const c of health.checks) { + lines.push( + ` ${c.id}: ${c.status}${c.details ? ` (${c.details})` : ""}` + ); + } + } + return lines.join("\n"); + }, [info, health]); + + if (loading) { + return ( + + + Loading system info… + + ); + } + + if (!info) { + return No system info available.; + } + + const paths: Array<{ label: string; value: string }> = [ + { label: "Settings", value: info.paths.settings_path }, + { label: "Secrets", value: info.paths.secrets_path }, + { label: "Data Dir", value: info.paths.data_dir }, + { label: "Core Logs Dir", value: info.paths.core_logs_dir }, + { label: "Core Log File", value: info.paths.core_log_file }, + { label: "Electron UserData", value: info.paths.electron_user_data }, + { label: "Electron Log File", value: info.paths.electron_log_file }, + { label: "Electron Logs Dir", value: info.paths.electron_logs_dir } + ]; + + return ( +
+
+
+ System +
+ +
+
+ + OS: {info.os.platform} {info.os.release} ({info.os.arch}) + + + Versions: python={info.versions.python ?? ""} core= + {info.versions.nodetool_core ?? ""} base= + {info.versions.nodetool_base ?? ""} + +
+ + + +
+ Paths + {paths.map(({ label, value }) => ( +
+ + {label} + + + {value} + +
+ + +
+
+ ))} +
+ + {health && ( + <> + +
+ Health + + Summary: ok={health.summary.ok} warn={health.summary.warn} error= + {health.summary.error} + + + {health.checks.map((c) => ( +
+ + + {c.id} + + + {c.details || ""} + + {c.fix_hint && ( + + {c.fix_hint} + + )} +
+ ))} +
+
+ + )} +
+ ); +} From 8390e1b5fdd347e35f619936fe9374340053f599 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Fri, 15 Aug 2025 23:27:35 +0200 Subject: [PATCH 02/18] feat: enhance Help component with scrollable tabs and system path updates - Added scrollable variant and auto scroll buttons to the Help component's tab structure. - Updated SystemTab to include additional paths for Ollama models and Hugging Face cache. - Implemented fallback fetching for missing paths in the SystemTab component. - Improved handling of undefined values in the paths display. --- web/src/components/content/Help/Help.tsx | 2 + web/src/components/content/Help/SystemTab.tsx | 87 +++++++++++++++---- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/web/src/components/content/Help/Help.tsx b/web/src/components/content/Help/Help.tsx index 6facf8b59..e94130699 100644 --- a/web/src/components/content/Help/Help.tsx +++ b/web/src/components/content/Help/Help.tsx @@ -247,6 +247,8 @@ const Help = ({ value={helpIndex} onChange={handleChange} aria-label="help tabs" + variant="scrollable" + scrollButtons="auto" > diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index df3f9655e..a5fe7d3a5 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -16,12 +16,14 @@ import ErrorIcon from "@mui/icons-material/Error"; import WarningAmberIcon from "@mui/icons-material/WarningAmber"; import { CopyToClipboardButton } from "../../common/CopyToClipboardButton"; import { BASE_URL, authHeader } from "../../../stores/ApiClient"; +import { getIsElectronDetails } from "../../../utils/browser"; type OSInfo = { platform: string; release: string; arch: string }; type VersionsInfo = { python?: string | null; nodetool_core?: string | null; nodetool_base?: string | null; + cuda?: string | null; }; type PathsInfo = { settings_path: string; @@ -29,6 +31,8 @@ type PathsInfo = { data_dir: string; core_logs_dir: string; core_log_file: string; + ollama_models_dir: string; + huggingface_cache_dir: string; electron_user_data: string; electron_log_file: string; electron_logs_dir: string; @@ -102,6 +106,7 @@ function StatusIcon({ status }: { status: HealthCheck["status"] }) { async function openInExplorer(path: string) { try { + if (!path) return; console.log("Opening in explorer:", path); const headers = await authHeader(); const res = await fetch( @@ -123,6 +128,7 @@ export default function SystemTab() { const [info, setInfo] = useState(null); const [health, setHealth] = useState(null); const [loading, setLoading] = useState(true); + const { isElectron } = getIsElectronDetails(); useEffect(() => { let cancelled = false; @@ -138,6 +144,38 @@ export default function SystemTab() { ]); const infoJson = (await infoRes.json()) as SystemInfoResponse; const healthJson = (await healthRes.json()) as HealthResponse; + + // Fallback: fetch Ollama/HF paths from existing endpoints if missing + try { + const headers = await authHeader(); + const updates: Partial = {}; + if (!infoJson.paths.ollama_models_dir) { + const r = await fetch(`${BASE_URL}/api/models/ollama_base_path`, { + headers + }); + if (r.ok) { + const j = (await r.json()) as any; + if (j && typeof j.path === "string") + updates.ollama_models_dir = j.path; + } + } + if (!infoJson.paths.huggingface_cache_dir) { + const r = await fetch( + `${BASE_URL}/api/models/huggingface_base_path`, + { headers } + ); + if (r.ok) { + const j = (await r.json()) as any; + if (j && typeof j.path === "string") + updates.huggingface_cache_dir = j.path; + } + } + if (Object.keys(updates).length > 0) { + infoJson.paths = { ...infoJson.paths, ...updates } as PathsInfo; + } + } catch (e) { + // ignore fallback errors + } if (!cancelled) { setInfo(infoJson); setHealth(healthJson); @@ -161,7 +199,9 @@ export default function SystemTab() { lines.push( `Versions: python=${info.versions.python ?? ""}, core=${ info.versions.nodetool_core ?? "" - }, base=${info.versions.nodetool_base ?? ""}` + }, base=${info.versions.nodetool_base ?? ""}, cuda=${ + info.versions.cuda ?? "" + }` ); lines.push("Paths:"); const p = info.paths; @@ -170,6 +210,8 @@ export default function SystemTab() { lines.push(` data_dir: ${p.data_dir}`); lines.push(` core_logs_dir: ${p.core_logs_dir}`); lines.push(` core_log_file: ${p.core_log_file}`); + lines.push(` ollama_models_dir: ${p.ollama_models_dir}`); + lines.push(` huggingface_cache_dir: ${p.huggingface_cache_dir}`); lines.push(` electron_user_data: ${p.electron_user_data}`); lines.push(` electron_log_file: ${p.electron_log_file}`); lines.push(` electron_logs_dir: ${p.electron_logs_dir}`); @@ -188,6 +230,28 @@ export default function SystemTab() { return lines.join("\n"); }, [info, health]); + // Build paths list unconditionally to keep hooks order stable + const paths: Array<{ label: string; value: string }> = useMemo(() => { + if (!info) return []; + const list: Array<{ label: string; value: string }> = [ + { label: "Settings", value: info.paths.settings_path }, + { label: "Secrets", value: info.paths.secrets_path }, + { label: "Data Dir", value: info.paths.data_dir }, + { label: "Core Logs Dir", value: info.paths.core_logs_dir }, + { label: "Core Log File", value: info.paths.core_log_file }, + { label: "Ollama Models", value: info.paths.ollama_models_dir }, + { label: "HF Cache (hub)", value: info.paths.huggingface_cache_dir } + ]; + if (isElectron) { + list.push( + { label: "Electron UserData", value: info.paths.electron_user_data }, + { label: "Electron Log File", value: info.paths.electron_log_file }, + { label: "Electron Logs Dir", value: info.paths.electron_logs_dir } + ); + } + return list; + }, [info, isElectron]); + if (loading) { return ( @@ -201,17 +265,6 @@ export default function SystemTab() { return No system info available.; } - const paths: Array<{ label: string; value: string }> = [ - { label: "Settings", value: info.paths.settings_path }, - { label: "Secrets", value: info.paths.secrets_path }, - { label: "Data Dir", value: info.paths.data_dir }, - { label: "Core Logs Dir", value: info.paths.core_logs_dir }, - { label: "Core Log File", value: info.paths.core_log_file }, - { label: "Electron UserData", value: info.paths.electron_user_data }, - { label: "Electron Log File", value: info.paths.electron_log_file }, - { label: "Electron Logs Dir", value: info.paths.electron_logs_dir } - ]; - return (
@@ -232,7 +285,8 @@ export default function SystemTab() { Versions: python={info.versions.python ?? ""} core= {info.versions.nodetool_core ?? ""} base= - {info.versions.nodetool_base ?? ""} + {info.versions.nodetool_base ?? ""} cuda= + {info.versions.cuda ?? ""}
@@ -246,18 +300,21 @@ export default function SystemTab() { {label} - {value} + {value || "-"}
From a6f4bcbd14ca8b425e1e59e1db7f2a951112a8b0 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 01:07:03 +0200 Subject: [PATCH 05/18] refactor: improve layout of SystemTab component in Help section - Updated the rendering structure of the SystemTab to enhance the layout and visual organization of status checks. - Introduced a flexbox layout for better alignment of status details and hints, improving readability and user experience. --- web/src/components/content/Help/SystemTab.tsx | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index a58cf5b98..c24097010 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -376,20 +376,27 @@ export default function SystemTab() { {health.checks.map((c) => (
- - {c.id} - - - {c.details || ""} - - {c.fix_hint && ( - - {c.fix_hint} - - )} + + + + {c.id} + + + {c.details || ""} + + + {c.fix_hint && ( + + {c.fix_hint} + + )} +
))}
From cc0c2fbd4e0288d2efe300c809c47db002d496ab Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 01:55:54 +0200 Subject: [PATCH 06/18] feat: update ThemeNodetool and vars.css for improved theming - Added default color scheme to ThemeNodetool for enhanced theme management. - Updated vars.css to include additional CSS variables for ReactFlow and Providers, while marking existing variables for future removal in favor of generated CSS vars. --- web/src/components/themes/ThemeNodetool.tsx | 3 ++- web/src/styles/vars.css | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/web/src/components/themes/ThemeNodetool.tsx b/web/src/components/themes/ThemeNodetool.tsx index 18e967113..efd29b8a2 100644 --- a/web/src/components/themes/ThemeNodetool.tsx +++ b/web/src/components/themes/ThemeNodetool.tsx @@ -14,13 +14,14 @@ import "@fontsource/jetbrains-mono/300.css"; import "@fontsource/jetbrains-mono/400.css"; import "@fontsource/jetbrains-mono/600.css"; -// Theme augmentation moved to a single global file `theme.d.ts` to avoid duplication +// Theme augmentation in `theme.d.ts` const ThemeNodetool = createTheme({ cssVariables: { cssVarPrefix: "", colorSchemeSelector: "class" }, + defaultColorScheme: "dark", colorSchemes: { light: { palette: paletteLight diff --git a/web/src/styles/vars.css b/web/src/styles/vars.css index 1a229cae5..7d1093acb 100644 --- a/web/src/styles/vars.css +++ b/web/src/styles/vars.css @@ -1,11 +1,14 @@ +/* SHOULD BE REMOVED IN FUTURE */ +/* USE GENERATED CSS VARS INSTEAD */ + :root { --font_family: Inter, Arial, sans-serif; --font_family2: "Jetbrains Mono", Inter, Arial, sans-serif; - + /* General */ --c_background: var(--palette-c_background); --c_node_menu: var(--palette-c_node_menu); - + /* Highlights */ --c_link: var(--palette-c_link); --c_link_visited: var(--palette-c_link_visited); @@ -34,5 +37,15 @@ --c_node_bg_group: var(--palette-c_node_bg_group); --c_node_header_bg: var(--palette-c_node_header_bg); --c_node_header_bg_group: var(--palette-c_node_header_bg_group); -} + /* REACTFLOW */ + --c_editor_bg_color: var(--palette-c_editor_bg_color); + --c_editor_grid_color: var(--palette-c_editor_grid_color); + --c_editor_axis_color: var(--palette-c_editor_axis_color); + --c_selection_rect: var(--palette-c_selection_rect); + + /* PROVIDERS */ + --c_provider_api: var(--palette-c_provider_api); + --c_provider_local: var(--palette-c_provider_local); + --c_provider_hf: var(--palette-c_provider_hf); +} From dea0f1647eb2599ee4e0ab665b7d1b7cc5afccbe Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 02:26:06 +0200 Subject: [PATCH 07/18] refactor: streamline SystemTab component and enhance error handling - Removed unused `authHeader` import and the `openInExplorer` function to simplify the code. - Added new types for Ollama and Hugging Face base path responses to improve type safety. - Updated fetch requests to include error handling for system and health info loading. - Improved path handling logic to ensure proper updates for Ollama and Hugging Face directories. - Enhanced the display logic for paths to avoid unnecessary type assertions. --- web/src/components/content/Help/SystemTab.tsx | 103 +++++++++--------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index c24097010..2bc1fa262 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -15,8 +15,9 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import ErrorIcon from "@mui/icons-material/Error"; import WarningAmberIcon from "@mui/icons-material/WarningAmber"; import { CopyToClipboardButton } from "../../common/CopyToClipboardButton"; -import { BASE_URL, authHeader } from "../../../stores/ApiClient"; +import { BASE_URL } from "../../../stores/ApiClient"; import { getIsElectronDetails } from "../../../utils/browser"; +import { isPathValid, openInExplorer } from "../../../utils/fileExplorer"; type OSInfo = { platform: string; release: string; arch: string }; type VersionsInfo = { @@ -39,6 +40,14 @@ type PathsInfo = { electron_logs_dir: string; electron_main_log_file?: string; }; + +type OllamaBasePathResponse = { + path: string; +}; + +type HuggingFaceBasePathResponse = { + path: string; +}; type SystemInfoResponse = { os: OSInfo; versions: VersionsInfo; @@ -106,25 +115,6 @@ function StatusIcon({ status }: { status: HealthCheck["status"] }) { return ; } -async function openInExplorer(path: string) { - try { - if (!path) return; - console.log("Opening in explorer:", path); - const headers = await authHeader(); - const res = await fetch( - `${BASE_URL}/api/models/open_in_explorer?path=${encodeURIComponent( - path - )}`, - { method: "POST", headers } - ); - if (!res.ok) { - console.warn("open_in_explorer failed with status", res.status); - } - } catch (e) { - console.error("open_in_explorer error", e); - } -} - export default function SystemTab() { const theme = useTheme(); const [info, setInfo] = useState(null); @@ -137,43 +127,58 @@ export default function SystemTab() { async function load() { try { const [infoRes, healthRes] = await Promise.all([ - fetch(`${BASE_URL}/api/system`, { + fetch(`${BASE_URL}/api/system/`, { headers: { Accept: "application/json" } }), fetch(`${BASE_URL}/api/system/health`, { headers: { Accept: "application/json" } }) ]); + + if (!infoRes.ok) { + throw new Error( + `Failed to load system info: ${infoRes.status} ${infoRes.statusText}` + ); + } + if (!healthRes.ok) { + throw new Error( + `Failed to load health info: ${healthRes.status} ${healthRes.statusText}` + ); + } + const infoJson = (await infoRes.json()) as SystemInfoResponse; const healthJson = (await healthRes.json()) as HealthResponse; // Fallback: fetch Ollama/HF paths from existing endpoints if missing try { - const headers = await authHeader(); const updates: Partial = {}; if (!infoJson.paths.ollama_models_dir) { - const r = await fetch(`${BASE_URL}/api/models/ollama_base_path`, { - headers + const res = await fetch(`${BASE_URL}/api/models/ollama_base_path`, { + headers: { Accept: "application/json" } }); - if (r.ok) { - const j = (await r.json()) as any; - if (j && typeof j.path === "string") - updates.ollama_models_dir = j.path; + if (res.ok) { + const data = (await res.json()) as OllamaBasePathResponse; + if (data?.path) { + updates.ollama_models_dir = data.path; + } } } if (!infoJson.paths.huggingface_cache_dir) { - const r = await fetch( + const res = await fetch( `${BASE_URL}/api/models/huggingface_base_path`, - { headers } + { + headers: { Accept: "application/json" } + } ); - if (r.ok) { - const j = (await r.json()) as any; - if (j && typeof j.path === "string") - updates.huggingface_cache_dir = j.path; + if (res.ok) { + const data = (await res.json()) as HuggingFaceBasePathResponse; + if (data?.path) { + updates.huggingface_cache_dir = data.path; + } } } if (Object.keys(updates).length > 0) { - infoJson.paths = { ...infoJson.paths, ...updates } as PathsInfo; + infoJson.paths = { ...infoJson.paths, ...updates }; } } catch (e) { // ignore fallback errors @@ -183,7 +188,8 @@ export default function SystemTab() { setHealth(healthJson); } } catch (e) { - console.error("Failed to load system info/health", e); + console.error("Failed to load system info/health:", e); + // You could set an error state here to show user-friendly error messages } finally { if (!cancelled) setLoading(false); } @@ -213,22 +219,17 @@ export default function SystemTab() { const chatLogs = isElectron ? p.electron_logs_dir : p.core_logs_dir; lines.push(` chat_logs_folder: ${chatLogs}`); // Show only if backend provides it to avoid guessing - if (!isElectron && (p as any).core_main_log_file) { - lines.push(` core_main_log_file: ${(p as any).core_main_log_file}`); + if (!isElectron && p.core_main_log_file) { + lines.push(` core_main_log_file: ${p.core_main_log_file}`); } lines.push(` ollama_models_dir: ${p.ollama_models_dir}`); lines.push(` huggingface_cache_dir: ${p.huggingface_cache_dir}`); - if (!isElectron && (p as any).core_main_log_file) { - lines.push(` core_main_log_file: ${(p as any).core_main_log_file}`); - } if (isElectron) { lines.push(` electron_user_data: ${p.electron_user_data}`); lines.push(` electron_log_file: ${p.electron_log_file}`); lines.push(` electron_logs_dir: ${p.electron_logs_dir}`); - if ((p as any).electron_main_log_file) - lines.push( - ` electron_main_log_file: ${(p as any).electron_main_log_file}` - ); + if (p.electron_main_log_file) + lines.push(` electron_main_log_file: ${p.electron_main_log_file}`); } if (health) { lines.push("Health Summary:"); @@ -258,11 +259,11 @@ export default function SystemTab() { ? info.paths.electron_logs_dir : info.paths.core_logs_dir }, - ...(!isElectron && (info.paths as any).core_main_log_file + ...(!isElectron && info.paths.core_main_log_file ? [ { label: "Main Log", - value: (info.paths as any).core_main_log_file as string + value: info.paths.core_main_log_file } ] : []), @@ -274,11 +275,11 @@ export default function SystemTab() { { label: "Electron UserData", value: info.paths.electron_user_data }, { label: "Electron Log File", value: info.paths.electron_log_file }, { label: "Electron Logs Dir", value: info.paths.electron_logs_dir }, - ...((info.paths as any).electron_main_log_file + ...(info.paths.electron_main_log_file ? [ { label: "Electron Main Log", - value: (info.paths as any).electron_main_log_file as string + value: info.paths.electron_main_log_file } ] : []) @@ -347,9 +348,7 @@ export default function SystemTab() { size="small" variant="outlined" onClick={() => openInExplorer(value)} - disabled={ - !value || value.startsWith("~") || value.includes("%") - } + disabled={!isPathValid(value)} startIcon={} sx={{ borderColor: theme.vars.palette.grey[600], From c0b62667bf72c85467c7c0ead01f9fd0f7079186 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 02:37:08 +0200 Subject: [PATCH 08/18] chore: update path validation logic in fileExplorer utility - Added support for Windows environment variables in the isPathValid function to enhance path validation. - Cleaned up the SystemTab component by removing an unnecessary blank line for improved code readability. --- web/src/components/content/Help/SystemTab.tsx | 1 + web/src/utils/fileExplorer.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index 2bc1fa262..498cee3d7 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -19,6 +19,7 @@ import { BASE_URL } from "../../../stores/ApiClient"; import { getIsElectronDetails } from "../../../utils/browser"; import { isPathValid, openInExplorer } from "../../../utils/fileExplorer"; + type OSInfo = { platform: string; release: string; arch: string }; type VersionsInfo = { python?: string | null; diff --git a/web/src/utils/fileExplorer.ts b/web/src/utils/fileExplorer.ts index 9b33c914e..66525e91d 100644 --- a/web/src/utils/fileExplorer.ts +++ b/web/src/utils/fileExplorer.ts @@ -40,13 +40,16 @@ export function isPathValid(path: string): boolean { // 1. POSIX absolute path starting with '/' // 2. Windows absolute path starting with a drive letter followed by ':' and either \\ or '/' // 3. Home‐relative path starting with '~' + // 4. Windows environment variables like %APPDATA%, %LOCALAPPDATA%, etc. const windowsAbsRegex = /^[a-zA-Z]:[\\/].+/; const posixAbsRegex = /^\/.+/; const homeRegex = /^~[\\/].+/; + const windowsEnvVarRegex = /^%[A-Z_]+%[\\/].*/; return ( windowsAbsRegex.test(path) || posixAbsRegex.test(path) || - homeRegex.test(path) + homeRegex.test(path) || + windowsEnvVarRegex.test(path) ); } From 42d5e7eb696561136e5a540a0d7d0663dfe72c91 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 02:37:26 +0200 Subject: [PATCH 09/18] chore: clean up SystemTab component by removing unnecessary blank line for improved readability --- web/src/components/content/Help/SystemTab.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index 498cee3d7..2bc1fa262 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -19,7 +19,6 @@ import { BASE_URL } from "../../../stores/ApiClient"; import { getIsElectronDetails } from "../../../utils/browser"; import { isPathValid, openInExplorer } from "../../../utils/fileExplorer"; - type OSInfo = { platform: string; release: string; arch: string }; type VersionsInfo = { python?: string | null; From b625045fbb5e0bf619b8c8c4e84a00fcb6ade9d1 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 03:08:07 +0200 Subject: [PATCH 10/18] feat: add SystemDiagnostics component for displaying system information - Introduced a new SystemDiagnostics component to fetch and display system information, including OS details, versions, server status, and key paths. - Implemented loading and error handling for system info retrieval. - Added functionality to copy system information to the clipboard. - Styled the component with new CSS for improved visual presentation. --- electron/src/components/SystemDiagnostics.tsx | 235 ++++++++++ electron/src/index.css | 416 +++++++++++++++--- 2 files changed, 582 insertions(+), 69 deletions(-) create mode 100644 electron/src/components/SystemDiagnostics.tsx diff --git a/electron/src/components/SystemDiagnostics.tsx b/electron/src/components/SystemDiagnostics.tsx new file mode 100644 index 000000000..410c594bc --- /dev/null +++ b/electron/src/components/SystemDiagnostics.tsx @@ -0,0 +1,235 @@ +import React, { useState, useEffect } from "react"; + +interface BasicSystemInfo { + os: { + platform: string; + release: string; + arch: string; + }; + versions: { + python?: string; + nodetool_core?: string; + nodetool_base?: string; + }; + paths: { + data_dir: string; + core_logs_dir: string; + electron_logs_dir: string; + }; + server: { + status: "connected" | "disconnected" | "checking"; + port?: number; + }; +} + +interface SystemDiagnosticsProps { + isVisible: boolean; + onToggle: () => void; +} + +const SystemDiagnostics: React.FC = ({ + isVisible, + onToggle, +}) => { + const [systemInfo, setSystemInfo] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + if (isVisible && !systemInfo) { + loadSystemInfo(); + } + }, [isVisible, systemInfo]); + + const loadSystemInfo = async () => { + setLoading(true); + setError(null); + + try { + // Try to get system info from backend + const info = await fetchBasicSystemInfo(); + setSystemInfo(info); + } catch (err) { + console.error("Failed to load system info:", err); + setError("Unable to load system information"); + + // Fallback to basic local info + setSystemInfo({ + os: { + platform: window.api.platform, + release: "Unknown", + arch: "Unknown", + }, + versions: {}, + paths: { + data_dir: "Unknown", + core_logs_dir: "Unknown", + electron_logs_dir: "Unknown", + }, + server: { + status: "disconnected", + }, + }); + } finally { + setLoading(false); + } + }; + + const fetchBasicSystemInfo = async (): Promise => { + // This will be implemented when we add the API integration + // For now, return mock data + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + os: { + platform: window.api.platform, + release: "Unknown", + arch: "Unknown", + }, + versions: { + python: "Loading...", + nodetool_core: "Loading...", + nodetool_base: "Loading...", + }, + paths: { + data_dir: "Loading...", + core_logs_dir: "Loading...", + electron_logs_dir: "Loading...", + }, + server: { + status: "checking", + }, + }); + }, 1000); + }); + }; + + const copySystemInfo = async () => { + if (!systemInfo) return; + + const info = [ + `OS: ${systemInfo.os.platform} ${systemInfo.os.release} (${systemInfo.os.arch})`, + `Python: ${systemInfo.versions.python || "Unknown"}`, + `NodeTool Core: ${systemInfo.versions.nodetool_core || "Unknown"}`, + `NodeTool Base: ${systemInfo.versions.nodetool_base || "Unknown"}`, + `Server Status: ${systemInfo.server.status}${ + systemInfo.server.port ? ` (port ${systemInfo.server.port})` : "" + }`, + `Data Directory: ${systemInfo.paths.data_dir}`, + `Core Logs: ${systemInfo.paths.core_logs_dir}`, + `Electron Logs: ${systemInfo.paths.electron_logs_dir}`, + ].join("\n"); + + try { + await window.api.clipboardWriteText(info); + // Could add a brief success indicator here + } catch (err) { + console.error("Failed to copy to clipboard:", err); + } + }; + + if (!isVisible) return null; + + return ( +
+
+

System Information

+ +
+ +
+ {loading && ( +
+ Loading system information... +
+ )} + + {error &&
{error}
} + + {systemInfo && !loading && ( + <> +
+
Operating System
+
+ Platform: + {systemInfo.os.platform} +
+
+ Release: + {systemInfo.os.release} +
+
+ Architecture: + {systemInfo.os.arch} +
+
+ +
+
Versions
+
+ Python: + + {systemInfo.versions.python || "Unknown"} + +
+
+ NodeTool Core: + + {systemInfo.versions.nodetool_core || "Unknown"} + +
+
+ NodeTool Base: + + {systemInfo.versions.nodetool_base || "Unknown"} + +
+
+ +
+
Server Status
+
+ Connection: + + {systemInfo.server.status} + {systemInfo.server.port && + ` (port ${systemInfo.server.port})`} + +
+
+ +
+
Key Paths
+
+ Data Directory: + {systemInfo.paths.data_dir} +
+
+ Core Logs: + + {systemInfo.paths.core_logs_dir} + +
+
+ Electron Logs: + + {systemInfo.paths.electron_logs_dir} + +
+
+ +
+ +
+ + )} +
+
+ ); +}; + +export default SystemDiagnostics; diff --git a/electron/src/index.css b/electron/src/index.css index 87cc1ab0c..8262edfe0 100644 --- a/electron/src/index.css +++ b/electron/src/index.css @@ -44,9 +44,17 @@ padding: 24px; height: 100vh; overflow-y: auto; - background: radial-gradient(1200px 800px at 10% 10%, rgba(94, 160, 255, 0.08), transparent), - radial-gradient(1000px 600px at 80% 30%, rgba(118, 229, 184, 0.06), transparent), - var(--c_bg_primary); + background: radial-gradient( + 1200px 800px at 10% 10%, + rgba(94, 160, 255, 0.08), + transparent + ), + radial-gradient( + 1000px 600px at 80% 30%, + rgba(118, 229, 184, 0.06), + transparent + ), + var(--c_bg_primary); color: var(--c_text_primary); } @@ -91,9 +99,15 @@ } @keyframes pulse { - 0% { opacity: 0.5; } - 50% { opacity: 1; } - 100% { opacity: 0.5; } + 0% { + opacity: 0.5; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.5; + } } .skip-button { @@ -179,7 +193,12 @@ align-items: center; padding: 14px 16px; margin-bottom: 10px; - background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0) 40%), var(--c_bg_secondary); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.02), + rgba(255, 255, 255, 0) 40% + ), + var(--c_bg_secondary); border-radius: 12px; border: 1px solid var(--c_border); transition: all 0.2s ease; @@ -188,7 +207,7 @@ .package-item:hover { border-color: var(--c_border_hover); transform: translateY(-1px); - box-shadow: 0 6px 20px rgba(0,0,0,0.25); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25); } .package-item.installed { @@ -260,7 +279,7 @@ } .installed-indicator { - background: rgba(255,255,255,0.06); + background: rgba(255, 255, 255, 0.06); color: var(--c_text_secondary); cursor: default; } @@ -338,10 +357,22 @@ html { position: fixed; inset: 0; padding: 24px; - background: radial-gradient(1200px 800px at 10% 10%, rgba(94, 160, 255, 0.08), transparent), - radial-gradient(1000px 600px at 80% 30%, rgba(118, 229, 184, 0.06), transparent), - radial-gradient(1000px 600px at 50% 100%, rgba(255,255,255,0.04), transparent), - #12161c; + background: radial-gradient( + 1200px 800px at 10% 10%, + rgba(94, 160, 255, 0.08), + transparent + ), + radial-gradient( + 1000px 600px at 80% 30%, + rgba(118, 229, 184, 0.06), + transparent + ), + radial-gradient( + 1000px 600px at 50% 100%, + rgba(255, 255, 255, 0.04), + transparent + ), + #12161c; border: 12px solid #0b0e13; border-radius: 36px; overflow: hidden; @@ -355,7 +386,8 @@ html { border-radius: 24px; background: var(--c_panel_bg); border: 1px solid var(--c_panel_border); - box-shadow: 0 20px 80px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.03); + box-shadow: 0 20px 80px rgba(0, 0, 0, 0.35), + inset 0 0 0 1px rgba(255, 255, 255, 0.03); display: flex; flex-direction: column; align-items: center; @@ -396,9 +428,9 @@ html { } .welcome-actions .secondary { - background: rgba(255,255,255,0.06); + background: rgba(255, 255, 255, 0.06); color: var(--c_text_secondary); - border: 1px solid rgba(255,255,255,0.12); + border: 1px solid rgba(255, 255, 255, 0.12); padding: 10px 16px; border-radius: 10px; cursor: pointer; @@ -415,7 +447,12 @@ html { } .feature-card { - background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0) 40%), var(--c_bg_secondary); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.02), + rgba(255, 255, 255, 0) 40% + ), + var(--c_bg_secondary); border-radius: 12px; border: 1px solid var(--c_border); padding: 14px; @@ -442,9 +479,9 @@ html { } .pill { - background: rgba(255,255,255,0.06); + background: rgba(255, 255, 255, 0.06); color: var(--c_text_secondary); - border: 1px solid rgba(255,255,255,0.12); + border: 1px solid rgba(255, 255, 255, 0.12); padding: 6px 10px; border-radius: 999px; font-size: 12px; @@ -615,10 +652,22 @@ ul li { position: fixed; inset: 0; padding: 24px; - background: radial-gradient(1200px 800px at 10% 10%, rgba(94, 160, 255, 0.08), transparent), - radial-gradient(1000px 600px at 80% 30%, rgba(118, 229, 184, 0.06), transparent), - radial-gradient(1000px 600px at 50% 100%, rgba(255,255,255,0.04), transparent), - #12161c; + background: radial-gradient( + 1200px 800px at 10% 10%, + rgba(94, 160, 255, 0.08), + transparent + ), + radial-gradient( + 1000px 600px at 80% 30%, + rgba(118, 229, 184, 0.06), + transparent + ), + radial-gradient( + 1000px 600px at 50% 100%, + rgba(255, 255, 255, 0.04), + transparent + ), + #12161c; border: 12px solid #0b0e13; border-radius: 36px; overflow: hidden; @@ -632,7 +681,8 @@ ul li { border-radius: 24px; background: var(--c_panel_bg); border: 1px solid var(--c_panel_border); - box-shadow: 0 20px 80px rgba(0,0,0,0.35), inset 0 0 0 1px rgba(255,255,255,0.03); + box-shadow: 0 20px 80px rgba(0, 0, 0, 0.35), + inset 0 0 0 1px rgba(255, 255, 255, 0.03); display: flex; flex-direction: column; align-items: center; @@ -653,8 +703,16 @@ ul li { inset: -1px; pointer-events: none; border-radius: inherit; - background: radial-gradient(400px 200px at 50% -20%, rgba(94,160,255,0.25), transparent 60%), - radial-gradient(500px 280px at 50% 120%, rgba(118,229,184,0.18), transparent 60%); + background: radial-gradient( + 400px 200px at 50% -20%, + rgba(94, 160, 255, 0.25), + transparent 60% + ), + radial-gradient( + 500px 280px at 50% 120%, + rgba(118, 229, 184, 0.18), + transparent 60% + ); } .boot-text { @@ -755,17 +813,26 @@ ul li { .progress-bar { width: 100%; height: 8px; - background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02)); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.06), + rgba(255, 255, 255, 0.02) + ); border-radius: 999px; overflow: hidden; margin: 0 auto; - border: 1px solid rgba(255,255,255,0.06); + border: 1px solid rgba(255, 255, 255, 0.06); } .progress { width: 0; height: 100%; - background: linear-gradient(90deg, rgba(118,229,184,0.1), rgba(118,229,184,0.85), rgba(118,229,184,0.1)); + background: linear-gradient( + 90deg, + rgba(118, 229, 184, 0.1), + rgba(118, 229, 184, 0.85), + rgba(118, 229, 184, 0.1) + ); transition: width 0.4s ease; border-radius: 999px; position: relative; @@ -775,7 +842,11 @@ ul li { content: ""; position: absolute; inset: 0; - background: radial-gradient(60px 60px at right center, rgba(255,255,255,0.65), transparent 60%); + background: radial-gradient( + 60px 60px at right center, + rgba(255, 255, 255, 0.65), + transparent 60% + ); mix-blend-mode: overlay; } @@ -830,8 +901,7 @@ ul li { background: rgba(16, 19, 19, 0.95); border-radius: 18px; padding: 0; - box-shadow: - 0 20px 40px rgba(0, 0, 0, 0.45), + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(118, 229, 184, 0.15) inset; border: 1px solid rgba(118, 229, 184, 0.25); backdrop-filter: blur(10px) saturate(120%); @@ -859,11 +929,13 @@ ul li { flex-direction: column; } -.installer-shell { padding: 20px 20px 24px; } +.installer-shell { + padding: 20px 20px 24px; +} .installer-header { padding: 12px 12px 16px; - border-bottom: 1px solid rgba(255,255,255,0.08); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); } .installer-brand { @@ -884,7 +956,7 @@ ul li { } .wizard-rail { - border-right: 1px solid rgba(255,255,255,0.08); + border-right: 1px solid rgba(255, 255, 255, 0.08); padding-right: 12px; } @@ -896,8 +968,12 @@ ul li { color: var(--c_text_tertiary); } -.wizard-step.active { color: var(--c_text_secondary); } -.wizard-step.current { color: var(--c_text_primary); } +.wizard-step.active { + color: var(--c_text_secondary); +} +.wizard-step.current { + color: var(--c_text_primary); +} .step-index { width: 22px; @@ -905,11 +981,15 @@ ul li { border-radius: 50%; display: grid; place-items: center; - background: rgba(255,255,255,0.08); + background: rgba(255, 255, 255, 0.08); font-size: 12px; } -.wizard-content { padding-left: 4px; min-width: 0; min-height: 0; } +.wizard-content { + padding-left: 4px; + min-width: 0; + min-height: 0; +} #environment-info { display: flex; @@ -1083,9 +1163,9 @@ ul li { } .chip-button { - background: rgba(255,255,255,0.06); + background: rgba(255, 255, 255, 0.06); color: var(--c_text_secondary); - border: 1px solid rgba(255,255,255,0.12); + border: 1px solid rgba(255, 255, 255, 0.12); padding: 8px 10px; border-radius: 999px; font-size: 12px; @@ -1093,7 +1173,7 @@ ul li { } .chip-button:hover { - background: rgba(255,255,255,0.1); + background: rgba(255, 255, 255, 0.1); } .toolbar-count { @@ -1104,11 +1184,15 @@ ul li { .package-group { width: 100%; margin-bottom: 20px; - background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)); - border: 1px solid rgba(255,255,255,0.08); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.02), + rgba(255, 255, 255, 0) + ); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 14px; padding: 12px 12px 10px; - box-shadow: 0 6px 24px rgba(0,0,0,0.25); + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.25); } .package-group h4 { @@ -1122,7 +1206,7 @@ ul li { align-items: center; justify-content: space-between; padding: 2px 4px 8px; - border-bottom: 1px solid rgba(255,255,255,0.08); + border-bottom: 1px solid rgba(255, 255, 255, 0.08); } .group-actions { @@ -1142,18 +1226,23 @@ ul li { display: grid; grid-template-columns: 40px 1fr; align-items: start; - transition: transform 0.18s ease, box-shadow 0.18s ease, background-color 0.18s ease; + transition: transform 0.18s ease, box-shadow 0.18s ease, + background-color 0.18s ease; cursor: pointer; - background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0)); - border: 1px solid rgba(255,255,255,0.08); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.02), + rgba(255, 255, 255, 0) + ); + border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 12px; - box-shadow: 0 4px 14px rgba(0,0,0,0.22); + box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22); } .package-option:hover { background-color: rgba(255, 255, 255, 0.05); transform: translateY(-1px); - box-shadow: 0 8px 18px rgba(0,0,0,0.28); + box-shadow: 0 8px 18px rgba(0, 0, 0, 0.28); } .checkbox-wrapper { @@ -1199,10 +1288,14 @@ ul li { padding: 2px 8px; border-radius: 999px; font-size: 11px; - background: linear-gradient(180deg, rgba(118,229,184,0.18), rgba(118,229,184,0.08)); - border: 1px solid rgba(118,229,184,0.35); + background: linear-gradient( + 180deg, + rgba(118, 229, 184, 0.18), + rgba(118, 229, 184, 0.08) + ); + border: 1px solid rgba(118, 229, 184, 0.35); color: var(--c_text_primary); - box-shadow: 0 0 0 1px rgba(255,255,255,0.03) inset; + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.03) inset; } .package-info { @@ -1225,7 +1318,7 @@ ul li { /* selected visual emphasis */ .package-option input[type="checkbox"]:checked ~ .package-content { - box-shadow: 0 0 0 2px rgba(94,158,255,0.45); + box-shadow: 0 0 0 2px rgba(94, 158, 255, 0.45); border-radius: 10px; } @@ -1298,19 +1391,31 @@ input[type="checkbox"]:checked::after { } .location-button.default-location { - background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02)); - border: 1px solid rgba(94,158,255,0.45); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.04), + rgba(255, 255, 255, 0.02) + ); + border: 1px solid rgba(94, 158, 255, 0.45); border-radius: 14px; color: #fff; - box-shadow: 0 8px 24px rgba(0,0,0,0.28), 0 0 0 1px rgba(255,255,255,0.03) inset; - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28), + 0 0 0 1px rgba(255, 255, 255, 0.03) inset; + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, + background 0.2s ease; } .location-button.default-location:hover { transform: translateY(-1px); - border-color: rgba(94,158,255,0.7); - background: linear-gradient(180deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03)); - box-shadow: 0 12px 28px rgba(0,0,0,0.35), 0 0 0 1px rgba(255,255,255,0.05) inset, 0 0 0 4px rgba(94,158,255,0.08); + border-color: rgba(94, 158, 255, 0.7); + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.06), + rgba(255, 255, 255, 0.03) + ); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35), + 0 0 0 1px rgba(255, 255, 255, 0.05) inset, + 0 0 0 4px rgba(94, 158, 255, 0.08); } @keyframes subtlePulse { @@ -1379,16 +1484,43 @@ body::before { z-index: 1; opacity: 0.18; pointer-events: none; - background: - radial-gradient(1200px 800px at 15% 10%, rgba(94,160,255,0.12), transparent 60%), - radial-gradient(900px 700px at 85% 30%, rgba(118,229,184,0.10), transparent 60%), - radial-gradient(800px 600px at 50% 100%, rgba(255,255,255,0.06), transparent 60%), - conic-gradient(from 180deg at 50% 50%, rgba(255,255,255,0.02), rgba(0,0,0,0.02) 50%, rgba(255,255,255,0.02)), - repeating-linear-gradient(45deg, rgba(255,255,255,0.04) 0 1px, transparent 1px 12px); + background: radial-gradient( + 1200px 800px at 15% 10%, + rgba(94, 160, 255, 0.12), + transparent 60% + ), + radial-gradient( + 900px 700px at 85% 30%, + rgba(118, 229, 184, 0.1), + transparent 60% + ), + radial-gradient( + 800px 600px at 50% 100%, + rgba(255, 255, 255, 0.06), + transparent 60% + ), + conic-gradient( + from 180deg at 50% 50%, + rgba(255, 255, 255, 0.02), + rgba(0, 0, 0, 0.02) 50%, + rgba(255, 255, 255, 0.02) + ), + repeating-linear-gradient( + 45deg, + rgba(255, 255, 255, 0.04) 0 1px, + transparent 1px 12px + ); animation: backgroundFloat 80s linear infinite; } -@keyframes backgroundFloat { from { transform: translateY(0); } to { transform: translateY(-2%); } } +@keyframes backgroundFloat { + from { + transform: translateY(0); + } + to { + transform: translateY(-2%); + } +} .setup-step { display: none; @@ -1457,3 +1589,149 @@ body::before { .nav-button.back { background: transparent; } + +/* System Diagnostics Styles */ +.system-diagnostics { + background: var(--c_panel_bg); + border: 1px solid var(--c_panel_border); + border-radius: 12px; + margin-top: 16px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); + backdrop-filter: blur(10px); + max-width: 600px; + width: 100%; +} + +.system-diagnostics-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid var(--c_border); +} + +.system-diagnostics-header h4 { + margin: 0; + font-size: 14px; + font-weight: 600; + color: var(--c_text_primary); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.system-diagnostics-close { + background: none; + border: none; + color: var(--c_text_tertiary); + font-size: 18px; + cursor: pointer; + padding: 4px; + border-radius: 4px; + transition: all 0.2s ease; +} + +.system-diagnostics-close:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--c_text_primary); +} + +.system-diagnostics-content { + padding: 16px; + max-height: 400px; + overflow-y: auto; +} + +.system-diagnostics-loading, +.system-diagnostics-error { + text-align: center; + padding: 20px; + color: var(--c_text_secondary); + font-size: 14px; +} + +.system-diagnostics-error { + color: var(--c_danger); +} + +.system-info-section { + margin-bottom: 16px; +} + +.system-info-section:last-of-type { + margin-bottom: 0; +} + +.system-info-section h5 { + margin: 0 0 8px 0; + font-size: 12px; + font-weight: 600; + color: var(--c_text_tertiary); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.system-info-item { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 4px 0; + gap: 12px; +} + +.system-info-item .label { + font-size: 13px; + color: var(--c_text_secondary); + min-width: 120px; + flex-shrink: 0; +} + +.system-info-item .value { + font-size: 13px; + color: var(--c_text_primary); + font-family: "Courier New", monospace; + word-break: break-all; + text-align: right; + flex: 1; +} + +.system-info-item .value.path { + font-size: 12px; + color: var(--c_text_secondary); +} + +.system-info-item .value.status-connected { + color: var(--c_success); +} + +.system-info-item .value.status-disconnected { + color: var(--c_danger); +} + +.system-info-item .value.status-checking { + color: var(--c_accent_alt); +} + +.system-diagnostics-actions { + margin-top: 16px; + padding-top: 16px; + border-top: 1px solid var(--c_border); + text-align: center; +} + +.copy-button { + background: linear-gradient(135deg, var(--c_accent), #4caf50); + color: white; + border: none; + padding: 8px 16px; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 600; + transition: all 0.2s ease; + box-shadow: 0 2px 8px rgba(118, 229, 184, 0.25); +} + +.copy-button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(118, 229, 184, 0.35); +} From a8f21fcb1295f05326c7ff86f58acb5504816ec2 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 03:12:49 +0200 Subject: [PATCH 11/18] feat: implement fetchBasicSystemInfo API and integrate with IPC - Added fetchBasicSystemInfo function to retrieve system diagnostics, including OS details, versions, and server status. - Integrated the new API call into IPC handlers for system information retrieval. - Updated preload script to expose getSystemInfo method for use in the renderer process. - Refactored SystemDiagnostics component to utilize the real API for fetching system information instead of mock data. --- electron/src/api.ts | 91 +++++++++++++++- electron/src/components/SystemDiagnostics.tsx | 32 +----- electron/src/ipc.ts | 103 +++++++++--------- electron/src/preload.ts | 18 ++- electron/src/types.d.ts | 42 +++++-- 5 files changed, 192 insertions(+), 94 deletions(-) diff --git a/electron/src/api.ts b/electron/src/api.ts index e66d6f734..b23ca8980 100644 --- a/electron/src/api.ts +++ b/electron/src/api.ts @@ -1,6 +1,7 @@ -import { Workflow } from "./types"; +import { Workflow, BasicSystemInfo } from "./types"; import { logMessage } from "./logger"; import { serverState } from "./state"; +import { app } from "electron"; export let isConnected = false; let healthCheckTimer: NodeJS.Timeout | null = null; @@ -75,3 +76,91 @@ export function stopPeriodicHealthCheck(): void { healthCheckTimer = null; } } + +/** + * Fetches basic system information for diagnostics + * @returns {Promise} Basic system info or null if unavailable + */ +export async function fetchBasicSystemInfo(): Promise { + logMessage("Fetching basic system information..."); + + try { + const port = serverState.serverPort ?? 8000; + const baseUrl = `http://127.0.0.1:${port}`; + + // Try to fetch system info from backend + const [systemResponse, healthResponse] = await Promise.allSettled([ + fetch(`${baseUrl}/api/system/`, { + method: "GET", + headers: { Accept: "application/json" }, + signal: AbortSignal.timeout(5000), // 5 second timeout + }), + fetch(`${baseUrl}/api/system/health`, { + method: "GET", + headers: { Accept: "application/json" }, + signal: AbortSignal.timeout(5000), + }), + ]); + + let systemData: any = null; + let serverStatus: "connected" | "disconnected" | "checking" = + "disconnected"; + + // Process system info response + if (systemResponse.status === "fulfilled" && systemResponse.value.ok) { + systemData = await systemResponse.value.json(); + serverStatus = "connected"; + logMessage("Successfully fetched system information from backend"); + } + + // Process health response + if (healthResponse.status === "fulfilled" && healthResponse.value.ok) { + serverStatus = "connected"; + } + + // Build system info object + const systemInfo: BasicSystemInfo = { + os: { + platform: systemData?.os?.platform || process.platform, + release: systemData?.os?.release || "Unknown", + arch: systemData?.os?.arch || process.arch, + }, + versions: { + python: systemData?.versions?.python || undefined, + nodetool_core: systemData?.versions?.nodetool_core || undefined, + nodetool_base: systemData?.versions?.nodetool_base || undefined, + }, + paths: { + data_dir: systemData?.paths?.data_dir || app.getPath("userData"), + core_logs_dir: systemData?.paths?.core_logs_dir || "Unknown", + electron_logs_dir: app.getPath("logs"), + }, + server: { + status: serverStatus, + port: serverStatus === "connected" ? port : undefined, + }, + }; + + return systemInfo; + } catch (error) { + logMessage(`Failed to fetch system information: ${error}`, "error"); + + // Return fallback system info + return { + os: { + platform: process.platform, + release: "Unknown", + arch: process.arch, + }, + versions: {}, + paths: { + data_dir: app.getPath("userData"), + core_logs_dir: "Unknown", + electron_logs_dir: app.getPath("logs"), + }, + server: { + status: "disconnected", + }, + }; + } +} diff --git a/electron/src/components/SystemDiagnostics.tsx b/electron/src/components/SystemDiagnostics.tsx index 410c594bc..2ba2626a0 100644 --- a/electron/src/components/SystemDiagnostics.tsx +++ b/electron/src/components/SystemDiagnostics.tsx @@ -76,32 +76,12 @@ const SystemDiagnostics: React.FC = ({ }; const fetchBasicSystemInfo = async (): Promise => { - // This will be implemented when we add the API integration - // For now, return mock data - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - os: { - platform: window.api.platform, - release: "Unknown", - arch: "Unknown", - }, - versions: { - python: "Loading...", - nodetool_core: "Loading...", - nodetool_base: "Loading...", - }, - paths: { - data_dir: "Loading...", - core_logs_dir: "Loading...", - electron_logs_dir: "Loading...", - }, - server: { - status: "checking", - }, - }); - }, 1000); - }); + // Use the real API integration + const systemInfo = await window.api.getSystemInfo(); + if (!systemInfo) { + throw new Error("Failed to fetch system information"); + } + return systemInfo; }; const copySystemInfo = async () => { diff --git a/electron/src/ipc.ts b/electron/src/ipc.ts index 5ae38f166..b6f90ace1 100644 --- a/electron/src/ipc.ts +++ b/electron/src/ipc.ts @@ -1,19 +1,32 @@ -import { ipcMain, BrowserWindow, clipboard, globalShortcut, shell } from "electron"; -import { getServerState, openLogFile, runApp, initializeBackendServer, stopServer } from "./server"; +import { + ipcMain, + BrowserWindow, + clipboard, + globalShortcut, + shell, +} from "electron"; +import { + getServerState, + openLogFile, + runApp, + initializeBackendServer, + stopServer, +} from "./server"; import { logMessage } from "./logger"; import { IpcChannels, IpcEvents, IpcResponse } from "./types.d"; import { createPackageManagerWindow } from "./window"; import { IpcRequest } from "./types.d"; import { registerWorkflowShortcut, setupWorkflowShortcuts } from "./shortcuts"; import { updateTrayMenu } from "./tray"; -import { - fetchAvailablePackages, - listInstalledPackages, - installPackage, - uninstallPackage, +import { + fetchAvailablePackages, + listInstalledPackages, + installPackage, + uninstallPackage, updatePackage, - validateRepoId + validateRepoId, } from "./packageManager"; +import { fetchBasicSystemInfo } from "./api"; /** * This module handles Inter-Process Communication (IPC) between the Electron main process @@ -86,6 +99,10 @@ export function initializeIpcHandlers(): void { return getServerState(); }); + createIpcMainHandler(IpcChannels.GET_SYSTEM_INFO, async () => { + return await fetchBasicSystemInfo(); + }); + createIpcMainHandler(IpcChannels.OPEN_LOG_FILE, async () => { openLogFile(); }); @@ -194,36 +211,27 @@ export function initializeIpcHandlers(): void { ); // Package manager handlers - createIpcMainHandler( - IpcChannels.PACKAGE_LIST_AVAILABLE, - async () => { - logMessage("Fetching available packages"); - return await fetchAvailablePackages(); - } - ); + createIpcMainHandler(IpcChannels.PACKAGE_LIST_AVAILABLE, async () => { + logMessage("Fetching available packages"); + return await fetchAvailablePackages(); + }); - createIpcMainHandler( - IpcChannels.PACKAGE_LIST_INSTALLED, - async () => { - logMessage("Listing installed packages"); - return await listInstalledPackages(); - } - ); + createIpcMainHandler(IpcChannels.PACKAGE_LIST_INSTALLED, async () => { + logMessage("Listing installed packages"); + return await listInstalledPackages(); + }); - createIpcMainHandler( - IpcChannels.PACKAGE_INSTALL, - async (_event, request) => { - logMessage(`Installing package: ${request.repo_id}`); - const validation = validateRepoId(request.repo_id); - if (!validation.valid) { - return { - success: false, - message: validation.error || "Invalid repository ID" - }; - } - return await installPackage(request.repo_id); + createIpcMainHandler(IpcChannels.PACKAGE_INSTALL, async (_event, request) => { + logMessage(`Installing package: ${request.repo_id}`); + const validation = validateRepoId(request.repo_id); + if (!validation.valid) { + return { + success: false, + message: validation.error || "Invalid repository ID", + }; } - ); + return await installPackage(request.repo_id); + }); createIpcMainHandler( IpcChannels.PACKAGE_UNINSTALL, @@ -233,27 +241,24 @@ export function initializeIpcHandlers(): void { if (!validation.valid) { return { success: false, - message: validation.error || "Invalid repository ID" + message: validation.error || "Invalid repository ID", }; } return await uninstallPackage(request.repo_id); } ); - createIpcMainHandler( - IpcChannels.PACKAGE_UPDATE, - async (_event, repoId) => { - logMessage(`Updating package: ${repoId}`); - const validation = validateRepoId(repoId); - if (!validation.valid) { - return { - success: false, - message: validation.error || "Invalid repository ID" - }; - } - return await updatePackage(repoId); + createIpcMainHandler(IpcChannels.PACKAGE_UPDATE, async (_event, repoId) => { + logMessage(`Updating package: ${repoId}`); + const validation = validateRepoId(repoId); + if (!validation.valid) { + return { + success: false, + message: validation.error || "Invalid repository ID", + }; } - ); + return await updatePackage(repoId); + }); createIpcMainHandler( IpcChannels.PACKAGE_OPEN_EXTERNAL, diff --git a/electron/src/preload.ts b/electron/src/preload.ts index c82714e9e..63651c7a9 100644 --- a/electron/src/preload.ts +++ b/electron/src/preload.ts @@ -87,6 +87,7 @@ function unregisterEventHandler( contextBridge.exposeInMainWorld("api", { // Request-response methods getServerState: () => ipcRenderer.invoke(IpcChannels.GET_SERVER_STATE), + getSystemInfo: () => ipcRenderer.invoke(IpcChannels.GET_SYSTEM_INFO), clipboardWriteText: (text: string) => ipcRenderer.invoke(IpcChannels.CLIPBOARD_WRITE_TEXT, text), clipboardReadText: () => ipcRenderer.invoke(IpcChannels.CLIPBOARD_READ_TEXT), @@ -95,12 +96,9 @@ contextBridge.exposeInMainWorld("api", { ipcRenderer.invoke(IpcChannels.INSTALL_TO_LOCATION, { location, packages }), selectCustomInstallLocation: () => ipcRenderer.invoke(IpcChannels.SELECT_CUSTOM_LOCATION), - continueToApp: () => - ipcRenderer.invoke(IpcChannels.START_SERVER), - startServer: () => - ipcRenderer.invoke(IpcChannels.START_SERVER), - restartServer: () => - ipcRenderer.invoke(IpcChannels.RESTART_SERVER), + continueToApp: () => ipcRenderer.invoke(IpcChannels.START_SERVER), + startServer: () => ipcRenderer.invoke(IpcChannels.START_SERVER), + restartServer: () => ipcRenderer.invoke(IpcChannels.RESTART_SERVER), showPackageManager: () => ipcRenderer.invoke(IpcChannels.SHOW_PACKAGE_MANAGER), runApp: (workflowId: string) => @@ -145,14 +143,14 @@ contextBridge.exposeInMainWorld("electronAPI", { packages: { listAvailable: () => ipcRenderer.invoke(IpcChannels.PACKAGE_LIST_AVAILABLE), listInstalled: () => ipcRenderer.invoke(IpcChannels.PACKAGE_LIST_INSTALLED), - install: (repo_id: string) => + install: (repo_id: string) => ipcRenderer.invoke(IpcChannels.PACKAGE_INSTALL, { repo_id }), - uninstall: (repo_id: string) => + uninstall: (repo_id: string) => ipcRenderer.invoke(IpcChannels.PACKAGE_UNINSTALL, { repo_id }), - update: (repo_id: string) => + update: (repo_id: string) => ipcRenderer.invoke(IpcChannels.PACKAGE_UPDATE, repo_id), }, openExternal: (url: string) => { ipcRenderer.invoke(IpcChannels.PACKAGE_OPEN_EXTERNAL, url); }, -}); \ No newline at end of file +}); diff --git a/electron/src/types.d.ts b/electron/src/types.d.ts index 8828086ab..84acb93f2 100644 --- a/electron/src/types.d.ts +++ b/electron/src/types.d.ts @@ -3,6 +3,7 @@ declare global { api: { platform: string; getServerState: () => Promise; + getSystemInfo: () => Promise; clipboardWriteText: (text: string) => void; clipboardReadText: () => string; openLogFile: () => Promise; @@ -131,19 +132,20 @@ export interface Workflow { export interface MenuEventData { type: - | "cut" - | "copy" - | "paste" - | "selectAll" - | "undo" - | "redo" - | "close" - | "fitView"; + | "cut" + | "copy" + | "paste" + | "selectAll" + | "undo" + | "redo" + | "close" + | "fitView"; } // IPC Channel names as const enum for type safety export enum IpcChannels { GET_SERVER_STATE = "get-server-state", + GET_SYSTEM_INFO = "get-system-info", OPEN_LOG_FILE = "open-log-file", INSTALL_TO_LOCATION = "install-to-location", SELECT_CUSTOM_LOCATION = "select-custom-location", @@ -184,6 +186,7 @@ export interface InstallToLocationData { // Request/Response types for each IPC channel export interface IpcRequest { [IpcChannels.GET_SERVER_STATE]: void; + [IpcChannels.GET_SYSTEM_INFO]: void; [IpcChannels.OPEN_LOG_FILE]: void; [IpcChannels.INSTALL_TO_LOCATION]: InstallToLocationData; [IpcChannels.SELECT_CUSTOM_LOCATION]: void; @@ -210,6 +213,7 @@ export interface IpcRequest { export interface IpcResponse { [IpcChannels.GET_SERVER_STATE]: ServerState; + [IpcChannels.GET_SYSTEM_INFO]: BasicSystemInfo | null; [IpcChannels.OPEN_LOG_FILE]: void; [IpcChannels.INSTALL_TO_LOCATION]: void; [IpcChannels.SELECT_CUSTOM_LOCATION]: string | null; @@ -259,6 +263,28 @@ export interface UpdateInfo { releaseUrl: string; } +export interface BasicSystemInfo { + os: { + platform: string; + release: string; + arch: string; + }; + versions: { + python?: string; + nodetool_core?: string; + nodetool_base?: string; + }; + paths: { + data_dir: string; + core_logs_dir: string; + electron_logs_dir: string; + }; + server: { + status: "connected" | "disconnected" | "checking"; + port?: number; + }; +} + export interface InstallLocationData { defaultPath: string; downloadSize?: string; From b078c440251cf3820409e9ab8556fcdbfe3409ef Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 03:14:36 +0200 Subject: [PATCH 12/18] feat: integrate boot message system info toggle and styling - Added a button to toggle the visibility of the SystemDiagnostics component within the BootMessage. - Implemented new CSS styles for the boot actions and system info toggle button to enhance user interaction. - Updated BootMessage component to manage the state for displaying system information. --- electron/src/components/BootMessage.tsx | 29 +++++++++++++++++++++---- electron/src/index.css | 29 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/electron/src/components/BootMessage.tsx b/electron/src/components/BootMessage.tsx index 2d7a7be9b..4bd335a6e 100644 --- a/electron/src/components/BootMessage.tsx +++ b/electron/src/components/BootMessage.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useState } from "react"; +import SystemDiagnostics from "./SystemDiagnostics"; interface UpdateProgressData { componentName: string; @@ -13,7 +14,13 @@ interface BootMessageProps { progressData: UpdateProgressData; } -const BootMessage: React.FC = ({ message, showUpdateSteps, progressData }) => { +const BootMessage: React.FC = ({ + message, + showUpdateSteps, + progressData, +}) => { + const [showSystemInfo, setShowSystemInfo] = useState(false); + return (
@@ -71,7 +78,7 @@ const BootMessage: React.FC = ({ message, showUpdateSteps, pro {Math.round(progressData.progress)}% - {progressData.eta ? ` (${progressData.eta})` : ''} + {progressData.eta ? ` (${progressData.eta})` : ""}
@@ -84,9 +91,23 @@ const BootMessage: React.FC = ({ message, showUpdateSteps, pro
)} + +
+ +
+ + setShowSystemInfo(!showSystemInfo)} + /> ); }; -export default BootMessage; \ No newline at end of file +export default BootMessage; diff --git a/electron/src/index.css b/electron/src/index.css index 8262edfe0..e2fa57a6e 100644 --- a/electron/src/index.css +++ b/electron/src/index.css @@ -1735,3 +1735,32 @@ body::before { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(118, 229, 184, 0.35); } + +/* Boot Message System Info Integration */ +.boot-actions { + margin-top: 16px; + display: flex; + justify-content: center; + align-items: center; +} + +.system-info-toggle { + background: rgba(255, 255, 255, 0.06); + color: var(--c_text_secondary); + border: 1px solid rgba(255, 255, 255, 0.12); + padding: 8px 12px; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 6px; +} + +.system-info-toggle:hover { + background: rgba(255, 255, 255, 0.1); + color: var(--c_text_primary); + border-color: rgba(255, 255, 255, 0.2); +} From 0332091b0c191c9ae1b7dc7092c4e41127b08bab Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 03:34:29 +0200 Subject: [PATCH 13/18] feat: enhance system diagnostics UI and functionality - Updated CSS styles for the system diagnostics panel, including positioning, animation, and improved visual effects. - Refactored BootMessage component to integrate a new header for the system info toggle button, enhancing user interaction. - Adjusted layout and styling of the system info toggle button for better alignment and usability. --- electron/src/components/BootMessage.tsx | 31 +++++++-------- electron/src/index.css | 50 ++++++++++++++++++------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/electron/src/components/BootMessage.tsx b/electron/src/components/BootMessage.tsx index 4bd335a6e..9a44e03e9 100644 --- a/electron/src/components/BootMessage.tsx +++ b/electron/src/components/BootMessage.tsx @@ -24,7 +24,16 @@ const BootMessage: React.FC = ({ return (
-
NodeTool
+
+
NodeTool
+ +
)} - -
- -
- - setShowSystemInfo(!showSystemInfo)} - />
+ + {/* System Diagnostics Overlay */} + setShowSystemInfo(!showSystemInfo)} + /> ); }; diff --git a/electron/src/index.css b/electron/src/index.css index e2fa57a6e..61ee41f3c 100644 --- a/electron/src/index.css +++ b/electron/src/index.css @@ -696,6 +696,7 @@ ul li { text-transform: uppercase; letter-spacing: 0.28em; color: var(--c_text_secondary); + flex: 1; } .brand-ring { @@ -1592,14 +1593,30 @@ body::before { /* System Diagnostics Styles */ .system-diagnostics { + position: absolute; + top: 20px; + left: 50%; + transform: translateX(-50%); background: var(--c_panel_bg); border: 1px solid var(--c_panel_border); border-radius: 12px; - margin-top: 16px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25); - backdrop-filter: blur(10px); - max-width: 600px; - width: 100%; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); + backdrop-filter: blur(15px); + max-width: 500px; + width: 90vw; + z-index: 1200; + animation: slideDown 0.2s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateX(-50%) translateY(-10px); + } + to { + opacity: 1; + transform: translateX(-50%) translateY(0); + } } .system-diagnostics-header { @@ -1637,7 +1654,7 @@ body::before { .system-diagnostics-content { padding: 16px; - max-height: 400px; + max-height: 60vh; overflow-y: auto; } @@ -1737,30 +1754,35 @@ body::before { } /* Boot Message System Info Integration */ -.boot-actions { - margin-top: 16px; +.boot-panel-header { display: flex; - justify-content: center; - align-items: center; + justify-content: space-between; + align-items: flex-start; + width: 100%; + margin-bottom: 16px; } .system-info-toggle { background: rgba(255, 255, 255, 0.06); color: var(--c_text_secondary); border: 1px solid rgba(255, 255, 255, 0.12); - padding: 8px 12px; - border-radius: 8px; + padding: 8px; + border-radius: 6px; cursor: pointer; - font-size: 13px; + font-size: 14px; font-weight: 500; transition: all 0.2s ease; display: flex; align-items: center; - gap: 6px; + justify-content: center; + width: 32px; + height: 32px; + flex-shrink: 0; } .system-info-toggle:hover { background: rgba(255, 255, 255, 0.1); color: var(--c_text_primary); border-color: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); } From 6b82ca53469f8335e62f93166f78e229039d4768 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 03:47:07 +0200 Subject: [PATCH 14/18] feat: enhance local system information retrieval and UI updates - Implemented functions to fetch local Python version and NodeTool package versions, providing fallbacks when backend is unavailable. - Updated fetchBasicSystemInfo to include local data in the system information response. - Modified CSS for the system diagnostics panel, adjusting positioning and animation for improved user experience. --- electron/src/api.ts | 148 ++++++++++++++++++++++++++++++++++++++--- electron/src/index.css | 12 ++-- 2 files changed, 146 insertions(+), 14 deletions(-) diff --git a/electron/src/api.ts b/electron/src/api.ts index b23ca8980..a03e4e8ce 100644 --- a/electron/src/api.ts +++ b/electron/src/api.ts @@ -2,6 +2,10 @@ import { Workflow, BasicSystemInfo } from "./types"; import { logMessage } from "./logger"; import { serverState } from "./state"; import { app } from "electron"; +import { spawn } from "child_process"; +import { getPythonPath } from "./config"; +import * as fs from "fs"; +import * as path from "path"; export let isConnected = false; let healthCheckTimer: NodeJS.Timeout | null = null; @@ -77,6 +81,107 @@ export function stopPeriodicHealthCheck(): void { } } +/** + * Gets Python version locally by executing python --version + * @returns {Promise} Python version or null if unavailable + */ +async function getLocalPythonVersion(): Promise { + try { + const pythonPath = getPythonPath(); + + // Check if Python executable exists + if (!fs.existsSync(pythonPath)) { + return null; + } + + return new Promise((resolve) => { + const child = spawn(pythonPath, ["--version"], { + timeout: 3000, // 3 second timeout + stdio: ["ignore", "pipe", "pipe"], + }); + + let output = ""; + let errorOutput = ""; + + child.stdout?.on("data", (data) => { + output += data.toString(); + }); + + child.stderr?.on("data", (data) => { + errorOutput += data.toString(); + }); + + child.on("close", (code) => { + if (code === 0) { + // Python version is usually in format "Python 3.x.x" + const version = (output || errorOutput) + .trim() + .replace(/^Python\s+/, ""); + resolve(version || null); + } else { + resolve(null); + } + }); + + child.on("error", () => { + resolve(null); + }); + }); + } catch (error) { + logMessage(`Failed to get local Python version: ${error}`, "error"); + return null; + } +} + +/** + * Gets NodeTool package versions from local package.json files + * @returns {Promise<{core?: string, base?: string}>} Package versions + */ +async function getLocalNodeToolVersions(): Promise<{ + core?: string; + base?: string; +}> { + const versions: { core?: string; base?: string } = {}; + + try { + // Try to find package.json files in common locations + const appPath = app.getAppPath(); + const possiblePaths = [ + path.join(appPath, "..", "..", "package.json"), // From electron app to root + path.join(appPath, "..", "package.json"), + path.join(process.cwd(), "package.json"), + ]; + + for (const pkgPath of possiblePaths) { + try { + if (fs.existsSync(pkgPath)) { + const pkgContent = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + + // Check dependencies for nodetool packages + const allDeps = { + ...pkgContent.dependencies, + ...pkgContent.devDependencies, + ...pkgContent.peerDependencies, + }; + + if (allDeps["nodetool-core"]) { + versions.core = allDeps["nodetool-core"]; + } + if (allDeps["nodetool-base"]) { + versions.base = allDeps["nodetool-base"]; + } + } + } catch (e) { + // Continue to next path + } + } + } catch (error) { + logMessage(`Failed to get local NodeTool versions: ${error}`, "error"); + } + + return versions; +} + /** * Fetches basic system information for diagnostics * @returns {Promise} Basic system info or null if unavailable @@ -118,17 +223,35 @@ export async function fetchBasicSystemInfo(): Promise { serverStatus = "connected"; } - // Build system info object + // Get local fallback information when backend is unavailable + let localPythonVersion: string | null = null; + let localNodeToolVersions: { core?: string; base?: string } = {}; + + if (serverStatus === "disconnected") { + logMessage("Backend unavailable, fetching local system information..."); + [localPythonVersion, localNodeToolVersions] = await Promise.all([ + getLocalPythonVersion(), + getLocalNodeToolVersions(), + ]); + } + + // Build system info object with enhanced fallbacks const systemInfo: BasicSystemInfo = { os: { platform: systemData?.os?.platform || process.platform, - release: systemData?.os?.release || "Unknown", + release: systemData?.os?.release || require("os").release(), arch: systemData?.os?.arch || process.arch, }, versions: { - python: systemData?.versions?.python || undefined, - nodetool_core: systemData?.versions?.nodetool_core || undefined, - nodetool_base: systemData?.versions?.nodetool_base || undefined, + python: systemData?.versions?.python || localPythonVersion || undefined, + nodetool_core: + systemData?.versions?.nodetool_core || + localNodeToolVersions.core || + undefined, + nodetool_base: + systemData?.versions?.nodetool_base || + localNodeToolVersions.base || + undefined, }, paths: { data_dir: systemData?.paths?.data_dir || app.getPath("userData"), @@ -145,14 +268,23 @@ export async function fetchBasicSystemInfo(): Promise { } catch (error) { logMessage(`Failed to fetch system information: ${error}`, "error"); - // Return fallback system info + // Enhanced fallback with local information + const [localPythonVersion, localNodeToolVersions] = await Promise.all([ + getLocalPythonVersion().catch(() => null), + getLocalNodeToolVersions().catch(() => ({})), + ]); + return { os: { platform: process.platform, - release: "Unknown", + release: require("os").release(), arch: process.arch, }, - versions: {}, + versions: { + python: localPythonVersion || undefined, + nodetool_core: localNodeToolVersions.core || undefined, + nodetool_base: localNodeToolVersions.base || undefined, + }, paths: { data_dir: app.getPath("userData"), core_logs_dir: "Unknown", diff --git a/electron/src/index.css b/electron/src/index.css index 61ee41f3c..58950bb27 100644 --- a/electron/src/index.css +++ b/electron/src/index.css @@ -1594,9 +1594,9 @@ body::before { /* System Diagnostics Styles */ .system-diagnostics { position: absolute; - top: 20px; + top: 50%; left: 50%; - transform: translateX(-50%); + transform: translate(-50%, -50%); background: var(--c_panel_bg); border: 1px solid var(--c_panel_border); border-radius: 12px; @@ -1605,17 +1605,17 @@ body::before { max-width: 500px; width: 90vw; z-index: 1200; - animation: slideDown 0.2s ease-out; + animation: fadeIn 0.2s ease-out; } -@keyframes slideDown { +@keyframes fadeIn { from { opacity: 0; - transform: translateX(-50%) translateY(-10px); + transform: translate(-50%, -50%) scale(0.95); } to { opacity: 1; - transform: translateX(-50%) translateY(0); + transform: translate(-50%, -50%) scale(1); } } From b7b26591a5c904f06b77bd3a67d9fcacbd4a6ef2 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 03:55:07 +0200 Subject: [PATCH 15/18] feat: add CUDA version retrieval to system diagnostics - Implemented a function to fetch the local CUDA version using nvcc and nvidia-smi as fallbacks. - Updated the BasicSystemInfo interface to include CUDA version. - Enhanced the SystemDiagnostics component to display the CUDA version alongside other system information. - Adjusted fallback values in the system info display for improved clarity. --- electron/src/api.ts | 110 ++++++++++++++++-- electron/src/components/SystemDiagnostics.tsx | 29 +++-- electron/src/types.d.ts | 1 + 3 files changed, 113 insertions(+), 27 deletions(-) diff --git a/electron/src/api.ts b/electron/src/api.ts index a03e4e8ce..2cc66c343 100644 --- a/electron/src/api.ts +++ b/electron/src/api.ts @@ -6,6 +6,7 @@ import { spawn } from "child_process"; import { getPythonPath } from "./config"; import * as fs from "fs"; import * as path from "path"; +import * as os from "os"; export let isConnected = false; let healthCheckTimer: NodeJS.Timeout | null = null; @@ -182,6 +183,81 @@ async function getLocalNodeToolVersions(): Promise<{ return versions; } +/** + * Gets CUDA version locally by checking nvcc or nvidia-smi + * @returns {Promise} CUDA version or null if unavailable + */ +async function getLocalCudaVersion(): Promise { + try { + // Try nvcc first to get actual CUDA toolkit version + const nvccVersion = await new Promise((resolve) => { + const child = spawn("nvcc", ["--version"], { + timeout: 3000, + stdio: ["ignore", "pipe", "pipe"], + }); + + let output = ""; + child.stdout?.on("data", (data) => { + output += data.toString(); + }); + + child.on("close", (code) => { + if (code === 0) { + // Extract CUDA version from nvcc output (e.g., "release 12.1") + const match = output.match(/release (\d+\.\d+)/); + resolve(match ? match[1] : null); + } else { + resolve(null); + } + }); + + child.on("error", () => { + resolve(null); + }); + }); + + if (nvccVersion) { + return nvccVersion; + } + + // Try nvidia-smi to get CUDA runtime version as fallback + const nvidiaSmiCudaVersion = await new Promise((resolve) => { + const child = spawn( + "nvidia-smi", + ["--query-gpu=cuda_version", "--format=csv,noheader,nounits"], + { + timeout: 3000, + stdio: ["ignore", "pipe", "pipe"], + } + ); + + let output = ""; + child.stdout?.on("data", (data) => { + output += data.toString(); + }); + + child.on("close", (code) => { + if (code === 0 && output.trim()) { + const cudaVersion = output.trim().split("\n")[0]; + // nvidia-smi sometimes returns "N/A" for CUDA version + resolve(cudaVersion !== "N/A" ? cudaVersion : null); + } else { + resolve(null); + } + }); + + child.on("error", () => { + resolve(null); + }); + }); + + return nvidiaSmiCudaVersion; + } catch (error) { + logMessage(`Failed to get local CUDA version: ${error}`, "error"); + return null; + } +} + /** * Fetches basic system information for diagnostics * @returns {Promise} Basic system info or null if unavailable @@ -226,20 +302,23 @@ export async function fetchBasicSystemInfo(): Promise { // Get local fallback information when backend is unavailable let localPythonVersion: string | null = null; let localNodeToolVersions: { core?: string; base?: string } = {}; + let localCudaVersion: string | null = null; if (serverStatus === "disconnected") { logMessage("Backend unavailable, fetching local system information..."); - [localPythonVersion, localNodeToolVersions] = await Promise.all([ - getLocalPythonVersion(), - getLocalNodeToolVersions(), - ]); + [localPythonVersion, localNodeToolVersions, localCudaVersion] = + await Promise.all([ + getLocalPythonVersion(), + getLocalNodeToolVersions(), + getLocalCudaVersion(), + ]); } // Build system info object with enhanced fallbacks const systemInfo: BasicSystemInfo = { os: { platform: systemData?.os?.platform || process.platform, - release: systemData?.os?.release || require("os").release(), + release: systemData?.os?.release || os.release(), arch: systemData?.os?.arch || process.arch, }, versions: { @@ -252,10 +331,11 @@ export async function fetchBasicSystemInfo(): Promise { systemData?.versions?.nodetool_base || localNodeToolVersions.base || undefined, + cuda: systemData?.versions?.cuda || localCudaVersion || undefined, }, paths: { data_dir: systemData?.paths?.data_dir || app.getPath("userData"), - core_logs_dir: systemData?.paths?.core_logs_dir || "Unknown", + core_logs_dir: systemData?.paths?.core_logs_dir || "-", electron_logs_dir: app.getPath("logs"), }, server: { @@ -269,25 +349,31 @@ export async function fetchBasicSystemInfo(): Promise { logMessage(`Failed to fetch system information: ${error}`, "error"); // Enhanced fallback with local information - const [localPythonVersion, localNodeToolVersions] = await Promise.all([ - getLocalPythonVersion().catch(() => null), - getLocalNodeToolVersions().catch(() => ({})), - ]); + const [localPythonVersion, localNodeToolVersions, localCudaVersion] = + await Promise.all([ + getLocalPythonVersion().catch(() => null), + getLocalNodeToolVersions().catch(() => ({ + core: undefined, + base: undefined, + })), + getLocalCudaVersion().catch(() => null), + ]); return { os: { platform: process.platform, - release: require("os").release(), + release: os.release(), arch: process.arch, }, versions: { python: localPythonVersion || undefined, nodetool_core: localNodeToolVersions.core || undefined, nodetool_base: localNodeToolVersions.base || undefined, + cuda: localCudaVersion || undefined, }, paths: { data_dir: app.getPath("userData"), - core_logs_dir: "Unknown", + core_logs_dir: "-", electron_logs_dir: app.getPath("logs"), }, server: { diff --git a/electron/src/components/SystemDiagnostics.tsx b/electron/src/components/SystemDiagnostics.tsx index 2ba2626a0..8c363a901 100644 --- a/electron/src/components/SystemDiagnostics.tsx +++ b/electron/src/components/SystemDiagnostics.tsx @@ -10,6 +10,7 @@ interface BasicSystemInfo { python?: string; nodetool_core?: string; nodetool_base?: string; + cuda?: string; }; paths: { data_dir: string; @@ -89,15 +90,15 @@ const SystemDiagnostics: React.FC = ({ const info = [ `OS: ${systemInfo.os.platform} ${systemInfo.os.release} (${systemInfo.os.arch})`, - `Python: ${systemInfo.versions.python || "Unknown"}`, - `NodeTool Core: ${systemInfo.versions.nodetool_core || "Unknown"}`, - `NodeTool Base: ${systemInfo.versions.nodetool_base || "Unknown"}`, + `Python: ${systemInfo.versions.python || "-"}`, + `NodeTool Core: ${systemInfo.versions.nodetool_core || "-"}`, + `NodeTool Base: ${systemInfo.versions.nodetool_base || "-"}`, + `CUDA: ${systemInfo.versions.cuda || "-"}`, `Server Status: ${systemInfo.server.status}${ systemInfo.server.port ? ` (port ${systemInfo.server.port})` : "" }`, `Data Directory: ${systemInfo.paths.data_dir}`, - `Core Logs: ${systemInfo.paths.core_logs_dir}`, - `Electron Logs: ${systemInfo.paths.electron_logs_dir}`, + `Logs: ${systemInfo.paths.electron_logs_dir}`, ].join("\n"); try { @@ -151,21 +152,25 @@ const SystemDiagnostics: React.FC = ({
Python: - {systemInfo.versions.python || "Unknown"} + {systemInfo.versions.python || "-"}
NodeTool Core: - {systemInfo.versions.nodetool_core || "Unknown"} + {systemInfo.versions.nodetool_core || "-"}
NodeTool Base: - {systemInfo.versions.nodetool_base || "Unknown"} + {systemInfo.versions.nodetool_base || "-"}
+
+ CUDA: + {systemInfo.versions.cuda || "-"} +
@@ -187,13 +192,7 @@ const SystemDiagnostics: React.FC = ({ {systemInfo.paths.data_dir}
- Core Logs: - - {systemInfo.paths.core_logs_dir} - -
-
- Electron Logs: + Logs: {systemInfo.paths.electron_logs_dir} diff --git a/electron/src/types.d.ts b/electron/src/types.d.ts index 84acb93f2..d09f32a05 100644 --- a/electron/src/types.d.ts +++ b/electron/src/types.d.ts @@ -273,6 +273,7 @@ export interface BasicSystemInfo { python?: string; nodetool_core?: string; nodetool_base?: string; + cuda?: string; }; paths: { data_dir: string; From 4dd5028191aeaa03779caf0faaf12f7901dac578 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Sat, 16 Aug 2025 15:50:09 +0200 Subject: [PATCH 16/18] feat: extend system diagnostics to include GPU information - Added new fields for GPU name, VRAM, and driver version in the BasicSystemInfo interface. - Enhanced the SystemDiagnostics component to display GPU details if available. - Updated the SystemTab component to include GPU information in the system status output. --- electron/src/components/SystemDiagnostics.tsx | 42 +++++++++++++++++++ web/src/components/content/Help/SystemTab.tsx | 28 +++++++++++++ 2 files changed, 70 insertions(+) diff --git a/electron/src/components/SystemDiagnostics.tsx b/electron/src/components/SystemDiagnostics.tsx index 8c363a901..2b85f064f 100644 --- a/electron/src/components/SystemDiagnostics.tsx +++ b/electron/src/components/SystemDiagnostics.tsx @@ -11,6 +11,9 @@ interface BasicSystemInfo { nodetool_core?: string; nodetool_base?: string; cuda?: string; + gpu_name?: string; + vram_total_gb?: string; + driver_version?: string; }; paths: { data_dir: string; @@ -94,6 +97,19 @@ const SystemDiagnostics: React.FC = ({ `NodeTool Core: ${systemInfo.versions.nodetool_core || "-"}`, `NodeTool Base: ${systemInfo.versions.nodetool_base || "-"}`, `CUDA: ${systemInfo.versions.cuda || "-"}`, + ...(systemInfo.versions.gpu_name || + systemInfo.versions.vram_total_gb || + systemInfo.versions.driver_version + ? [ + `GPU: ${systemInfo.versions.gpu_name || "N/A"}`, + `VRAM: ${ + systemInfo.versions.vram_total_gb + ? systemInfo.versions.vram_total_gb + "GB" + : "N/A" + }`, + `Driver: ${systemInfo.versions.driver_version || "N/A"}`, + ] + : []), `Server Status: ${systemInfo.server.status}${ systemInfo.server.port ? ` (port ${systemInfo.server.port})` : "" }`, @@ -171,6 +187,32 @@ const SystemDiagnostics: React.FC = ({ CUDA: {systemInfo.versions.cuda || "-"}
+ {(systemInfo.versions.gpu_name || + systemInfo.versions.vram_total_gb || + systemInfo.versions.driver_version) && ( + <> +
+ GPU: + + {systemInfo.versions.gpu_name || "N/A"} + +
+
+ VRAM: + + {systemInfo.versions.vram_total_gb + ? systemInfo.versions.vram_total_gb + "GB" + : "N/A"} + +
+
+ Driver: + + {systemInfo.versions.driver_version || "N/A"} + +
+ + )}
diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index 2bc1fa262..55fc4d6b5 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -25,6 +25,9 @@ type VersionsInfo = { nodetool_core?: string | null; nodetool_base?: string | null; cuda?: string | null; + gpu_name?: string | null; + vram_total_gb?: string | null; + driver_version?: string | null; }; type PathsInfo = { settings_path: string; @@ -211,6 +214,19 @@ export default function SystemTab() { info.versions.cuda ?? "" }` ); + if ( + info.versions.gpu_name || + info.versions.vram_total_gb || + info.versions.driver_version + ) { + lines.push( + `GPU: ${info.versions.gpu_name ?? "N/A"}, VRAM=${ + info.versions.vram_total_gb + ? info.versions.vram_total_gb + "GB" + : "N/A" + }, driver=${info.versions.driver_version ?? "N/A"}` + ); + } lines.push("Paths:"); const p = info.paths; lines.push(` settings_path: ${p.settings_path}`); @@ -324,6 +340,18 @@ export default function SystemTab() { {info.versions.nodetool_base ?? ""} cuda= {info.versions.cuda ?? ""} + {(info.versions.gpu_name || + info.versions.vram_total_gb || + info.versions.driver_version) && ( + + GPU: {info.versions.gpu_name ?? "N/A"}, VRAM= + {info.versions.vram_total_gb + ? info.versions.vram_total_gb + "GB" + : "N/A"} + , driver= + {info.versions.driver_version ?? "N/A"} + + )}
From 2675328f140629f19af95a36eb2f3954985616af Mon Sep 17 00:00:00 2001 From: heavy-d Date: Tue, 19 Aug 2025 19:18:25 +0200 Subject: [PATCH 17/18] feat: update SystemTab to handle missing API endpoints and improve system info retrieval - Replaced BASE_URL with client for API calls to enhance flexibility. - Implemented fallback logic to create basic system and health information structures when specific endpoints are unavailable. - Updated error handling to ensure fallback data is set even when API calls fail. - Adjusted CSS styles for better presentation of system information. --- web/src/components/content/Help/SystemTab.tsx | 161 ++++++++++++------ 1 file changed, 105 insertions(+), 56 deletions(-) diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index 55fc4d6b5..fac8b1ad1 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -15,7 +15,7 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import ErrorIcon from "@mui/icons-material/Error"; import WarningAmberIcon from "@mui/icons-material/WarningAmber"; import { CopyToClipboardButton } from "../../common/CopyToClipboardButton"; -import { BASE_URL } from "../../../stores/ApiClient"; +import { client } from "../../../stores/ApiClient"; import { getIsElectronDetails } from "../../../utils/browser"; import { isPathValid, openInExplorer } from "../../../utils/fileExplorer"; @@ -91,7 +91,7 @@ const systemTabStyles = (theme: Theme) => color: theme.vars.palette.grey[300] }, ".value": { - fontFamily: "var(--fontFamilyMonospace)", + fontFamily: "var(--fontFamily2)", color: theme.vars.palette.grey[100], wordBreak: "break-all", flex: 1 @@ -129,70 +129,119 @@ export default function SystemTab() { let cancelled = false; async function load() { try { - const [infoRes, healthRes] = await Promise.all([ - fetch(`${BASE_URL}/api/system/`, { - headers: { Accept: "application/json" } - }), - fetch(`${BASE_URL}/api/system/health`, { - headers: { Accept: "application/json" } - }) + // Since /api/system/ endpoint doesn't exist, we'll create a basic system info + // and fetch what we can from available endpoints + const [healthResult, ollamaPathResult, hfPathResult] = await Promise.all([ + // Use the available /health endpoint instead of /api/system/health + client.GET("/health", {}), + client.GET("/api/models/ollama_base_path", {}), + client.GET("/api/models/huggingface_base_path", {}) ]); - if (!infoRes.ok) { - throw new Error( - `Failed to load system info: ${infoRes.status} ${infoRes.statusText}` - ); - } - if (!healthRes.ok) { - throw new Error( - `Failed to load health info: ${healthRes.status} ${healthRes.statusText}` - ); - } - - const infoJson = (await infoRes.json()) as SystemInfoResponse; - const healthJson = (await healthRes.json()) as HealthResponse; + console.log("Health result:", healthResult); + console.log("Ollama path result:", ollamaPathResult); + console.log("HF path result:", hfPathResult); - // Fallback: fetch Ollama/HF paths from existing endpoints if missing - try { - const updates: Partial = {}; - if (!infoJson.paths.ollama_models_dir) { - const res = await fetch(`${BASE_URL}/api/models/ollama_base_path`, { - headers: { Accept: "application/json" } - }); - if (res.ok) { - const data = (await res.json()) as OllamaBasePathResponse; - if (data?.path) { - updates.ollama_models_dir = data.path; - } - } + // Create a basic system info structure since the endpoint doesn't exist + const basicSystemInfo: SystemInfoResponse = { + os: { + platform: navigator.platform || "Unknown", + release: "Unknown", + arch: "Unknown" + }, + versions: { + python: null, + nodetool_core: null, + nodetool_base: null, + cuda: null, + gpu_name: null, + vram_total_gb: null, + driver_version: null + }, + paths: { + settings_path: "Not available", + secrets_path: "Not available", + data_dir: "Not available", + core_logs_dir: "Not available", + core_log_file: "Not available", + ollama_models_dir: (ollamaPathResult.data as any)?.path || "Not available", + huggingface_cache_dir: (hfPathResult.data as any)?.path || "Not available", + electron_user_data: "Not available", + electron_log_file: "Not available", + electron_logs_dir: "Not available" } - if (!infoJson.paths.huggingface_cache_dir) { - const res = await fetch( - `${BASE_URL}/api/models/huggingface_base_path`, - { - headers: { Accept: "application/json" } - } - ); - if (res.ok) { - const data = (await res.json()) as HuggingFaceBasePathResponse; - if (data?.path) { - updates.huggingface_cache_dir = data.path; - } + }; + + // Create a basic health response since the system health endpoint doesn't exist + const hasHealthError = !healthResult.data; + const basicHealthInfo: HealthResponse = { + checks: [ + { + id: "api_connection", + status: hasHealthError ? "error" : "ok", + details: hasHealthError ? "API connection failed" : "API connection successful", + fix_hint: hasHealthError ? "Check if the server is running" : null } + ], + summary: { + ok: hasHealthError ? 0 : 1, + warn: 0, + error: hasHealthError ? 1 : 0 } - if (Object.keys(updates).length > 0) { - infoJson.paths = { ...infoJson.paths, ...updates }; - } - } catch (e) { - // ignore fallback errors - } + }; + if (!cancelled) { - setInfo(infoJson); - setHealth(healthJson); + setInfo(basicSystemInfo); + setHealth(basicHealthInfo); } } catch (e) { console.error("Failed to load system info/health:", e); - // You could set an error state here to show user-friendly error messages + // Create fallback info even on error + if (!cancelled) { + const fallbackInfo: SystemInfoResponse = { + os: { + platform: navigator.platform || "Unknown", + release: "Unknown", + arch: "Unknown" + }, + versions: { + python: null, + nodetool_core: null, + nodetool_base: null, + cuda: null, + gpu_name: null, + vram_total_gb: null, + driver_version: null + }, + paths: { + settings_path: "Not available", + secrets_path: "Not available", + data_dir: "Not available", + core_logs_dir: "Not available", + core_log_file: "Not available", + ollama_models_dir: "Not available", + huggingface_cache_dir: "Not available", + electron_user_data: "Not available", + electron_log_file: "Not available", + electron_logs_dir: "Not available" + } + }; + + const fallbackHealth: HealthResponse = { + checks: [ + { + id: "system_check", + status: "error", + details: "Unable to retrieve system information", + fix_hint: "Check server connection and try again" + } + ], + summary: { ok: 0, warn: 0, error: 1 } + }; + + setInfo(fallbackInfo); + setHealth(fallbackHealth); + } } finally { if (!cancelled) setLoading(false); } From eed347ccd2086b84dd19b2658d1b90c9b851cd97 Mon Sep 17 00:00:00 2001 From: heavy-d Date: Tue, 19 Aug 2025 19:18:37 +0200 Subject: [PATCH 18/18] refactor: clean up formatting and improve readability in SystemTab component - Adjusted code formatting for better readability, including consistent indentation and removal of unnecessary blank lines. - Ensured clarity in the structure of API call handling and fallback data assignment. --- web/src/components/content/Help/SystemTab.tsx | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/web/src/components/content/Help/SystemTab.tsx b/web/src/components/content/Help/SystemTab.tsx index fac8b1ad1..41790a0fc 100644 --- a/web/src/components/content/Help/SystemTab.tsx +++ b/web/src/components/content/Help/SystemTab.tsx @@ -131,12 +131,13 @@ export default function SystemTab() { try { // Since /api/system/ endpoint doesn't exist, we'll create a basic system info // and fetch what we can from available endpoints - const [healthResult, ollamaPathResult, hfPathResult] = await Promise.all([ - // Use the available /health endpoint instead of /api/system/health - client.GET("/health", {}), - client.GET("/api/models/ollama_base_path", {}), - client.GET("/api/models/huggingface_base_path", {}) - ]); + const [healthResult, ollamaPathResult, hfPathResult] = + await Promise.all([ + // Use the available /health endpoint instead of /api/system/health + client.GET("/health", {}), + client.GET("/api/models/ollama_base_path", {}), + client.GET("/api/models/huggingface_base_path", {}) + ]); console.log("Health result:", healthResult); console.log("Ollama path result:", ollamaPathResult); @@ -160,12 +161,14 @@ export default function SystemTab() { }, paths: { settings_path: "Not available", - secrets_path: "Not available", + secrets_path: "Not available", data_dir: "Not available", core_logs_dir: "Not available", core_log_file: "Not available", - ollama_models_dir: (ollamaPathResult.data as any)?.path || "Not available", - huggingface_cache_dir: (hfPathResult.data as any)?.path || "Not available", + ollama_models_dir: + (ollamaPathResult.data as any)?.path || "Not available", + huggingface_cache_dir: + (hfPathResult.data as any)?.path || "Not available", electron_user_data: "Not available", electron_log_file: "Not available", electron_logs_dir: "Not available" @@ -179,7 +182,9 @@ export default function SystemTab() { { id: "api_connection", status: hasHealthError ? "error" : "ok", - details: hasHealthError ? "API connection failed" : "API connection successful", + details: hasHealthError + ? "API connection failed" + : "API connection successful", fix_hint: hasHealthError ? "Check if the server is running" : null } ], @@ -201,7 +206,7 @@ export default function SystemTab() { const fallbackInfo: SystemInfoResponse = { os: { platform: navigator.platform || "Unknown", - release: "Unknown", + release: "Unknown", arch: "Unknown" }, versions: { @@ -216,7 +221,7 @@ export default function SystemTab() { paths: { settings_path: "Not available", secrets_path: "Not available", - data_dir: "Not available", + data_dir: "Not available", core_logs_dir: "Not available", core_log_file: "Not available", ollama_models_dir: "Not available", @@ -226,7 +231,7 @@ export default function SystemTab() { electron_logs_dir: "Not available" } }; - + const fallbackHealth: HealthResponse = { checks: [ { @@ -238,7 +243,7 @@ export default function SystemTab() { ], summary: { ok: 0, warn: 0, error: 1 } }; - + setInfo(fallbackInfo); setHealth(fallbackHealth); }