Code + prompt
I made the smartest AI models debate: Messi or Ronaldo?
AI Panel puts your question and documents in front of a panel of top AI models, lets them debate over several rounds, then tells you what they agreed on: a one-line verdict, the to-dos every model backed, and the points still contested. Runs locally with one OpenRouter key. The main idea is to counter the sycophancy that comes with individual models and get a more balanced answer. Built by AppMunchiesOfficial.
View the code ↗Remix this
Paste this into your AI coding assistant to rebuild it your way.
Take this repo: https://github.com/ebauch/AIPanel and rebuild "I made the smartest AI models debate: Messi or Ronaldo?" for my platform. Build **AI Panel**, a local web app where a user puts a question and source
documents to a panel of AI models, the models debate it over several rounds,
and a final "takeaways" model tells the user what the panel agreed on. The
debate is the show; the agreed takeaways are the product.
## Stack
Next.js (App Router) with React and Tailwind. All model calls go to
OpenRouter's chat completions endpoint over streaming SSE; no provider SDKs.
The API key comes from `OPENROUTER_API_KEY` in `.env.local`, or from a key the
user pastes on a Settings page (stored in localStorage and sent as a request
header that the server forwards). Ship `.env.local.example`, an MIT license,
and a README.
## Inputs (left panel, scrollable, sticky)
- Context brief: a textarea for the question or situation.
- Source documents: a list of rows, each with a label, a "Upload PDF" button
(server extracts text), an "Add URL" button (server fetches the page and
strips it to text, with a Wikipedia fast path via its extract API, and
rejects localhost and private IP ranges), and a textarea for pasted text.
Documents over 150,000 characters are truncated with a visible warning.
- Debaters: a model list fetched from OpenRouter's catalog, chat models
only, excluding free, batch, image, audio, and embedding variants. Show
"Top models" (one flagship per provider: Anthropic, OpenAI, Google, xAI,
picked as the newest release, tie broken by highest completion price)
and "More models" grouped by provider. Each row shows name, id, context
length, price per million tokens, and a "reasoning" tag. Above the list,
a segmented control with three presets, Flagship, Balanced (about a
quarter of the flagship price), and Budget (about a tenth), each showing
its estimated cost; a fourth "Custom" state when the selection matches
none. Balanced is the default. Under it, a checkbox "Debaters remember
their own earlier responses", on by default.
- Format: debate mode (Conversation with no stances, Assigned stances,
Randomized stances with N iterations), reasoning effort (Normal, High),
rounds (default 3). For stance modes, editable stance rows with a label
and instruction, defaulting to Pro, Against, and Balanced with
topic-neutral instructions.
- Takeaways: a checkbox "Distill takeaways when the debate ends", on by
default, and a takeaways model dropdown defaulting to the newest Claude
Fable, then Opus, then the first debater.
- A sticky bar at the top of the panel with "Run debate", "Stop", a cost
estimate pill, and a "Start fresh" link that resets every input except
the model selection, confirming first if a finished debate is on screen.
- An "Examples" strip above the form with three one-click examples:
Messi vs Ronaldo (two Wikipedia URLs), a fictional resume vs job
description (two bundled PDFs, generated by a script, nothing real),
and Paris vs New York (two Wikipedia URLs). Loading one fills the form
through the same PDF and URL paths a user would use.
## Orchestration
- For each iteration, assign stances to models in order (shuffled per
iteration in randomized mode). For each round, each model speaks in turn.
- Every turn is a fresh chat completion with no conversation history. The
system message is two parts: (1) panel context, the model's stance
instruction, and the brief; (2) the documents, carrying an Anthropic-style
`cache_control` marker with a one-hour TTL. This prefix must be
byte-identical across a model's turns so providers cache it. The user
message holds only the task: on the opening turn, "open the debate"; on
rebuttals, the model's own earlier responses (when memory is on), then
the other debaters' messages since its last turn, then "respond to their
points".
- Send `max_tokens` of about 8k and `usage: { include: true }`. Map
reasoning effort to OpenRouter's `reasoning.effort` only for models that
list it in supported parameters.
- Retries: transient errors get three attempts; HTTP 429 gets five attempts
spread over about two minutes, with the countdown shown on the turn card.
Never retry once a token has streamed. If a turn still fails on a
transient error, mark it Skipped and continue the debate; stop only on
errors retrying cannot fix, showing the provider's message verbatim.
- Stream events to the browser over SSE: debate start, iteration start
with assignments, round start, turn start, reasoning tokens, content
tokens, activity messages, turn end with usage (prompt, completion,
cached, cache-write tokens, and cost), round end, debate end, then the
takeaways events.
## Takeaways
After the debate, one call to the takeaways model with the brief, the
documents, the panel roster (short model names with stances), and the full
transcript. Ask for JSON only: a one-sentence verdict of at most 25 words,
a confidence of high, medium, or low, up to eight takeaways each with a
title, one or two sentences of detail, a kind of "action" or "finding",
and lists of supporter and dissenter model names, plus up to four
disagreements with per-model positions. Append the JSON schema in code
after the user-editable instructions so edits cannot break parsing.
Request `response_format: json_object` when the model supports it. Parse
leniently (strip fences, slice from the first brace to the last), and
match supporter names to the roster tolerantly, ignoring provider
prefixes, stance suffixes, and partial names. On parse failure, show the
raw text as markdown.
## Main pane
- While running: a sticky header with panel chips colored by stance (Pro
violet, Against amber, Balanced gray), "Round 2 of 3, model X speaking",
elapsed time, running cost and tokens with a cached percentage, and Stop.
Below it, turn cards grouped by iteration and round, rendering markdown,
with a collapsible "Thinking" block that is open until content starts,
a per-turn line like "in 44.1k (38.0k cached) / out 4.2k, $0.65", and
collapse to about eight lines once finished with "Show more".
- When takeaways arrive, the pane becomes the result and scrolls to top:
the verdict as a calm headline with a confidence pill and a meta line
(panel, mode, rounds, cost, time); "Agreed by the whole panel" in green
listing takeaways every model backed, actions before findings, each with
one vote dot per model; "Contested" in amber for the rest with a "For:
A, B, Against: C" line; "Where they split" listing disagreements. Then a
row with "Show debate" (expands the transcript), Export (a select for
Transcript, Takeaways, or both, plus "Download .md", with agreed actions
written as a markdown task list), "Regenerate takeaways", "Edit and run
again", and "Start fresh".
- The Run button shows "Starting…", "Running…" with a spinner, and
"Distilling…" while work is in progress.
## Cost estimate
Estimate tokens as characters divided by four. For each turn, input is the
system prefix plus the transcript so far in that iteration, output assumed
at about 1,500 tokens; on a model's repeat turns count the document tokens
at a quarter to reflect caching. Add the takeaways call. Multiply by each
model's per-token prices from the catalog.
## Settings page
An OpenRouter key field with Save and Clear, and editable prompt templates
for the system context, the opening turn with and without a stance, the
rebuttal, and the takeaways instructions, each with a reset. Store
overrides in localStorage.
## Copy
Headline: "Put it to the panel. Walk away with what they agree on." Keep
every default prompt and example topic-neutral.