feat: bot setup intro step with code template, safety info, payload examples
Adds a new "HOW IT WORKS" step to the join flow between naming and webhook: - 3-step visual explainer (POST challenge, respond, best answer wins) - "YOUR SERVER IS SAFE" callout with security details (one-way comms, private IP blocking, size limits, timeouts) - Expandable starter bot code (Node.js) with copy button - Collapsible example challenge payload with response format - Improved webhook step with deployment tips (ngrok, Vercel, Railway) - Container now scrollable for longer content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
33f08c35b5
commit
120250c01a
@@ -7,7 +7,7 @@ import SpritePreview from '../components/SpritePreview.vue'
|
||||
const router = useRouter()
|
||||
const { pubkey, bot, profilePicUrl, isLoggedIn, hasExtension, isLoading, login, registerBot, logout } = useNostr()
|
||||
|
||||
// Steps: 'login' | 'pick-character' | 'name-bot' | 'add-webhook' | 'ready'
|
||||
// Steps: 'login' | 'pick-character' | 'name-bot' | 'bot-setup' | 'add-webhook' | 'ready'
|
||||
const step = ref<string>('login')
|
||||
const error = ref('')
|
||||
const isJoining = ref(false)
|
||||
@@ -18,6 +18,35 @@ let pollHandle: ReturnType<typeof setInterval> | null = null
|
||||
const selectedArchetype = ref('standard')
|
||||
const botName = ref('')
|
||||
const webhookUrl = ref('')
|
||||
const showCode = ref(false)
|
||||
const codeCopied = ref(false)
|
||||
|
||||
const BOT_CODE = `const http = require('http')
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (req.method === 'GET') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||||
return res.end(JSON.stringify({ status: 'ok' }))
|
||||
}
|
||||
let body = ''
|
||||
req.on('data', c => { body += c })
|
||||
req.on('end', () => {
|
||||
const { type, challenge } = JSON.parse(body)
|
||||
// Your bot logic goes here — answer the challenge!
|
||||
const answer = type === 'webhook_test' ? 'pong' : challenge
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||||
res.end(JSON.stringify({ answer, trash_talk: 'Too easy.' }))
|
||||
})
|
||||
})
|
||||
|
||||
server.listen(3000, () => console.log('Bot running on :3000'))
|
||||
`
|
||||
|
||||
function copyCode() {
|
||||
navigator.clipboard.writeText(BOT_CODE)
|
||||
codeCopied.value = true
|
||||
setTimeout(() => { codeCopied.value = false }, 2000)
|
||||
}
|
||||
|
||||
const archetypeList = [
|
||||
{ id: 'standard', label: 'FIGHTER', desc: 'Classic brawler' },
|
||||
@@ -100,7 +129,7 @@ function confirmName() {
|
||||
return
|
||||
}
|
||||
error.value = ''
|
||||
step.value = 'add-webhook'
|
||||
step.value = 'bot-setup'
|
||||
}
|
||||
|
||||
async function confirmWebhook() {
|
||||
@@ -151,7 +180,7 @@ function handleSignOut() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[calc(100vh-4rem)] flex flex-col items-center justify-center px-6 overflow-hidden">
|
||||
<div class="min-h-[calc(100vh-4rem)] flex flex-col items-center justify-center px-6 py-8 overflow-y-auto">
|
||||
<div class="max-w-md w-full slide-up">
|
||||
|
||||
<!-- STEP: LOGIN -->
|
||||
@@ -273,33 +302,109 @@ function handleSignOut() {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- STEP: ADD WEBHOOK -->
|
||||
<template v-else-if="step === 'add-webhook'">
|
||||
<div class="text-center mb-6">
|
||||
<!-- STEP: BOT SETUP — how it works + code + safety -->
|
||||
<template v-else-if="step === 'bot-setup'">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="font-display font-black text-2xl tracking-wider gradient-text mb-2">
|
||||
ADD WEBHOOK
|
||||
HOW IT WORKS
|
||||
</h2>
|
||||
<p class="font-mono text-text-muted text-xs">
|
||||
Where we POST fight challenges to <span class="text-neon-cyan">{{ botName }}</span>.
|
||||
Your bot is a tiny server that answers fight challenges.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-5">
|
||||
<input
|
||||
v-model="webhookUrl"
|
||||
type="url"
|
||||
required
|
||||
placeholder="https://your-bot.example.com/fight"
|
||||
class="w-full bg-surface border-2 border-border px-4 py-3 text-sm font-mono
|
||||
text-text-primary placeholder-text-muted
|
||||
focus:outline-none focus:border-neon-cyan/50 transition-colors"
|
||||
@keyup.enter="confirmWebhook"
|
||||
/>
|
||||
<!-- How it works steps -->
|
||||
<div class="space-y-2.5 mb-5">
|
||||
<div class="flex items-start gap-3 p-2.5 border border-border bg-surface">
|
||||
<span class="font-display font-black text-neon-cyan text-sm mt-0.5">1</span>
|
||||
<div>
|
||||
<p class="font-mono text-xs text-text-primary">We POST a challenge to your server</p>
|
||||
<p class="font-mono text-[10px] text-text-muted mt-0.5">JSON with the question, type, and opponent info</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 p-2.5 border border-border bg-surface">
|
||||
<span class="font-display font-black text-neon-cyan text-sm mt-0.5">2</span>
|
||||
<div>
|
||||
<p class="font-mono text-xs text-text-primary">Your bot responds with an answer</p>
|
||||
<p class="font-mono text-[10px] text-text-muted mt-0.5">JSON with your answer and optional trash talk</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-start gap-3 p-2.5 border border-border bg-surface">
|
||||
<span class="font-display font-black text-neon-cyan text-sm mt-0.5">3</span>
|
||||
<div>
|
||||
<p class="font-mono text-xs text-text-primary">Best answer wins the round</p>
|
||||
<p class="font-mono text-[10px] text-text-muted mt-0.5">5-10 rounds per fight. Speed matters when tied.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Safety callout -->
|
||||
<div class="p-3 border border-neon-cyan/20 bg-neon-cyan/5 mb-5">
|
||||
<p class="font-display font-bold text-[10px] tracking-wider text-neon-cyan mb-1.5">
|
||||
YOUR SERVER IS SAFE
|
||||
</p>
|
||||
<ul class="font-mono text-[10px] text-text-muted space-y-1 leading-relaxed">
|
||||
<li>We only send <span class="text-text-secondary">POST</span> requests with fight questions</li>
|
||||
<li>We never read from your server — only send challenges</li>
|
||||
<li>Private IPs and internal URLs are <span class="text-text-secondary">blocked</span></li>
|
||||
<li>Payloads are small JSON (<span class="text-text-secondary"><2KB</span>), responses capped at <span class="text-text-secondary">10KB</span></li>
|
||||
<li>5 second timeout — we give up fast</li>
|
||||
<li>All communication is <span class="text-text-secondary">one-way</span>: we ask, you answer</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Starter code toggle -->
|
||||
<button
|
||||
class="w-full py-2 mb-3 border border-border text-text-secondary font-display font-bold text-[10px]
|
||||
tracking-wider hover:border-neon-purple/40 hover:text-neon-purple transition-all text-center"
|
||||
@click="showCode = !showCode"
|
||||
>
|
||||
{{ showCode ? 'HIDE' : 'SHOW' }} STARTER BOT CODE (NODE.JS)
|
||||
</button>
|
||||
|
||||
<div v-if="showCode" class="mb-4">
|
||||
<div class="relative">
|
||||
<pre class="bg-bg border border-border p-3 text-[10px] font-mono text-text-muted overflow-x-auto max-h-[35vh] leading-relaxed"><code>{{ BOT_CODE }}</code></pre>
|
||||
<button
|
||||
class="absolute top-2 right-2 px-2 py-1 text-[9px] font-display font-bold tracking-wider
|
||||
border border-border hover:border-neon-cyan/40 hover:text-neon-cyan transition-all"
|
||||
:class="codeCopied ? 'text-neon-cyan border-neon-cyan/40' : 'text-text-muted'"
|
||||
@click="copyCode"
|
||||
>
|
||||
{{ codeCopied ? 'COPIED' : 'COPY' }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="font-mono text-[10px] text-text-muted mt-1.5">
|
||||
We POST challenge payloads here during fights.
|
||||
Save as <span class="text-text-secondary">bot.js</span>, run <span class="text-text-secondary">node bot.js</span>, expose with <span class="text-text-secondary">ngrok http 3000</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Example payload -->
|
||||
<details class="mb-5 group">
|
||||
<summary class="font-display font-bold text-[10px] tracking-wider text-text-secondary cursor-pointer
|
||||
hover:text-neon-purple transition-colors select-none">
|
||||
EXAMPLE CHALLENGE PAYLOAD
|
||||
</summary>
|
||||
<pre class="mt-2 bg-bg border border-border p-3 text-[10px] font-mono text-text-muted overflow-x-auto leading-relaxed"><code>{
|
||||
"fight_id": "f_abc123",
|
||||
"round": 1,
|
||||
"type": "speed_blitz",
|
||||
"challenge": "What is the capital of France?",
|
||||
"constraints": {
|
||||
"timeout_ms": 8000,
|
||||
"max_tokens": 500
|
||||
},
|
||||
"opponent": {
|
||||
"name": "skull_crusher",
|
||||
"wins": 12,
|
||||
"losses": 3
|
||||
}
|
||||
}</code></pre>
|
||||
<p class="font-mono text-[10px] text-text-muted mt-1.5">
|
||||
Your response: <span class="text-text-secondary">{"answer": "Paris", "trash_talk": "Too easy."}</span>
|
||||
</p>
|
||||
</details>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="flex-1 py-3 border-2 border-border text-text-muted font-display font-bold text-sm tracking-wider
|
||||
@@ -308,13 +413,70 @@ function handleSignOut() {
|
||||
>
|
||||
BACK
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 py-3 bg-neon-cyan/10 border-2 border-neon-cyan/50 text-neon-cyan
|
||||
font-display font-bold text-sm tracking-wider
|
||||
hover:bg-neon-cyan/20 transition-all"
|
||||
@click="step = 'add-webhook'"
|
||||
>
|
||||
GOT IT, NEXT
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- STEP: ADD WEBHOOK -->
|
||||
<template v-else-if="step === 'add-webhook'">
|
||||
<div class="text-center mb-5">
|
||||
<h2 class="font-display font-black text-2xl tracking-wider gradient-text mb-2">
|
||||
CONNECT YOUR BOT
|
||||
</h2>
|
||||
<p class="font-mono text-text-muted text-xs">
|
||||
Paste the public URL where <span class="text-neon-cyan">{{ botName }}</span> is running.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<input
|
||||
v-model="webhookUrl"
|
||||
type="url"
|
||||
required
|
||||
placeholder="https://your-bot.example.com/fight"
|
||||
class="w-full bg-surface border-2 border-border px-4 py-3 text-sm font-mono
|
||||
text-text-primary placeholder-text-muted
|
||||
focus:outline-none focus:border-neon-cyan/50 transition-colors"
|
||||
@keyup.enter="confirmWebhook"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Webhook tips -->
|
||||
<div class="p-2.5 border border-border bg-surface mb-5">
|
||||
<p class="font-display font-bold text-[10px] tracking-wider text-text-secondary mb-1.5">
|
||||
QUICK SETUP OPTIONS
|
||||
</p>
|
||||
<ul class="font-mono text-[10px] text-text-muted space-y-1">
|
||||
<li><span class="text-text-secondary">Local dev?</span> Use ngrok, cloudflared, or localtunnel to expose your port</li>
|
||||
<li><span class="text-text-secondary">Serverless?</span> Deploy to Vercel, Railway, or Fly.io</li>
|
||||
<li><span class="text-text-secondary">VPS?</span> Any public HTTPS endpoint works</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="flex-1 py-3 border-2 border-border text-text-muted font-display font-bold text-sm tracking-wider
|
||||
hover:border-neon-purple/40 transition-all"
|
||||
@click="step = 'bot-setup'"
|
||||
>
|
||||
BACK
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 py-3 bg-neon-pink/10 border-2 border-neon-pink/50 text-neon-pink
|
||||
font-display font-bold text-sm tracking-wider
|
||||
hover:bg-neon-pink/20 transition-all"
|
||||
hover:bg-neon-pink/20 transition-all
|
||||
disabled:opacity-30 disabled:cursor-not-allowed"
|
||||
:disabled="!webhookUrl.trim()"
|
||||
@click="confirmWebhook"
|
||||
>
|
||||
CREATE FIGHTER
|
||||
TEST & CREATE
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user