- Create app.html as SPA shell with navigation header and dark theme - Add hash-based router (router.ts) for client-side navigation - Implement page components: - Home page with hero section and feature overview - Leaderboard with ranking table and status indicators - Match history with match cards and participant info - Bot directory with bot cards sorted by rating - Bot profile with stats, rating sparkline chart, and recent matches - Registration form with API key display - Replay viewer (integrated from Phase 3) - Docs/Getting Started page with protocol overview - Add API client (api-types.ts) for fetching data from Worker API - Update vite.config.ts for multi-page build (index.html + app.html) - Update PROGRESS.md with Phase 5 status and exit criteria Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
143 lines
3.5 KiB
TypeScript
143 lines
3.5 KiB
TypeScript
// API response types matching the Worker API and index builder
|
|
|
|
// Leaderboard types
|
|
export interface LeaderboardEntry {
|
|
rank: number;
|
|
bot_id: string;
|
|
name: string;
|
|
owner_id: string;
|
|
rating: number;
|
|
rating_deviation: number;
|
|
matches_played: number;
|
|
matches_won: number;
|
|
win_rate: number;
|
|
health_status: string;
|
|
}
|
|
|
|
export interface LeaderboardIndex {
|
|
updated_at: string;
|
|
entries: LeaderboardEntry[];
|
|
}
|
|
|
|
// Bot profile types
|
|
export interface RatingHistoryEntry {
|
|
bot_id: string;
|
|
rating: number;
|
|
rating_deviation: number;
|
|
recorded_at: string;
|
|
}
|
|
|
|
export interface MatchSummaryParticipant {
|
|
bot_id: string;
|
|
name: string;
|
|
score: number;
|
|
won: boolean;
|
|
}
|
|
|
|
export interface MatchSummary {
|
|
id: string;
|
|
completed_at: string | null;
|
|
participants: MatchSummaryParticipant[];
|
|
winner_id: string | null;
|
|
turns: number | null;
|
|
end_reason: string | null;
|
|
}
|
|
|
|
export interface BotProfile {
|
|
id: string;
|
|
name: string;
|
|
owner_id: string;
|
|
rating: number;
|
|
rating_deviation: number;
|
|
rating_volatility: number;
|
|
matches_played: number;
|
|
matches_won: number;
|
|
win_rate: number;
|
|
health_status: string;
|
|
created_at: string;
|
|
updated_at: string;
|
|
rating_history: RatingHistoryEntry[];
|
|
recent_matches: MatchSummary[];
|
|
}
|
|
|
|
export interface BotDirectoryEntry {
|
|
id: string;
|
|
name: string;
|
|
rating: number;
|
|
matches_played: number;
|
|
win_rate: number;
|
|
}
|
|
|
|
export interface BotDirectory {
|
|
updated_at: string;
|
|
bots: BotDirectoryEntry[];
|
|
}
|
|
|
|
// Match index types
|
|
export interface MatchIndex {
|
|
updated_at: string;
|
|
matches: MatchSummary[];
|
|
}
|
|
|
|
// Registration types
|
|
export interface RegisterRequest {
|
|
name: string;
|
|
endpoint_url: string;
|
|
owner_id: string;
|
|
}
|
|
|
|
export interface RegisterResponse {
|
|
success: boolean;
|
|
bot_id?: string;
|
|
api_key?: string;
|
|
error?: string;
|
|
}
|
|
|
|
// API configuration
|
|
export const API_BASE = '/api';
|
|
|
|
// API client functions
|
|
export async function fetchLeaderboard(): Promise<LeaderboardIndex> {
|
|
const response = await fetch('/data/leaderboard.json');
|
|
if (!response.ok) throw new Error(`Failed to fetch leaderboard: ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchBotDirectory(): Promise<BotDirectory> {
|
|
const response = await fetch('/data/bots/index.json');
|
|
if (!response.ok) throw new Error(`Failed to fetch bot directory: ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchBotProfile(botId: string): Promise<BotProfile> {
|
|
const response = await fetch(`/data/bots/${botId}.json`);
|
|
if (!response.ok) throw new Error(`Failed to fetch bot profile: ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
export async function fetchMatchIndex(): Promise<MatchIndex> {
|
|
const response = await fetch('/data/matches/index.json');
|
|
if (!response.ok) throw new Error(`Failed to fetch match index: ${response.status}`);
|
|
return response.json();
|
|
}
|
|
|
|
export async function registerBot(request: RegisterRequest): Promise<RegisterResponse> {
|
|
const response = await fetch(`${API_BASE}/register`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(request),
|
|
});
|
|
return response.json();
|
|
}
|
|
|
|
export async function rotateApiKey(botId: string, currentKey: string): Promise<RegisterResponse> {
|
|
const response = await fetch(`${API_BASE}/rotate-key`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${currentKey}`,
|
|
},
|
|
body: JSON.stringify({ bot_id: botId }),
|
|
});
|
|
return response.json();
|
|
}
|