profileShare

rasmusjy / roundtable

Read-only snapshot

No repository description.

main default branch 181 files Expires Sep 13, 2026, 9:06 AM
use-models.ts 1,356 bytes
1 'use client';
2
3 import { useCallback, useEffect, useState } from 'react';
4
5 export interface ModelInfo {
6 id: string;
7 name: string;
8 contextLength: number | null;
9 promptPrice: number;
10 completionPrice: number;
11 }
12
13 let cache: ModelInfo[] | null = null;
14
15 /** Fetch the OpenRouter model catalog once and share it across the session. */
16 export function useModels() {
17 const [models, setModels] = useState<ModelInfo[]>(cache ?? []);
18 const [loading, setLoading] = useState(!cache);
19 const [error, setError] = useState<string | null>(null);
20
21 const load = useCallback(() => {
22 setLoading(true);
23 setError(null);
24 fetch('/api/models')
25 .then((r) => {
26 if (!r.ok) throw new Error(`Catalog request failed (${r.status})`);
27 return r.json();
28 })
29 .then((data: { models: ModelInfo[] }) => {
30 cache = data.models;
31 setModels(data.models);
32 })
33 .catch((e: unknown) => setError(e instanceof Error ? e.message : 'Failed to load models'))
34 .finally(() => setLoading(false));
35 }, []);
36
37 useEffect(() => {
38 if (!cache) load();
39 }, [load]);
40
41 return { models, loading, error, reload: load };
42 }
43
44 export function pricePerMillion(perToken: number): string {
45 const v = perToken * 1_000_000;
46 if (v === 0) return 'free';
47 if (v < 1) return `$${v.toFixed(2)}/M`;
48 return `$${v.toFixed(2)}/M`;
49 }
50