TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1"use client";2import * as React from "react";3import { Coins } from "lucide-react";4import type { PolyModel } from "@/lib/client/types";5import type { RouteCandidate } from "@/lib/client/router";6import type { CostEstimate } from "@/lib/client/tokens";7import { ResponsiveDialog } from "@/components/ui/sheet";8import { Button } from "@/components/ui/button";9import { ProviderIcon } from "@/components/brand/provider-icon";10import { formatTokens } from "@/lib/utils";1112function usd(n: number | null): string {13 if (n === null) return "—";14 return n < 0.01 ? `$${n.toFixed(4)}` : `$${n.toFixed(2)}`;15}1617/** Shown before sending when the estimate exceeds `COST_CONFIRM_THRESHOLD_USD`; offers cheaper models with one-tap switching. */18export function CostConfirm({ open, onOpenChange, model, estimate, alternatives, onSend, onSwitch }: { open: boolean; onOpenChange: (o: boolean) => void; model: PolyModel | null | undefined; estimate: CostEstimate | null; alternatives: RouteCandidate[]; onSend: () => void; onSwitch: (modelKey: string) => void }) {19 return (20 <ResponsiveDialog21 open={open}22 onOpenChange={onOpenChange}23 title={24 <span className="inline-flex items-center gap-2">25 <Coins className="size-4 text-warning" /> This request may cost ≈ {usd(estimate?.usd ?? null)}26 </span>27 }28 description={model && estimate ? `${model.displayName} · ~${formatTokens(estimate.inputTokens)} input tokens + ~${formatTokens(estimate.outputTokens)} expected output, at provider list prices.` : undefined}29 size="sm"30 footer={31 <div className="flex gap-2 sm:justify-end">32 <Button variant="ghost" className="flex-1 sm:flex-none" onClick={() => onOpenChange(false)}>33 Cancel34 </Button>35 <Button variant="primary" className="flex-1 sm:flex-none" onClick={onSend}>36 Send anyway37 </Button>38 </div>39 }40 >41 <div className="space-y-3 pt-1">42 {alternatives.length ? (43 <div>44 <p className="mb-1.5 text-[11px] font-medium uppercase tracking-wide text-fg-subtle">Cheaper alternatives</p>45 <ul className="divide-y divide-hairline overflow-hidden rounded-xl border border-border">46 {alternatives.map((a) => (47 <li key={a.model.key}>48 <button type="button" onClick={() => onSwitch(a.model.key)} className="flex min-h-[48px] w-full items-center gap-3 px-3 py-2 text-left transition-colors hover:bg-bg-subtle">49 <ProviderIcon provider={a.model.provider} size={14} />50 <span className="min-w-0 flex-1">51 <span className="block truncate text-[13.5px] font-medium">{a.model.displayName}</span>52 <span className="block truncate text-[12px] text-fg-muted">{a.reasons.length ? a.reasons.join(" · ") : "Fits this prompt"}</span>53 </span>54 <span className="shrink-0 text-[12.5px] font-medium tabular-nums text-success">≈ {usd(a.estimatedUsd)}</span>55 </button>56 </li>57 ))}58 </ul>59 <p className="mt-1.5 text-[11.5px] text-fg-subtle">Tap a model to switch and send immediately.</p>60 </div>61 ) : (62 <p className="text-[13px] text-fg-muted">No cheaper connected model can take this prompt (context, vision or file requirements).</p>63 )}64 </div>65 </ResponsiveDialog>66 );67}68