Fetches from /api/docs/webhook and renders as a styled tabbed page. Added /docs route and DOCS nav link. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
263 lines
9.9 KiB
Vue
263 lines
9.9 KiB
Vue
<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>
|