Some checks failed
Autonomous Repo Agent / autonomous-build (push) Has been cancelled
BlackRoad AI Agents / agent-response (push) Has been cancelled
🔍 BlackRoad CodeQL Security Analysis / CodeQL Analysis (javascript) (push) Has been cancelled
🔍 BlackRoad CodeQL Security Analysis / CodeQL Analysis (python) (push) Has been cancelled
CI / Test (push) Has been cancelled
Deploy to Cloudflare Pages / Deploy to Cloudflare Pages (push) Has been cancelled
Trinity Compliance Check / check-compliance (push) Has been cancelled
Synced from BlackRoad-OS-Inc/blackroad-operator/orgs/core/blackroad-os-web BlackRoad OS — Pave Tomorrow. RoadChain-SHA2048: 13032509284e1f6c RoadChain-Identity: alexa@sovereign RoadChain-Full: 13032509284e1f6ca60f7004aa28e90fdc0fdae165e934d79f9ee91ee80caa9c42b57ad6c0ed9c400d303a39716259ad59602b6bc19ba3ea0720412c7957b64908250e99db1c5debc19331e7d473bb26d0c501cf1f02155ec53315372f62c0a36ca9d67d033e42c4d9683c2220eda4b4f4487eff9e474726e279d738e8a613870d38f5197ee4504b40c95ce73a1df4eb837b18bfce046609b29fbb4a7bdb83501806d25bfaa79be4f46f31b9616511733690a6b2a6257084c264223462161aca13e0608a59f5a0cc55f9835d640a1dde518b15c019a4ba62e8513cbbd58fd436d9e401fa12a1a8c82908b4688359b829c90e76067668e4793638a8d33fb9a77c
130 lines
4.0 KiB
TypeScript
130 lines
4.0 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
interface WorldArtifact {
|
|
id: string;
|
|
title: string;
|
|
node: string;
|
|
type: 'world' | 'lore' | 'code';
|
|
timestamp: string;
|
|
link: string;
|
|
}
|
|
|
|
const TYPE_COLORS: Record<string, string> = {
|
|
world: '#2979FF',
|
|
lore: '#9C27B0',
|
|
code: '#FF1D6C',
|
|
};
|
|
|
|
const NODE_EMOJI: Record<string, string> = {
|
|
aria64: '🔴',
|
|
alice: '🔵',
|
|
};
|
|
|
|
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');
|
|
|
|
useEffect(() => {
|
|
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);
|
|
}
|
|
}
|
|
load();
|
|
const interval = setInterval(load, 60000); // refresh every 60s
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const filtered = filter === 'all' ? worlds : worlds.filter(w => w.type === filter || w.node === filter);
|
|
|
|
return (
|
|
<div className="p-6 max-w-5xl mx-auto">
|
|
<div className="mb-6 flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-white">🌍 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>
|
|
<a
|
|
href="https://worlds.blackroad.io/rss"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="text-xs px-3 py-1 rounded border border-gray-600 text-gray-400 hover:text-white hover:border-gray-400 transition-colors"
|
|
>
|
|
RSS Feed ↗
|
|
</a>
|
|
</div>
|
|
|
|
{/* Filter pills */}
|
|
<div className="flex gap-2 mb-4 flex-wrap">
|
|
{['all', 'world', 'lore', 'code', 'aria64', 'alice'].map(f => (
|
|
<button
|
|
key={f}
|
|
onClick={() => setFilter(f)}
|
|
className={`px-3 py-1 rounded-full text-xs font-medium transition-colors ${
|
|
filter === f
|
|
? 'bg-white text-black'
|
|
: 'bg-gray-800 text-gray-400 hover:bg-gray-700 hover:text-white'
|
|
}`}
|
|
>
|
|
{f === 'all' ? 'All' : f}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="text-gray-500 text-sm">Loading worlds…</div>
|
|
) : (
|
|
<div className="grid gap-2">
|
|
{filtered.map(w => (
|
|
<a
|
|
key={w.id}
|
|
href={w.link}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="flex items-center gap-3 p-3 rounded-lg bg-gray-900 border border-gray-800 hover:border-gray-600 transition-colors group"
|
|
>
|
|
<span
|
|
className="w-2 h-2 rounded-full flex-shrink-0"
|
|
style={{ backgroundColor: TYPE_COLORS[w.type] || '#666' }}
|
|
/>
|
|
<span className="text-white text-sm font-medium flex-1 group-hover:text-blue-300 transition-colors">
|
|
{w.title}
|
|
</span>
|
|
<span className="text-xs text-gray-500">
|
|
{NODE_EMOJI[w.node] || '⚪'} {w.node}
|
|
</span>
|
|
<span
|
|
className="text-xs px-2 py-0.5 rounded"
|
|
style={{
|
|
backgroundColor: (TYPE_COLORS[w.type] || '#666') + '22',
|
|
color: TYPE_COLORS[w.type] || '#aaa',
|
|
}}
|
|
>
|
|
{w.type}
|
|
</span>
|
|
<span className="text-xs text-gray-600 font-mono">
|
|
{new Date(w.timestamp).toLocaleTimeString()}
|
|
</span>
|
|
</a>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{!loading && filtered.length === 0 && (
|
|
<div className="text-gray-500 text-sm text-center py-12">No artifacts match this filter.</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|