Split the monolithic 5393-line app.js into 32 focused ES modules under modules/ with a thin ~330-line orchestrator. No bundler required — runs in-browser via import maps. Module structure: core/ — scene, ticker, state, theme, audio data/ — gitea, weather, bitcoin, loaders terrain/ — stars, clouds, island effects/ — matrix-rain, energy-beam, lightning, shockwave, rune-ring, gravity-zones panels/ — heatmap, sigil, sovereignty, dual-brain, batcave, earth, agent-board, lora-panel portals/ — portal-system, commit-banners narrative/ — bookshelves, oath, chat utils/ — perlin All files pass node --check. No new dependencies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
202 lines
7.1 KiB
JavaScript
202 lines
7.1 KiB
JavaScript
// modules/data/gitea.js — All Gitea API calls
|
|
import { state } from '../core/state.js';
|
|
|
|
const GITEA_BASE = 'http://143.198.27.163:3000/api/v1';
|
|
const GITEA_TOKEN = '81a88f46684e398abe081f5786a11ae9532aae2d';
|
|
const GITEA_REPOS = ['Timmy_Foundation/the-nexus', 'Timmy_Foundation/hermes-agent'];
|
|
const AGENT_NAMES = ['Claude', 'Kimi', 'Perplexity', 'Groq', 'Grok', 'Ollama'];
|
|
const HEATMAP_DECAY_MS = 24 * 60 * 60 * 1000;
|
|
|
|
export const HEATMAP_ZONES = [
|
|
{ name: 'Claude', color: [255, 100, 60], authorMatch: /^claude$/i, angleDeg: 0 },
|
|
{ name: 'Timmy', color: [ 60, 160, 255], authorMatch: /^timmy/i, angleDeg: 90 },
|
|
{ name: 'Kimi', color: [ 60, 255, 140], authorMatch: /^kimi/i, angleDeg: 180 },
|
|
{ name: 'Perplexity', color: [200, 60, 255], authorMatch: /^perplexity/i, angleDeg: 270 },
|
|
];
|
|
|
|
export async function fetchCommits() {
|
|
let commits = [];
|
|
try {
|
|
const res = await fetch(
|
|
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/commits?limit=50`,
|
|
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
|
|
);
|
|
if (res.ok) commits = await res.json();
|
|
} catch { /* silently use zero-activity baseline */ }
|
|
|
|
state.commitHashes = commits.slice(0, 20).map(c => (c.sha || '').slice(0, 7)).filter(h => h.length > 0);
|
|
state.commits = commits;
|
|
|
|
const now = Date.now();
|
|
const rawWeights = Object.fromEntries(HEATMAP_ZONES.map(z => [z.name, 0]));
|
|
|
|
for (const commit of commits) {
|
|
const author = commit.commit?.author?.name || commit.author?.login || '';
|
|
const ts = new Date(commit.commit?.author?.date || 0).getTime();
|
|
const age = now - ts;
|
|
if (age > HEATMAP_DECAY_MS) continue;
|
|
const weight = 1 - age / HEATMAP_DECAY_MS;
|
|
for (const zone of HEATMAP_ZONES) {
|
|
if (zone.authorMatch.test(author)) {
|
|
rawWeights[zone.name] += weight;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const MAX_WEIGHT = 8;
|
|
for (const zone of HEATMAP_ZONES) {
|
|
state.zoneIntensity[zone.name] = Math.min(rawWeights[zone.name] / MAX_WEIGHT, 1.0);
|
|
}
|
|
}
|
|
|
|
let _agentStatusCache = null;
|
|
let _agentStatusCacheTime = 0;
|
|
const AGENT_STATUS_CACHE_MS = 5 * 60 * 1000;
|
|
|
|
export async function fetchAgentStatus() {
|
|
const now = Date.now();
|
|
if (_agentStatusCache && (now - _agentStatusCacheTime < AGENT_STATUS_CACHE_MS)) {
|
|
return _agentStatusCache;
|
|
}
|
|
|
|
const DAY_MS = 86400000;
|
|
const HOUR_MS = 3600000;
|
|
const agents = [];
|
|
|
|
const allRepoCommits = await Promise.all(GITEA_REPOS.map(async (repo) => {
|
|
try {
|
|
const res = await fetch(`${GITEA_BASE}/repos/${repo}/commits?sha=main&limit=30&token=${GITEA_TOKEN}`);
|
|
if (!res.ok) return [];
|
|
return await res.json();
|
|
} catch { return []; }
|
|
}));
|
|
|
|
let openPRs = [];
|
|
try {
|
|
const prRes = await fetch(`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/pulls?state=open&limit=50&token=${GITEA_TOKEN}`);
|
|
if (prRes.ok) openPRs = await prRes.json();
|
|
} catch { /* ignore */ }
|
|
|
|
for (const agentName of AGENT_NAMES) {
|
|
const nameLower = agentName.toLowerCase();
|
|
const allCommits = [];
|
|
for (const repoCommits of allRepoCommits) {
|
|
if (!Array.isArray(repoCommits)) continue;
|
|
const matching = repoCommits.filter(c =>
|
|
(c.commit?.author?.name || '').toLowerCase().includes(nameLower)
|
|
);
|
|
allCommits.push(...matching);
|
|
}
|
|
|
|
let status = 'dormant';
|
|
let lastSeen = null;
|
|
let currentWork = null;
|
|
|
|
if (allCommits.length > 0) {
|
|
allCommits.sort((a, b) => new Date(b.commit.author.date) - new Date(a.commit.author.date));
|
|
const latest = allCommits[0];
|
|
const commitTime = new Date(latest.commit.author.date).getTime();
|
|
lastSeen = latest.commit.author.date;
|
|
currentWork = latest.commit.message.split('\n')[0];
|
|
if (now - commitTime < HOUR_MS) status = 'working';
|
|
else if (now - commitTime < DAY_MS) status = 'idle';
|
|
else status = 'dormant';
|
|
}
|
|
|
|
const agentPRs = openPRs.filter(pr =>
|
|
(pr.user?.login || '').toLowerCase().includes(nameLower) ||
|
|
(pr.head?.label || '').toLowerCase().includes(nameLower)
|
|
);
|
|
|
|
agents.push({
|
|
name: agentName.toLowerCase(),
|
|
status,
|
|
issue: currentWork,
|
|
prs_today: agentPRs.length,
|
|
local: nameLower === 'ollama',
|
|
});
|
|
}
|
|
|
|
_agentStatusCache = { agents };
|
|
_agentStatusCacheTime = now;
|
|
state.agentStatus = _agentStatusCache;
|
|
state.activeAgentCount = agents.filter(a => a.status === 'working').length;
|
|
return _agentStatusCache;
|
|
}
|
|
|
|
export async function fetchRecentCommitsForBanners() {
|
|
try {
|
|
const res = await fetch(
|
|
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/commits?limit=5`,
|
|
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
|
|
);
|
|
if (!res.ok) throw new Error('fetch failed');
|
|
const data = await res.json();
|
|
return data.map(c => ({
|
|
hash: c.sha.slice(0, 7),
|
|
message: c.commit.message.split('\n')[0],
|
|
}));
|
|
} catch {
|
|
return [
|
|
{ hash: 'a1b2c3d', message: 'feat: depth of field effect on distant objects' },
|
|
{ hash: 'e4f5g6h', message: 'feat: photo mode with orbit controls' },
|
|
{ hash: 'i7j8k9l', message: 'feat: sovereignty easter egg animation' },
|
|
{ hash: 'm0n1o2p', message: 'feat: overview mode bird\'s-eye view' },
|
|
{ hash: 'q3r4s5t', message: 'feat: star field and constellation lines' },
|
|
];
|
|
}
|
|
}
|
|
|
|
export async function fetchClosedPRsForBookshelf() {
|
|
try {
|
|
const res = await fetch(
|
|
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/pulls?state=closed&limit=20`,
|
|
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
|
|
);
|
|
if (!res.ok) throw new Error('fetch failed');
|
|
const data = await res.json();
|
|
return data
|
|
.filter(p => p.merged)
|
|
.map(p => ({
|
|
prNum: p.number,
|
|
title: p.title.replace(/^\[[\w\s]+\]\s*/i, '').replace(/\s*\(#\d+\)\s*$/, ''),
|
|
}));
|
|
} catch {
|
|
return [
|
|
{ prNum: 324, title: 'Model training status — LoRA adapters' },
|
|
{ prNum: 323, title: 'The Oath — interactive SOUL.md reading' },
|
|
{ prNum: 320, title: 'Hermes session save/load' },
|
|
{ prNum: 304, title: 'Session export as markdown' },
|
|
{ prNum: 303, title: 'Procedural Web Audio ambient soundtrack' },
|
|
{ prNum: 301, title: 'Warp tunnel effect for portals' },
|
|
{ prNum: 296, title: 'Procedural terrain for floating island' },
|
|
{ prNum: 294, title: 'Northern lights flash on PR merge' },
|
|
];
|
|
}
|
|
}
|
|
|
|
export async function fetchTimelapseCommits() {
|
|
try {
|
|
const res = await fetch(
|
|
`${GITEA_BASE}/repos/Timmy_Foundation/the-nexus/commits?limit=50`,
|
|
{ headers: { 'Authorization': 'token dc0517a965226b7a0c5ffdd961b1ba26521ac592' } }
|
|
);
|
|
if (!res.ok) throw new Error('fetch failed');
|
|
const data = await res.json();
|
|
const midnight = new Date();
|
|
midnight.setHours(0, 0, 0, 0);
|
|
return data
|
|
.map(c => ({
|
|
ts: new Date(c.commit?.author?.date || 0).getTime(),
|
|
author: c.commit?.author?.name || c.author?.login || 'unknown',
|
|
message: (c.commit?.message || '').split('\n')[0],
|
|
hash: (c.sha || '').slice(0, 7),
|
|
}))
|
|
.filter(c => c.ts >= midnight.getTime())
|
|
.sort((a, b) => a.ts - b.ts);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|