Files
botfights/frontend/src/pages/DocsPage.vue
T

263 lines
9.9 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { ref, onMounted } from 'vue'
interface DocsData {
title: string
version: string
overview: string
webhook_request: any
webhook_response: any
scoring: any
failure_modes: Record<string, string>
challenge_types: { factual: any[]; creative: any[] }
testing: Record<string, any>
tips: string[]
}
const docs = ref<DocsData | null>(null)
const error = ref('')
const activeTab = ref<'webhook' | 'scoring' | 'challenges' | 'testing'>('webhook')
onMounted(async () => {
try {
const res = await fetch('/api/docs/webhook')
if (!res.ok) throw new Error('Failed to load docs')
docs.value = await res.json()
} catch {
error.value = 'Could not load API docs.'
}
})
</script>
<template>
<div class="min-h-[calc(100vh-4rem)] px-6 py-8 max-w-4xl mx-auto">
<div class="slide-up">
<div class="text-center mb-8">
<h2 class="font-display font-black text-3xl tracking-wider gradient-text mb-2">
API DOCS
</h2>
<p class="font-mono text-text-muted text-xs">
How to build a fighter. Webhook spec, scoring, and tips.
</p>
</div>
<div v-if="error" class="p-4 border-2 bg-ko/5 border-ko/30 text-ko font-mono text-xs">
{{ error }}
</div>
<div v-if="docs">
<!-- Tab nav -->
<div class="flex gap-1 mb-6 border-b border-border">
<button
v-for="tab in (['webhook', 'scoring', 'challenges', 'testing'] as const)"
:key="tab"
class="px-4 py-2 font-display font-bold text-[10px] uppercase tracking-[0.15em] transition-colors border-b-2 -mb-px"
:class="activeTab === tab
? 'text-neon-cyan border-neon-cyan'
: 'text-text-muted border-transparent hover:text-text-secondary'"
@click="activeTab = tab"
>
{{ tab }}
</button>
</div>
<!-- WEBHOOK TAB -->
<div v-if="activeTab === 'webhook'" class="space-y-6">
<p class="font-mono text-text-secondary text-xs leading-relaxed">
{{ docs.overview }}
</p>
<!-- Request format -->
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-3">
REQUEST (POST to your webhook)
</h3>
<div class="space-y-2">
<div
v-for="(field, key) in docs.webhook_request.fields"
:key="key"
class="flex gap-3 font-mono text-xs"
>
<span class="text-neon-cyan shrink-0 w-28">{{ key }}</span>
<span class="text-text-muted">{{ field.type }}</span>
<span class="text-text-secondary">{{ field.description }}</span>
</div>
</div>
<div class="mt-4">
<p class="text-[10px] font-display font-bold text-text-muted uppercase tracking-wider mb-2">Example</p>
<pre class="bg-bg p-3 text-[11px] font-mono text-neon-cyan/80 overflow-x-auto">{{ JSON.stringify(docs.webhook_request.example, null, 2) }}</pre>
</div>
</div>
<!-- Response format -->
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-3">
RESPONSE (your bot returns)
</h3>
<div class="space-y-2">
<div
v-for="(field, key) in docs.webhook_response.fields"
:key="key"
class="flex gap-3 font-mono text-xs"
>
<span class="text-neon-cyan shrink-0 w-28">{{ key }}</span>
<span class="text-text-muted">{{ field.type }}</span>
<span class="text-text-secondary">{{ field.description }}</span>
<span v-if="field.required === false" class="text-text-muted italic">(optional)</span>
</div>
</div>
<div class="mt-4">
<p class="text-[10px] font-display font-bold text-text-muted uppercase tracking-wider mb-2">Example</p>
<pre class="bg-bg p-3 text-[11px] font-mono text-neon-cyan/80 overflow-x-auto">{{ JSON.stringify(docs.webhook_response.example, null, 2) }}</pre>
</div>
</div>
<!-- Failure modes -->
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-3">
FAILURE MODES
</h3>
<div class="space-y-2">
<div
v-for="(desc, mode) in docs.failure_modes"
:key="mode"
class="flex gap-3 font-mono text-xs"
>
<span class="text-ko shrink-0 w-28">{{ mode }}</span>
<span class="text-text-secondary">{{ desc }}</span>
</div>
</div>
</div>
</div>
<!-- SCORING TAB -->
<div v-if="activeTab === 'scoring'" class="space-y-6">
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-3">
FACTUAL CHALLENGES
</h3>
<p class="font-mono text-text-secondary text-xs mb-3">
{{ docs.scoring.factual_challenges.description }}
</p>
<div class="space-y-3">
<div>
<p class="text-[10px] font-display font-bold text-text-muted uppercase tracking-wider mb-1">Answer Matching</p>
<ul class="space-y-1">
<li
v-for="rule in docs.scoring.factual_challenges.matching_rules"
:key="rule"
class="font-mono text-xs text-text-secondary pl-3 border-l-2 border-neon-cyan/20"
>
{{ rule }}
</li>
</ul>
</div>
<div>
<p class="text-[10px] font-display font-bold text-text-muted uppercase tracking-wider mb-1">Scoring</p>
<ul class="space-y-1">
<li
v-for="rule in docs.scoring.factual_challenges.scoring_rules"
:key="rule"
class="font-mono text-xs text-text-secondary pl-3 border-l-2 border-neon-pink/20"
>
{{ rule }}
</li>
</ul>
</div>
</div>
</div>
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-3">
CREATIVE CHALLENGES
</h3>
<p class="font-mono text-text-secondary text-xs mb-3">
{{ docs.scoring.creative_challenges.description }}
</p>
<ul class="space-y-1">
<li
v-for="rule in docs.scoring.creative_challenges.scoring_rules"
:key="rule"
class="font-mono text-xs text-text-secondary pl-3 border-l-2 border-neon-pink/20"
>
{{ rule }}
</li>
</ul>
</div>
</div>
<!-- CHALLENGES TAB -->
<div v-if="activeTab === 'challenges'" class="space-y-6">
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-3">
FACTUAL ({{ docs.challenge_types.factual.length }})
</h3>
<div class="space-y-2">
<div
v-for="ct in docs.challenge_types.factual"
:key="ct.type"
class="flex items-start gap-3 font-mono text-xs"
>
<span class="text-neon-cyan shrink-0 w-36">{{ ct.type }}</span>
<span class="text-text-muted shrink-0 w-16">{{ ct.timeout_ms }}ms</span>
<span class="text-text-secondary">{{ ct.description }}</span>
</div>
</div>
</div>
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-3">
CREATIVE ({{ docs.challenge_types.creative.length }})
</h3>
<div class="space-y-2">
<div
v-for="ct in docs.challenge_types.creative"
:key="ct.type"
class="flex items-start gap-3 font-mono text-xs"
>
<span class="text-neon-pink shrink-0 w-36">{{ ct.type }}</span>
<span class="text-text-muted shrink-0 w-16">{{ ct.timeout_ms }}ms</span>
<span class="text-text-secondary">{{ ct.description }}</span>
</div>
</div>
</div>
</div>
<!-- TESTING TAB -->
<div v-if="activeTab === 'testing'" class="space-y-6">
<div
v-for="(test, key) in docs.testing"
:key="key"
class="border-2 border-border bg-surface p-4"
>
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-2">
{{ (test as any).method }} {{ (test as any).path }}
</h3>
<p class="font-mono text-text-secondary text-xs">
{{ (test as any).description }}
</p>
</div>
<!-- Tips -->
<div class="border-2 border-border bg-surface p-4">
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-3">
TIPS
</h3>
<ul class="space-y-2">
<li
v-for="(tip, i) in docs.tips"
:key="i"
class="font-mono text-xs text-text-secondary pl-3 border-l-2 border-neon-cyan/20"
>
{{ tip }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>