profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM

Commit

docker, ci and docs

commit 0a9fe03

9 changed files with +499 and −0

Jump to a changed file
  1. .dockerignore +15 −0
  2. .github/workflows/ci.yml +43 −0
  3. .github/workflows/deploy.yml +74 −0
  4. Caddyfile +22 −0
  5. Dockerfile +58 −0
  6. README.md +196 −0
  7. docker-compose.yml +69 −0
  8. docker/entrypoint.sh +11 −0
  9. docs/README.md +11 −0
added .dockerignore +15 −0
@@ -0,0 +1,15 @@
1 +node_modules
2 +.next
3 +.git
4 +.github
5 +coverage
6 +.env
7 +.env.*
8 +!.env.example
9 +npm-debug.log*
10 +Dockerfile
11 +.dockerignore
12 +docker-compose.yml
13 +*.md
14 +.vscode
15 +.idea
added .github/workflows/ci.yml +43 −0
@@ -0,0 +1,43 @@
1 +name: CI
2 +
3 +on:
4 + push:
5 + branches: [main]
6 + pull_request:
7 +
8 +jobs:
9 + quality:
10 + name: lint · typecheck · test · build
11 + runs-on: ubuntu-latest
12 + steps:
13 + - uses: actions/checkout@v4
14 +
15 + - uses: pnpm/action-setup@v4
16 + with:
17 + version: 9.15.0
18 +
19 + - uses: actions/setup-node@v4
20 + with:
21 + node-version: 20
22 + cache: pnpm
23 +
24 + - name: Install dependencies
25 + run: pnpm install --frozen-lockfile
26 +
27 + - name: Generate Prisma client
28 + run: pnpm prisma generate
29 +
30 + - name: Lint
31 + run: pnpm lint
32 +
33 + - name: Typecheck
34 + run: pnpm typecheck
35 +
36 + - name: Unit tests
37 + run: pnpm test
38 +
39 + - name: Build
40 + run: pnpm build
41 + env:
42 + SKIP_ENV_VALIDATION: '1'
43 + NEXT_TELEMETRY_DISABLED: '1'
added .github/workflows/deploy.yml +74 −0
@@ -0,0 +1,74 @@
1 +name: Deploy
2 +
3 +# Build a container image, push it to GHCR, then SSH into the VPS and roll it
4 +# out with docker compose. Requires these repository secrets:
5 +# VPS_HOST, VPS_USER, VPS_SSH_KEY, VPS_APP_DIR
6 +# (VPS_APP_DIR is the directory on the box containing docker-compose.yml + .env)
7 +
8 +on:
9 + push:
10 + branches: [main]
11 + workflow_dispatch:
12 +
13 +concurrency:
14 + group: deploy-${{ github.ref }}
15 + cancel-in-progress: true
16 +
17 +jobs:
18 + build-and-push:
19 + runs-on: ubuntu-latest
20 + permissions:
21 + contents: read
22 + packages: write
23 + outputs:
24 + image: ${{ steps.image.outputs.ref }}
25 + steps:
26 + - uses: actions/checkout@v4
27 +
28 + - name: Lowercase image name
29 + id: image
30 + run: echo "ref=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
31 +
32 + - uses: docker/setup-buildx-action@v3
33 +
34 + - uses: docker/login-action@v3
35 + with:
36 + registry: ghcr.io
37 + username: ${{ github.actor }}
38 + password: ${{ secrets.GITHUB_TOKEN }}
39 +
40 + - id: meta
41 + uses: docker/metadata-action@v5
42 + with:
43 + images: ${{ steps.image.outputs.ref }}
44 + tags: |
45 + type=sha
46 + type=raw,value=latest,enable={{is_default_branch}}
47 +
48 + - uses: docker/build-push-action@v6
49 + with:
50 + context: .
51 + push: true
52 + tags: ${{ steps.meta.outputs.tags }}
53 + labels: ${{ steps.meta.outputs.labels }}
54 + cache-from: type=gha
55 + cache-to: type=gha,mode=max
56 +
57 + deploy:
58 + needs: build-and-push
59 + runs-on: ubuntu-latest
60 + steps:
61 + - name: Deploy over SSH
62 + uses: appleboy/ssh-action@v1
63 + with:
64 + host: ${{ secrets.VPS_HOST }}
65 + username: ${{ secrets.VPS_USER }}
66 + key: ${{ secrets.VPS_SSH_KEY }}
67 + script: |
68 + set -e
69 + cd "${{ secrets.VPS_APP_DIR }}"
70 + echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
71 + export APP_IMAGE="${{ needs.build-and-push.outputs.image }}:latest"
72 + docker compose pull app
73 + docker compose up -d
74 + docker image prune -f
added Caddyfile +22 −0
@@ -0,0 +1,22 @@
1 +# Reverse proxy in front of the Next.js app.
2 +# `flush_interval -1` disables response buffering — essential for the long-lived
3 +# SSE debate streams, which push events over several minutes.
4 +
5 +{$SITE_ADDRESS} {
6 + encode zstd gzip
7 +
8 + reverse_proxy app:3000 {
9 + flush_interval -1
10 + transport http {
11 + # Debates can stream for minutes; give generous read/write windows.
12 + read_timeout 15m
13 + write_timeout 15m
14 + }
15 + }
16 +
17 + header {
18 + X-Content-Type-Options nosniff
19 + Referrer-Policy strict-origin-when-cross-origin
20 + -Server
21 + }
22 +}
added Dockerfile +58 −0
@@ -0,0 +1,58 @@
1 +# syntax=docker/dockerfile:1
2 +
3 +# ---------------------------------------------------------------------------
4 +# Roundtable — multi-stage build producing a standalone Next.js server image.
5 +# Base is debian-slim (not alpine) so the Prisma engine "just works".
6 +# ---------------------------------------------------------------------------
7 +FROM node:20-bookworm-slim AS base
8 +ENV PNPM_HOME=/pnpm
9 +ENV PATH=$PNPM_HOME:$PATH
10 +RUN corepack enable
11 +RUN apt-get update \
12 + && apt-get install -y --no-install-recommends openssl ca-certificates \
13 + && rm -rf /var/lib/apt/lists/*
14 +WORKDIR /app
15 +
16 +# --- Install dependencies (cached on the lockfile) -------------------------
17 +FROM base AS deps
18 +COPY package.json pnpm-lock.yaml ./
19 +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
20 +
21 +# --- Build the app (standalone output) -------------------------------------
22 +FROM base AS builder
23 +COPY --from=deps /app/node_modules ./node_modules
24 +COPY . .
25 +ENV NEXT_TELEMETRY_DISABLED=1
26 +# No runtime secrets needed to compile; env is validated at runtime instead.
27 +ENV SKIP_ENV_VALIDATION=1
28 +# Produce the self-contained standalone server for the runtime stage.
29 +ENV BUILD_STANDALONE=1
30 +RUN pnpm prisma generate
31 +RUN pnpm build
32 +
33 +# --- Runtime image ---------------------------------------------------------
34 +FROM base AS runner
35 +ENV NODE_ENV=production
36 +ENV NEXT_TELEMETRY_DISABLED=1
37 +ENV PORT=3000
38 +ENV HOSTNAME=0.0.0.0
39 +
40 +RUN groupadd -g 1001 nodejs && useradd -u 1001 -g nodejs -m nextjs
41 +
42 +# Standalone server + assets.
43 +COPY --from=builder /app/public ./public
44 +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
45 +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
46 +
47 +# Prisma CLI + engine + schema so the entrypoint can apply the schema at boot.
48 +COPY --from=builder /app/prisma ./prisma
49 +COPY --from=builder /app/node_modules/prisma ./node_modules/prisma
50 +COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
51 +COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
52 +
53 +COPY --chown=nextjs:nodejs docker/entrypoint.sh ./entrypoint.sh
54 +RUN chmod +x ./entrypoint.sh
55 +
56 +USER nextjs
57 +EXPOSE 3000
58 +CMD ["./entrypoint.sh"]
added README.md +196 −0
@@ -0,0 +1,196 @@
1 +<div align="center">
2 +
3 +# 🗣️ Roundtable
4 +
5 +**Don't ask one model. Convene a council.**
6 +
7 +A production-grade web app where your question is answered by several LLMs that **debate over multiple rounds** - critiquing and revising each other's answers anonymously - before a chairman synthesizes a final answer and an honest **dissent report**.
8 +
9 +Unlike one-shot "LLM jury" tools, models _see each other's critiques and revise across iterations_, and the UI exposes the **entire deliberation**: every answer per round, every critique, what changed between revisions, and where the models still disagree.
10 +
11 +[Live demo](#-demo-mode) · [Quick start](#-quick-start-no-api-key) · [Architecture](#-architecture) · [Design decisions](#-design-decisions)
12 +
13 +</div>
14 +
15 +---
16 +
17 +## Why this exists
18 +
19 +Asking a single model gives you a single model's blind spots. Ensembling by majority vote throws away the reasoning. **Roundtable** keeps the reasoning: models answer independently, critique each other blind to authorship, revise (or defend), and a neutral chairman synthesizes - surfacing genuine disagreement instead of averaging it away.
20 +
21 +Everything a reviewer needs to _trust_ the answer is on screen: the critique matrix, the revision diffs, the convergence score per round, and the per-model cost.
22 +
23 +## ✨ Features
24 +
25 +| | |
26 +|---|---|
27 +| **Multi-round debate engine** | Round 0 independent answers → per-round critique → revision → convergence check → chairman synthesis, as an explicit, unit-tested state machine. |
28 +| **Live streaming** | Per-model, per-stage progress streamed to the browser over SSE. Answers stream token-by-token. |
29 +| **Anonymized critique** | Each model reviews the _others'_ answers, relabeled `A/B/C...` in a per-reviewer randomized order - it cannot tell whose answer is whose. |
30 +| **Revision diffs** | Toggleable inline word-diff between a model's round _N_ and _N+1_ answers, with a structured changelog ("what changed & why" / "defending original"). |
31 +| **Critique matrix** | N×N grid of who-scored-whom; hover for the full critique; toggle to de-anonymize. |
32 +| **Dissent report** | The chairman explicitly records unresolved disagreements and which model held which position. |
33 +| **Self-preference mitigation** | Answers are anonymized to the chairman too; a warning badge flags when the chairman shares a provider family with a council member. |
34 +| **Cost transparency** | Live cost meter during the debate + a per-model breakdown chart after, with per-user monthly spend. |
35 +| **BYOK, two modes** | Bring your own OpenRouter key - saved (AES-256-GCM encrypted at rest) or session-only (encrypted HttpOnly cookie). No server-paid key. |
36 +| **Portfolio demo mode** | Public, no-login page that replays real recorded debates through the full UI with simulated streaming. |
37 +| **Export & share** | Download a Markdown deliberation report, or mint an unlisted share link. |
38 +| **Robust by design** | Per-model timeouts, exponential backoff, one-shot JSON repair, and graceful model drop-out (the debate proceeds with ≥2 healthy models). Debates continue server-side if the client disconnects. |
39 +
40 +## 🎬 Demo mode
41 +
42 +`/demo` ships seeded, pre-recorded debates you can replay end-to-end - timeline, streaming playback, revision diffs, critique matrix - **with zero configuration and no API key**. This is the recruiter-first experience; it runs entirely on `MOCK_LLM` fixtures.
43 +
44 +> Screenshots / GIF live in [`docs/`](docs/). Run the app (`pnpm dev`) and open `/demo` to see it live.
45 +
46 +## 🚀 Quick start (no API key)
47 +
48 +Everything runs offline in **mock mode** - a deterministic fake model powers real debates so you can develop and demo without spending a cent.
49 +
50 +```bash
51 +# 1. Install
52 +corepack enable && pnpm install
53 +
54 +# 2. Configure - the ONLY required secret for mock mode is the encryption key
55 +cp .env.example .env
56 +# set ENCRYPTION_KEY (openssl rand -base64 32), point DATABASE_URL at a Postgres,
57 +# and set MOCK_LLM=1
58 +
59 +# 3. Database
60 +pnpm prisma db push # create the schema
61 +pnpm db:seed # seed the public demo debates
62 +
63 +# 4. Run
64 +pnpm dev # http://localhost:3000
65 +```
66 +
67 +No Postgres handy? `docker run -d -e POSTGRES_USER=roundtable -e POSTGRES_PASSWORD=roundtable -e POSTGRES_DB=roundtable -p 5432:5432 postgres:16-alpine`.
68 +
69 +### Running real debates
70 +
71 +Flip `MOCK_LLM=0`, then add your [OpenRouter](https://openrouter.ai/) key in the app (the key badge in the header). The key is validated against OpenRouter and stored encrypted (saved) or in an HttpOnly cookie (session-only). Optional: set `AUTH_GITHUB_ID/SECRET` + `AUTH_SECRET` to enable GitHub sign-in and persistent per-user history.
72 +
73 +## 🧱 Architecture
74 +
75 +The debate **orchestrator is a framework-agnostic TypeScript module** - pure functions plus a state machine - with the Next.js route handler as a thin adapter. It never imports Next.js, Prisma, React, or a network client, so it is unit-testable without HTTP and reusable as a library/CLI.
76 +
77 +```mermaid
78 +flowchart TB
79 + subgraph client["Browser"]
80 + UI["Debate console<br/>(timeline · panels · diffs · matrix)"]
81 + RED["Event reducer<br/>(applyEvent → DebateView)"]
82 + end
83 + subgraph server["Next.js server (Node runtime)"]
84 + API["/api/debates/run<br/>(SSE adapter + rate limit)"]
85 + RUN["debate-runner<br/>(client select · persist · stream)"]
86 + subgraph core["core/ - framework-agnostic engine"]
87 + ORCH["orchestrator<br/>(state machine)"]
88 + PR["prompts/"]
89 + AN["anonymize · json-repair · scoring · convergence"]
90 + end
91 + LLM["LlmClient interface"]
92 + OR["OpenRouter client<br/>(Vercel AI SDK)"]
93 + MOCK["Mock client<br/>(deterministic)"]
94 + DB[("Postgres<br/>via Prisma")]
95 + end
96 + OPENROUTER(["OpenRouter gateway"])
97 +
98 + UI --> API
99 + API --> RUN --> ORCH
100 + ORCH --> PR & AN
101 + ORCH --> LLM
102 + LLM --> OR --> OPENROUTER
103 + LLM --> MOCK
104 + RUN -- "DebateEvent stream" --> API -- "SSE" --> RED --> UI
105 + RUN -- "persist each stage" --> DB
106 + API -- "replay snapshot" --> DB
107 +```
108 +
109 +A single typed **`DebateEvent`** discriminated union (`stage_started`, `token_delta`, `answer_completed`, `critique_completed`, `convergence_result`, `model_failed`, `synthesis_completed`, `debate_completed`, ...) is the one source of truth: the SSE endpoint serializes it, the persistence layer reacts to it, and the client reducer folds it into the `DebateView` that every component renders - live streaming and recorded replay share the exact same components.
110 +
111 +### The debate state machine
112 +
113 +```mermaid
114 +stateDiagram-v2
115 + [*] --> Answers: round 0 (parallel, streamed)
116 + Answers --> Critique: ≥2 healthy
117 + Answers --> Failed: <2 healthy
118 + Critique --> Revision: anonymized peer review (JSON)
119 + Revision --> Convergence: revise or defend (JSON + changelog)
120 + Convergence --> Critique: score < threshold & rounds left
121 + Convergence --> Synthesis: score ≥ threshold OR max rounds
122 + Synthesis --> [*]: final answer + dissent report
123 +```
124 +
125 +### Project layout
126 +
127 +```
128 +src/
129 +├── core/ # ★ framework-agnostic engine (no Next/Prisma/React imports)
130 +│ ├── orchestrator.ts # the debate state machine
131 +│ ├── prompts/ # versioned, documented prompt templates
132 +│ ├── anonymize.ts # seeded PRNG + Fisher-Yates shuffle
133 +│ ├── json-repair.ts # defensive parse + Zod validate
134 +│ ├── convergence.ts # pure early-stop decision
135 +│ ├── scoring.ts # critique score matrix
136 +│ ├── llm-client.ts # the LlmClient interface the engine depends on
137 +│ ├── mock-client.ts # deterministic offline model
138 +│ └── *.test.ts # Vitest unit tests
139 +├── lib/ # env (Zod), OpenRouter client, AES-256-GCM crypto, SSE, BYOK
140 +├── db/ # Prisma client + repositories (write path + replay reconstruction)
141 +├── app/ # Next.js App Router: routes (thin adapters) + pages
142 +└── components/ # shadcn/ui primitives + the debate UI
143 +```
144 +
145 +## 🧠 Design decisions
146 +
147 +**Why multiple rounds instead of one-shot voting?** A single critique pass catches surface errors; iteration lets a model _change its mind_ when a critique is right and _defend_ when it's wrong. The structured changelog forces an explicit `changed` boolean so "defending the original with reasons" is a first-class outcome, not social-pressure capitulation.
148 +
149 +**How self-preference bias is mitigated.** Models reliably over-reward their own style. Three defenses: (1) during critique, each reviewer sees peers' answers **anonymized and in a per-reviewer randomized order** (a seeded shuffle, so it's reproducible and testable); (2) the **chairman also sees anonymized answers** - labels are mapped back to real models only _after_ it commits its judgment; (3) the UI shows a **provider-overlap warning** when the chairman shares a vendor family with a council member, and the chairman defaults to a model outside the council.
150 +
151 +**Cost tradeoffs.** A debate is N models × (1 answer + R×(1 critique + 1 revision)) + R convergence checks + 1 synthesis calls - cost grows roughly linearly in rounds and models. Mitigations: early stopping via a **cheap** convergence model (gemini-flash class), a configurable max-rounds cap, per-model/per-stage cost recorded so you can see exactly where spend goes, and a live meter so nothing runs away silently.
152 +
153 +**Robustness over perfection.** Model APIs are flaky and models emit malformed JSON. Every structured call gets one JSON-repair retry; every call is timeout-bounded and cancellable; a model that fails a stage is dropped from _that stage_, not the debate, as long as ≥2 remain. Stage results are persisted as they complete, so a client disconnect never loses a debate.
154 +
155 +**Why the engine is isolated.** Keeping `core/` free of framework imports means the debate logic is exercised by fast unit tests with a deterministic mock client - the state machine, convergence, anonymization shuffle, and JSON repair are all covered without a browser, network, or database.
156 +
157 +## 🧪 Testing
158 +
159 +```bash
160 +pnpm test # Vitest - orchestrator, convergence, anonymization, JSON repair, crypto
161 +pnpm typecheck # tsc --noEmit (strict)
162 +pnpm lint # ESLint
163 +pnpm build # production build
164 +```
165 +
166 +The orchestrator suite drives full debates through the mock client and asserts on the emitted event stream: happy path, early convergence, max-rounds, model drop-out (and the ≥2-healthy floor), JSON-repair recovery, chairman provider-conflict detection, and cancellation.
167 +
168 +## 🚢 Deployment (Linux VPS + Caddy)
169 +
170 +Not serverless - the app is a long-running Node server designed for a VPS, with SSE debate streams that last minutes.
171 +
172 +```bash
173 +# On the VPS
174 +cp .env.example .env # set ENCRYPTION_KEY, SITE_ADDRESS (your domain), auth vars
175 +docker compose up -d # Postgres + app (standalone) + Caddy (auto-HTTPS)
176 +docker compose exec app node node_modules/prisma/build/index.js db seed # optional demos
177 +```
178 +
179 +- **Multi-stage `Dockerfile`** builds Next.js `standalone` output on `node:20-slim`; the entrypoint applies the schema (`prisma db push`) on boot.
180 +- **`docker-compose.yml`** wires app + Postgres + Caddy with health checks and named volumes.
181 +- **`Caddyfile`** reverse-proxies with `flush_interval -1` (no buffering) and long read/write windows so SSE streams flush immediately - automatic HTTPS via `SITE_ADDRESS`.
182 +- **CI/CD** - [`ci.yml`](.github/workflows/ci.yml) runs lint · typecheck · test · build on every push; [`deploy.yml`](.github/workflows/deploy.yml) builds & pushes the image to GHCR and SSH-deploys to the VPS (`docker compose pull && up -d`).
183 +
184 +### Environment
185 +
186 +All variables are validated at boot with Zod ([`src/lib/env.ts`](src/lib/env.ts)); a misconfigured deploy fails loudly at startup. `ENCRYPTION_KEY` (32-byte base64) is the only secret required for public/demo mode. See [`.env.example`](.env.example).
187 +
188 +## 🛠️ Tech stack
189 +
190 +Next.js 15 (App Router) · TypeScript (strict) · Tailwind CSS + shadcn/ui · Vercel AI SDK · OpenRouter · PostgreSQL + Prisma · Auth.js (GitHub) · Zod · Vitest · Docker + Caddy · GitHub Actions.
191 +
192 +---
193 +
194 +<div align="center">
195 +<sub>Built as a portfolio project. The debate engine is deliberately reusable - lift <code>src/core/</code> into a CLI or a different frontend and it just works.</sub>
196 +</div>
added docker-compose.yml +69 −0
@@ -0,0 +1,69 @@
1 +# Production compose for a single VPS: Postgres + the app + Caddy (HTTPS).
2 +#
3 +# cp .env.example .env # set ENCRYPTION_KEY (required), auth vars, SITE_ADDRESS
4 +# docker compose up -d # first boot applies the schema automatically
5 +# docker compose exec app node node_modules/prisma/build/index.js db seed # (optional) demo data
6 +#
7 +# APP_IMAGE lets the CI deploy workflow pin a pushed ghcr.io image instead of
8 +# building on the box; leave unset to build locally.
9 +
10 +services:
11 + db:
12 + image: postgres:16-alpine
13 + restart: unless-stopped
14 + environment:
15 + POSTGRES_USER: roundtable
16 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-roundtable}
17 + POSTGRES_DB: roundtable
18 + volumes:
19 + - pgdata:/var/lib/postgresql/data
20 + healthcheck:
21 + test: ['CMD-SHELL', 'pg_isready -U roundtable -d roundtable']
22 + interval: 5s
23 + timeout: 5s
24 + retries: 12
25 +
26 + app:
27 + image: ${APP_IMAGE:-roundtable:local}
28 + build:
29 + context: .
30 + restart: unless-stopped
31 + depends_on:
32 + db:
33 + condition: service_healthy
34 + environment:
35 + DATABASE_URL: postgresql://roundtable:${POSTGRES_PASSWORD:-roundtable}@db:5432/roundtable?schema=public
36 + ENCRYPTION_KEY: ${ENCRYPTION_KEY:?set ENCRYPTION_KEY in .env}
37 + AUTH_SECRET: ${AUTH_SECRET:-}
38 + AUTH_URL: ${AUTH_URL:-}
39 + AUTH_TRUST_HOST: 'true'
40 + AUTH_GITHUB_ID: ${AUTH_GITHUB_ID:-}
41 + AUTH_GITHUB_SECRET: ${AUTH_GITHUB_SECRET:-}
42 + OPENROUTER_APP_URL: ${OPENROUTER_APP_URL:-http://localhost:3000}
43 + OPENROUTER_APP_TITLE: ${OPENROUTER_APP_TITLE:-Roundtable}
44 + MOCK_LLM: ${MOCK_LLM:-0}
45 + RATE_LIMIT_DEBATES_PER_HOUR: ${RATE_LIMIT_DEBATES_PER_HOUR:-20}
46 + PORT: '3000'
47 + expose:
48 + - '3000'
49 +
50 + caddy:
51 + image: caddy:2-alpine
52 + restart: unless-stopped
53 + depends_on:
54 + - app
55 + ports:
56 + - '80:80'
57 + - '443:443'
58 + environment:
59 + # e.g. "roundtable.example.com" for auto-HTTPS, or ":80" for local.
60 + SITE_ADDRESS: ${SITE_ADDRESS:-:80}
61 + volumes:
62 + - ./Caddyfile:/etc/caddy/Caddyfile:ro
63 + - caddy_data:/data
64 + - caddy_config:/config
65 +
66 +volumes:
67 + pgdata:
68 + caddy_data:
69 + caddy_config:
added docker/entrypoint.sh +11 −0
@@ -0,0 +1,11 @@
1 +#!/bin/sh
2 +# Apply the schema (idempotent, additive) then launch the standalone server.
3 +# `db push` avoids needing checked-in migration files for a single-app deploy;
4 +# swap for `prisma migrate deploy` if you adopt migrations.
5 +set -e
6 +
7 +echo "→ Applying database schema…"
8 +node node_modules/prisma/build/index.js db push --skip-generate
9 +
10 +echo "→ Starting Roundtable on port ${PORT:-3000}…"
11 +exec node server.js
added docs/README.md +11 −0
@@ -0,0 +1,11 @@
1 +# Screenshots & media
2 +
3 +Drop UI screenshots and a demo GIF here and reference them from the root `README.md`.
4 +
5 +Suggested captures (run `pnpm dev` with `MOCK_LLM=1`, open `/demo`):
6 +
7 +- `debate-console.png` — a completed debate: timeline, final answer + dissent, council panels.
8 +- `critique-matrix.png` — the N×N critique grid with a cell tooltip open.
9 +- `revision-diff.png` — an inline revision diff with the changelog.
10 +- `demo-playback.gif` — the demo playing back with streaming + controls.
11 +- `cost-breakdown.png` — the per-model cost chart.