Spaces:
Running
Running
Commit
•
343b64b
1
Parent(s):
7298018
init
Browse files- .eslintrc.cjs +18 -0
- .gitignore +24 -0
- Dockerfile +33 -0
- README.md +64 -1
- components.json +17 -0
- index.html +13 -0
- nginx.conf +11 -0
- package-lock.json +0 -0
- package.json +39 -0
- postcss.config.js +6 -0
- public/vite.svg +1 -0
- src/App.css +42 -0
- src/App.tsx +188 -0
- src/components/ui/card.tsx +41 -0
- src/components/ui/checkbox.tsx +26 -0
- src/components/ui/input.tsx +25 -0
- src/components/ui/table.tsx +117 -0
- src/index.css +76 -0
- src/lib/data.ts +74 -0
- src/lib/utils.ts +6 -0
- src/main.tsx +10 -0
- src/vite-env.d.ts +1 -0
- tailwind.config.js +77 -0
- tsconfig.app.json +30 -0
- tsconfig.json +11 -0
- tsconfig.node.json +13 -0
- vite.config.ts +12 -0
.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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
})
|