936 lines
43 KiB
HTML
936 lines
43 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Nexus One AI Portal</title>
|
|
<link rel="stylesheet" href="style.css?v=10">
|
|
</head>
|
|
<body>
|
|
|
|
<header class="topnav">
|
|
<a href="index.html" class="brand">Nexus One <span>AI</span></a>
|
|
<nav>
|
|
<a href="index.html" class="active">Home</a>
|
|
<a href="quickstart.html">Quick Start</a>
|
|
<a href="prompts.html">Prompt Library</a>
|
|
<a href="usecases.html">Use Cases</a>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">AI Tools ▾</button>
|
|
<div class="nav-drop-menu">
|
|
<span class="nav-drop-cat">INTELLIGENCE /</span>
|
|
<a href="documents.html">Document Intelligence</a>
|
|
<a href="chat-multi.html">Multimodal Chat</a>
|
|
<a href="prompt-studio.html">Prompt Studio</a>
|
|
<a href="meeting.html">Meeting Assistant</a>
|
|
<span class="nav-drop-cat">AUTOMATION /</span>
|
|
<a href="agents.html">Agent Builder</a>
|
|
<a href="schedules.html">Scheduled Jobs</a>
|
|
<a href="workflows.html">Workflow Automation</a>
|
|
<span class="nav-drop-cat">QUALITY /</span>
|
|
<a href="evals.html">AI Eval Suite</a>
|
|
<a href="chatrooms.html">Chat Rooms</a>
|
|
</div>
|
|
</div>
|
|
<span class="nav-sep"></span>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">Help ▾</button>
|
|
<div class="nav-drop-menu">
|
|
<span class="nav-drop-cat">LEARN /</span>
|
|
<a href="quickstart.html">Quick Start</a>
|
|
<a href="models.html">Models</a>
|
|
<span class="nav-drop-cat">SUPPORT /</span>
|
|
<a href="troubleshooting.html">Troubleshoot</a>
|
|
<a href="faq.html">FAQ</a>
|
|
<span class="nav-drop-cat">MORE /</span>
|
|
<a href="glossary.html">Glossary</a>
|
|
<a href="whats-new.html">What's New</a>
|
|
</div>
|
|
</div>
|
|
<div class="nav-dropdown">
|
|
<button class="nav-drop-btn">Admin ▾</button>
|
|
<div class="nav-drop-menu nav-drop-menu-wide">
|
|
<span class="nav-drop-cat">DOCS /</span>
|
|
<a href="security.html">Security & Privacy</a>
|
|
<a href="admin.html">Admin Guide</a>
|
|
<span class="nav-drop-cat">MONITOR /</span>
|
|
<a href="dashboard.html">Dashboard</a>
|
|
<a href="analytics.html">Usage Analytics</a>
|
|
<a href="audit.html">Audit Log</a>
|
|
<a href="feedback.html">Feedback & Ratings</a>
|
|
<span class="nav-drop-cat">MANAGE /</span>
|
|
<a href="users.html">Users</a>
|
|
<a href="teams.html">Teams</a>
|
|
<a href="models-admin.html">Model Manager</a>
|
|
<a href="training.html">Training</a>
|
|
<a href="knowledge.html">Knowledge Base</a>
|
|
<span class="nav-drop-cat">TOOLS /</span>
|
|
<a href="apikeys.html">API Keys</a>
|
|
<a href="benchmark.html">Benchmarking</a>
|
|
<a href="model-compare.html">Model Compare</a>
|
|
<a href="api-playground.html">API Playground</a>
|
|
<a href="guardrails.html">Guardrails</a>
|
|
<a href="rag-quality.html">RAG Quality</a>
|
|
<a href="router.html">Model Router</a>
|
|
<a href="connectors.html">Connectors</a>
|
|
<span class="nav-drop-cat">SYSTEM /</span>
|
|
<a href="appliance.html">Appliance Ops</a>
|
|
<a href="console.html">Console</a>
|
|
<a href="settings.html">Settings</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<div class="nav-right">
|
|
<div id="nav-org-logo" class="nav-org-logo"></div>
|
|
<!-- online pill + user chip injected here by auth.js -->
|
|
<span class="nav-tier-badge" data-brand="tier" data-tier-slug="basic">Basic Tier</span>
|
|
<a href="notifications.html" class="nav-icon-btn" title="Notifications">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
|
</a>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="page-hero">
|
|
<!-- Animated background orbs -->
|
|
<div class="hero-orb hero-orb-1" aria-hidden="true"></div>
|
|
<div class="hero-orb hero-orb-2" aria-hidden="true"></div>
|
|
<div class="hero-orb hero-orb-3" aria-hidden="true"></div>
|
|
|
|
<div class="eyebrow hero-animate d1">
|
|
<svg viewBox="0 0 12 12" width="10" height="10"><circle cx="6" cy="6" r="4" fill="currentColor"/></svg>
|
|
Knowledge Portal
|
|
</div>
|
|
<h1 class="hero-animate d2">Your <em>Enterprise AI</em> Platform,<br>Running On-Premises</h1>
|
|
<p class="hero-animate d3">Access every AI tool, model, and resource on your dedicated system — private, secure, and fully under your control.</p>
|
|
<div class="hero-ctas hero-animate d4">
|
|
<a href="quickstart.html" class="btn-primary">
|
|
<i data-lucide="zap" style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"></i>
|
|
Get Started
|
|
</a>
|
|
<a href="chat-multi.html" class="btn-secondary">
|
|
<i data-lucide="message-square" style="width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i>
|
|
Open Chat
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- PLATFORM STATS STRIP -->
|
|
<div class="stats-strip">
|
|
<div class="stat-item">
|
|
<div class="stat-num">49</div>
|
|
<div class="stat-label">Portal Pages</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-num">12+</div>
|
|
<div class="stat-label">AI Tools</div>
|
|
</div>
|
|
<div class="stat-item">
|
|
<div class="stat-num" id="stat-vram">—</div>
|
|
<div class="stat-label">GPU VRAM</div>
|
|
</div>
|
|
<div class="stat-item" style="border-right:none">
|
|
<div class="stat-num">100%</div>
|
|
<div class="stat-label">On-Premises</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
|
|
<!-- STATUS -->
|
|
<div class="status-bar" id="status-bar">
|
|
<div class="status-bar-header">
|
|
<div class="status-bar-title">
|
|
<span id="status-overall-dot" class="overall-dot checking"></span>
|
|
<span id="status-overall-label">Checking system…</span>
|
|
</div>
|
|
<div class="status-bar-right">
|
|
<span id="status-last-checked" class="status-ts"></span>
|
|
<button class="status-refresh-btn" onclick="runChecks()" title="Refresh status">↺ Refresh</button>
|
|
</div>
|
|
</div>
|
|
<div class="status-cards" id="status-cards">
|
|
<!-- injected by JS -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SYSTEM INFORMATION -->
|
|
<div class="section-title" id="system">System Information</div>
|
|
<div class="sysinfo-wrap">
|
|
|
|
<!-- Static specs -->
|
|
<div class="sysinfo-specs">
|
|
<div class="sysinfo-specs-title">Server Specifications</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">Hostname</span>
|
|
<span class="sysinfo-spec-value" id="spec-hostname">Detecting…</span>
|
|
</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">IP Address</span>
|
|
<span class="sysinfo-spec-value" id="spec-ip">Detecting…</span>
|
|
</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">GPU</span>
|
|
<span class="sysinfo-spec-value" id="spec-gpu">Detecting…</span>
|
|
</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">System RAM</span>
|
|
<span class="sysinfo-spec-value" id="spec-ram">Detecting…</span>
|
|
</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">Storage</span>
|
|
<span class="sysinfo-spec-value" id="spec-storage">Detecting…</span>
|
|
</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">OS</span>
|
|
<span class="sysinfo-spec-value" id="spec-os">Detecting…</span>
|
|
</div>
|
|
<div class="sysinfo-spec-row">
|
|
<span class="sysinfo-spec-label">Tier</span>
|
|
<span class="sysinfo-spec-value"><span class="sysinfo-tier-pill" data-brand="tier">Basic Tier</span></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Live metrics -->
|
|
<div class="sysinfo-metrics">
|
|
<div class="sysinfo-metrics-header">
|
|
<div class="sysinfo-specs-title">Live System Metrics</div>
|
|
<span class="sysinfo-updated" id="metrics-ts">—</span>
|
|
</div>
|
|
<div class="sysinfo-metric-grid">
|
|
<div class="sysinfo-metric-card">
|
|
<div class="sysinfo-metric-icon"><i data-lucide="cpu" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div class="sysinfo-metric-label">CPU Load</div>
|
|
<div class="sysinfo-metric-val" id="met-cpu">—</div>
|
|
<div class="sysinfo-bar-wrap"><div class="sysinfo-bar" id="bar-cpu"></div></div>
|
|
</div>
|
|
<div class="sysinfo-metric-card">
|
|
<div class="sysinfo-metric-icon"><i data-lucide="activity" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div class="sysinfo-metric-label">GPU Utilisation</div>
|
|
<div class="sysinfo-metric-val" id="met-gpu">—</div>
|
|
<div class="sysinfo-bar-wrap"><div class="sysinfo-bar" id="bar-gpu"></div></div>
|
|
</div>
|
|
<div class="sysinfo-metric-card">
|
|
<div class="sysinfo-metric-icon"><i data-lucide="thermometer" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div class="sysinfo-metric-label">GPU Temp</div>
|
|
<div class="sysinfo-metric-val" id="met-gtemp">—</div>
|
|
<div class="sysinfo-bar-wrap"><div class="sysinfo-bar" id="bar-gtemp"></div></div>
|
|
</div>
|
|
<div class="sysinfo-metric-card">
|
|
<div class="sysinfo-metric-icon"><i data-lucide="memory-stick" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div class="sysinfo-metric-label">RAM Used</div>
|
|
<div class="sysinfo-metric-val" id="met-ram">—</div>
|
|
<div class="sysinfo-bar-wrap"><div class="sysinfo-bar" id="bar-ram"></div></div>
|
|
</div>
|
|
<div class="sysinfo-metric-card">
|
|
<div class="sysinfo-metric-icon"><i data-lucide="hard-drive" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div class="sysinfo-metric-label">Disk Used</div>
|
|
<div class="sysinfo-metric-val" id="met-disk">—</div>
|
|
<div class="sysinfo-bar-wrap"><div class="sysinfo-bar" id="bar-disk"></div></div>
|
|
</div>
|
|
<div class="sysinfo-metric-card">
|
|
<div class="sysinfo-metric-icon"><i data-lucide="clock" style="width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div class="sysinfo-metric-label">Uptime</div>
|
|
<div class="sysinfo-metric-val sysinfo-uptime" id="met-uptime">—</div>
|
|
</div>
|
|
</div>
|
|
<div class="sysinfo-notice" id="metrics-notice" style="display:none">
|
|
<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
|
Live metrics unavailable — backend API not reachable.
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- TASKS -->
|
|
<div class="section-title" id="tasks">What do you want to do?</div>
|
|
<div class="task-grid">
|
|
|
|
<a class="task-card" href="tool-openwebui.html">
|
|
<div class="task-icon"><i data-lucide="message-circle" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Chat with an AI model</h3>
|
|
<p>Have a conversation, ask questions, or get help with any task using a local AI model.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">Open WebUI</span>
|
|
<span class="tag">Ollama</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-chromadb.html">
|
|
<div class="task-icon"><i data-lucide="file-search" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Chat with my documents</h3>
|
|
<p>Upload PDFs, reports, or SOPs and ask the AI questions about them.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">Open WebUI</span>
|
|
<span class="tag">ChromaDB</span>
|
|
<span class="tag">Ollama</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-ollama.html">
|
|
<div class="task-icon"><i data-lucide="bot" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Run or switch AI models</h3>
|
|
<p>Download, manage, and run different open-weight AI models on your system.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">Ollama</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-jupyter.html">
|
|
<div class="task-icon"><i data-lucide="code-2" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Write and run AI code</h3>
|
|
<p>Use Python notebooks to build AI workflows, process data, or experiment with models.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">Jupyter</span>
|
|
<span class="tag">LangChain</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-fastapi.html">
|
|
<div class="task-icon"><i data-lucide="plug" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Connect AI to my application</h3>
|
|
<p>Call AI from your own software using a REST API — no Python knowledge required.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">Ollama API</span>
|
|
<span class="tag">FastAPI</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-dcgm.html">
|
|
<div class="task-icon"><i data-lucide="gauge" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Monitor GPU health and usage</h3>
|
|
<p>Check if your GPUs are being used, how hot they are, and how much memory is free.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">DCGM</span>
|
|
<span class="tag">nvtop</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-langchain.html">
|
|
<div class="task-icon"><i data-lucide="git-branch" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Build an AI pipeline or workflow</h3>
|
|
<p>Chain AI steps together — summarise, extract, classify, and route — in code.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">LangChain</span>
|
|
<span class="tag">Jupyter</span>
|
|
<span class="tag">ChromaDB</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="task-card" href="tool-qlora.html">
|
|
<div class="task-icon"><i data-lucide="target" style="width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<h3>Fine-tune a model on my data</h3>
|
|
<p>Train an AI model to behave in a way specific to your organisation or domain.</p>
|
|
<div class="tools-used">
|
|
<span class="tag teal">QLoRA</span>
|
|
<span class="tag">Unsloth</span>
|
|
<span class="tag">Jupyter</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
</div>
|
|
|
|
<!-- FEATURE HIGHLIGHTS -->
|
|
<div class="section-title" id="features">Enterprise AI Features</div>
|
|
<div class="feature-grid">
|
|
|
|
<a href="prompt-studio.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="pencil-line" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Prompt Studio</div><div class="feature-card-desc">Engineer, test & iterate prompts with streaming output and run history.</div></div>
|
|
</a>
|
|
|
|
<a href="documents.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="file-text" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Document Intelligence</div><div class="feature-card-desc">Extract, classify & summarise PDFs and documents using AI.</div></div>
|
|
</a>
|
|
|
|
<a href="agents.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="brain-circuit" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Agent Builder</div><div class="feature-card-desc">Design autonomous AI agents with tools, memory & approval gates.</div></div>
|
|
</a>
|
|
|
|
<a href="knowledge.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="database" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Knowledge Base (RAG)</div><div class="feature-card-desc">Ingest your documents into a vector store for private AI search.</div></div>
|
|
</a>
|
|
|
|
<a href="model-compare.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="scale" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Model Compare</div><div class="feature-card-desc">Run the same prompt across multiple models side-by-side instantly.</div></div>
|
|
</a>
|
|
|
|
<a href="guardrails.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="shield-check" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Guardrails</div><div class="feature-card-desc">Content filters and policy rules that protect every AI interaction.</div></div>
|
|
</a>
|
|
|
|
<a href="meeting.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="mic" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Meeting Assistant</div><div class="feature-card-desc">Transcribe, summarise and action meetings using Whisper AI.</div></div>
|
|
</a>
|
|
|
|
<a href="notifications.html" class="feature-card">
|
|
<div class="feature-card-icon"><i data-lucide="bell" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div><div class="feature-card-title">Notifications</div><div class="feature-card-desc">Unified alert inbox for guardrail triggers, job failures & system events.</div></div>
|
|
</a>
|
|
|
|
</div>
|
|
|
|
<!-- TOOLS -->
|
|
<div class="section-title" id="tools">All tools on your system</div>
|
|
<div class="tool-grid">
|
|
|
|
<a class="tool-card" href="tool-ollama.html">
|
|
<div class="t-icon"><i data-lucide="server" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">Model Serving</div>
|
|
<h3>Ollama</h3>
|
|
<p>Runs AI models locally. The engine behind everything on this system.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-openwebui.html">
|
|
<div class="t-icon"><i data-lucide="layout-dashboard" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">Chat Interface</div>
|
|
<h3>Open WebUI</h3>
|
|
<p>Browser-based chat interface — your local alternative to ChatGPT.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-chromadb.html">
|
|
<div class="t-icon"><i data-lucide="layers" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">Document Search</div>
|
|
<h3>ChromaDB</h3>
|
|
<p>Stores and searches your documents so the AI can answer questions about them.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-langchain.html">
|
|
<div class="t-icon"><i data-lucide="link-2" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">AI Framework</div>
|
|
<h3>LangChain</h3>
|
|
<p>Python toolkit for building AI workflows — RAG, agents, chains.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-jupyter.html">
|
|
<div class="t-icon"><i data-lucide="notebook-pen" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">Development</div>
|
|
<h3>Jupyter Notebook</h3>
|
|
<p>Run Python code in your browser — no IDE or setup needed.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-fastapi.html">
|
|
<div class="t-icon"><i data-lucide="zap" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">API Layer</div>
|
|
<h3>FastAPI</h3>
|
|
<p>Build REST APIs that wrap AI functionality for your applications.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-dcgm.html">
|
|
<div class="t-icon"><i data-lucide="radar" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">GPU Monitoring</div>
|
|
<h3>DCGM</h3>
|
|
<p>NVIDIA's GPU health and metrics tool — tracks utilisation, temperature, errors.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-nvtop.html">
|
|
<div class="t-icon"><i data-lucide="trending-up" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">GPU Monitoring</div>
|
|
<h3>nvtop</h3>
|
|
<p>Live GPU dashboard in the terminal — see what's running right now.</p>
|
|
</div>
|
|
</a>
|
|
|
|
<a class="tool-card" href="tool-qlora.html">
|
|
<div class="t-icon"><i data-lucide="sliders" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<div>
|
|
<div class="cat">Fine-Tuning</div>
|
|
<h3>QLoRA / Unsloth</h3>
|
|
<p>Efficiently fine-tune large models on your own data using minimal VRAM.</p>
|
|
</div>
|
|
</a>
|
|
|
|
</div>
|
|
|
|
<!-- HELP & REFERENCE -->
|
|
<div class="section-title">Help & Reference</div>
|
|
<div class="help-grid">
|
|
|
|
<a class="help-card" href="quickstart.html">
|
|
<div class="h-icon"><i data-lucide="rocket" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Quick Start</h3>
|
|
<p>New to the system? Follow 5 steps to have your first AI conversation in minutes.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="models.html">
|
|
<div class="h-icon"><i data-lucide="box" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Model Library</h3>
|
|
<p>All installed AI models — what each is good for and which to use for your task.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="troubleshooting.html">
|
|
<div class="h-icon"><i data-lucide="wrench" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Troubleshooting</h3>
|
|
<p>Something not working? Find your issue and fix it without calling support.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="faq.html">
|
|
<div class="h-icon"><i data-lucide="circle-help" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>FAQ</h3>
|
|
<p>Plain answers to the questions we hear most often about security, capacity, and usage.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="glossary.html">
|
|
<div class="h-icon"><i data-lucide="book-open" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Glossary</h3>
|
|
<p>AI terms explained in plain English — RAG, VRAM, fine-tuning, embeddings, and more.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="prompts.html">
|
|
<div class="h-icon"><i data-lucide="sparkles" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Prompt Library</h3>
|
|
<p>32 ready-to-use prompts for procurement, HR, legal, finance, and more. Copy and go.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="usecases.html">
|
|
<div class="h-icon"><i data-lucide="lightbulb" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Use Cases</h3>
|
|
<p>See what other teams are doing with AI — real tasks, real time savings, by department.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="security.html">
|
|
<div class="h-icon"><i data-lucide="lock" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Security & Privacy</h3>
|
|
<p>How your data stays on your own servers — and never reaches any cloud or third party.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="admin.html">
|
|
<div class="h-icon"><i data-lucide="settings-2" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Admin Guide</h3>
|
|
<p>For system administrators — restart services, manage users, add models, check logs.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="appliance.html">
|
|
<div class="h-icon"><i data-lucide="server-cog" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Appliance Ops</h3>
|
|
<p>Readiness report, license tier, audit summary, and backup/restore controls.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="whats-new.html">
|
|
<div class="h-icon"><i data-lucide="sparkles" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>What's New</h3>
|
|
<p>Release notes, changelog, and what's planned for the Mid and Advanced tier upgrades.</p>
|
|
</a>
|
|
|
|
<a class="help-card" href="mailto:support@cezentech.com">
|
|
<div class="h-icon"><i data-lucide="life-buoy" style="width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round"></i></div>
|
|
<h3>Contact Support</h3>
|
|
<p>Still stuck? Email support@cezentech.com — include a screenshot of the status panel above.</p>
|
|
</a>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<footer>
|
|
<p>Nexus One AI · Powered by Cezen · <span data-brand="tier">Basic Tier</span></p>
|
|
<p>Questions? <a href="mailto:support@cezentech.com">support@cezentech.com</a> · <a href="https://cezentech.com" target="_blank">cezentech.com</a></p>
|
|
</footer>
|
|
|
|
<script>
|
|
const SVC_ICONS = {
|
|
openwebui: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>`,
|
|
ollama: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>`,
|
|
chromadb: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3"/></svg>`,
|
|
jupyter: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>`,
|
|
mlflow: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>`,
|
|
grafana: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>`,
|
|
};
|
|
|
|
const SERVICES = [
|
|
{
|
|
id: 'openwebui',
|
|
name: 'Open WebUI',
|
|
desc: 'Chat Interface',
|
|
icon: SVC_ICONS.openwebui,
|
|
port: 3001,
|
|
url: 'http://ai.local:3001',
|
|
link: 'tool-openwebui.html',
|
|
openLink: 'http://ai.local:3001',
|
|
},
|
|
{
|
|
id: 'ollama',
|
|
name: 'Ollama',
|
|
desc: 'Model Engine',
|
|
icon: SVC_ICONS.ollama,
|
|
port: 11434,
|
|
url: 'http://ai.local:11434',
|
|
link: 'tool-ollama.html',
|
|
openLink: null,
|
|
},
|
|
{
|
|
id: 'chromadb',
|
|
name: 'ChromaDB',
|
|
desc: 'Document Store',
|
|
icon: SVC_ICONS.chromadb,
|
|
port: 8000,
|
|
url: 'http://ai.local:8000',
|
|
link: 'tool-chromadb.html',
|
|
openLink: null,
|
|
},
|
|
{
|
|
id: 'jupyter',
|
|
name: 'Jupyter',
|
|
desc: 'Notebooks',
|
|
icon: SVC_ICONS.jupyter,
|
|
port: 8888,
|
|
url: 'http://ai.local:8888',
|
|
link: 'tool-jupyter.html',
|
|
openLink: 'http://ai.local:8888',
|
|
},
|
|
];
|
|
|
|
// Check a single service — resolves to 'up', 'down', or 'timeout'
|
|
async function checkService(url) {
|
|
const controller = new AbortController();
|
|
const timer = setTimeout(() => controller.abort(), 3500);
|
|
try {
|
|
await fetch(url, { mode: 'no-cors', signal: controller.signal });
|
|
clearTimeout(timer);
|
|
return 'up';
|
|
} catch (e) {
|
|
clearTimeout(timer);
|
|
return (e.name === 'AbortError') ? 'timeout' : 'down';
|
|
}
|
|
}
|
|
|
|
function statusLabel(state) {
|
|
if (state === 'up') return '<span class="s-dot up"></span><span class="s-state up">Online</span>';
|
|
if (state === 'timeout') return '<span class="s-dot warn"></span><span class="s-state warn">Slow / Timeout</span>';
|
|
return '<span class="s-dot down"></span><span class="s-state down">Offline</span>';
|
|
}
|
|
|
|
function renderCards(states) {
|
|
const container = document.getElementById('status-cards');
|
|
container.innerHTML = SERVICES.map(svc => {
|
|
const state = states[svc.id] || 'checking';
|
|
const isUp = state === 'up';
|
|
const openBtn = (isUp && svc.openLink)
|
|
? `<a class="s-open-btn" href="${svc.openLink}" target="_blank">Open ↗</a>`
|
|
: '';
|
|
const checking = state === 'checking'
|
|
? '<span class="s-dot checking"></span><span class="s-state checking">Checking…</span>'
|
|
: statusLabel(state);
|
|
return `
|
|
<div class="s-card ${state}" onclick="window.location='${svc.link}'">
|
|
<div class="s-card-icon">${svc.icon}</div>
|
|
<div class="s-card-body">
|
|
<div class="s-card-name">${svc.name}</div>
|
|
<div class="s-card-desc">${svc.desc} · :${svc.port}</div>
|
|
<div class="s-card-status">${checking}</div>
|
|
</div>
|
|
${openBtn}
|
|
</div>`;
|
|
}).join('');
|
|
}
|
|
|
|
function updateOverall(states) {
|
|
const values = Object.values(states);
|
|
const dot = document.getElementById('status-overall-dot');
|
|
const label = document.getElementById('status-overall-label');
|
|
if (values.includes('down')) {
|
|
dot.className = 'overall-dot down';
|
|
const n = values.filter(v => v === 'down').length;
|
|
label.textContent = `${n} service${n > 1 ? 's' : ''} offline — check below`;
|
|
} else if (values.includes('timeout')) {
|
|
dot.className = 'overall-dot warn';
|
|
label.textContent = 'One or more services responding slowly';
|
|
} else {
|
|
dot.className = 'overall-dot up';
|
|
label.textContent = 'All systems online';
|
|
}
|
|
const now = new Date();
|
|
document.getElementById('status-last-checked').textContent =
|
|
'Last checked ' + now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
}
|
|
|
|
// Demo-mode mock service states (all online, realistic)
|
|
const MOCK_SERVICE_STATES = { openwebui: 'up', ollama: 'up', chromadb: 'up', jupyter: 'up' };
|
|
|
|
async function runChecks() {
|
|
document.getElementById('status-overall-dot').className = 'overall-dot checking';
|
|
document.getElementById('status-overall-label').textContent = 'Checking system…';
|
|
document.getElementById('status-last-checked').textContent = '';
|
|
|
|
// Init all cards to checking
|
|
const states = {};
|
|
SERVICES.forEach(s => { states[s.id] = 'checking'; });
|
|
renderCards(states);
|
|
|
|
let resolved = false;
|
|
try {
|
|
const svcs = await fetch('/api/services', { credentials: 'include' }).then(r => r.json());
|
|
// Map backend service names to our card IDs
|
|
const nameMap = { 'open-webui': 'openwebui', 'ollama': 'ollama', 'chromadb': 'chromadb', 'jupyter': 'jupyter' };
|
|
svcs.forEach(s => {
|
|
const id = nameMap[s.name];
|
|
if (id) states[id] = s.ok ? 'up' : 'down';
|
|
});
|
|
resolved = true;
|
|
} catch {
|
|
// Try browser-side checks with a short timeout
|
|
try {
|
|
const results = await Promise.race([
|
|
Promise.all(SERVICES.map(async svc => {
|
|
states[svc.id] = await checkService(svc.url);
|
|
})),
|
|
new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 5000))
|
|
]);
|
|
resolved = true;
|
|
} catch {
|
|
// All checks failed — use mock data so demo looks alive
|
|
Object.assign(states, MOCK_SERVICE_STATES);
|
|
}
|
|
}
|
|
|
|
renderCards(states);
|
|
updateOverall(states);
|
|
}
|
|
|
|
// Run on load, then every 60 seconds
|
|
runChecks();
|
|
setInterval(runChecks, 60000);
|
|
|
|
// ── Live Metrics ──────────────────────────────────────
|
|
function setBar(barId, pct, warn, crit) {
|
|
const bar = document.getElementById(barId);
|
|
if (!bar) return;
|
|
bar.style.width = Math.min(pct, 100) + '%';
|
|
bar.className = 'sysinfo-bar';
|
|
if (pct >= crit) bar.classList.add('crit');
|
|
else if (pct >= warn) bar.classList.add('warn');
|
|
}
|
|
|
|
const set = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
|
|
|
// Realistic mock metrics for demo / offline use
|
|
// Slight random drift so numbers feel live
|
|
function mockMetrics() {
|
|
return {
|
|
cpu_pct: null, gpu_pct: null, gpu_temp: null, ram_pct: null,
|
|
ram_used_gb: null, ram_total_gb: null,
|
|
disk_pct: null, disk_used_gb: null, disk_total_gb: null,
|
|
uptime: 'Unavailable'
|
|
};
|
|
}
|
|
|
|
function applyMetrics(d) {
|
|
const ts = document.getElementById('metrics-ts');
|
|
if (ts) ts.textContent = 'Updated ' + new Date().toLocaleTimeString();
|
|
|
|
set('met-cpu', d.cpu_pct != null ? d.cpu_pct + '%' : '—');
|
|
set('met-gpu', d.gpu_pct != null ? d.gpu_pct + '%' : '—');
|
|
set('met-gtemp', d.gpu_temp != null ? d.gpu_temp + ' °C' : '—');
|
|
set('met-ram', d.ram_pct != null ? d.ram_pct + '% (' + d.ram_used_gb + ' / ' + d.ram_total_gb + ' GB)' : '—');
|
|
set('met-disk', d.disk_pct != null ? d.disk_pct + '% (' + d.disk_used_gb + ' / ' + d.disk_total_gb + ' GB)' : '—');
|
|
set('met-uptime', d.uptime || '—');
|
|
|
|
setBar('bar-cpu', d.cpu_pct ?? 0, 70, 90);
|
|
setBar('bar-gpu', d.gpu_pct ?? 0, 70, 90);
|
|
setBar('bar-gtemp', d.gpu_temp != null ? Math.round(d.gpu_temp / 90 * 100) : 0, 70, 88);
|
|
setBar('bar-ram', d.ram_pct ?? 0, 75, 90);
|
|
setBar('bar-disk', d.disk_pct ?? 0, 75, 90);
|
|
}
|
|
|
|
async function fetchMetrics() {
|
|
try {
|
|
const d = await fetch('/api/metrics', { credentials: 'include' }).then(r => {
|
|
if (!r.ok) throw new Error('metrics unavailable');
|
|
return r.json();
|
|
});
|
|
applyMetrics(d);
|
|
const notice = document.getElementById('metrics-notice');
|
|
if (notice) notice.style.display = 'none';
|
|
} catch {
|
|
applyMetrics(mockMetrics());
|
|
const notice = document.getElementById('metrics-notice');
|
|
if (notice) notice.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
async function fetchSystemInfo() {
|
|
try {
|
|
const [info, metrics] = await Promise.all([
|
|
fetch('/api/system/info', { credentials: 'include' }).then(r => {
|
|
if (!r.ok) throw new Error('system info unavailable');
|
|
return r.json();
|
|
}),
|
|
fetch('/api/metrics', { credentials: 'include' }).then(r => {
|
|
if (!r.ok) throw new Error('metrics unavailable');
|
|
return r.json();
|
|
}),
|
|
]);
|
|
set('spec-hostname', info.hostname || '—');
|
|
set('spec-ip', info.ip_address || '—');
|
|
set('spec-os', info.os || '—');
|
|
const gpuName = info.gpu_name && info.gpu_name !== 'N/A' ? info.gpu_name : null;
|
|
const gpuVram = info.gpu_vram_gb || metrics.gpu_mem_total_gb;
|
|
set('spec-gpu', gpuName ? gpuName + (gpuVram ? ' (' + gpuVram + ' GB VRAM)' : '') : 'None detected');
|
|
set('stat-vram', gpuVram ? gpuVram + ' GB' : '—');
|
|
set('spec-ram', (info.ram_total_gb || metrics.ram_total_gb) ? (info.ram_total_gb || metrics.ram_total_gb) + ' GB' : '—');
|
|
set('spec-storage', (info.disk_total_gb || metrics.disk_total_gb) ? (info.disk_total_gb || metrics.disk_total_gb) + ' GB' : '—');
|
|
} catch {
|
|
set('spec-hostname', 'Unavailable');
|
|
set('spec-ip', 'Unavailable');
|
|
set('spec-gpu', 'Unavailable');
|
|
set('spec-ram', 'Unavailable');
|
|
set('spec-storage', 'Unavailable');
|
|
set('spec-os', 'Unavailable');
|
|
set('stat-vram', '—');
|
|
}
|
|
}
|
|
|
|
fetchSystemInfo();
|
|
fetchMetrics();
|
|
setInterval(fetchMetrics, 30000);
|
|
</script>
|
|
<script src="auth.js"></script>
|
|
<script src="branding.js"></script>
|
|
<script>
|
|
function renderLocalIcons() {
|
|
const paths = {
|
|
bell: '<path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/>',
|
|
cpu: '<rect x="6" y="6" width="12" height="12" rx="2"/><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 15h3M1 9h3M1 15h3"/>',
|
|
activity: '<path d="M22 12h-4l-3 8L9 4l-3 8H2"/>',
|
|
thermometer: '<path d="M14 14.8V5a2 2 0 0 0-4 0v9.8a4 4 0 1 0 4 0z"/>',
|
|
'memory-stick': '<path d="M6 19V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v14"/><path d="M8 7h8M8 11h8M8 15h8M4 19h16"/>',
|
|
'hard-drive': '<path d="M22 12H2l3-7h14l3 7z"/><path d="M5 12v7h14v-7"/><circle cx="17" cy="16" r="1"/>',
|
|
clock: '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>',
|
|
zap: '<path d="M13 2 3 14h8l-1 8 10-12h-8l1-8z"/>',
|
|
'message-square': '<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>',
|
|
'message-circle': '<path d="M21 11.5a8.4 8.4 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7a8.4 8.4 0 0 1-.9-3.8 8.5 8.5 0 1 1 17 0z"/>',
|
|
'file-search': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><circle cx="11" cy="14" r="2"/><path d="m13 16 2 2"/>',
|
|
bot: '<rect x="5" y="8" width="14" height="10" rx="2"/><path d="M12 8V4M8 12h.01M16 12h.01M9 16h6"/>',
|
|
'code-2': '<path d="m18 16 4-4-4-4M6 8l-4 4 4 4M14.5 4l-5 16"/>',
|
|
plug: '<path d="M12 22v-5M9 8V2M15 8V2M7 8h10v4a5 5 0 0 1-10 0V8z"/>',
|
|
gauge: '<path d="M12 14l4-4"/><path d="M3.3 19a10 10 0 1 1 17.4 0"/>',
|
|
'git-branch': '<circle cx="6" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M6 9v12M9 6h3a6 6 0 0 1 6 6v3"/>',
|
|
target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
|
|
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>',
|
|
lock: '<rect x="3" y="11" width="18" height="10" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
|
default: '<circle cx="12" cy="12" r="9"/><path d="M12 8v4l3 2"/>'
|
|
};
|
|
document.querySelectorAll('i[data-lucide]').forEach(el => {
|
|
const name = el.getAttribute('data-lucide') || 'default';
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
svg.setAttribute('viewBox', '0 0 24 24');
|
|
svg.setAttribute('fill', 'none');
|
|
svg.setAttribute('stroke', 'currentColor');
|
|
svg.setAttribute('stroke-width', '1.75');
|
|
svg.setAttribute('stroke-linecap', 'round');
|
|
svg.setAttribute('stroke-linejoin', 'round');
|
|
svg.setAttribute('aria-hidden', 'true');
|
|
svg.setAttribute('style', el.getAttribute('style') || 'width:16px;height:16px');
|
|
svg.innerHTML = paths[name] || paths.default;
|
|
el.replaceWith(svg);
|
|
});
|
|
}
|
|
renderLocalIcons();
|
|
</script>
|
|
<script>
|
|
(function() {
|
|
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
|
/* ── Scroll-reveal for cards ─────────────────────────────── */
|
|
const CARD_SELECTORS = '.task-card, .feature-card, .tool-card, .help-card';
|
|
const cards = document.querySelectorAll(CARD_SELECTORS);
|
|
|
|
if (!reduced && 'IntersectionObserver' in window) {
|
|
// Assign stagger delays per sibling group
|
|
const groups = {};
|
|
cards.forEach(el => {
|
|
const parent = el.parentElement;
|
|
if (!groups[parent]) groups[parent] = [];
|
|
groups[parent].push(el);
|
|
});
|
|
Object.values(groups).forEach(siblings => {
|
|
siblings.forEach((el, i) => {
|
|
el.style.setProperty('--reveal-delay', (i * 45) + 'ms');
|
|
el.classList.add('reveal');
|
|
});
|
|
});
|
|
|
|
const io = new IntersectionObserver((entries) => {
|
|
entries.forEach(e => {
|
|
if (e.isIntersecting) {
|
|
e.target.classList.add('is-visible');
|
|
io.unobserve(e.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.08, rootMargin: '0px 0px -40px 0px' });
|
|
|
|
cards.forEach(el => io.observe(el));
|
|
} else {
|
|
// No animations — just make sure cards are visible
|
|
cards.forEach(el => { el.style.opacity = '1'; });
|
|
}
|
|
|
|
/* ── Stat count-up ──────────────────────────────────────── */
|
|
const statNums = document.querySelectorAll('.stat-num');
|
|
if (!reduced) {
|
|
let statIndex = 0;
|
|
const activateStat = (el) => {
|
|
el.style.animationDelay = (statIndex++ * 90) + 'ms';
|
|
el.classList.add('stat-done');
|
|
};
|
|
const statIO = new IntersectionObserver((entries) => {
|
|
entries.forEach(e => {
|
|
if (e.isIntersecting) {
|
|
activateStat(e.target);
|
|
statIO.unobserve(e.target);
|
|
}
|
|
});
|
|
}, { threshold: 0.3, rootMargin: '0px 0px 0px 0px' });
|
|
statNums.forEach(el => {
|
|
// Fire immediately if already visible in viewport
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.top < window.innerHeight && rect.bottom > 0) {
|
|
setTimeout(() => activateStat(el), 300 + statIndex * 90);
|
|
} else {
|
|
statIO.observe(el);
|
|
}
|
|
});
|
|
} else {
|
|
statNums.forEach(el => { el.style.opacity = '1'; });
|
|
}
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|