尚未检测到更新。GitHub 发布版本后会显示在这里。
AI negotiation simulator — practise against an opponent that actually pushes back, then get a report that quotes you back to yourself. Next.js 16, Supabase RLS, Gemini.
翻译准备中
正在加载完整 README。
查看完整 README ↗Practice the conversation before it costs you.
A negotiation simulator that puts you across the table from an AI opponent that actually pushes back — one that anchors low, stalls, manufactures deadlines, and will walk away from you. When it's over, it hands you a report that quotes your own messages back to you and tells you exactly where you gave ground you didn't have to.
Built with Next.js 16, Supabase and Gemini, running entirely on free tiers.
Open the live demo — one click, no signup, into an account that already has negotiation history in it.
Negotiation is a skill you almost only practise live, in the situations where getting it wrong is most expensive. There is no low-stakes rehearsal for asking for a raise. Worse, the feedback loop is broken at both ends: you rarely find out what the other side would have accepted, and generic advice ("be confident", "anchor high") is impossible to act on in the moment.
Two things had to be true for this to be worth building:
Everything below follows from those two constraints.
Each turn, the model returns structured JSON: its reply, the tactic it read in your message, and the position it wants to move to. Application code then clamps that position into a range the persona's playbook allows.
model proposes → ledger clamps → user sees the clamped number
Conceding past the reservation value is arithmetically impossible rather than merely discouraged. No amount of persuasion — or instruction-shaped text pasted into a message — moves an opponent further than its budget permits.
Two things are deliberately withheld from the model:
Tone alone produces four models that cave at the same rate in different words.
The real differentiation lives in playbooks.ts
as numbers the model never sees: how far each will ever move, what makes it
move, what it does when you concede, and what makes it leave.
Measured across a 4-turn scripted replay:
| user behaviour | Lowballer | Staller | Professional | Closer |
|---|---|---|---|---|
| holds firm, no justification | 0% | 0% | 0% | walks |
| concedes unprompted each turn | 0% | 0% | 0% | 0% |
| brings verifiable evidence | 15% | 7% | 8% | 25% |
| turns hostile | walks | stalls on | walks | walks |
The teaching signal falls straight out of that table: evidence is the only thing that moves anyone. Repeating your number moves nobody. Conceding unprompted is never reciprocated.
The Friendly-but-Firm Professional is the difficulty-4 opponent, not the aggressive one — it has the smallest concession budget of the four. Losing to someone pleasant feels like winning, which is exactly what makes it hard.
The report generator is given the transcript with explicit [#n] markers, and
its output schema requires a message_seq and a verbatim quote on every
observation. Then every citation is verified against the transcript:
If too few survive, the whole report is regenerated with the failures named. A model cannot bluff a citation past this, which is what makes the report worth reading rather than merely well-written.
| Framework | Next.js 16 (App Router), React 19, TypeScript |
| Styling | Tailwind CSS v4 |
| Database | Supabase Postgres with RLS |
| Auth | Supabase Auth — email/password + Google OAuth |
| AI | Gemini (3.5-flash-lite for turns, 3.8-flash for reports) |
| Charts | Recharts |
| Motion | Motion (Framer Motion) |
| Voice | Web Speech API — no external service |
| Hosting | Vercel |
Type: Fraunces (display), Instrument Sans (body), IBM Plex Mono — every figure renders in tabular mono so offers and deltas align down the page. Colour carries meaning rather than decoration: brass is you, steel is the opponent who has not moved.
Multi-tenancy is enforced by Postgres, not by application code. Every table
has RLS enabled and every policy is scoped to auth.uid(). Server routes are
built on the publishable key plus the user's own cookie, not the secret key —
so server-side reads pass through the same policies the browser does. The secret
key never touches a user's data: its only runtime uses are the keepalive cron
and housekeeping for the shared demo account (below).
Transcripts and reports are append-only at the database level. No
UPDATE/DELETE policy exists on either. A transcript you can edit after the
fact makes the feedback report meaningless, so the database refuses rather than
trusting the app. Once a session finishes it is frozen too: its owner can no
longer update it, delete it, or append to its transcript.
以上为 README 的开头部分。其余内容请阅读原文。 查看完整 README ↗