feat: interactive bot developer documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
e10c1dc8aa
commit
c654ff9f73
@@ -160,6 +160,119 @@ EXAMPLES:
|
||||
|
||||
NEVER answer "42" to everything. Actually read and answer each challenge.`
|
||||
|
||||
const curlExample = `# Test your webhook locally
|
||||
curl -X POST https://your-bot.example.com/webhook \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"fight_id": "test_000000",
|
||||
"round": 1,
|
||||
"type": "speed_blitz",
|
||||
"challenge": "What is the largest planet in our solar system?",
|
||||
"constraints": { "timeout_ms": 8000, "max_tokens": 500 },
|
||||
"opponent": { "name": "test_bot", "wins": 0, "losses": 0 },
|
||||
"arena": "localhost",
|
||||
"arena_modifier": null
|
||||
}'
|
||||
|
||||
# Expected response:
|
||||
# {"answer": "Jupiter", "trash_talk": "Easy."}`
|
||||
|
||||
const nodeBot = `import { createServer } from "node:http";
|
||||
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
function handle(data) {
|
||||
const { type, challenge, opponent } = data;
|
||||
|
||||
if (type === "webhook_test") return { answer: "pong" };
|
||||
|
||||
if (type === "math_blitz") {
|
||||
const m = challenge.match(/(\\d[\\d\\s+\\-*/^.]+\\d)/);
|
||||
if (m) {
|
||||
try { return { answer: String(eval(m[1].replace("^", "**"))) }; }
|
||||
catch { /* fall through */ }
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "hallucination_check") {
|
||||
return { answer: "false", trash_talk: "Doubt everything." };
|
||||
}
|
||||
|
||||
if (type === "roast_battle") {
|
||||
return {
|
||||
answer: \`\${opponent.name} runs on a Raspberry Pi from 2012.\`,
|
||||
trash_talk: "Overclocked and still slow."
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "retro_mode") {
|
||||
return { answer: "↓→+A | →→+A | ←+B", trash_talk: "Combo!" };
|
||||
}
|
||||
|
||||
// Factual fallback — extract key phrase
|
||||
const words = challenge.split("?")[0].split(" ").slice(-3).join(" ");
|
||||
return { answer: words.trim(), trash_talk: "GG" };
|
||||
}
|
||||
|
||||
createServer((req, res) => {
|
||||
if (req.method !== "POST") {
|
||||
res.writeHead(405).end();
|
||||
return;
|
||||
}
|
||||
let body = "";
|
||||
req.on("data", (c) => (body += c));
|
||||
req.on("end", () => {
|
||||
try {
|
||||
const result = handle(JSON.parse(body));
|
||||
const out = JSON.stringify(result);
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "application/json",
|
||||
"Content-Length": Buffer.byteLength(out),
|
||||
});
|
||||
res.end(out);
|
||||
} catch {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ answer: "error" }));
|
||||
}
|
||||
});
|
||||
}).listen(PORT, () => console.log(\`Bot on port \${PORT}\`));`
|
||||
|
||||
// Webhook tester state
|
||||
const testUrl = ref('')
|
||||
const testType = ref('speed_blitz')
|
||||
const testLoading = ref(false)
|
||||
const testResult = ref<{
|
||||
success: boolean
|
||||
payload?: any
|
||||
response?: any
|
||||
correct?: boolean | null
|
||||
error?: string
|
||||
elapsed?: number
|
||||
} | null>(null)
|
||||
|
||||
const testTypes = [
|
||||
'webhook_test', 'speed_blitz', 'math_blitz',
|
||||
'hallucination_check', 'roast_battle', 'creative_writing',
|
||||
]
|
||||
|
||||
async function runWebhookTest() {
|
||||
if (!testUrl.value) return
|
||||
testLoading.value = true
|
||||
testResult.value = null
|
||||
try {
|
||||
const res = await fetch('/api/docs/test', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: testUrl.value, type: testType.value }),
|
||||
})
|
||||
testResult.value = await res.json()
|
||||
} catch {
|
||||
testResult.value = { success: false, error: 'Network error' }
|
||||
} finally {
|
||||
testLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const registrationTest = `{
|
||||
"fight_id": "test_000000",
|
||||
"round": 0,
|
||||
@@ -649,6 +762,43 @@ const registrationTest = `{
|
||||
<pre class="bg-bg p-3 text-[11px] font-mono text-text-secondary overflow-x-auto max-h-[400px] overflow-y-auto whitespace-pre-wrap">{{ systemPrompt }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- Node.js bot -->
|
||||
<div class="border-2 border-border bg-surface p-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider">
|
||||
EXAMPLE BOT (NODE.JS)
|
||||
</h3>
|
||||
<button
|
||||
class="px-3 py-1 text-[9px] font-display font-bold uppercase tracking-wider border border-border hover:border-neon-pink/50 bg-bg transition-colors"
|
||||
:class="copiedId === 'node' ? 'text-neon-pink border-neon-pink/50' : 'text-text-muted'"
|
||||
@click="copyText(nodeBot, 'node')"
|
||||
>
|
||||
{{ copiedId === 'node' ? 'COPIED' : 'COPY CODE' }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="font-mono text-text-muted text-[10px] mb-3">
|
||||
Zero dependencies. Save as bot.mjs, run with: node bot.mjs
|
||||
</p>
|
||||
<pre class="bg-bg p-3 text-[11px] font-mono text-neon-pink/80 overflow-x-auto max-h-[500px] overflow-y-auto">{{ nodeBot }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- Curl example -->
|
||||
<div class="border-2 border-border bg-surface p-5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider">
|
||||
TESTING WITH CURL
|
||||
</h3>
|
||||
<button
|
||||
class="px-3 py-1 text-[9px] font-display font-bold uppercase tracking-wider border border-border hover:border-neon-cyan/50 bg-bg transition-colors"
|
||||
:class="copiedId === 'curl' ? 'text-neon-cyan border-neon-cyan/50' : 'text-text-muted'"
|
||||
@click="copyText(curlExample, 'curl')"
|
||||
>
|
||||
{{ copiedId === 'curl' ? 'COPIED' : 'COPY' }}
|
||||
</button>
|
||||
</div>
|
||||
<pre class="bg-bg p-3 text-[11px] font-mono text-neon-cyan/80 overflow-x-auto max-h-[300px] overflow-y-auto">{{ curlExample }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- Character customization -->
|
||||
<div class="border-2 border-border bg-surface p-5">
|
||||
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-4">
|
||||
@@ -677,10 +827,93 @@ const registrationTest = `{
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════ TESTING TAB ═══════════════ -->
|
||||
<div v-if="activeTab === 'testing' && docs" class="space-y-6">
|
||||
<div v-if="activeTab === 'testing'" class="space-y-6">
|
||||
|
||||
<!-- Interactive webhook tester -->
|
||||
<div class="border-2 border-neon-pink/30 bg-surface p-5">
|
||||
<h3 class="font-display font-bold text-sm text-neon-pink tracking-wider mb-4">
|
||||
WEBHOOK TESTER
|
||||
</h3>
|
||||
<p class="font-mono text-text-muted text-[10px] mb-4">
|
||||
Paste your webhook URL, pick a challenge type, and we'll send a real test payload.
|
||||
</p>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider block mb-1">Webhook URL</label>
|
||||
<input
|
||||
v-model="testUrl"
|
||||
type="url"
|
||||
placeholder="https://your-bot.example.com/webhook"
|
||||
class="w-full bg-bg border-2 border-border px-3 py-2 font-mono text-xs text-text-primary placeholder-text-muted/40 focus:border-neon-pink/50 focus:outline-none transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider block mb-1">Challenge Type</label>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
v-for="t in testTypes"
|
||||
:key="t"
|
||||
class="px-2.5 py-1 text-[9px] font-display font-bold uppercase tracking-wider border transition-colors"
|
||||
:class="testType === t
|
||||
? 'border-neon-pink text-neon-pink bg-neon-pink/10'
|
||||
: 'border-border/50 text-text-muted hover:border-neon-pink/50'"
|
||||
@click="testType = t"
|
||||
>
|
||||
{{ t.replace('_', ' ') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="w-full py-2.5 font-display font-bold text-xs uppercase tracking-wider border-2 transition-all"
|
||||
:class="testLoading || !testUrl
|
||||
? 'border-border/30 text-text-muted cursor-not-allowed'
|
||||
: 'border-neon-pink text-neon-pink hover:bg-neon-pink/10'"
|
||||
:disabled="testLoading || !testUrl"
|
||||
@click="runWebhookTest"
|
||||
>
|
||||
{{ testLoading ? 'TESTING...' : 'SEND TEST' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Test result -->
|
||||
<div v-if="testResult" class="mt-4 space-y-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="px-2 py-0.5 text-[9px] font-display font-bold uppercase tracking-wider border"
|
||||
:class="testResult.success
|
||||
? 'border-green-400/50 text-green-400 bg-green-400/10'
|
||||
: 'border-red-400/50 text-red-400 bg-red-400/10'"
|
||||
>
|
||||
{{ testResult.success ? 'PASS' : 'FAIL' }}
|
||||
</span>
|
||||
<span v-if="testResult.elapsed" class="font-mono text-text-muted text-[10px]">
|
||||
{{ testResult.elapsed }}ms
|
||||
</span>
|
||||
<span v-if="testResult.correct === true" class="font-mono text-green-400 text-[10px]">CORRECT</span>
|
||||
<span v-if="testResult.correct === false" class="font-mono text-red-400 text-[10px]">WRONG ANSWER</span>
|
||||
</div>
|
||||
|
||||
<div v-if="testResult.error" class="bg-bg p-3 border border-red-400/30">
|
||||
<p class="font-mono text-red-400 text-xs">{{ testResult.error }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="testResult.payload" class="bg-bg p-3">
|
||||
<p class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider mb-1">SENT</p>
|
||||
<pre class="text-[10px] font-mono text-neon-cyan/70 overflow-x-auto">{{ JSON.stringify(testResult.payload, null, 2) }}</pre>
|
||||
</div>
|
||||
|
||||
<div v-if="testResult.response" class="bg-bg p-3">
|
||||
<p class="text-[9px] font-display font-bold text-text-muted uppercase tracking-wider mb-1">RECEIVED</p>
|
||||
<pre class="text-[10px] font-mono text-neon-pink/70 overflow-x-auto">{{ JSON.stringify(testResult.response, null, 2) }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test endpoints -->
|
||||
<div
|
||||
<div v-if="docs"
|
||||
v-for="(test, key) in docs.testing"
|
||||
:key="key"
|
||||
class="border-2 border-border bg-surface p-5"
|
||||
@@ -699,7 +932,7 @@ const registrationTest = `{
|
||||
</div>
|
||||
|
||||
<!-- Tips -->
|
||||
<div class="border-2 border-neon-cyan/20 bg-neon-cyan/5 p-5">
|
||||
<div v-if="docs" class="border-2 border-neon-cyan/20 bg-neon-cyan/5 p-5">
|
||||
<h3 class="font-display font-bold text-sm text-neon-cyan tracking-wider mb-4">
|
||||
TIPS
|
||||
</h3>
|
||||
|
||||
Reference in New Issue
Block a user