Spaces:
Sleeping
Sleeping
File size: 23,738 Bytes
f25362a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 | <!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>
|