WIP: Claude Code progress on #31
Automated salvage commit — agent session ended (exit 124). Work in progress, may need continuation.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { Router, type Request, type Response } from "express";
|
import { Router, type Request, type Response } from "express";
|
||||||
import { randomUUID, createHash } from "crypto";
|
import { randomUUID, createHash } from "crypto";
|
||||||
import { db, jobs, invoices, jobDebates, type Job } from "@workspace/db";
|
import { db, jobs, invoices, jobDebates, type Job } from "@workspace/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, desc } from "drizzle-orm";
|
||||||
import { CreateJobBody, GetJobParams } from "@workspace/api-zod";
|
import { CreateJobBody, GetJobParams } from "@workspace/api-zod";
|
||||||
import { lnbitsService } from "../lib/lnbits.js";
|
import { lnbitsService } from "../lib/lnbits.js";
|
||||||
import { agentService } from "../lib/agent.js";
|
import { agentService } from "../lib/agent.js";
|
||||||
@@ -494,6 +494,67 @@ async function advanceJob(job: Job): Promise<Job | null> {
|
|||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── GET /jobs ─────────────────────────────────────────────────────────────────
|
||||||
|
// Returns the caller's completed/rejected job history (requires Nostr token).
|
||||||
|
|
||||||
|
router.get("/jobs", async (req: Request, res: Response) => {
|
||||||
|
const header = req.headers["x-nostr-token"];
|
||||||
|
const raw = typeof header === "string" ? header.trim() : null;
|
||||||
|
if (!raw) {
|
||||||
|
res.status(401).json({ error: "X-Nostr-Token header required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsed = trustService.verifyToken(raw);
|
||||||
|
if (!parsed) {
|
||||||
|
res.status(401).json({ error: "Invalid or expired token" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rows = await db
|
||||||
|
.select({
|
||||||
|
id: jobs.id,
|
||||||
|
request: jobs.request,
|
||||||
|
state: jobs.state,
|
||||||
|
workAmountSats: jobs.workAmountSats,
|
||||||
|
actualAmountSats: jobs.actualAmountSats,
|
||||||
|
result: jobs.result,
|
||||||
|
rejectionReason: jobs.rejectionReason,
|
||||||
|
freeTier: jobs.freeTier,
|
||||||
|
absorbedSats: jobs.absorbedSats,
|
||||||
|
createdAt: jobs.createdAt,
|
||||||
|
updatedAt: jobs.updatedAt,
|
||||||
|
})
|
||||||
|
.from(jobs)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(jobs.nostrPubkey, parsed.pubkey),
|
||||||
|
// Only terminal states are useful for history
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(desc(jobs.createdAt))
|
||||||
|
.limit(50);
|
||||||
|
|
||||||
|
res.json({ jobs: rows.map(j => ({
|
||||||
|
id: j.id,
|
||||||
|
request: j.request,
|
||||||
|
state: j.state,
|
||||||
|
costSats: j.actualAmountSats ?? j.workAmountSats ?? null,
|
||||||
|
freeTier: j.freeTier,
|
||||||
|
absorbedSats: j.absorbedSats ?? null,
|
||||||
|
result: j.state === "complete" ? j.result : null,
|
||||||
|
rejectionReason: j.state === "rejected" ? j.rejectionReason : null,
|
||||||
|
createdAt: j.createdAt.toISOString(),
|
||||||
|
completedAt: (j.state === "complete" || j.state === "rejected")
|
||||||
|
? j.updatedAt.toISOString()
|
||||||
|
: null,
|
||||||
|
})) });
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : "Failed to fetch jobs";
|
||||||
|
res.status(500).json({ error: message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ── POST /jobs ────────────────────────────────────────────────────────────────
|
// ── POST /jobs ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// ── Resolve Nostr pubkey from token header or body ────────────────────────────
|
// ── Resolve Nostr pubkey from token header or body ────────────────────────────
|
||||||
|
|||||||
@@ -514,6 +514,132 @@
|
|||||||
}
|
}
|
||||||
#timmy-id-card .id-npub:hover { color: #88aadd; }
|
#timmy-id-card .id-npub:hover { color: #88aadd; }
|
||||||
#timmy-id-card .id-zaps { color: #556688; font-size: 9px; }
|
#timmy-id-card .id-zaps { color: #556688; font-size: 9px; }
|
||||||
|
|
||||||
|
/* ── History panel (bottom sheet) ────────────────────────────────── */
|
||||||
|
#open-history-btn {
|
||||||
|
font-family: 'Courier New', monospace; font-size: 11px; font-weight: bold;
|
||||||
|
color: #ccaaff; background: rgba(25, 10, 45, 0.85); border: 1px solid #7755aa55;
|
||||||
|
padding: 7px 18px; cursor: pointer; letter-spacing: 1px;
|
||||||
|
box-shadow: 0 0 14px #5533aa22;
|
||||||
|
transition: background 0.15s, box-shadow 0.15s, color 0.15s;
|
||||||
|
border-radius: 2px;
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
#open-history-btn:hover, #open-history-btn:active {
|
||||||
|
background: rgba(40, 18, 70, 0.95);
|
||||||
|
box-shadow: 0 0 20px #7755aa44;
|
||||||
|
color: #eeddff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#history-panel {
|
||||||
|
position: fixed; bottom: -100%; left: 0; right: 0;
|
||||||
|
height: 80vh;
|
||||||
|
background: rgba(5, 3, 14, 0.97);
|
||||||
|
border-top: 1px solid #1a1030;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden; z-index: 100;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
transition: bottom 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 -8px 32px rgba(60, 30, 100, 0.18);
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
}
|
||||||
|
#history-panel.open { bottom: 0; }
|
||||||
|
|
||||||
|
#history-panel-header {
|
||||||
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
|
padding: 16px 20px 10px;
|
||||||
|
border-bottom: 1px solid #1a1030;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#history-panel-header h2 {
|
||||||
|
font-size: 13px; letter-spacing: 3px; color: #9966dd;
|
||||||
|
text-shadow: 0 0 10px #5533aa;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
#history-panel-actions {
|
||||||
|
display: flex; gap: 8px; align-items: center;
|
||||||
|
}
|
||||||
|
#history-refresh-btn {
|
||||||
|
background: transparent; border: 1px solid #2a1a44;
|
||||||
|
color: #7755aa; font-family: 'Courier New', monospace;
|
||||||
|
font-size: 11px; padding: 4px 10px; cursor: pointer;
|
||||||
|
transition: all 0.15s; letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
#history-refresh-btn:hover { border-color: #9966dd; color: #ccaaff; }
|
||||||
|
#history-close {
|
||||||
|
background: transparent; border: 1px solid #1a1030;
|
||||||
|
color: #554477; font-family: 'Courier New', monospace;
|
||||||
|
font-size: 16px; width: 28px; height: 28px;
|
||||||
|
cursor: pointer; transition: color 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
#history-close:hover { color: #9966dd; border-color: #7755aa; }
|
||||||
|
|
||||||
|
#history-status {
|
||||||
|
font-size: 11px; padding: 12px 20px;
|
||||||
|
color: #334466; letter-spacing: 1px; min-height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#history-list {
|
||||||
|
flex: 1; overflow-y: auto;
|
||||||
|
padding: 0 0 16px;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-row {
|
||||||
|
border-bottom: 1px solid #100a20;
|
||||||
|
}
|
||||||
|
.history-row-header {
|
||||||
|
padding: 12px 20px;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.history-row-header.history-expandable {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.history-row-header.history-expandable:active {
|
||||||
|
background: rgba(100, 60, 160, 0.08);
|
||||||
|
}
|
||||||
|
.history-prompt {
|
||||||
|
color: #aabbdd; font-size: 12px; line-height: 1.4;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.history-row-open .history-prompt {
|
||||||
|
-webkit-line-clamp: unset;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.history-meta {
|
||||||
|
display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.history-agent {
|
||||||
|
font-size: 9px; letter-spacing: 2px; color: #7755aa;
|
||||||
|
border: 1px solid #3a2555; padding: 1px 5px;
|
||||||
|
}
|
||||||
|
.history-cost { font-size: 10px; color: #ffcc44; letter-spacing: 1px; }
|
||||||
|
.history-cost-free { color: #44dd88; }
|
||||||
|
.history-time { font-size: 10px; color: #445566; flex: 1; }
|
||||||
|
.history-state { font-size: 12px; font-weight: bold; }
|
||||||
|
.state-complete { color: #44dd88; }
|
||||||
|
.state-rejected { color: #dd6644; }
|
||||||
|
.state-pending { color: #ffcc44; }
|
||||||
|
|
||||||
|
.history-row-body {
|
||||||
|
max-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
.history-result {
|
||||||
|
margin: 0 20px 12px;
|
||||||
|
background: #060310; border: 1px solid #1a1030;
|
||||||
|
color: #aabbdd; padding: 12px;
|
||||||
|
font-size: 11px; line-height: 1.6;
|
||||||
|
white-space: pre-wrap; word-break: break-word;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
.history-result-rejected { color: #dd8866; border-color: #3a1a10; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -541,6 +667,7 @@
|
|||||||
<div id="top-buttons">
|
<div id="top-buttons">
|
||||||
<button id="open-panel-btn">⚡ SUBMIT JOB</button>
|
<button id="open-panel-btn">⚡ SUBMIT JOB</button>
|
||||||
<button id="open-session-btn">⚡ FUND SESSION</button>
|
<button id="open-session-btn">⚡ FUND SESSION</button>
|
||||||
|
<button id="open-history-btn">◷ HISTORY</button>
|
||||||
<a id="relay-admin-btn" href="/admin/relay">⚙ RELAY ADMIN</a>
|
<a id="relay-admin-btn" href="/admin/relay">⚙ RELAY ADMIN</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -690,6 +817,19 @@
|
|||||||
<div id="session-error"></div>
|
<div id="session-error"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ── History panel (bottom sheet) ──────────────────────────────── -->
|
||||||
|
<div id="history-panel">
|
||||||
|
<div id="history-panel-header">
|
||||||
|
<h2>◷ JOB HISTORY</h2>
|
||||||
|
<div id="history-panel-actions">
|
||||||
|
<button id="history-refresh-btn">↻ REFRESH</button>
|
||||||
|
<button id="history-close">✕</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="history-status"></div>
|
||||||
|
<div id="history-list"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- ── FPS crosshair ─────────────────────────────────────────────── -->
|
<!-- ── FPS crosshair ─────────────────────────────────────────────── -->
|
||||||
<div id="crosshair"></div>
|
<div id="crosshair"></div>
|
||||||
|
|
||||||
|
|||||||
222
the-matrix/js/history.js
Normal file
222
the-matrix/js/history.js
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
/**
|
||||||
|
* history.js — Job history panel for Timmy Tower mobile.
|
||||||
|
*
|
||||||
|
* Shows completed jobs from GET /api/jobs in reverse chronological order.
|
||||||
|
* Each row is expandable to reveal the full result.
|
||||||
|
* Supports pull-to-refresh (scroll to top + overscroll) and a refresh button.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getOrRefreshToken } from './nostr-identity.js';
|
||||||
|
|
||||||
|
const API_BASE = '/api';
|
||||||
|
|
||||||
|
// Deterministic agent label from job id (purely cosmetic — no real agent tracking)
|
||||||
|
const AGENT_LABELS = ['ALPHA', 'BETA', 'GAMMA', 'DELTA'];
|
||||||
|
function _agentForId(id) {
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < Math.min(8, id.length); i++) sum += id.charCodeAt(i);
|
||||||
|
return AGENT_LABELS[sum % AGENT_LABELS.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _relativeTime(isoString) {
|
||||||
|
if (!isoString) return '';
|
||||||
|
const diff = Date.now() - new Date(isoString).getTime();
|
||||||
|
const sec = Math.floor(diff / 1000);
|
||||||
|
if (sec < 60) return `${sec}s ago`;
|
||||||
|
const min = Math.floor(sec / 60);
|
||||||
|
if (min < 60) return `${min} min ago`;
|
||||||
|
const hrs = Math.floor(min / 60);
|
||||||
|
if (hrs < 24) return `${hrs}h ago`;
|
||||||
|
const days = Math.floor(hrs / 24);
|
||||||
|
return `${days}d ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _panel = null;
|
||||||
|
let _list = null;
|
||||||
|
let _status = null;
|
||||||
|
let _loading = false;
|
||||||
|
|
||||||
|
// Pull-to-refresh state
|
||||||
|
let _ptStart = 0;
|
||||||
|
let _ptActive = false;
|
||||||
|
const PT_THRESHOLD = 60; // px
|
||||||
|
|
||||||
|
export function initHistoryPanel() {
|
||||||
|
_panel = document.getElementById('history-panel');
|
||||||
|
_list = document.getElementById('history-list');
|
||||||
|
_status = document.getElementById('history-status');
|
||||||
|
if (!_panel) return;
|
||||||
|
|
||||||
|
document.getElementById('open-history-btn')
|
||||||
|
?.addEventListener('click', openHistoryPanel);
|
||||||
|
document.getElementById('history-close')
|
||||||
|
?.addEventListener('click', closeHistoryPanel);
|
||||||
|
document.getElementById('history-refresh-btn')
|
||||||
|
?.addEventListener('click', () => loadHistory());
|
||||||
|
|
||||||
|
// Pull-to-refresh on the list container
|
||||||
|
if (_list) {
|
||||||
|
_list.addEventListener('touchstart', _onPtStart, { passive: true });
|
||||||
|
_list.addEventListener('touchmove', _onPtMove, { passive: true });
|
||||||
|
_list.addEventListener('touchend', _onPtEnd, { passive: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHistoryPanel() {
|
||||||
|
if (!_panel) return;
|
||||||
|
_panel.classList.add('open');
|
||||||
|
loadHistory();
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeHistoryPanel() {
|
||||||
|
_panel?.classList.remove('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Pull-to-refresh ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
function _onPtStart(e) {
|
||||||
|
if (_list.scrollTop === 0 && e.touches.length === 1) {
|
||||||
|
_ptStart = e.touches[0].clientY;
|
||||||
|
_ptActive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function _onPtMove(e) {
|
||||||
|
if (!_ptActive) return;
|
||||||
|
const dy = e.touches[0].clientY - _ptStart;
|
||||||
|
if (dy > PT_THRESHOLD) {
|
||||||
|
_ptActive = false;
|
||||||
|
if (_status) _status.textContent = 'Refreshing…';
|
||||||
|
loadHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function _onPtEnd() { _ptActive = false; }
|
||||||
|
|
||||||
|
// ── Data loading ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async function loadHistory() {
|
||||||
|
if (_loading) return;
|
||||||
|
_loading = true;
|
||||||
|
if (_status) { _status.textContent = 'Loading…'; _status.style.color = '#5577aa'; }
|
||||||
|
|
||||||
|
try {
|
||||||
|
const token = await getOrRefreshToken('/api');
|
||||||
|
if (!token) {
|
||||||
|
renderEmpty('Sign in with Nostr to view your job history.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${API_BASE}/jobs`, {
|
||||||
|
headers: { 'X-Nostr-Token': token },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.status === 401) {
|
||||||
|
renderEmpty('Session expired — reload the page to sign in again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!res.ok) {
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (_status) { _status.textContent = data.error || 'Failed to load history.'; _status.style.color = '#994444'; }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
renderJobs(data.jobs ?? []);
|
||||||
|
} catch (err) {
|
||||||
|
if (_status) { _status.textContent = 'Network error: ' + err.message; _status.style.color = '#994444'; }
|
||||||
|
} finally {
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderEmpty(msg) {
|
||||||
|
if (_list) { _list.innerHTML = ''; }
|
||||||
|
if (_status) { _status.textContent = msg; _status.style.color = '#334466'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderJobs(jobList) {
|
||||||
|
if (!_list) return;
|
||||||
|
_list.innerHTML = '';
|
||||||
|
if (_status) _status.textContent = '';
|
||||||
|
|
||||||
|
if (!jobList.length) {
|
||||||
|
renderEmpty('No completed jobs yet. Submit a job to get started!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const job of jobList) {
|
||||||
|
_list.appendChild(_buildJobRow(job));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _buildJobRow(job) {
|
||||||
|
const isComplete = job.state === 'complete';
|
||||||
|
const isRejected = job.state === 'rejected';
|
||||||
|
const hasContent = isComplete || isRejected;
|
||||||
|
|
||||||
|
const row = document.createElement('div');
|
||||||
|
row.className = 'history-row';
|
||||||
|
|
||||||
|
// ── Header (always visible) ────────────────────────────────────────────────
|
||||||
|
const header = document.createElement('div');
|
||||||
|
header.className = 'history-row-header';
|
||||||
|
|
||||||
|
const prompt = document.createElement('div');
|
||||||
|
prompt.className = 'history-prompt';
|
||||||
|
prompt.textContent = job.request;
|
||||||
|
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'history-meta';
|
||||||
|
|
||||||
|
const agentSpan = document.createElement('span');
|
||||||
|
agentSpan.className = 'history-agent';
|
||||||
|
agentSpan.textContent = _agentForId(job.id);
|
||||||
|
|
||||||
|
const costSpan = document.createElement('span');
|
||||||
|
costSpan.className = 'history-cost';
|
||||||
|
if (job.freeTier) {
|
||||||
|
costSpan.textContent = 'FREE';
|
||||||
|
costSpan.classList.add('history-cost-free');
|
||||||
|
} else {
|
||||||
|
costSpan.textContent = job.costSats != null ? `${job.costSats} sats` : '— sats';
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeSpan = document.createElement('span');
|
||||||
|
timeSpan.className = 'history-time';
|
||||||
|
timeSpan.textContent = _relativeTime(job.completedAt ?? job.createdAt);
|
||||||
|
|
||||||
|
const stateSpan = document.createElement('span');
|
||||||
|
stateSpan.className = 'history-state';
|
||||||
|
if (isComplete) { stateSpan.textContent = '✓'; stateSpan.classList.add('state-complete'); }
|
||||||
|
else if (isRejected) { stateSpan.textContent = '✗'; stateSpan.classList.add('state-rejected'); }
|
||||||
|
else { stateSpan.textContent = '…'; stateSpan.classList.add('state-pending'); }
|
||||||
|
|
||||||
|
meta.append(agentSpan, costSpan, timeSpan, stateSpan);
|
||||||
|
header.append(prompt, meta);
|
||||||
|
|
||||||
|
// ── Expandable result ──────────────────────────────────────────────────────
|
||||||
|
const body = document.createElement('div');
|
||||||
|
body.className = 'history-row-body';
|
||||||
|
|
||||||
|
if (hasContent) {
|
||||||
|
const content = isComplete ? (job.result || '') : (job.rejectionReason || 'Request rejected.');
|
||||||
|
const pre = document.createElement('pre');
|
||||||
|
pre.className = 'history-result';
|
||||||
|
pre.textContent = content;
|
||||||
|
if (isRejected) pre.classList.add('history-result-rejected');
|
||||||
|
body.appendChild(pre);
|
||||||
|
|
||||||
|
header.classList.add('history-expandable');
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
const isOpen = row.classList.toggle('history-row-open');
|
||||||
|
// Animate body height
|
||||||
|
if (isOpen) {
|
||||||
|
body.style.maxHeight = body.scrollHeight + 'px';
|
||||||
|
} else {
|
||||||
|
body.style.maxHeight = '0';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
row.append(header, body);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import { initInteraction, disposeInteraction, registerSlapTarget } from './inter
|
|||||||
import { initWebSocket, getConnectionState, getJobCount } from './websocket.js';
|
import { initWebSocket, getConnectionState, getJobCount } from './websocket.js';
|
||||||
import { initPaymentPanel } from './payment.js';
|
import { initPaymentPanel } from './payment.js';
|
||||||
import { initSessionPanel } from './session.js';
|
import { initSessionPanel } from './session.js';
|
||||||
|
import { initHistoryPanel } from './history.js';
|
||||||
import { initNostrIdentity } from './nostr-identity.js';
|
import { initNostrIdentity } from './nostr-identity.js';
|
||||||
import { warmup as warmupEdgeWorker, onReady as onEdgeWorkerReady } from './edge-worker-client.js';
|
import { warmup as warmupEdgeWorker, onReady as onEdgeWorkerReady } from './edge-worker-client.js';
|
||||||
import { setEdgeWorkerReady } from './ui.js';
|
import { setEdgeWorkerReady } from './ui.js';
|
||||||
@@ -46,6 +47,7 @@ function buildWorld(firstInit, stateSnapshot) {
|
|||||||
initWebSocket(scene);
|
initWebSocket(scene);
|
||||||
initPaymentPanel();
|
initPaymentPanel();
|
||||||
initSessionPanel();
|
initSessionPanel();
|
||||||
|
initHistoryPanel();
|
||||||
void initNostrIdentity('/api');
|
void initNostrIdentity('/api');
|
||||||
warmupEdgeWorker();
|
warmupEdgeWorker();
|
||||||
onEdgeWorkerReady(() => setEdgeWorkerReady());
|
onEdgeWorkerReady(() => setEdgeWorkerReady());
|
||||||
|
|||||||
Reference in New Issue
Block a user