jackailocal / webui /index.html
jackboy70's picture
Deploy: accurate lite-builder note
f25362a
Raw
History Blame Contribute Delete
23.7 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark light" />
<title>JackAILocal</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="app" class="app-shell">
<aside class="sidebar" aria-label="JackAILocal navigation">
<div class="brand-card">
<div class="brand-mark">J</div>
<div>
<div class="brand-name">JackAILocal</div>
<div class="brand-subtitle" data-i18n="brandSubtitle">Offline local AI</div>
</div>
</div>
<div class="language-switch" aria-label="Language">
<button type="button" class="lang-btn active" data-lang="en">EN</button>
<button type="button" class="lang-btn" data-lang="fr">FR</button>
</div>
<div id="statusBox" class="status-card state-loading" role="status" aria-live="polite">
<div class="status-header">
<span class="status-dot"></span>
<span id="runtimeStatus" class="status-title" data-i18n="statusStarting">Starting</span>
</div>
<div id="hardwareLine" class="status-subtitle" data-i18n="statusLocalScan">Local scan...</div>
</div>
<nav class="nav-list">
<button class="nav active" data-view="dashboard"><span></span><span data-i18n="navDashboard">Dashboard</span></button>
<button class="nav" data-view="chat"><span></span><span data-i18n="navChat">Chat</span></button>
<button class="nav" data-view="models"><span></span><span data-i18n="navModels">Models</span></button>
<button class="nav" data-view="documents"><span></span><span data-i18n="navDocuments">Documents</span></button>
<button class="nav" data-view="benchmark"><span></span><span data-i18n="navBenchmark">Benchmark</span></button>
<button class="nav" data-view="settings"><span></span><span data-i18n="navSettings">Settings</span></button>
<button class="nav" data-view="support"><span>?</span><span data-i18n="navSupport">Support</span></button>
<button class="nav" data-view="scout"><span>V</span><span data-i18n="navScout">SCOUT Vision</span></button>
<button class="nav" data-view="voice"><span>A</span><span data-i18n="navVoice">Voice</span></button>
<button class="nav" data-view="field-manual"><span>F</span><span data-i18n="navFieldManual">Field Manual</span></button>
<button class="nav" data-view="transfers"><span>X</span><span data-i18n="navTransfers">Import / Export</span></button>
<button class="nav" data-view="phone"><span>P</span><span data-i18n="navPhone">Phone Access</span></button>
</nav>
<div class="sidebar-footer">
<div class="mini-label" data-i18n="networkMode">Network mode</div>
<div class="security-pill">127.0.0.1</div>
<div class="mini-copy" data-i18n="loopbackOnly">Local access only</div>
<div id="licenseWarning" class="security-pill availability-off hidden"></div>
</div>
</aside>
<main class="main">
<div class="app-topbar">
<div>
<div class="eyebrow" data-i18n="eyebrow">Portable private AI</div>
<h1 id="viewTitle">Dashboard</h1>
</div>
<div class="top-actions">
<button id="refreshAll" class="secondary" data-i18n="refreshButton">Refresh</button>
<button id="openSupport" class="secondary" data-i18n="supportButton">Support</button>
</div>
</div>
<div id="errorBanner" class="banner hidden" role="status"></div>
<section id="view-dashboard" class="view active">
<div class="hero-panel">
<div>
<div class="hero-kicker" data-i18n="heroKicker">Ready for offline work</div>
<h2 data-i18n="heroTitle">Local AI, loaded from your USB drive.</h2>
<p data-i18n="heroSubtitle">JackAILocal runs the launcher, runtime, models, documents, and UI locally. No cloud is required after preparation.</p>
</div>
<div class="hero-actions">
<button class="primary" data-action-view="chat" data-i18n="startChatButton">Start chat</button>
<button class="secondary" data-action-view="models" data-i18n="checkModelsButton">Check models</button>
</div>
</div>
<div class="metric-grid">
<article class="metric-card">
<span class="metric-label" data-i18n="runtimeCard">Runtime</span>
<strong id="runtimeMetric">...</strong>
<small id="backendMetric">...</small>
</article>
<article class="metric-card">
<span class="metric-label" data-i18n="hardwareCard">Hardware</span>
<strong id="hardwareMetric">...</strong>
<small id="gpuMetric">...</small>
</article>
<article class="metric-card">
<span class="metric-label" data-i18n="modelCard">Recommended model</span>
<strong id="recommendedMetric">Auto</strong>
<small data-i18n="autoSelectHint">Selected by local policy</small>
</article>
<article class="metric-card">
<span class="metric-label" data-i18n="privacyCard">Privacy</span>
<strong data-i18n="offlineLocked">Offline locked</strong>
<small data-i18n="localOnlyHint">Loopback only</small>
</article>
</div>
<div class="split-hero">
<div class="panel">
<h2 data-i18n="setupTitle">Preparation state</h2>
<div class="checklist">
<div class="check-row" id="checkRuntime"><span></span><div><strong data-i18n="checkRuntimeTitle">Local API</strong><small data-i18n="checkRuntimeText">jackailocald must be running.</small></div></div>
<div class="check-row" id="checkModels"><span></span><div><strong data-i18n="checkModelsTitle">Model catalog</strong><small data-i18n="checkModelsText">The USB must contain configured model weights.</small></div></div>
<div class="check-row" id="checkSecurity"><span></span><div><strong data-i18n="checkSecurityTitle">Security mode</strong><small data-i18n="checkSecurityText">Shell tools and LAN exposure remain disabled.</small></div></div>
</div>
</div>
<div class="panel">
<h2 data-i18n="quickActionsTitle">Quick actions</h2>
<div class="quick-actions">
<button class="secondary" data-action-view="benchmark" data-i18n="runBenchmarkQuick">Run benchmark</button>
<button class="secondary" data-action-view="documents" data-i18n="openDocsQuick">Open documents</button>
<button class="secondary" id="supportBundleQuick" data-i18n="createSupportQuick">Create support bundle</button>
</div>
<pre id="dashboardOut" class="terminal small-terminal"></pre>
</div>
</div>
</section>
<section id="view-chat" class="view">
<div class="chat-workspace">
<aside class="thread-panel">
<div class="thread-header">
<h2 data-i18n="threadsTitle">Threads</h2>
<button id="newThread" class="primary compact" data-i18n="newThreadButton">New</button>
</div>
<input id="threadFilter" data-i18n-placeholder="threadFilterPlaceholder" placeholder="Filter threads" />
<div id="threadList" class="thread-list"></div>
</aside>
<div class="chat-panel">
<div class="chat-thread-bar">
<strong id="activeThreadTitle" data-i18n="newConversation">New conversation</strong>
<div>
<button id="renameThread" class="secondary compact" data-i18n="renameButton">Rename</button>
<button id="deleteThread" class="secondary compact danger-button" data-i18n="deleteButton">Delete</button>
</div>
</div>
<div id="chatLog" class="chat-log" aria-live="polite"></div>
<div class="composer-card">
<textarea id="prompt" spellcheck="true" data-i18n-placeholder="promptPlaceholder" placeholder="Write your request here. Enter = send, Shift+Enter = new line."></textarea>
<div class="composer-actions">
<label class="select-label"><span data-i18n="modelLabel">Model</span><select id="modelSelect"><option value="auto">Auto</option></select></label>
<button id="clearChat" class="secondary" data-i18n="clearButton">Clear</button>
<button id="sendBtn" class="primary" data-i18n="sendButton">Send</button>
</div>
</div>
</div>
<aside class="side-panel">
<h2 data-i18n="chatHelperTitle">Assistant mode</h2>
<p data-i18n="chatHelperText">Use Auto unless you need a specific installed model. Threads are stored on this JackAILocal drive.</p>
<div id="modelChips" class="chip-list"></div>
<div class="hint-box"><strong data-i18n="offlineNoteTitle">Offline note</strong><span data-i18n="offlineNoteText">Answers come from local models. Do not expect cloud frontier quality.</span></div>
</aside>
</div>
</section>
<section id="view-scout" class="view">
<div class="section-header">
<div><h2 data-i18n="scoutTitle">SCOUT Vision</h2><p data-i18n="scoutSubtitle">Analyze an image with an installed local vision model.</p></div>
</div>
<div class="split">
<div class="panel">
<input id="scoutImage" type="file" accept="image/*" />
<img id="scoutPreview" class="image-preview hidden" alt="Selected image preview" />
<textarea id="scoutPrompt" class="doc-textarea" data-i18n-placeholder="scoutPromptPlaceholder" placeholder="Describe the image and extract readable text."></textarea>
<button id="runScout" class="primary" data-i18n="analyzeButton">Analyze locally</button>
</div>
<div class="panel">
<h2 data-i18n="scoutResultTitle">Analysis result</h2>
<pre id="scoutOut" class="terminal"></pre>
</div>
</div>
</section>
<section id="view-voice" class="view">
<div class="section-header">
<div><h2 data-i18n="voiceTitle">Voice Mode</h2><p data-i18n="voiceSubtitle">Local speech-to-text with whisper.cpp and text-to-speech with Piper.</p></div>
<button id="refreshVoice" class="secondary" data-i18n="refreshButton">Refresh</button>
</div>
<div class="split">
<div class="panel">
<h2 data-i18n="sttTitle">Speech to text</h2>
<p id="sttStatus" class="muted"></p>
<div class="button-row">
<button id="startRecording" class="primary" data-i18n="recordButton">Record</button>
<button id="stopRecording" class="secondary" disabled data-i18n="stopButton">Stop</button>
</div>
<textarea id="voiceTranscript" class="doc-textarea" data-i18n-placeholder="transcriptPlaceholder" placeholder="Transcript"></textarea>
<button id="sendTranscript" class="secondary" data-i18n="sendToChatButton">Send to chat</button>
</div>
<div class="panel">
<h2 data-i18n="ttsTitle">Text to speech</h2>
<p id="ttsStatus" class="muted"></p>
<textarea id="ttsText" class="doc-textarea" data-i18n-placeholder="ttsPlaceholder" placeholder="Text to speak"></textarea>
<button id="speakText" class="primary" data-i18n="speakButton">Speak locally</button>
<audio id="ttsAudio" controls class="audio-player hidden"></audio>
<pre id="voiceOut" class="terminal small-terminal"></pre>
</div>
</div>
</section>
<section id="view-field-manual" class="view">
<div class="section-header">
<div><h2 data-i18n="fieldManualTitle">Field Manual</h2><p data-i18n="fieldManualSubtitle">Offline reference cards with an Ask AI action.</p></div>
</div>
<div id="fieldManualWarning" class="banner hidden"></div>
<div id="fieldManualCards" class="cards"></div>
</section>
<section id="view-models" class="view">
<div class="section-header">
<div><h2 data-i18n="modelsTitle">Models</h2><p data-i18n="modelsSubtitle">The catalog shows what is configured. Model weights must be present in models/.</p></div>
<button id="refreshModels" class="secondary" data-i18n="refreshButton">Refresh</button>
</div>
<div id="modelCards" class="cards"></div>
<div class="panel safe-panel">
<h2 data-i18n="agentTitle">LLM Config Agent</h2>
<p data-i18n="agentSubtitle">A real installed local LLM can review hardware, model selection, and client configuration. Preferred model: gemma4:12b.</p>
<div class="form-grid">
<label class="select-label"><span data-i18n="agentModelLabel">Agent model</span><select id="agentModelSelect"><option value="auto">Auto</option></select></label>
<textarea id="agentNotes" class="doc-textarea" data-i18n-placeholder="agentNotesPlaceholder" placeholder="Client constraints, use case, budget, privacy needs..."></textarea>
<button id="runAgentReview" class="primary" data-i18n="agentRunButton">Run local agent review</button>
</div>
<pre id="agentOut" class="terminal small-terminal"></pre>
</div>
<div class="panel safe-panel">
<h2 data-i18n="addModelTitle">Add a model to the catalog</h2>
<p data-i18n="addModelSubtitle">This registers a model. It does not download weights and it does not bypass builder validation.</p>
<div class="form-grid">
<input id="addModelId" data-i18n-placeholder="addModelIdPlaceholder" placeholder="local id, example: qwen-custom" />
<input id="addModelLabel" data-i18n-placeholder="addModelLabelPlaceholder" placeholder="display name" />
<button id="addModelBtn" class="primary" data-i18n="addButton">Add</button>
</div>
<pre id="addModelOut" class="terminal small-terminal"></pre>
</div>
</section>
<section id="view-documents" class="view">
<div class="section-header">
<div><h2 data-i18n="documentsTitle">Documents</h2><p data-i18n="documentsSubtitle">Local storage in workspace/documents. No arbitrary disk access.</p></div>
<button id="reloadDocs" class="secondary" data-i18n="refreshButton">Refresh</button>
</div>
<div class="split">
<div class="panel">
<h2 data-i18n="createNoteTitle">Create a note</h2>
<input id="docName" data-i18n-placeholder="docNamePlaceholder" placeholder="document-name.txt" />
<textarea id="docContent" data-i18n-placeholder="docContentPlaceholder" placeholder="Text, notes, procedures, prompts..." class="doc-textarea"></textarea>
<button id="saveDoc" class="primary" data-i18n="saveLocalButton">Save locally</button>
<pre id="docOut" class="terminal small-terminal"></pre>
</div>
<div class="panel">
<h2 data-i18n="localDocsTitle">Local documents</h2>
<div id="docList" class="doc-list"></div>
</div>
</div>
</section>
<section id="view-transfers" class="view">
<div class="section-header">
<div><h2 data-i18n="transfersTitle">Encrypted Import / Export</h2><p data-i18n="transfersSubtitle">Transfer threads and documents with an AES-256-GCM encrypted pack.</p></div>
</div>
<div class="split">
<div class="panel">
<h2 data-i18n="exportTitle">Export</h2>
<input id="exportPassphrase" type="password" data-i18n-placeholder="passphrasePlaceholder" placeholder="Passphrase, minimum 8 characters" />
<div class="option-stack compact-stack">
<label class="option-card"><input id="exportThreads" type="checkbox" checked /><span class="option-copy"><strong data-i18n="includeThreads">Include threads</strong></span></label>
<label class="option-card"><input id="exportDocuments" type="checkbox" checked /><span class="option-copy"><strong data-i18n="includeDocuments">Include documents</strong></span></label>
<label class="option-card"><input id="exportSettings" type="checkbox" /><span class="option-copy"><strong data-i18n="includeSettings">Include safe settings</strong></span></label>
</div>
<button id="exportPack" class="primary" data-i18n="exportButton">Export encrypted pack</button>
</div>
<div class="panel">
<h2 data-i18n="importTitle">Import</h2>
<input id="importPackFile" type="file" accept=".jackaipack,application/json" />
<input id="importPassphrase" type="password" data-i18n-placeholder="passphrasePlaceholder" placeholder="Passphrase, minimum 8 characters" />
<label class="select-label"><span data-i18n="importMode">Import mode</span><select id="importMode"><option value="merge">Merge</option><option value="replace">Replace threads</option></select></label>
<button id="importPack" class="primary" data-i18n="importButton">Import encrypted pack</button>
</div>
</div>
<pre id="transferOut" class="terminal small-terminal"></pre>
</section>
<section id="view-phone" class="view">
<div class="section-header">
<div><h2 data-i18n="phoneTitle">Phone Access</h2><p data-i18n="phoneSubtitle">Explicit local-network access with a pairing token. Disabled by default.</p></div>
<button id="refreshPhone" class="secondary" data-i18n="refreshButton">Refresh</button>
</div>
<div class="split">
<div class="panel">
<div id="phoneState" class="terminal small-terminal"></div>
<div class="button-row">
<button id="enablePhone" class="primary" data-i18n="enablePhoneButton">Enable Phone Access</button>
<button id="disablePhone" class="secondary" data-i18n="disablePhoneButton">Disable Phone Access</button>
<button id="restartRuntime" class="secondary" data-i18n="restartRuntimeButton">Restart runtime</button>
</div>
<p class="muted" data-i18n="phoneRestartNote">A runtime restart is required after changing this mode.</p>
</div>
<div class="panel qr-panel">
<div id="phoneQr"></div>
</div>
</div>
</section>
<section id="view-benchmark" class="view">
<div class="section-header">
<div><h2 data-i18n="benchmarkTitle">Benchmark</h2><p data-i18n="benchmarkSubtitle">Short test to validate loading, latency, and a real response.</p></div>
<button id="runBenchmark" class="primary" data-i18n="runTestButton">Run test</button>
</div>
<pre id="benchmarkOut" class="terminal"></pre>
</section>
<section id="view-settings" class="view">
<div class="section-header">
<div>
<h2 data-i18n="settingsTitle">Settings</h2>
<p data-i18n="settingsSubtitle">Options stay simple. Dangerous features remain disabled.</p>
</div>
</div>
<div class="settings-grid">
<div class="panel settings-panel">
<h2 data-i18n="modeTitle">Mode</h2>
<p class="muted" data-i18n="settingsModeHint">Simple mode is the default for non-technical users. Advanced mode keeps unsafe tools blocked.</p>
<div class="option-stack">
<label class="option-card">
<input type="radio" name="mode" value="simple" checked />
<span class="option-copy">
<strong data-i18n="simpleMode">Simple</strong>
<small data-i18n="simpleModeDesc">Recommended default. Minimal choices and automatic model selection.</small>
</span>
</label>
<label class="option-card">
<input type="radio" name="mode" value="advanced" />
<span class="option-copy">
<strong data-i18n="advancedMode">Advanced</strong>
<small data-i18n="advancedModeDesc">Shows more controls without enabling shell or system tools.</small>
</span>
</label>
<label class="option-card">
<input type="checkbox" id="offlineLock" checked />
<span class="option-copy">
<strong data-i18n="offlineLock">Offline lock</strong>
<small data-i18n="offlineLockDesc">Keeps JackAILocal bound to the local machine by default.</small>
</span>
</label>
<label class="option-card">
<input type="checkbox" id="toolsLock" checked />
<span class="option-copy">
<strong data-i18n="toolsLock">Block shell/system tools</strong>
<small data-i18n="toolsLockDesc">Prevents agent-style system actions in the consumer build.</small>
</span>
</label>
</div>
<div class="settings-actions">
<button id="saveSettings" class="primary" data-i18n="saveButton">Save</button>
</div>
<pre id="settingsOut" class="terminal small-terminal"></pre>
</div>
<div class="panel settings-panel">
<h2 data-i18n="activeFeaturesTitle">Active features</h2>
<p class="muted" data-i18n="activeFeaturesSubtitle">Runtime capabilities are read from the local API when it is running.</p>
<div id="featureList" class="feature-list"></div>
</div>
<div class="panel settings-panel">
<h2 data-i18n="licenseTitle">License</h2>
<p class="muted" data-i18n="licenseSubtitle">License status of this installation. Verified locally, no internet required.</p>
<div id="licenseInfo" class="feature-list"></div>
<div class="license-install">
<input id="licenseFile" type="file" accept=".json,application/json" />
<button id="installLicense" class="secondary" data-i18n="installLicenseButton">Install license file</button>
<div id="licenseMessage" class="muted small"></div>
</div>
<p class="muted small" data-i18n="legalDocsHint">Legal documents are in the legal/ and licenses/ folders of this installation.</p>
</div>
</div>
</section>
<section id="view-support" class="view">
<div class="section-header">
<div><h2 data-i18n="supportTitle">Support</h2><p data-i18n="supportSubtitle">Local diagnostics without conversations by default.</p></div>
<button id="supportBundle" class="primary" data-i18n="prepareReportButton">Prepare report</button>
</div>
<pre id="supportOut" class="terminal"></pre>
</section>
</main>
</div>
<div id="eulaOverlay" class="eula-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="eulaTitle">
<div class="panel eula-modal">
<h2 id="eulaTitle" data-i18n="eulaTitle">End User License Agreement</h2>
<div id="eulaText" class="eula-text" tabindex="0"></div>
<div class="eula-actions">
<button id="eulaAccept" class="primary" data-i18n="eulaAcceptButton">I accept</button>
</div>
</div>
</div>
<script src="app-v15.js"></script>
</body>
</html>