## What was done
- **`the-matrix/js/session.js`** (new module): Full session mode UI lifecycle:
- Create session flow: amount presets → POST /api/sessions → deposit invoice step
- Deposit payment: stub simulate → 2s polling until state=active
- macaroon + sessionId stored in localStorage (`timmy_session_v1`)
- Request submission: intercepts input bar when session active → POST /api/sessions/:id/request
→ Timmy speech bubble shows result, balance updates in HUD
- Low-balance (< 50 sats): paused state, low-balance notice shown, topup quick-button
- Topup flow: preset amount → POST /api/sessions/:id/topup → topup invoice → stub pay → poll
- Restore from localStorage on page reload: validates session via GET, restores full UI state
- Session expiry / 401 macaroon rejection: clears storage, resets to unfunded state
- **`the-matrix/js/ui.js`**: Added `setSessionSendHandler(fn)` + `setInputBarSessionMode(active, placeholder)` exports; send() routes to session handler when active, falls back to WS visitor_message
- **`the-matrix/index.html`**:
- `#top-buttons` flex container: "⚡ SUBMIT JOB" (blue) + "⚡ FUND SESSION" (teal) side-by-side
- `#session-hud` balance line in HUD (green, hidden until session active)
- `#session-panel` left-side slide-in panel: fund / invoice / active / topup steps
- `.session-amount-btn` presets (200, 500, 1000, 2000, 5000 sats) with active state
- `#visitor-input.session-active` CSS: green border + 3s pulse keyframe animation
- `#low-balance-notice` strip above input bar with Top Up quick-button
- `.primary-green` / `.muted` panel button variants for session panel theme
- `#session-panel` inherits shared `.panel-btn`, `.invoice-box`, `.copy-btn` with green overrides
- **`the-matrix/js/main.js`**: Import + call `initSessionPanel()` in firstInit block
## Verification
- `npm run build` in the-matrix → clean build (0 errors)
- Full testkit: 27/27 PASS (all session tests 11–16, 22 still green)
99 lines
2.9 KiB
JavaScript
99 lines
2.9 KiB
JavaScript
import { sendVisitorMessage } from './websocket.js';
|
|
|
|
const $fps = document.getElementById('fps');
|
|
const $activeJobs = document.getElementById('active-jobs');
|
|
const $connStatus = document.getElementById('connection-status');
|
|
const $log = document.getElementById('event-log');
|
|
|
|
const MAX_LOG = 6;
|
|
const logEntries = [];
|
|
let uiInitialized = false;
|
|
|
|
// ── Session-mode send override ────────────────────────────────────────────────
|
|
let _sessionSendHandler = null;
|
|
|
|
export function setSessionSendHandler(fn) {
|
|
_sessionSendHandler = fn;
|
|
}
|
|
|
|
export function setInputBarSessionMode(active, placeholder) {
|
|
const $input = document.getElementById('visitor-input');
|
|
if (!$input) return;
|
|
if (active) {
|
|
$input.classList.add('session-active');
|
|
$input.placeholder = placeholder || 'Ask Timmy (session active)…';
|
|
} else {
|
|
$input.classList.remove('session-active');
|
|
$input.placeholder = 'Say something to Timmy…';
|
|
}
|
|
}
|
|
|
|
export function initUI() {
|
|
if (uiInitialized) return;
|
|
uiInitialized = true;
|
|
initInputBar();
|
|
}
|
|
|
|
function initInputBar() {
|
|
const $input = document.getElementById('visitor-input');
|
|
const $sendBtn = document.getElementById('send-btn');
|
|
if (!$input || !$sendBtn) return;
|
|
|
|
function send() {
|
|
const text = $input.value.trim();
|
|
if (!text) return;
|
|
$input.value = '';
|
|
if (_sessionSendHandler) {
|
|
_sessionSendHandler(text);
|
|
} else {
|
|
sendVisitorMessage(text);
|
|
appendSystemMessage(`you: ${text}`);
|
|
}
|
|
}
|
|
|
|
$sendBtn.addEventListener('click', send);
|
|
$input.addEventListener('keydown', e => {
|
|
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); }
|
|
});
|
|
}
|
|
|
|
export function updateUI({ fps, jobCount, connectionState }) {
|
|
if ($fps) $fps.textContent = `FPS: ${fps}`;
|
|
if ($activeJobs) $activeJobs.textContent = `JOBS: ${jobCount}`;
|
|
|
|
if ($connStatus) {
|
|
if (connectionState === 'connected') {
|
|
$connStatus.textContent = '● CONNECTED';
|
|
$connStatus.className = 'connected';
|
|
} else if (connectionState === 'connecting') {
|
|
$connStatus.textContent = '◌ CONNECTING...';
|
|
$connStatus.className = '';
|
|
} else {
|
|
$connStatus.textContent = '○ OFFLINE';
|
|
$connStatus.className = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
export function appendSystemMessage(text) {
|
|
if (!$log) return;
|
|
const el = document.createElement('div');
|
|
el.className = 'log-entry';
|
|
el.textContent = text;
|
|
logEntries.push(el);
|
|
if (logEntries.length > MAX_LOG) {
|
|
const removed = logEntries.shift();
|
|
$log.removeChild(removed);
|
|
}
|
|
$log.appendChild(el);
|
|
$log.scrollTop = $log.scrollHeight;
|
|
}
|
|
|
|
export function appendChatMessage(agentLabel, message, cssColor, agentId) {
|
|
void agentLabel; void cssColor; void agentId;
|
|
appendSystemMessage(message);
|
|
}
|
|
|
|
export function loadChatHistory() { return []; }
|
|
export function saveChatHistory() {}
|