Commit
show estimated cost in debate builder
commit
22505ed
1 changed file with +27 and −2
modified src/components/debate/new-debate.tsx +27 −2
| @@ -1,7 +1,7 @@ | ||
| 1 | 1 | 'use client'; |
| 2 | 2 | |
| 3 | 3 | import { AlertTriangle, Loader2, Play, RotateCcw, Save, Sparkles, Square } from 'lucide-react'; |
| 4 | -import { useEffect, useState } from 'react'; | |
| 4 | +import { useEffect, useMemo, useState } from 'react'; | |
| 5 | 5 | import { DebateConsole } from '@/components/debate/debate-console'; |
| 6 | 6 | import { ModelCombobox } from '@/components/debate/model-combobox'; |
| 7 | 7 | import { ModelPicker } from '@/components/debate/model-picker'; |
| @@ -15,6 +15,8 @@import { toast } from '@/components/ui/sonner'; | ||
| 15 | 15 | import { chairmanSharesProvider, DEFAULT_CONVERGENCE_MODEL } from '@/core/models'; |
| 16 | 16 | import { useDebateStream } from '@/hooks/use-debate-stream'; |
| 17 | 17 | import { useModels } from '@/hooks/use-models'; |
| 18 | +import { estimateDebateCostUsd } from '@/lib/cost-estimate'; | |
| 19 | +import { formatUsd } from '@/lib/utils'; | |
| 18 | 20 | |
| 19 | 21 | interface Preset { |
| 20 | 22 | id: string; |
| @@ -71,6 +73,24 @@export function NewDebate() { | ||
| 71 | 73 | const canStart = question.trim().length >= 3 && council.length >= 3 && council.length <= 6 && Boolean(chairman); |
| 72 | 74 | const running = phase === 'connecting' || phase === 'streaming'; |
| 73 | 75 | |
| 76 | + const priceMap = useMemo( | |
| 77 | + () => new Map(models.map((m) => [m.id, { prompt: m.promptPrice, completion: m.completionPrice }])), | |
| 78 | + [models], | |
| 79 | + ); | |
| 80 | + const estimate = useMemo( | |
| 81 | + () => | |
| 82 | + canStart | |
| 83 | + ? estimateDebateCostUsd({ | |
| 84 | + councilModels: council, | |
| 85 | + chairmanModel: chairman, | |
| 86 | + convergenceModel, | |
| 87 | + rounds: maxRounds, | |
| 88 | + price: (id) => priceMap.get(id), | |
| 89 | + }) | |
| 90 | + : 0, | |
| 91 | + [canStart, council, chairman, convergenceModel, maxRounds, priceMap], | |
| 92 | + ); | |
| 93 | + | |
| 74 | 94 | const applyPreset = (p: Preset) => { |
| 75 | 95 | setCouncil(p.models); |
| 76 | 96 | setChairman(p.chairmanModel); |
| @@ -233,7 +253,12 @@export function NewDebate() { | ||
| 233 | 253 | </div> |
| 234 | 254 | )} |
| 235 | 255 | |
| 236 | - <div className="sticky bottom-4 flex justify-end"> | |
| 256 | + <div className="sticky bottom-4 flex items-center justify-end gap-3"> | |
| 257 | + {canStart && ( | |
| 258 | + <span className="rounded-md bg-background/80 px-2.5 py-1.5 text-xs text-muted-foreground shadow-sm backdrop-blur"> | |
| 259 | + ~{formatUsd(estimate)} est. ยท up to {maxRounds} {maxRounds === 1 ? 'round' : 'rounds'} | |
| 260 | + </span> | |
| 261 | + )} | |
| 237 | 262 | <Button size="lg" disabled={!canStart} onClick={launch} className="shadow-lg"> |
| 238 | 263 | <Play className="h-4 w-4" /> Start debate |
| 239 | 264 | </Button> |