feat: real-time live data integration

- lib/live-data.ts: Shared TypeScript client for blackroad-live-data Worker
- components/live-stats.tsx: LiveStatsBar, RecentRepos, AgentStatusGrid components
- app/page.tsx: Import LiveStatsBar in main page header

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Alexa Amundson
2026-02-24 14:18:59 -06:00
parent 263f9f171e
commit 458c2c044b
97 changed files with 8715 additions and 1701 deletions

View File

@@ -1,77 +1,212 @@
// app/(app)/worlds/page.tsx
// Shows world generation stats from worlds.blackroad.io
// Server component with 10s revalidation
'use client';
async function getWorldStats() {
try {
const res = await fetch('https://worlds.blackroad.io/stats', { next: { revalidate: 10 } })
if (!res.ok) return null
return await res.json()
} catch { return null }
import { useEffect, useState } from 'react';
import { Globe, Scroll, Code2, Rss, Sparkles, RefreshCw, ExternalLink } from 'lucide-react';
interface WorldArtifact {
id: string;
title: string;
node: string;
type: 'world' | 'lore' | 'code';
timestamp: string;
link: string;
}
async function getRecentWorlds() {
try {
const res = await fetch('https://blackroad-agents-status.amundsonalexa.workers.dev/worlds?limit=10',
{ next: { revalidate: 10 } })
if (!res.ok) return []
const data = await res.json()
return data.worlds || []
} catch { return [] }
}
const TYPE_META: Record<string, { color: string; icon: any; label: string }> = {
world: { color: '#2979FF', icon: Globe, label: 'World' },
lore: { color: '#9C27B0', icon: Scroll, label: 'Lore' },
code: { color: '#FF1D6C', icon: Code2, label: 'Code' },
};
const NODE_DOT: Record<string, string> = {
aria64: '#FF1D6C',
alice: '#2979FF',
};
export default function WorldsPage() {
const [worlds, setWorlds] = useState<WorldArtifact[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState<string>('all');
const [generating, setGenerating] = useState(false);
async function load() {
try {
const res = await fetch('/api/worlds?limit=60');
const data = await res.json();
setWorlds(data.worlds || []);
setTotal(data.total || 0);
} finally {
setLoading(false);
}
}
useEffect(() => {
load();
const interval = setInterval(load, 60000);
return () => clearInterval(interval);
}, []);
const filtered = filter === 'all' ? worlds : worlds.filter(w => w.type === filter || w.node === filter);
const generateWorld = async () => {
setGenerating(true);
try {
// Ask the chat API to generate a world
const chatRes = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Generate a new BlackRoad world artifact. Return ONLY valid JSON with fields: name (string), type (one of: world|lore|code|artifact), description (2 sentences), lore (one evocative sentence), tags (array of 3 strings).',
}),
});
if (chatRes.ok) {
const chatData = await chatRes.json();
// Try to parse JSON from the response text
const text: string = chatData.text || chatData.message || '';
const match = text.match(/\{[\s\S]*\}/);
if (match) {
try {
const parsed = JSON.parse(match[0]);
// POST to /api/worlds to persist it
await fetch('/api/worlds', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ...parsed, agent: 'lucidia' }),
});
} catch { /* JSON parse failed — still reload */ }
}
}
await load();
} finally {
setGenerating(false);
}
};
export default async function WorldsPage() {
const stats = await getWorldStats()
const worlds = await getRecentWorlds()
const total = stats?.total || 0
const nodes = stats?.by_node || {}
return (
<div className="p-8 max-w-5xl">
<h1 className="text-3xl font-bold mb-2">🌍 World Generator</h1>
<p className="text-muted-foreground mb-8">
Autonomous AI worlds generated by BlackRoad Pi fleet
</p>
{/* Stats row */}
<div className="grid grid-cols-3 gap-4 mb-8">
<div className="rounded-xl border p-5 text-center">
<div className="text-4xl font-bold text-green-500">{total}</div>
<div className="text-sm text-muted-foreground mt-1">Total Worlds</div>
<div className="p-6 space-y-6">
{/* Header */}
<div className="flex items-start justify-between">
<div>
<h1 className="text-2xl font-bold text-white flex items-center gap-3">
<Globe className="h-7 w-7 text-[#2979FF]" />
World Artifacts
</h1>
<p className="text-gray-400 text-sm mt-1">
{total} artifacts generated by the Pi fleet · auto-refreshes every 60s
</p>
</div>
<div className="rounded-xl border p-5 text-center">
<div className="text-4xl font-bold text-blue-500">{nodes.aria64 || '—'}</div>
<div className="text-sm text-muted-foreground mt-1">aria64 Node</div>
</div>
<div className="rounded-xl border p-5 text-center">
<div className="text-4xl font-bold text-purple-500">{nodes.alice || '—'}</div>
<div className="text-sm text-muted-foreground mt-1">alice Node</div>
<div className="flex gap-2">
<button
onClick={generateWorld}
disabled={generating}
className="flex items-center gap-2 px-4 py-2 bg-gradient-to-r from-[#FF1D6C] to-violet-600 text-white text-sm font-medium rounded-xl hover:opacity-90 disabled:opacity-50 transition-opacity"
>
{generating ? <RefreshCw className="h-4 w-4 animate-spin" /> : <Sparkles className="h-4 w-4" />}
{generating ? 'Generating…' : 'Generate World'}
</button>
<a
href="https://worlds.blackroad.io/rss"
target="_blank"
rel="noreferrer"
className="flex items-center gap-1.5 px-3 py-2 bg-white/5 border border-white/10 rounded-xl text-gray-400 text-sm hover:text-white hover:bg-white/10 transition-all"
>
<Rss className="h-4 w-4" />
RSS
</a>
</div>
</div>
{/* Type breakdown */}
{stats?.by_type && (
<div className="mb-8">
<h2 className="text-lg font-semibold mb-3">By Type</h2>
<div className="flex gap-3 flex-wrap">
{Object.entries(stats.by_type).map(([type, count]: [string, any]) => {
const emojis: Record<string, string> = { lore: '📜', world: '🌍', code: '💻', story: '✨', tech: '🔧' }
return (
<div key={type} className="rounded-lg border px-4 py-2 flex items-center gap-2">
<span>{emojis[type] || '📄'}</span>
<span className="font-mono text-sm">{type}</span>
<span className="text-muted-foreground text-sm">{count}</span>
</div>
)
})}
{/* Stats */}
<div className="grid grid-cols-3 gap-4">
{[
{ label: 'Worlds', count: worlds.filter(w => w.type === 'world').length, color: '#2979FF', icon: Globe },
{ label: 'Lore', count: worlds.filter(w => w.type === 'lore').length, color: '#9C27B0', icon: Scroll },
{ label: 'Code', count: worlds.filter(w => w.type === 'code').length, color: '#FF1D6C', icon: Code2 },
].map(s => (
<div key={s.label} className="bg-white/5 border border-white/10 rounded-xl p-4 flex items-center gap-3">
<div className="w-9 h-9 rounded-lg flex items-center justify-center" style={{ background: s.color + '20' }}>
<s.icon className="h-4 w-4" style={{ color: s.color }} />
</div>
<div>
<div className="text-xl font-bold text-white">{s.count || '—'}</div>
<div className="text-xs text-gray-500">{s.label} artifacts</div>
</div>
</div>
))}
</div>
{/* Filter Pills */}
<div className="flex gap-2 flex-wrap">
{['all', 'world', 'lore', 'code', 'aria64', 'alice'].map(f => (
<button
key={f}
onClick={() => setFilter(f)}
className={`px-4 py-1.5 rounded-xl text-xs font-medium transition-all capitalize ${
filter === f ? 'bg-white/10 text-white' : 'text-gray-400 hover:text-white bg-white/5 hover:bg-white/8'
}`}
>
{f === 'aria64' ? '🔴 aria64' : f === 'alice' ? '🔵 alice' : f}
</button>
))}
</div>
{/* Artifacts */}
{loading ? (
<div className="flex items-center gap-3 text-gray-400">
<RefreshCw className="h-4 w-4 animate-spin" />
Loading worlds
</div>
) : (
<div className="space-y-2">
{filtered.map(w => {
const meta = TYPE_META[w.type] || TYPE_META.world;
const Icon = meta.icon;
return (
<a
key={w.id}
href={w.link}
target="_blank"
rel="noreferrer"
className="flex items-center gap-3 p-4 rounded-xl bg-white/5 border border-white/10 hover:bg-white/8 hover:border-white/20 transition-all group"
>
<div className="w-8 h-8 rounded-lg flex items-center justify-center shrink-0" style={{ background: meta.color + '20' }}>
<Icon className="h-4 w-4" style={{ color: meta.color }} />
</div>
<div className="flex-1 min-w-0">
<div className="text-white text-sm font-medium group-hover:text-[#2979FF] transition-colors truncate">
{w.title}
</div>
<div className="flex items-center gap-2 mt-0.5">
<span className="text-xs text-gray-500 font-mono">{w.node}</span>
<span className="text-gray-700">·</span>
<span className="text-xs text-gray-500">{new Date(w.timestamp).toLocaleTimeString()}</span>
</div>
</div>
<div className="flex items-center gap-2 shrink-0">
<span className="text-xs px-2 py-0.5 rounded-md font-medium" style={{ background: meta.color + '15', color: meta.color }}>
{meta.label}
</span>
<ExternalLink className="h-3.5 w-3.5 text-gray-600 group-hover:text-gray-400 transition-colors" />
</div>
</a>
);
})}
</div>
)}
{!loading && filtered.length === 0 && (
<div className="text-center py-16 text-gray-500">
<Globe className="h-12 w-12 mx-auto mb-3 opacity-20" />
<div>No artifacts match this filter.</div>
<button onClick={generateWorld} className="mt-4 text-[#FF1D6C] text-sm hover:underline">
Generate the first one
</button>
</div>
)}
<p className="text-sm text-muted-foreground">
Generating ~2 worlds/min · Live RSS: <a href="https://worlds-feed.blackroad.io/feed.rss" className="underline">worlds-feed.blackroad.io</a>
</p>
</div>
)
);
}