philschmid HF staff commited on
Commit
343b64b
1 Parent(s): 7298018
.eslintrc.cjs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ root: true,
3
+ env: { browser: true, es2020: true },
4
+ extends: [
5
+ 'eslint:recommended',
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:react-hooks/recommended',
8
+ ],
9
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
10
+ parser: '@typescript-eslint/parser',
11
+ plugins: ['react-refresh'],
12
+ rules: {
13
+ 'react-refresh/only-export-components': [
14
+ 'warn',
15
+ { allowConstantExport: true },
16
+ ],
17
+ },
18
+ }
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build the React app
2
+ FROM node:16-alpine AS build
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy package.json and package-lock.json
8
+ COPY package*.json ./
9
+
10
+ # Install dependencies
11
+ RUN npm install
12
+
13
+ # Copy the rest of the application code
14
+ COPY . .
15
+
16
+ # Build the React app
17
+ RUN npm run build
18
+
19
+ # Stage 2: Serve the built app with a lightweight web server
20
+ FROM nginx:alpine
21
+
22
+ # Copy the built files from the previous stage
23
+ COPY --from=build /app/dist /usr/share/nginx/html
24
+
25
+ # Expose port 7860
26
+ EXPOSE 7860
27
+
28
+ # Replace the default nginx.conf with our configuration
29
+ RUN rm /etc/nginx/conf.d/default.conf
30
+ COPY nginx.conf /etc/nginx/conf.d
31
+
32
+ # Start Nginx server
33
+ CMD ["nginx", "-g", "daemon off;"]
README.md CHANGED
@@ -8,4 +8,67 @@ pinned: false
8
  license: apache-2.0
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  license: apache-2.0
9
  ---
10
 
11
+ # LLM Comparison React App
12
+
13
+ ## Overview
14
+
15
+ This React application, developed in collaboration with Anthropic Claude 3.5, is designed to compare hosted Large Language Models (LLMs) using Shadcn UI. The comparison data is based on a sheet that we have created. You can access the sheet [here]([pseudo-link](https://docs.google.com/spreadsheets/d/1NX8ZW9Jnfpy88PC2d6Bwla87JRiv3GTeqwXoB4mKU_s/edit?gid=0#gid=0)).
16
+
17
+ ## Features
18
+
19
+ - **Compare LLM Providers**: Easily compare different LLM providers and their pricing.
20
+ - **User-friendly Interface**: Built using Shadcn UI for a seamless and intuitive user experience.
21
+ - **Dynamic Data**: Data is dynamically fetched and displayed, making it easy to update and compare new models and providers.
22
+
23
+ ## Getting Started
24
+
25
+ 1. **Clone the repository**:
26
+ ```bash
27
+ git clone https://huggingface.co/spaces/philschmid/llm-pricing
28
+ cd llm-comparison-react-app
29
+ ```
30
+
31
+ 2. **Install dependencies**:
32
+ ```bash
33
+ npm install
34
+ ```
35
+
36
+ 3. **Run the app**:
37
+ ```bash
38
+ npm start
39
+ ```
40
+
41
+ 4. **Open your browser**: Navigate to `http://localhost:5173` to see the app in action.
42
+
43
+ ## Contributing
44
+
45
+ We welcome contributions! If you want to add a new provider, please follow these steps:
46
+
47
+ 1. **Navigate to the data file**: Open the file where `mockData` array is located.
48
+ 2. **Add your provider**: Add a new object to the `mockData` array with the provider details.
49
+
50
+ Here is an example of how to add a new provider:
51
+
52
+ ```javascript
53
+ export const mockData: Provider[] = [
54
+ {
55
+ provider: 'OpenAI',
56
+ uri: 'https://openai.com/api/pricing/',
57
+ models: [
58
+ { name: 'GPT-4', inputPrice: 5.0, outputPrice: 15.0 },
59
+ { name: 'GPT-4 (8K)', inputPrice: 30.0, outputPrice: 60.0 },
60
+ { name: 'GPT-4 Turbo', inputPrice: 10.0, outputPrice: 30.0 },
61
+ { name: 'GPT-3.5-turbo', inputPrice: 0.5, outputPrice: 1.5 },
62
+ ],
63
+ },
64
+ {
65
+ provider: 'NewProvider',
66
+ uri: 'https://newprovider.com/api/pricing/',
67
+ models: [
68
+ { name: 'Model-A', inputPrice: 4.0, outputPrice: 12.0 },
69
+ { name: 'Model-B', inputPrice: 25.0, outputPrice: 50.0 },
70
+ ],
71
+ },
72
+ // Add more providers here
73
+ ];
74
+ ```
components.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "src/index.css",
9
+ "baseColor": "slate",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "src/components",
15
+ "utils": "src/lib/utils"
16
+ }
17
+ }
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite + React + TS</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
nginx.conf ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ server {
2
+ listen 7860;
3
+
4
+ server_name localhost;
5
+
6
+ location / {
7
+ root /usr/share/nginx/html;
8
+ index index.html;
9
+ try_files $uri $uri/ /index.html;
10
+ }
11
+ }
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "llm-pricing",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@radix-ui/react-checkbox": "^1.1.1",
14
+ "class-variance-authority": "^0.7.0",
15
+ "clsx": "^2.1.1",
16
+ "lucide-react": "^0.399.0",
17
+ "react": "^18.3.1",
18
+ "react-dom": "^18.3.1",
19
+ "recharts": "^2.12.7",
20
+ "tailwind-merge": "^2.3.0",
21
+ "tailwindcss-animate": "^1.0.7"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^20.14.9",
25
+ "@types/react": "^18.3.3",
26
+ "@types/react-dom": "^18.3.0",
27
+ "@typescript-eslint/eslint-plugin": "^7.13.1",
28
+ "@typescript-eslint/parser": "^7.13.1",
29
+ "@vitejs/plugin-react": "^4.3.1",
30
+ "autoprefixer": "^10.4.19",
31
+ "eslint": "^8.57.0",
32
+ "eslint-plugin-react-hooks": "^4.6.2",
33
+ "eslint-plugin-react-refresh": "^0.4.7",
34
+ "postcss": "^8.4.39",
35
+ "tailwindcss": "^3.4.4",
36
+ "typescript": "^5.2.2",
37
+ "vite": "^5.3.1"
38
+ }
39
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/vite.svg ADDED
src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
src/App.tsx ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react'
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3
+ import { Checkbox } from '@/components/ui/checkbox'
4
+ import { Input } from '@/components/ui/input'
5
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
6
+ import { mockData } from './lib/data'
7
+
8
+ export interface Model {
9
+ name: string
10
+ inputPrice: number
11
+ outputPrice: number
12
+ }
13
+
14
+ export interface Provider {
15
+ provider: string
16
+ uri: string
17
+ models: Model[]
18
+ }
19
+
20
+ const App: React.FC = () => {
21
+ const [data, setData] = useState<Provider[]>([])
22
+ const [comparisonModels, setComparisonModels] = useState<string[]>([])
23
+ const [inputTokens, setInputTokens] = useState<number>(1)
24
+ const [outputTokens, setOutputTokens] = useState<number>(1)
25
+
26
+ useEffect(() => {
27
+ setData(mockData)
28
+ // Set default comparison models
29
+ setComparisonModels(['GPT-4o', 'Claude 3.5 (Sonnet)', 'Gemini 1.5 Pro'])
30
+ }, [])
31
+
32
+ const calculatePrice = (price: number, tokens: number): number => {
33
+ return price * tokens
34
+ }
35
+
36
+ const calculateComparison = (modelPrice: number, comparisonPrice: number): string => {
37
+ return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(2)
38
+ }
39
+
40
+ return (
41
+ <Card className="w-full max-w-6xl mx-auto">
42
+ <CardHeader>
43
+ <CardTitle>LLM Pricing Comparison Tool</CardTitle>
44
+ </CardHeader>
45
+ <CardContent>
46
+ <div className="">
47
+ <h3 className="text-lg font-semibold mb-2">Select Comparison Models</h3>
48
+
49
+ <div className="flex gap-4 mb-4">
50
+ <div className="min-w-96 ">
51
+ <div className="flex-1">
52
+ <label htmlFor="inputTokens" className="block text-sm font-medium text-gray-700">
53
+ Input Tokens (millions)
54
+ </label>
55
+ <Input
56
+ id="inputTokens"
57
+ type="number"
58
+ value={inputTokens}
59
+ onChange={(e) => setInputTokens(Number(e.target.value))}
60
+ className="mt-1"
61
+ />
62
+ </div>
63
+ <div className="flex-1">
64
+ <label htmlFor="outputTokens" className="block text-sm font-medium text-gray-700">
65
+ Output Tokens (millions)
66
+ </label>
67
+ <Input
68
+ id="outputTokens"
69
+ type="number"
70
+ value={outputTokens}
71
+ onChange={(e) => setOutputTokens(Number(e.target.value))}
72
+ className="mt-1"
73
+ />
74
+ </div>
75
+ </div>
76
+
77
+ <div>
78
+ <div className="flex flex-wrap gap-4">
79
+ {data
80
+ .flatMap((provider) => provider.models)
81
+ .map((model) => (
82
+ <div key={model.name} className="flex items-center space-x-2">
83
+ <Checkbox
84
+ id={model.name}
85
+ checked={comparisonModels.includes(model.name)}
86
+ onCheckedChange={(checked) => {
87
+ if (checked) {
88
+ setComparisonModels((prev) => [...prev, model.name])
89
+ } else {
90
+ setComparisonModels((prev) => prev.filter((m) => m !== model.name))
91
+ }
92
+ }}
93
+ />
94
+ <label htmlFor={model.name} className="text-sm font-medium text-gray-700">
95
+ {model.name}
96
+ </label>
97
+ </div>
98
+ ))}
99
+ </div>
100
+ </div>
101
+ </div>
102
+ <p className="italic text-sm text-muted-foreground">
103
+ Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere or OpenAI should be the same.
104
+ </p>
105
+ <Table>
106
+ <TableHeader>
107
+ <TableRow>
108
+ <TableHead rowSpan={2}>Provider</TableHead>
109
+ <TableHead rowSpan={2}>Model</TableHead>
110
+ <TableHead rowSpan={2}>Input Price (per 1M tokens)</TableHead>
111
+ <TableHead rowSpan={2}>Output Price (per 1M tokens)</TableHead>
112
+ <TableHead rowSpan={2}>Total Price</TableHead>
113
+ {comparisonModels.map((model) => (
114
+ <TableHead key={model} colSpan={2}>
115
+ Compared to {model}
116
+ </TableHead>
117
+ ))}
118
+ </TableRow>
119
+ <TableRow>
120
+ {comparisonModels.flatMap((model) => [
121
+ <TableHead key={`${model}-input`}>Input</TableHead>,
122
+ <TableHead key={`${model}-output`}>Output</TableHead>,
123
+ ])}
124
+ </TableRow>
125
+ </TableHeader>
126
+ <TableBody>
127
+ {data.flatMap((provider) =>
128
+ provider.models.map((model) => (
129
+ <TableRow key={`${provider.provider}-${model.name}`}>
130
+ <TableCell>
131
+ <a href={provider.uri} className="underline">
132
+ {provider.provider}
133
+ </a>
134
+ </TableCell>
135
+ <TableCell>{model.name}</TableCell>
136
+ <TableCell>${model.inputPrice.toFixed(2)}</TableCell>
137
+ <TableCell>${model.outputPrice.toFixed(2)}</TableCell>
138
+ <TableCell className="font-bold border border-r-1 border-r-slate-600 border-l-0">
139
+ $
140
+ {(
141
+ calculatePrice(model.inputPrice, inputTokens) + calculatePrice(model.outputPrice, outputTokens)
142
+ ).toFixed(2)}
143
+ </TableCell>
144
+ {comparisonModels.flatMap((comparisonModel) => {
145
+ const comparisonModelData = data.flatMap((p) => p.models).find((m) => m.name === comparisonModel)!
146
+ return [
147
+ <TableCell
148
+ key={`${comparisonModel}-input`}
149
+ className={`${
150
+ parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) < 0
151
+ ? 'bg-green-100'
152
+ : parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) > 0
153
+ ? 'bg-red-100'
154
+ : ''
155
+ }`}
156
+ >
157
+ {model.name === comparisonModel
158
+ ? '0.00%'
159
+ : `${calculateComparison(model.inputPrice, comparisonModelData.inputPrice)}%`}
160
+ </TableCell>,
161
+ <TableCell
162
+ key={`${comparisonModel}-output`}
163
+ className={`${
164
+ parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) < 0
165
+ ? 'bg-green-100'
166
+ : parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) > 0
167
+ ? 'bg-red-100'
168
+ : ''
169
+ }`}
170
+ >
171
+ {model.name === comparisonModel
172
+ ? '0.00%'
173
+ : `${calculateComparison(model.outputPrice, comparisonModelData.outputPrice)}%`}
174
+ </TableCell>,
175
+ ]
176
+ })}
177
+ </TableRow>
178
+ ))
179
+ )}
180
+ </TableBody>
181
+ </Table>
182
+ </div>
183
+ </CardContent>
184
+ </Card>
185
+ )
186
+ }
187
+
188
+ export default App
src/components/ui/card.tsx ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
6
+ <div ref={ref} className={cn('rounded-lg border bg-card text-card-foreground shadow-sm', className)} {...props} />
7
+ ))
8
+ Card.displayName = 'Card'
9
+
10
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
11
+ ({ className, ...props }, ref) => (
12
+ <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
13
+ )
14
+ )
15
+ CardHeader.displayName = 'CardHeader'
16
+
17
+ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
18
+ ({ className, ...props }, ref) => (
19
+ <h3 ref={ref} className={cn('text-2xl font-semibold leading-none tracking-tight', className)} {...props} />
20
+ )
21
+ )
22
+ CardTitle.displayName = 'CardTitle'
23
+
24
+ const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
25
+ ({ className, ...props }, ref) => (
26
+ <p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
27
+ )
28
+ )
29
+ CardDescription.displayName = 'CardDescription'
30
+
31
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
32
+ ({ className, ...props }, ref) => <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
33
+ )
34
+ CardContent.displayName = 'CardContent'
35
+
36
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
37
+ ({ className, ...props }, ref) => <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
38
+ )
39
+ CardFooter.displayName = 'CardFooter'
40
+
41
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
3
+ import { Check } from 'lucide-react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ 'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator className={cn('flex items-center justify-center text-current')}>
20
+ <Check className="h-4 w-4" />
21
+ </CheckboxPrimitive.Indicator>
22
+ </CheckboxPrimitive.Root>
23
+ ))
24
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
25
+
26
+ export { Checkbox }
src/components/ui/input.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface InputProps
6
+ extends React.InputHTMLAttributes<HTMLInputElement> {}
7
+
8
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
9
+ ({ className, type, ...props }, ref) => {
10
+ return (
11
+ <input
12
+ type={type}
13
+ className={cn(
14
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
src/components/ui/table.tsx ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Table = React.forwardRef<
6
+ HTMLTableElement,
7
+ React.HTMLAttributes<HTMLTableElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div className="relative w-full overflow-auto">
10
+ <table
11
+ ref={ref}
12
+ className={cn("w-full caption-bottom text-sm", className)}
13
+ {...props}
14
+ />
15
+ </div>
16
+ ))
17
+ Table.displayName = "Table"
18
+
19
+ const TableHeader = React.forwardRef<
20
+ HTMLTableSectionElement,
21
+ React.HTMLAttributes<HTMLTableSectionElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
24
+ ))
25
+ TableHeader.displayName = "TableHeader"
26
+
27
+ const TableBody = React.forwardRef<
28
+ HTMLTableSectionElement,
29
+ React.HTMLAttributes<HTMLTableSectionElement>
30
+ >(({ className, ...props }, ref) => (
31
+ <tbody
32
+ ref={ref}
33
+ className={cn("[&_tr:last-child]:border-0", className)}
34
+ {...props}
35
+ />
36
+ ))
37
+ TableBody.displayName = "TableBody"
38
+
39
+ const TableFooter = React.forwardRef<
40
+ HTMLTableSectionElement,
41
+ React.HTMLAttributes<HTMLTableSectionElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <tfoot
44
+ ref={ref}
45
+ className={cn(
46
+ "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ ))
52
+ TableFooter.displayName = "TableFooter"
53
+
54
+ const TableRow = React.forwardRef<
55
+ HTMLTableRowElement,
56
+ React.HTMLAttributes<HTMLTableRowElement>
57
+ >(({ className, ...props }, ref) => (
58
+ <tr
59
+ ref={ref}
60
+ className={cn(
61
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
62
+ className
63
+ )}
64
+ {...props}
65
+ />
66
+ ))
67
+ TableRow.displayName = "TableRow"
68
+
69
+ const TableHead = React.forwardRef<
70
+ HTMLTableCellElement,
71
+ React.ThHTMLAttributes<HTMLTableCellElement>
72
+ >(({ className, ...props }, ref) => (
73
+ <th
74
+ ref={ref}
75
+ className={cn(
76
+ "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
77
+ className
78
+ )}
79
+ {...props}
80
+ />
81
+ ))
82
+ TableHead.displayName = "TableHead"
83
+
84
+ const TableCell = React.forwardRef<
85
+ HTMLTableCellElement,
86
+ React.TdHTMLAttributes<HTMLTableCellElement>
87
+ >(({ className, ...props }, ref) => (
88
+ <td
89
+ ref={ref}
90
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
91
+ {...props}
92
+ />
93
+ ))
94
+ TableCell.displayName = "TableCell"
95
+
96
+ const TableCaption = React.forwardRef<
97
+ HTMLTableCaptionElement,
98
+ React.HTMLAttributes<HTMLTableCaptionElement>
99
+ >(({ className, ...props }, ref) => (
100
+ <caption
101
+ ref={ref}
102
+ className={cn("mt-4 text-sm text-muted-foreground", className)}
103
+ {...props}
104
+ />
105
+ ))
106
+ TableCaption.displayName = "TableCaption"
107
+
108
+ export {
109
+ Table,
110
+ TableHeader,
111
+ TableBody,
112
+ TableFooter,
113
+ TableHead,
114
+ TableRow,
115
+ TableCell,
116
+ TableCaption,
117
+ }
src/index.css ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer base {
6
+ :root {
7
+ --background: 0 0% 100%;
8
+ --foreground: 222.2 84% 4.9%;
9
+
10
+ --card: 0 0% 100%;
11
+ --card-foreground: 222.2 84% 4.9%;
12
+
13
+ --popover: 0 0% 100%;
14
+ --popover-foreground: 222.2 84% 4.9%;
15
+
16
+ --primary: 222.2 47.4% 11.2%;
17
+ --primary-foreground: 210 40% 98%;
18
+
19
+ --secondary: 210 40% 96.1%;
20
+ --secondary-foreground: 222.2 47.4% 11.2%;
21
+
22
+ --muted: 210 40% 96.1%;
23
+ --muted-foreground: 215.4 16.3% 46.9%;
24
+
25
+ --accent: 210 40% 96.1%;
26
+ --accent-foreground: 222.2 47.4% 11.2%;
27
+
28
+ --destructive: 0 84.2% 60.2%;
29
+ --destructive-foreground: 210 40% 98%;
30
+
31
+ --border: 214.3 31.8% 91.4%;
32
+ --input: 214.3 31.8% 91.4%;
33
+ --ring: 222.2 84% 4.9%;
34
+
35
+ --radius: 0.5rem;
36
+ }
37
+
38
+ .dark {
39
+ --background: 222.2 84% 4.9%;
40
+ --foreground: 210 40% 98%;
41
+
42
+ --card: 222.2 84% 4.9%;
43
+ --card-foreground: 210 40% 98%;
44
+
45
+ --popover: 222.2 84% 4.9%;
46
+ --popover-foreground: 210 40% 98%;
47
+
48
+ --primary: 210 40% 98%;
49
+ --primary-foreground: 222.2 47.4% 11.2%;
50
+
51
+ --secondary: 217.2 32.6% 17.5%;
52
+ --secondary-foreground: 210 40% 98%;
53
+
54
+ --muted: 217.2 32.6% 17.5%;
55
+ --muted-foreground: 215 20.2% 65.1%;
56
+
57
+ --accent: 217.2 32.6% 17.5%;
58
+ --accent-foreground: 210 40% 98%;
59
+
60
+ --destructive: 0 62.8% 30.6%;
61
+ --destructive-foreground: 210 40% 98%;
62
+
63
+ --border: 217.2 32.6% 17.5%;
64
+ --input: 217.2 32.6% 17.5%;
65
+ --ring: 212.7 26.8% 83.9%;
66
+ }
67
+ }
68
+
69
+ @layer base {
70
+ * {
71
+ @apply border-border;
72
+ }
73
+ body {
74
+ @apply bg-background text-foreground;
75
+ }
76
+ }
src/lib/data.ts ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Provider } from "@/App";
2
+
3
+ export const mockData: Provider[] = [
4
+ {
5
+ provider: 'OpenAI',
6
+ uri: 'https://openai.com/api/pricing/',
7
+ models: [
8
+ { name: 'GPT-4o', inputPrice: 5.0, outputPrice: 15.0 },
9
+ { name: 'GPT-4 (8K)', inputPrice: 30.0, outputPrice: 60.0 },
10
+ { name: 'GPT-4 Turbo', inputPrice: 10.0, outputPrice: 30.0 },
11
+ { name: 'GPT-3.5-turbo', inputPrice: 0.5, outputPrice: 1.5 },
12
+ ],
13
+ },
14
+ {
15
+ provider: 'Anthropic',
16
+ uri: 'https://www.anthropic.com/pricing',
17
+ models: [
18
+ { name: 'Claude 3 (Opus)', inputPrice: 15.0, outputPrice: 75.0 },
19
+ { name: 'Claude 3.5 (Sonnet)', inputPrice: 3.0, outputPrice: 15.0 },
20
+ { name: 'Claude 3 (Haiku)', inputPrice: 0.25, outputPrice: 1.25 },
21
+ ],
22
+ },
23
+ {
24
+ provider: 'Google Vertex AI',
25
+ uri: 'https://ai.google.dev/pricing?hl=en',
26
+ models: [
27
+ { name: 'Gemini 1.5 Pro', inputPrice: 3.5, outputPrice: 7.0 },
28
+ { name: 'Gemini 1.5 Flash', inputPrice: 0.35, outputPrice: 0.7 },
29
+ ],
30
+ },
31
+ {
32
+ provider: 'Cohere',
33
+ uri: 'https://cohere.com/pricing',
34
+ models: [
35
+ { name: 'Command R+', inputPrice: 3.0, outputPrice: 15.0 },
36
+ { name: 'Command R', inputPrice: 0.5, outputPrice: 1.5 },
37
+ ],
38
+ },
39
+ {
40
+ provider: 'Mistral',
41
+ uri: 'https://docs.mistral.ai/platform/pricing',
42
+ models: [
43
+ { name: 'mistral-large-2402', inputPrice: 4.0, outputPrice: 12.0 },
44
+ { name: 'codestral-2405', inputPrice: 1.0, outputPrice: 3.0 },
45
+ { name: 'open-mixtral-8x22b', inputPrice: 2.0, outputPrice: 6.0 },
46
+ { name: 'open-mixtral-8x7b', inputPrice: 0.7, outputPrice: 0.7 },
47
+ ],
48
+ },
49
+ {
50
+ provider: 'Deepspeek',
51
+ uri: 'https://platform.deepseek.com/api-docs/pricing/',
52
+ models: [
53
+ { name: 'deepseek-chat', inputPrice: 0.14, outputPrice: 0.28 },
54
+ { name: 'deepseek-coder', inputPrice: 0.14, outputPrice: 0.28 },
55
+ ],
56
+ },
57
+ {
58
+ provider: 'Anyscale',
59
+ uri: 'https://www.anyscale.com/pricing-detail',
60
+ models: [
61
+ { name: 'Mistral-small (8x7B)', inputPrice: 0.5, outputPrice: 0.5 },
62
+ { name: 'Llama 3 70b', inputPrice: 1.0, outputPrice: 1.0 },
63
+ ],
64
+ },
65
+
66
+ {
67
+ provider: 'Together.AI',
68
+ uri: 'https://www.together.ai/pricing',
69
+ models: [
70
+ { name: 'Mistral-small (8x7B)', inputPrice: 0.6, outputPrice: 0.6 },
71
+ { name: 'Llama 3 70b', inputPrice: 0.9, outputPrice: 0.9 },
72
+ ],
73
+ },
74
+ ]
src/lib/utils.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.tsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
src/vite-env.d.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ /// <reference types="vite/client" />
tailwind.config.js ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ["class"],
4
+ content: [
5
+ './pages/**/*.{ts,tsx}',
6
+ './components/**/*.{ts,tsx}',
7
+ './app/**/*.{ts,tsx}',
8
+ './src/**/*.{ts,tsx}',
9
+ ],
10
+ prefix: "",
11
+ theme: {
12
+ container: {
13
+ center: true,
14
+ padding: "2rem",
15
+ screens: {
16
+ "2xl": "1400px",
17
+ },
18
+ },
19
+ extend: {
20
+ colors: {
21
+ border: "hsl(var(--border))",
22
+ input: "hsl(var(--input))",
23
+ ring: "hsl(var(--ring))",
24
+ background: "hsl(var(--background))",
25
+ foreground: "hsl(var(--foreground))",
26
+ primary: {
27
+ DEFAULT: "hsl(var(--primary))",
28
+ foreground: "hsl(var(--primary-foreground))",
29
+ },
30
+ secondary: {
31
+ DEFAULT: "hsl(var(--secondary))",
32
+ foreground: "hsl(var(--secondary-foreground))",
33
+ },
34
+ destructive: {
35
+ DEFAULT: "hsl(var(--destructive))",
36
+ foreground: "hsl(var(--destructive-foreground))",
37
+ },
38
+ muted: {
39
+ DEFAULT: "hsl(var(--muted))",
40
+ foreground: "hsl(var(--muted-foreground))",
41
+ },
42
+ accent: {
43
+ DEFAULT: "hsl(var(--accent))",
44
+ foreground: "hsl(var(--accent-foreground))",
45
+ },
46
+ popover: {
47
+ DEFAULT: "hsl(var(--popover))",
48
+ foreground: "hsl(var(--popover-foreground))",
49
+ },
50
+ card: {
51
+ DEFAULT: "hsl(var(--card))",
52
+ foreground: "hsl(var(--card-foreground))",
53
+ },
54
+ },
55
+ borderRadius: {
56
+ lg: "var(--radius)",
57
+ md: "calc(var(--radius) - 2px)",
58
+ sm: "calc(var(--radius) - 4px)",
59
+ },
60
+ keyframes: {
61
+ "accordion-down": {
62
+ from: { height: "0" },
63
+ to: { height: "var(--radix-accordion-content-height)" },
64
+ },
65
+ "accordion-up": {
66
+ from: { height: "var(--radix-accordion-content-height)" },
67
+ to: { height: "0" },
68
+ },
69
+ },
70
+ animation: {
71
+ "accordion-down": "accordion-down 0.2s ease-out",
72
+ "accordion-up": "accordion-up 0.2s ease-out",
73
+ },
74
+ },
75
+ },
76
+ plugins: [require("tailwindcss-animate")],
77
+ }
tsconfig.app.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "target": "ES2020",
6
+ "useDefineForClassFields": true,
7
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
8
+ "module": "ESNext",
9
+ "skipLibCheck": true,
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "noFallthroughCasesInSwitch": true,
24
+ "baseUrl": ".",
25
+ "paths": {
26
+ "@/*": ["src/*"]
27
+ }
28
+ },
29
+ "include": ["src"]
30
+ }
tsconfig.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ {
5
+ "path": "./tsconfig.app.json"
6
+ },
7
+ {
8
+ "path": "./tsconfig.node.json"
9
+ }
10
+ ]
11
+ }
tsconfig.node.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5
+ "skipLibCheck": true,
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "noEmit": true
11
+ },
12
+ "include": ["vite.config.ts"]
13
+ }
vite.config.ts ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from "path"
2
+ import react from "@vitejs/plugin-react"
3
+ import { defineConfig } from "vite"
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ resolve: {
8
+ alias: {
9
+ "@": path.resolve(__dirname, "./src"),
10
+ },
11
+ },
12
+ })