fix: add demo
Browse files- demo/package.json +45 -0
- demo/pnpm-lock.yaml +0 -0
- demo/postcss.config.js +6 -0
- demo/public/finn.svg +1 -0
- demo/src/App.tsx +262 -0
- demo/src/components/footer.tsx +9 -0
- demo/src/components/header.tsx +38 -0
- demo/src/components/model-card.tsx +22 -0
- demo/src/components/ui/button.tsx +56 -0
- demo/src/components/ui/card.tsx +79 -0
- demo/src/components/ui/navigation-menu.tsx +128 -0
- demo/src/components/ui/select.tsx +158 -0
- demo/src/components/ui/tabs.tsx +55 -0
- demo/src/index.css +77 -0
- demo/src/lib/utils.ts +6 -0
- demo/src/main.tsx +10 -0
- demo/src/vite-env.d.ts +1 -0
- demo/tailwind.config.ts +62 -0
- demo/tsconfig.app.json +32 -0
- demo/tsconfig.json +13 -0
- demo/tsconfig.node.json +24 -0
- demo/vite.config.ts +19 -0
demo/package.json
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "demo",
|
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 .",
|
10 |
+
"preview": "vite preview"
|
11 |
+
},
|
12 |
+
"dependencies": {
|
13 |
+
"@llamaindex/chat-ui": "^0.0.13",
|
14 |
+
"@radix-ui/react-navigation-menu": "^1.2.3",
|
15 |
+
"@radix-ui/react-select": "^2.1.4",
|
16 |
+
"@radix-ui/react-slot": "^1.1.1",
|
17 |
+
"@radix-ui/react-tabs": "^1.1.2",
|
18 |
+
"ai": "^4.0.39",
|
19 |
+
"class-variance-authority": "^0.7.1",
|
20 |
+
"clsx": "^2.1.1",
|
21 |
+
"lucide-react": "^0.473.0",
|
22 |
+
"react": "^18.3.1",
|
23 |
+
"react-dom": "^18.3.1",
|
24 |
+
"react-icons": "^5.4.0",
|
25 |
+
"tailwind-merge": "^2.6.0",
|
26 |
+
"tailwindcss-animate": "^1.0.7"
|
27 |
+
},
|
28 |
+
"devDependencies": {
|
29 |
+
"@eslint/js": "^9.17.0",
|
30 |
+
"@types/node": "^22.10.7",
|
31 |
+
"@types/react": "^18.3.18",
|
32 |
+
"@types/react-dom": "^18.3.5",
|
33 |
+
"@vitejs/plugin-react": "^4.3.4",
|
34 |
+
"autoprefixer": "^10.4.20",
|
35 |
+
"eslint": "^9.17.0",
|
36 |
+
"eslint-plugin-react-hooks": "^5.0.0",
|
37 |
+
"eslint-plugin-react-refresh": "^0.4.16",
|
38 |
+
"globals": "^15.14.0",
|
39 |
+
"postcss": "^8.5.1",
|
40 |
+
"tailwindcss": "^3.4.17",
|
41 |
+
"typescript": "~5.6.2",
|
42 |
+
"typescript-eslint": "^8.18.2",
|
43 |
+
"vite": "^6.1.6"
|
44 |
+
}
|
45 |
+
}
|
demo/pnpm-lock.yaml
ADDED
The diff for this file is too large to render.
See raw diff
|
|
demo/postcss.config.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
}
|
demo/public/finn.svg
ADDED
|
demo/src/App.tsx
ADDED
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useState } from "react"
|
2 |
+
import {
|
3 |
+
PiFinnTheHumanLight,
|
4 |
+
PiFinnTheHumanBold,
|
5 |
+
PiFinnTheHumanFill,
|
6 |
+
PiFinnTheHumanDuotone,
|
7 |
+
} from "react-icons/pi"
|
8 |
+
import { Play } from "lucide-react"
|
9 |
+
import {
|
10 |
+
Select,
|
11 |
+
SelectContent,
|
12 |
+
SelectItem,
|
13 |
+
SelectTrigger,
|
14 |
+
SelectValue,
|
15 |
+
} from "@/components/ui/select"
|
16 |
+
import { Button } from "@/components/ui/button"
|
17 |
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
|
18 |
+
|
19 |
+
import { Footer } from "@/components/footer"
|
20 |
+
import { Header } from "@/components/header"
|
21 |
+
|
22 |
+
// ----------------------------------------------------
|
23 |
+
// Category + Prompt Mapping
|
24 |
+
// ----------------------------------------------------
|
25 |
+
const categories = [
|
26 |
+
{ value: "lgbtq+", label: "LGBT+", prompt: "The gay people are" },
|
27 |
+
{ value: "nationality", label: "Nationality", prompt: "The French people are" },
|
28 |
+
{ value: "race", label: "Race", prompt: "The Black people are" },
|
29 |
+
]
|
30 |
+
|
31 |
+
// ----------------------------------------------------
|
32 |
+
// Minimal card
|
33 |
+
// ----------------------------------------------------
|
34 |
+
function ModelCard({
|
35 |
+
Icon,
|
36 |
+
title,
|
37 |
+
text,
|
38 |
+
streaming,
|
39 |
+
}: {
|
40 |
+
Icon: React.ElementType
|
41 |
+
title: string
|
42 |
+
text: string
|
43 |
+
streaming?: boolean
|
44 |
+
}) {
|
45 |
+
return (
|
46 |
+
<div className="border border-gray-200 dark:border-gray-700 rounded-lg p-6 flex flex-col min-h-[120px]">
|
47 |
+
<div className="flex items-center gap-2 mb-2">
|
48 |
+
<Icon className="h-5 w-5" />
|
49 |
+
<h3 className="font-semibold">{title}</h3>
|
50 |
+
</div>
|
51 |
+
{/* If streaming == true, we append "●" at the end to mimic a typing indicator */}
|
52 |
+
<div className="min-h-40 w-80 text-left">
|
53 |
+
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-wrap break-words">
|
54 |
+
{text}
|
55 |
+
{streaming && "●"}
|
56 |
+
{/* // ⏺ or ⬤ or ● */}
|
57 |
+
</p>
|
58 |
+
</div>
|
59 |
+
</div>
|
60 |
+
)
|
61 |
+
}
|
62 |
+
|
63 |
+
// ----------------------------------------------------
|
64 |
+
// Tab Panel that holds 4 model cards + "Play" button
|
65 |
+
// ----------------------------------------------------
|
66 |
+
function TabPanel({
|
67 |
+
datasetKey,
|
68 |
+
modelKey,
|
69 |
+
categoryKey,
|
70 |
+
prompt,
|
71 |
+
}: {
|
72 |
+
datasetKey: string
|
73 |
+
modelKey: string
|
74 |
+
categoryKey: string
|
75 |
+
prompt: string
|
76 |
+
}) {
|
77 |
+
// These are the four generation “modes” in sequence
|
78 |
+
const modelSequence = [
|
79 |
+
{ type: "original", title: "Original Model", key: "origin", icon: PiFinnTheHumanLight },
|
80 |
+
{ type: "origin+steer", title: "Original + Steering", key: "origin+steer", icon: PiFinnTheHumanBold },
|
81 |
+
{ type: "trained", title: "Trained Model", key: "trained", icon: PiFinnTheHumanFill },
|
82 |
+
{ type: "trained-steer", title: "Trained - Steering", key: "trained-steer", icon: PiFinnTheHumanDuotone },
|
83 |
+
]
|
84 |
+
|
85 |
+
// Holds the partial or final text for each of the 4 slots
|
86 |
+
const [outputs, setOutputs] = useState(["", "", "", ""])
|
87 |
+
// Which slot is currently streaming? -1 if none
|
88 |
+
const [activeIndex, setActiveIndex] = useState(-1)
|
89 |
+
|
90 |
+
// Helper to fetch in streaming chunks
|
91 |
+
async function fetchInChunks(genType: string, index: number) {
|
92 |
+
const payload = {
|
93 |
+
model: modelKey,
|
94 |
+
dataset: datasetKey,
|
95 |
+
category: categoryKey,
|
96 |
+
type: genType,
|
97 |
+
}
|
98 |
+
|
99 |
+
const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || ""
|
100 |
+
const response = await fetch(`${apiBaseUrl}/api/generate`, {
|
101 |
+
method: "POST",
|
102 |
+
headers: { "Content-Type": "application/json" },
|
103 |
+
body: JSON.stringify(payload),
|
104 |
+
})
|
105 |
+
|
106 |
+
// Stream the response
|
107 |
+
const reader = response.body?.getReader()
|
108 |
+
if (!reader) return
|
109 |
+
const decoder = new TextDecoder("utf-8")
|
110 |
+
let partial = ""
|
111 |
+
|
112 |
+
while (true) {
|
113 |
+
const { done, value } = await reader.read()
|
114 |
+
if (done) break
|
115 |
+
|
116 |
+
// Decode chunk and update partial text
|
117 |
+
partial += decoder.decode(value, { stream: true })
|
118 |
+
// Update outputs[i] in real-time
|
119 |
+
setOutputs((prev) => {
|
120 |
+
const copy = [...prev]
|
121 |
+
copy[index] = partial
|
122 |
+
return copy
|
123 |
+
})
|
124 |
+
}
|
125 |
+
}
|
126 |
+
|
127 |
+
// Called on "Play"
|
128 |
+
async function handlePlay() {
|
129 |
+
// Reset everything
|
130 |
+
setOutputs(["", "", "", ""])
|
131 |
+
setActiveIndex(-1)
|
132 |
+
|
133 |
+
// Stream each model's text in sequence
|
134 |
+
for (let i = 0; i < modelSequence.length; i++) {
|
135 |
+
setActiveIndex(i)
|
136 |
+
await fetchInChunks(modelSequence[i].type, i)
|
137 |
+
setActiveIndex(-1) // or keep streaming indicator until next loop
|
138 |
+
}
|
139 |
+
}
|
140 |
+
|
141 |
+
return (
|
142 |
+
<div className="space-y-6">
|
143 |
+
<div className="grid md:grid-cols-2 gap-6">
|
144 |
+
{modelSequence.map((seq, i) => {
|
145 |
+
const Icon = seq.icon
|
146 |
+
return (
|
147 |
+
<ModelCard
|
148 |
+
key={seq.type}
|
149 |
+
Icon={Icon}
|
150 |
+
title={seq.title}
|
151 |
+
text={prompt + outputs[i] || ""}
|
152 |
+
streaming={i === activeIndex}
|
153 |
+
/>
|
154 |
+
)
|
155 |
+
})}
|
156 |
+
</div>
|
157 |
+
|
158 |
+
<div className="flex justify-center">
|
159 |
+
<Button
|
160 |
+
size="lg"
|
161 |
+
className="bg-blue-500 hover:bg-blue-600 text-white px-8 rounded-full"
|
162 |
+
onClick={handlePlay}
|
163 |
+
>
|
164 |
+
<Play className="w-5 h-5 mr-2" />
|
165 |
+
Play
|
166 |
+
</Button>
|
167 |
+
</div>
|
168 |
+
</div>
|
169 |
+
)
|
170 |
+
}
|
171 |
+
|
172 |
+
// ----------------------------------------------------
|
173 |
+
// Main App
|
174 |
+
// ----------------------------------------------------
|
175 |
+
export default function App() {
|
176 |
+
const [dataset, setDataset] = useState("Bias (EMGSD)")
|
177 |
+
const [model, setModel] = useState("GPT-2")
|
178 |
+
const [category, setCategory] = useState(categories[0].value)
|
179 |
+
|
180 |
+
// Convert front-end selection to server keys
|
181 |
+
const datasetKey = dataset === "Bias (EMGSD)" ? "emgsd" : "emgsd"
|
182 |
+
const modelKey = model === "GPT-2" ? "gpt2" : "gpt2"
|
183 |
+
|
184 |
+
return (
|
185 |
+
<div className="min-h-screen flex flex-col dark:bg-transparent">
|
186 |
+
<Header />
|
187 |
+
{/* Main content */}
|
188 |
+
<main className="flex-grow flex flex-col items-center justify-center">
|
189 |
+
<div className="text-center space-y-8">
|
190 |
+
<div className="space-y-4">
|
191 |
+
<h1 className="text-7xl font-mono tracking-tighter text-black dark:text-white">
|
192 |
+
CorrSteer
|
193 |
+
</h1>
|
194 |
+
<p className="text-xl leading-relaxed text-gray-700 dark:text-gray-300 italic px-6">
|
195 |
+
Text Classification dataset can be used to <span className="font-bold">Steer</span> LLMs,
|
196 |
+
<br />
|
197 |
+
<span className="font-bold">Corr</span>elating with SAE features
|
198 |
+
</p>
|
199 |
+
</div>
|
200 |
+
|
201 |
+
{/* Dropdowns */}
|
202 |
+
<div className="grid md:grid-cols-2 gap-8">
|
203 |
+
<div className="space-y-2 px-6 mx-8">
|
204 |
+
<label className="text-sm font-medium dark:text-gray-300">
|
205 |
+
Dataset
|
206 |
+
</label>
|
207 |
+
<Select value={dataset} onValueChange={setDataset}>
|
208 |
+
<SelectTrigger className="dark:bg-gray-800 dark:text-white">
|
209 |
+
<SelectValue />
|
210 |
+
</SelectTrigger>
|
211 |
+
<SelectContent className="dark:bg-gray-800 dark:text-white">
|
212 |
+
<SelectItem value="Bias (EMGSD)">Bias (EMGSD)</SelectItem>
|
213 |
+
</SelectContent>
|
214 |
+
</Select>
|
215 |
+
</div>
|
216 |
+
|
217 |
+
<div className="space-y-2 px-6 mx-8">
|
218 |
+
<label className="text-sm font-medium dark:text-gray-300">
|
219 |
+
Language Model
|
220 |
+
</label>
|
221 |
+
<Select value={model} onValueChange={setModel}>
|
222 |
+
<SelectTrigger className="dark:bg-gray-800 dark:text-white">
|
223 |
+
<SelectValue />
|
224 |
+
</SelectTrigger>
|
225 |
+
<SelectContent className="dark:bg-gray-800 dark:text-white">
|
226 |
+
<SelectItem value="GPT-2">GPT-2</SelectItem>
|
227 |
+
</SelectContent>
|
228 |
+
</Select>
|
229 |
+
</div>
|
230 |
+
</div>
|
231 |
+
|
232 |
+
{/* Tabs: 3 categories -> each has its own content */}
|
233 |
+
<Tabs value={category} onValueChange={setCategory}>
|
234 |
+
<TabsList className="gap-1 bg-transparent">
|
235 |
+
{categories.map((cat) => (
|
236 |
+
<TabsTrigger
|
237 |
+
key={cat.value}
|
238 |
+
value={cat.value}
|
239 |
+
className="data-[state=active]:bg-blue-400 dark:data-[state=active]:bg-blue-500 data-[state=inactive]:bg-slate-200 dark:data-[state=inactive]:bg-slate-800 data-[state=active]:border-gray-300 px-4 py-2 text-sm"
|
240 |
+
>
|
241 |
+
{cat.label}
|
242 |
+
</TabsTrigger>
|
243 |
+
))}
|
244 |
+
</TabsList>
|
245 |
+
|
246 |
+
{categories.map((cat) => (
|
247 |
+
<TabsContent key={cat.value} value={cat.value} className="p-6">
|
248 |
+
<TabPanel
|
249 |
+
datasetKey={datasetKey}
|
250 |
+
modelKey={modelKey}
|
251 |
+
categoryKey={category}
|
252 |
+
prompt={cat.prompt}
|
253 |
+
/>
|
254 |
+
</TabsContent>
|
255 |
+
))}
|
256 |
+
</Tabs>
|
257 |
+
</div>
|
258 |
+
</main>
|
259 |
+
<Footer />
|
260 |
+
</div>
|
261 |
+
)
|
262 |
+
}
|
demo/src/components/footer.tsx
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export function Footer() {
|
2 |
+
return (
|
3 |
+
<footer className="flex justify-center items-center py-4">
|
4 |
+
<p className="text-sm text-gray-500 dark:text-gray-400">
|
5 |
+
© 2025 <a href="https://github.com/seonglae" className="underline text-blue-600 dark:text-blue-300">Seonglae Cho</a>
|
6 |
+
</p>
|
7 |
+
</footer>
|
8 |
+
)
|
9 |
+
}
|
demo/src/components/header.tsx
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useEffect, useState } from "react"
|
2 |
+
|
3 |
+
import { Moon } from "lucide-react"
|
4 |
+
import { Button } from "@/components/ui/button"
|
5 |
+
import {
|
6 |
+
NavigationMenu,
|
7 |
+
NavigationMenuItem,
|
8 |
+
NavigationMenuList,
|
9 |
+
NavigationMenuTrigger,
|
10 |
+
} from "@/components/ui/navigation-menu"
|
11 |
+
|
12 |
+
|
13 |
+
export function Header() {
|
14 |
+
const [darkMode, setDarkMode] = useState(false)
|
15 |
+
useEffect(() => {
|
16 |
+
if (darkMode) document.documentElement.classList.add("dark")
|
17 |
+
else document.documentElement.classList.remove("dark")
|
18 |
+
}, [darkMode])
|
19 |
+
|
20 |
+
return (
|
21 |
+
<header className="flex justify-end items-center gap-4 p-6">
|
22 |
+
<NavigationMenu>
|
23 |
+
<NavigationMenuList>
|
24 |
+
<NavigationMenuItem>
|
25 |
+
<NavigationMenuTrigger>Paper</NavigationMenuTrigger>
|
26 |
+
</NavigationMenuItem>
|
27 |
+
<NavigationMenuItem>
|
28 |
+
<NavigationMenuTrigger>Github</NavigationMenuTrigger>
|
29 |
+
</NavigationMenuItem>
|
30 |
+
</NavigationMenuList>
|
31 |
+
</NavigationMenu>
|
32 |
+
|
33 |
+
<Button variant="outline" size="icon" onClick={() => setDarkMode(!darkMode)}>
|
34 |
+
<Moon className="h-5 w-5" />
|
35 |
+
</Button>
|
36 |
+
</header>
|
37 |
+
)
|
38 |
+
}
|
demo/src/components/model-card.tsx
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Info } from "lucide-react"
|
2 |
+
import { Card, CardContent } from "@/components/ui/card"
|
3 |
+
|
4 |
+
interface ModelCardProps {
|
5 |
+
title: string
|
6 |
+
text: string
|
7 |
+
}
|
8 |
+
|
9 |
+
export function ModelCard({ title, text }: ModelCardProps) {
|
10 |
+
return (
|
11 |
+
<Card className="overflow-hidden">
|
12 |
+
<CardContent className="p-6 space-y-4">
|
13 |
+
<div className="flex items-center gap-2">
|
14 |
+
<Info className="h-5 w-5" />
|
15 |
+
<h3 className="text-lg font-medium">{title}</h3>
|
16 |
+
</div>
|
17 |
+
<p>{text}</p>
|
18 |
+
</CardContent>
|
19 |
+
</Card>
|
20 |
+
)
|
21 |
+
}
|
22 |
+
|
demo/src/components/ui/button.tsx
ADDED
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import { Slot } from "@radix-ui/react-slot"
|
3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const buttonVariants = cva(
|
8 |
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
9 |
+
{
|
10 |
+
variants: {
|
11 |
+
variant: {
|
12 |
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
13 |
+
destructive:
|
14 |
+
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
15 |
+
outline:
|
16 |
+
"bg-background hover:bg-accent hover:text-accent-foreground",
|
17 |
+
secondary:
|
18 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
19 |
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
20 |
+
link: "text-primary underline-offset-4 hover:underline",
|
21 |
+
},
|
22 |
+
size: {
|
23 |
+
default: "h-10 px-4 py-2",
|
24 |
+
sm: "h-9 rounded-md px-3",
|
25 |
+
lg: "h-11 rounded-md px-8",
|
26 |
+
icon: "h-10 w-10",
|
27 |
+
},
|
28 |
+
},
|
29 |
+
defaultVariants: {
|
30 |
+
variant: "default",
|
31 |
+
size: "default",
|
32 |
+
},
|
33 |
+
}
|
34 |
+
)
|
35 |
+
|
36 |
+
export interface ButtonProps
|
37 |
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
38 |
+
VariantProps<typeof buttonVariants> {
|
39 |
+
asChild?: boolean
|
40 |
+
}
|
41 |
+
|
42 |
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
43 |
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
44 |
+
const Comp = asChild ? Slot : "button"
|
45 |
+
return (
|
46 |
+
<Comp
|
47 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
48 |
+
ref={ref}
|
49 |
+
{...props}
|
50 |
+
/>
|
51 |
+
)
|
52 |
+
}
|
53 |
+
)
|
54 |
+
Button.displayName = "Button"
|
55 |
+
|
56 |
+
export { Button, buttonVariants }
|
demo/src/components/ui/card.tsx
ADDED
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
|
3 |
+
import { cn } from "@/lib/utils"
|
4 |
+
|
5 |
+
const Card = React.forwardRef<
|
6 |
+
HTMLDivElement,
|
7 |
+
React.HTMLAttributes<HTMLDivElement>
|
8 |
+
>(({ className, ...props }, ref) => (
|
9 |
+
<div
|
10 |
+
ref={ref}
|
11 |
+
className={cn(
|
12 |
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
13 |
+
className
|
14 |
+
)}
|
15 |
+
{...props}
|
16 |
+
/>
|
17 |
+
))
|
18 |
+
Card.displayName = "Card"
|
19 |
+
|
20 |
+
const CardHeader = React.forwardRef<
|
21 |
+
HTMLDivElement,
|
22 |
+
React.HTMLAttributes<HTMLDivElement>
|
23 |
+
>(({ className, ...props }, ref) => (
|
24 |
+
<div
|
25 |
+
ref={ref}
|
26 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
27 |
+
{...props}
|
28 |
+
/>
|
29 |
+
))
|
30 |
+
CardHeader.displayName = "CardHeader"
|
31 |
+
|
32 |
+
const CardTitle = React.forwardRef<
|
33 |
+
HTMLDivElement,
|
34 |
+
React.HTMLAttributes<HTMLDivElement>
|
35 |
+
>(({ className, ...props }, ref) => (
|
36 |
+
<div
|
37 |
+
ref={ref}
|
38 |
+
className={cn(
|
39 |
+
"text-2xl font-semibold leading-none tracking-tight",
|
40 |
+
className
|
41 |
+
)}
|
42 |
+
{...props}
|
43 |
+
/>
|
44 |
+
))
|
45 |
+
CardTitle.displayName = "CardTitle"
|
46 |
+
|
47 |
+
const CardDescription = React.forwardRef<
|
48 |
+
HTMLDivElement,
|
49 |
+
React.HTMLAttributes<HTMLDivElement>
|
50 |
+
>(({ className, ...props }, ref) => (
|
51 |
+
<div
|
52 |
+
ref={ref}
|
53 |
+
className={cn("text-sm text-muted-foreground", className)}
|
54 |
+
{...props}
|
55 |
+
/>
|
56 |
+
))
|
57 |
+
CardDescription.displayName = "CardDescription"
|
58 |
+
|
59 |
+
const CardContent = React.forwardRef<
|
60 |
+
HTMLDivElement,
|
61 |
+
React.HTMLAttributes<HTMLDivElement>
|
62 |
+
>(({ className, ...props }, ref) => (
|
63 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
64 |
+
))
|
65 |
+
CardContent.displayName = "CardContent"
|
66 |
+
|
67 |
+
const CardFooter = React.forwardRef<
|
68 |
+
HTMLDivElement,
|
69 |
+
React.HTMLAttributes<HTMLDivElement>
|
70 |
+
>(({ className, ...props }, ref) => (
|
71 |
+
<div
|
72 |
+
ref={ref}
|
73 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
74 |
+
{...props}
|
75 |
+
/>
|
76 |
+
))
|
77 |
+
CardFooter.displayName = "CardFooter"
|
78 |
+
|
79 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
demo/src/components/ui/navigation-menu.tsx
ADDED
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
3 |
+
import { cva } from "class-variance-authority"
|
4 |
+
import { ChevronDown } from "lucide-react"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const NavigationMenu = React.forwardRef<
|
9 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
10 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
11 |
+
>(({ className, children, ...props }, ref) => (
|
12 |
+
<NavigationMenuPrimitive.Root
|
13 |
+
ref={ref}
|
14 |
+
className={cn(
|
15 |
+
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
16 |
+
className
|
17 |
+
)}
|
18 |
+
{...props}
|
19 |
+
>
|
20 |
+
{children}
|
21 |
+
<NavigationMenuViewport />
|
22 |
+
</NavigationMenuPrimitive.Root>
|
23 |
+
))
|
24 |
+
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
25 |
+
|
26 |
+
const NavigationMenuList = React.forwardRef<
|
27 |
+
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
28 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
29 |
+
>(({ className, ...props }, ref) => (
|
30 |
+
<NavigationMenuPrimitive.List
|
31 |
+
ref={ref}
|
32 |
+
className={cn(
|
33 |
+
"group flex flex-1 list-none items-center justify-center space-x-1",
|
34 |
+
className
|
35 |
+
)}
|
36 |
+
{...props}
|
37 |
+
/>
|
38 |
+
))
|
39 |
+
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
40 |
+
|
41 |
+
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
42 |
+
|
43 |
+
const navigationMenuTriggerStyle = cva(
|
44 |
+
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
45 |
+
)
|
46 |
+
|
47 |
+
const NavigationMenuTrigger = React.forwardRef<
|
48 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
49 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
50 |
+
>(({ className, children, ...props }, ref) => (
|
51 |
+
<NavigationMenuPrimitive.Trigger
|
52 |
+
ref={ref}
|
53 |
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
54 |
+
{...props}
|
55 |
+
>
|
56 |
+
{children}{" "}
|
57 |
+
<ChevronDown
|
58 |
+
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
59 |
+
aria-hidden="true"
|
60 |
+
/>
|
61 |
+
</NavigationMenuPrimitive.Trigger>
|
62 |
+
))
|
63 |
+
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
64 |
+
|
65 |
+
const NavigationMenuContent = React.forwardRef<
|
66 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
67 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
68 |
+
>(({ className, ...props }, ref) => (
|
69 |
+
<NavigationMenuPrimitive.Content
|
70 |
+
ref={ref}
|
71 |
+
className={cn(
|
72 |
+
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
73 |
+
className
|
74 |
+
)}
|
75 |
+
{...props}
|
76 |
+
/>
|
77 |
+
))
|
78 |
+
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
79 |
+
|
80 |
+
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
81 |
+
|
82 |
+
const NavigationMenuViewport = React.forwardRef<
|
83 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
84 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
85 |
+
>(({ className, ...props }, ref) => (
|
86 |
+
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
87 |
+
<NavigationMenuPrimitive.Viewport
|
88 |
+
className={cn(
|
89 |
+
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
90 |
+
className
|
91 |
+
)}
|
92 |
+
ref={ref}
|
93 |
+
{...props}
|
94 |
+
/>
|
95 |
+
</div>
|
96 |
+
))
|
97 |
+
NavigationMenuViewport.displayName =
|
98 |
+
NavigationMenuPrimitive.Viewport.displayName
|
99 |
+
|
100 |
+
const NavigationMenuIndicator = React.forwardRef<
|
101 |
+
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
102 |
+
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
103 |
+
>(({ className, ...props }, ref) => (
|
104 |
+
<NavigationMenuPrimitive.Indicator
|
105 |
+
ref={ref}
|
106 |
+
className={cn(
|
107 |
+
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
108 |
+
className
|
109 |
+
)}
|
110 |
+
{...props}
|
111 |
+
>
|
112 |
+
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
113 |
+
</NavigationMenuPrimitive.Indicator>
|
114 |
+
))
|
115 |
+
NavigationMenuIndicator.displayName =
|
116 |
+
NavigationMenuPrimitive.Indicator.displayName
|
117 |
+
|
118 |
+
export {
|
119 |
+
navigationMenuTriggerStyle,
|
120 |
+
NavigationMenu,
|
121 |
+
NavigationMenuList,
|
122 |
+
NavigationMenuItem,
|
123 |
+
NavigationMenuContent,
|
124 |
+
NavigationMenuTrigger,
|
125 |
+
NavigationMenuLink,
|
126 |
+
NavigationMenuIndicator,
|
127 |
+
NavigationMenuViewport,
|
128 |
+
}
|
demo/src/components/ui/select.tsx
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as React from "react"
|
2 |
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
3 |
+
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
4 |
+
|
5 |
+
import { cn } from "@/lib/utils"
|
6 |
+
|
7 |
+
const Select = SelectPrimitive.Root
|
8 |
+
|
9 |
+
const SelectGroup = SelectPrimitive.Group
|
10 |
+
|
11 |
+
const SelectValue = SelectPrimitive.Value
|
12 |
+
|
13 |
+
const SelectTrigger = React.forwardRef<
|
14 |
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
15 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
16 |
+
>(({ className, children, ...props }, ref) => (
|
17 |
+
<SelectPrimitive.Trigger
|
18 |
+
ref={ref}
|
19 |
+
className={cn(
|
20 |
+
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
21 |
+
className
|
22 |
+
)}
|
23 |
+
{...props}
|
24 |
+
>
|
25 |
+
{children}
|
26 |
+
<SelectPrimitive.Icon asChild>
|
27 |
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
28 |
+
</SelectPrimitive.Icon>
|
29 |
+
</SelectPrimitive.Trigger>
|
30 |
+
))
|
31 |
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
32 |
+
|
33 |
+
const SelectScrollUpButton = React.forwardRef<
|
34 |
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
35 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
36 |
+
>(({ className, ...props }, ref) => (
|
37 |
+
<SelectPrimitive.ScrollUpButton
|
38 |
+
ref={ref}
|
39 |
+
className={cn(
|
40 |
+
"flex cursor-default items-center justify-center py-1",
|
41 |
+
className
|
42 |
+
)}
|
43 |
+
{...props}
|
44 |
+
>
|
45 |
+
<ChevronUp className="h-4 w-4" />
|
46 |
+
</SelectPrimitive.ScrollUpButton>
|
47 |
+
))
|
48 |
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
49 |
+
|
50 |
+
const SelectScrollDownButton = React.forwardRef<
|
51 |
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
52 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
53 |
+
>(({ className, ...props }, ref) => (
|
54 |
+
<SelectPrimitive.ScrollDownButton
|
55 |
+
ref={ref}
|
56 |
+
className={cn(
|
57 |
+
"flex cursor-default items-center justify-center py-1",
|
58 |
+
className
|
59 |
+
)}
|
60 |
+
{...props}
|
61 |
+
>
|
62 |
+
<ChevronDown className="h-4 w-4" />
|
63 |
+
</SelectPrimitive.ScrollDownButton>
|
64 |
+
))
|
65 |
+
SelectScrollDownButton.displayName =
|
66 |
+
SelectPrimitive.ScrollDownButton.displayName
|
67 |
+
|
68 |
+
const SelectContent = React.forwardRef<
|
69 |
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
70 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
71 |
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
72 |
+
<SelectPrimitive.Portal>
|
73 |
+
<SelectPrimitive.Content
|
74 |
+
ref={ref}
|
75 |
+
className={cn(
|
76 |
+
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
77 |
+
position === "popper" &&
|
78 |
+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
79 |
+
className
|
80 |
+
)}
|
81 |
+
position={position}
|
82 |
+
{...props}
|
83 |
+
>
|
84 |
+
<SelectScrollUpButton />
|
85 |
+
<SelectPrimitive.Viewport
|
86 |
+
className={cn(
|
87 |
+
"p-1",
|
88 |
+
position === "popper" &&
|
89 |
+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
90 |
+
)}
|
91 |
+
>
|
92 |
+
{children}
|
93 |
+
</SelectPrimitive.Viewport>
|
94 |
+
<SelectScrollDownButton />
|
95 |
+
</SelectPrimitive.Content>
|
96 |
+
</SelectPrimitive.Portal>
|
97 |
+
))
|
98 |
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
99 |
+
|
100 |
+
const SelectLabel = React.forwardRef<
|
101 |
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
102 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
103 |
+
>(({ className, ...props }, ref) => (
|
104 |
+
<SelectPrimitive.Label
|
105 |
+
ref={ref}
|
106 |
+
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
107 |
+
{...props}
|
108 |
+
/>
|
109 |
+
))
|
110 |
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
111 |
+
|
112 |
+
const SelectItem = React.forwardRef<
|
113 |
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
114 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
115 |
+
>(({ className, children, ...props }, ref) => (
|
116 |
+
<SelectPrimitive.Item
|
117 |
+
ref={ref}
|
118 |
+
className={cn(
|
119 |
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
120 |
+
className
|
121 |
+
)}
|
122 |
+
{...props}
|
123 |
+
>
|
124 |
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
125 |
+
<SelectPrimitive.ItemIndicator>
|
126 |
+
<Check className="h-4 w-4" />
|
127 |
+
</SelectPrimitive.ItemIndicator>
|
128 |
+
</span>
|
129 |
+
|
130 |
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
131 |
+
</SelectPrimitive.Item>
|
132 |
+
))
|
133 |
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
134 |
+
|
135 |
+
const SelectSeparator = React.forwardRef<
|
136 |
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
137 |
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
138 |
+
>(({ className, ...props }, ref) => (
|
139 |
+
<SelectPrimitive.Separator
|
140 |
+
ref={ref}
|
141 |
+
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
142 |
+
{...props}
|
143 |
+
/>
|
144 |
+
))
|
145 |
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
146 |
+
|
147 |
+
export {
|
148 |
+
Select,
|
149 |
+
SelectGroup,
|
150 |
+
SelectValue,
|
151 |
+
SelectTrigger,
|
152 |
+
SelectContent,
|
153 |
+
SelectLabel,
|
154 |
+
SelectItem,
|
155 |
+
SelectSeparator,
|
156 |
+
SelectScrollUpButton,
|
157 |
+
SelectScrollDownButton,
|
158 |
+
}
|
demo/src/components/ui/tabs.tsx
ADDED
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client"
|
2 |
+
|
3 |
+
import * as React from "react"
|
4 |
+
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
5 |
+
|
6 |
+
import { cn } from "@/lib/utils"
|
7 |
+
|
8 |
+
const Tabs = TabsPrimitive.Root
|
9 |
+
|
10 |
+
const TabsList = React.forwardRef<
|
11 |
+
React.ElementRef<typeof TabsPrimitive.List>,
|
12 |
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
13 |
+
>(({ className, ...props }, ref) => (
|
14 |
+
<TabsPrimitive.List
|
15 |
+
ref={ref}
|
16 |
+
className={cn(
|
17 |
+
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
18 |
+
className
|
19 |
+
)}
|
20 |
+
{...props}
|
21 |
+
/>
|
22 |
+
))
|
23 |
+
TabsList.displayName = TabsPrimitive.List.displayName
|
24 |
+
|
25 |
+
const TabsTrigger = React.forwardRef<
|
26 |
+
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
27 |
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
28 |
+
>(({ className, ...props }, ref) => (
|
29 |
+
<TabsPrimitive.Trigger
|
30 |
+
ref={ref}
|
31 |
+
className={cn(
|
32 |
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
33 |
+
className
|
34 |
+
)}
|
35 |
+
{...props}
|
36 |
+
/>
|
37 |
+
))
|
38 |
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
39 |
+
|
40 |
+
const TabsContent = React.forwardRef<
|
41 |
+
React.ElementRef<typeof TabsPrimitive.Content>,
|
42 |
+
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
43 |
+
>(({ className, ...props }, ref) => (
|
44 |
+
<TabsPrimitive.Content
|
45 |
+
ref={ref}
|
46 |
+
className={cn(
|
47 |
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
48 |
+
className
|
49 |
+
)}
|
50 |
+
{...props}
|
51 |
+
/>
|
52 |
+
))
|
53 |
+
TabsContent.displayName = TabsPrimitive.Content.displayName
|
54 |
+
|
55 |
+
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
demo/src/index.css
ADDED
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
4 |
+
|
5 |
+
:root {
|
6 |
+
font-family: Arial, sans-serif;
|
7 |
+
color-scheme: light dark;
|
8 |
+
font-synthesis: none;
|
9 |
+
text-rendering: optimizeLegibility;
|
10 |
+
-webkit-font-smoothing: antialiased;
|
11 |
+
-moz-osx-font-smoothing: grayscale;
|
12 |
+
}
|
13 |
+
|
14 |
+
@layer base {
|
15 |
+
:root {
|
16 |
+
--background: 0 0% 100%;
|
17 |
+
--foreground: 222.2 84% 4.9%;
|
18 |
+
--card: 0 0% 100%;
|
19 |
+
--card-foreground: 222.2 84% 4.9%;
|
20 |
+
--popover: 0 0% 100%;
|
21 |
+
--popover-foreground: 222.2 84% 4.9%;
|
22 |
+
--primary: 222.2 47.4% 11.2%;
|
23 |
+
--primary-foreground: 210 40% 98%;
|
24 |
+
--secondary: 210 40% 96.1%;
|
25 |
+
--secondary-foreground: 222.2 47.4% 11.2%;
|
26 |
+
--muted: 210 40% 96.1%;
|
27 |
+
--muted-foreground: 215.4 16.3% 46.9%;
|
28 |
+
--accent: 210 40% 96.1%;
|
29 |
+
--accent-foreground: 222.2 47.4% 11.2%;
|
30 |
+
--destructive: 0 84.2% 60.2%;
|
31 |
+
--destructive-foreground: 210 40% 98%;
|
32 |
+
--border: 214.3 31.8% 91.4%;
|
33 |
+
--input: 214.3 31.8% 91.4%;
|
34 |
+
--ring: 222.2 84% 4.9%;
|
35 |
+
--chart-1: 12 76% 61%;
|
36 |
+
--chart-2: 173 58% 39%;
|
37 |
+
--chart-3: 197 37% 24%;
|
38 |
+
--chart-4: 43 74% 66%;
|
39 |
+
--chart-5: 27 87% 67%;
|
40 |
+
--radius: 0.5rem;
|
41 |
+
}
|
42 |
+
.dark {
|
43 |
+
--background: 222.2 84% 4.9%;
|
44 |
+
--foreground: 210 40% 98%;
|
45 |
+
--card: 222.2 84% 4.9%;
|
46 |
+
--card-foreground: 210 40% 98%;
|
47 |
+
--popover: 222.2 84% 4.9%;
|
48 |
+
--popover-foreground: 210 40% 98%;
|
49 |
+
--primary: 210 40% 98%;
|
50 |
+
--primary-foreground: 222.2 47.4% 11.2%;
|
51 |
+
--secondary: 217.2 32.6% 17.5%;
|
52 |
+
--secondary-foreground: 210 40% 98%;
|
53 |
+
--muted: 217.2 32.6% 17.5%;
|
54 |
+
--muted-foreground: 215 20.2% 65.1%;
|
55 |
+
--accent: 217.2 32.6% 17.5%;
|
56 |
+
--accent-foreground: 210 40% 98%;
|
57 |
+
--destructive: 0 62.8% 30.6%;
|
58 |
+
--destructive-foreground: 210 40% 98%;
|
59 |
+
--border: 217.2 32.6% 17.5%;
|
60 |
+
--input: 217.2 32.6% 17.5%;
|
61 |
+
--ring: 212.7 26.8% 83.9%;
|
62 |
+
--chart-1: 220 70% 50%;
|
63 |
+
--chart-2: 160 60% 45%;
|
64 |
+
--chart-3: 30 80% 55%;
|
65 |
+
--chart-4: 280 65% 60%;
|
66 |
+
--chart-5: 340 75% 55%;
|
67 |
+
}
|
68 |
+
}
|
69 |
+
|
70 |
+
@layer base {
|
71 |
+
* {
|
72 |
+
@apply border-border;
|
73 |
+
}
|
74 |
+
body {
|
75 |
+
@apply bg-background text-foreground;
|
76 |
+
}
|
77 |
+
}
|
demo/src/lib/utils.ts
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { clsx, type ClassValue } from "clsx"
|
2 |
+
import { twMerge } from "tailwind-merge"
|
3 |
+
|
4 |
+
export function cn(...inputs: ClassValue[]) {
|
5 |
+
return twMerge(clsx(inputs))
|
6 |
+
}
|
demo/src/main.tsx
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StrictMode } from 'react'
|
2 |
+
import { createRoot } from 'react-dom/client'
|
3 |
+
import './index.css'
|
4 |
+
import App from './App.tsx'
|
5 |
+
|
6 |
+
createRoot(document.getElementById('root')!).render(
|
7 |
+
<StrictMode>
|
8 |
+
<App />
|
9 |
+
</StrictMode>,
|
10 |
+
)
|
demo/src/vite-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="vite/client" />
|
demo/tailwind.config.ts
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import type { Config } from 'tailwindcss'
|
2 |
+
|
3 |
+
export default {
|
4 |
+
darkMode: ['class'],
|
5 |
+
content: [
|
6 |
+
'./index.html',
|
7 |
+
'./src/**/*.{ts,tsx}',
|
8 |
+
'node_modules/@llamaindex/chat-ui/**/*.{ts,tsx}',
|
9 |
+
],
|
10 |
+
theme: {
|
11 |
+
extend: {
|
12 |
+
borderRadius: {
|
13 |
+
lg: 'var(--radius)',
|
14 |
+
md: 'calc(var(--radius) - 2px)',
|
15 |
+
sm: 'calc(var(--radius) - 4px)'
|
16 |
+
},
|
17 |
+
colors: {
|
18 |
+
background: 'hsl(var(--background))',
|
19 |
+
foreground: 'hsl(var(--foreground))',
|
20 |
+
card: {
|
21 |
+
DEFAULT: 'hsl(var(--card))',
|
22 |
+
foreground: 'hsl(var(--card-foreground))'
|
23 |
+
},
|
24 |
+
popover: {
|
25 |
+
DEFAULT: 'hsl(var(--popover))',
|
26 |
+
foreground: 'hsl(var(--popover-foreground))'
|
27 |
+
},
|
28 |
+
primary: {
|
29 |
+
DEFAULT: 'hsl(var(--primary))',
|
30 |
+
foreground: 'hsl(var(--primary-foreground))'
|
31 |
+
},
|
32 |
+
secondary: {
|
33 |
+
DEFAULT: 'hsl(var(--secondary))',
|
34 |
+
foreground: 'hsl(var(--secondary-foreground))'
|
35 |
+
},
|
36 |
+
muted: {
|
37 |
+
DEFAULT: 'hsl(var(--muted))',
|
38 |
+
foreground: 'hsl(var(--muted-foreground))'
|
39 |
+
},
|
40 |
+
accent: {
|
41 |
+
DEFAULT: 'hsl(var(--accent))',
|
42 |
+
foreground: 'hsl(var(--accent-foreground))'
|
43 |
+
},
|
44 |
+
destructive: {
|
45 |
+
DEFAULT: 'hsl(var(--destructive))',
|
46 |
+
foreground: 'hsl(var(--destructive-foreground))'
|
47 |
+
},
|
48 |
+
border: 'hsl(var(--border))',
|
49 |
+
input: 'hsl(var(--input))',
|
50 |
+
ring: 'hsl(var(--ring))',
|
51 |
+
chart: {
|
52 |
+
'1': 'hsl(var(--chart-1))',
|
53 |
+
'2': 'hsl(var(--chart-2))',
|
54 |
+
'3': 'hsl(var(--chart-3))',
|
55 |
+
'4': 'hsl(var(--chart-4))',
|
56 |
+
'5': 'hsl(var(--chart-5))'
|
57 |
+
}
|
58 |
+
}
|
59 |
+
}
|
60 |
+
},
|
61 |
+
plugins: [require("tailwindcss-animate")],
|
62 |
+
} satisfies Config
|
demo/tsconfig.app.json
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"baseUrl": ".",
|
4 |
+
"paths": {
|
5 |
+
"@/*": [
|
6 |
+
"./src/*"
|
7 |
+
]
|
8 |
+
},
|
9 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
10 |
+
"target": "ES2020",
|
11 |
+
"useDefineForClassFields": true,
|
12 |
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
13 |
+
"module": "ESNext",
|
14 |
+
"skipLibCheck": true,
|
15 |
+
|
16 |
+
/* Bundler mode */
|
17 |
+
"moduleResolution": "bundler",
|
18 |
+
"allowImportingTsExtensions": true,
|
19 |
+
"isolatedModules": true,
|
20 |
+
"moduleDetection": "force",
|
21 |
+
"noEmit": true,
|
22 |
+
"jsx": "react-jsx",
|
23 |
+
|
24 |
+
/* Linting */
|
25 |
+
"strict": true,
|
26 |
+
"noUnusedLocals": true,
|
27 |
+
"noUnusedParameters": true,
|
28 |
+
"noFallthroughCasesInSwitch": true,
|
29 |
+
"noUncheckedSideEffectImports": true
|
30 |
+
},
|
31 |
+
"include": ["src"]
|
32 |
+
}
|
demo/tsconfig.json
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"files": [],
|
3 |
+
"references": [
|
4 |
+
{ "path": "./tsconfig.app.json" },
|
5 |
+
{ "path": "./tsconfig.node.json" }
|
6 |
+
],
|
7 |
+
"compilerOptions": {
|
8 |
+
"baseUrl": ".",
|
9 |
+
"paths": {
|
10 |
+
"@/*": ["./src/*"]
|
11 |
+
}
|
12 |
+
}
|
13 |
+
}
|
demo/tsconfig.node.json
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
4 |
+
"target": "ES2022",
|
5 |
+
"lib": ["ES2023"],
|
6 |
+
"module": "ESNext",
|
7 |
+
"skipLibCheck": true,
|
8 |
+
|
9 |
+
/* Bundler mode */
|
10 |
+
"moduleResolution": "bundler",
|
11 |
+
"allowImportingTsExtensions": true,
|
12 |
+
"isolatedModules": true,
|
13 |
+
"moduleDetection": "force",
|
14 |
+
"noEmit": true,
|
15 |
+
|
16 |
+
/* Linting */
|
17 |
+
"strict": true,
|
18 |
+
"noUnusedLocals": true,
|
19 |
+
"noUnusedParameters": true,
|
20 |
+
"noFallthroughCasesInSwitch": true,
|
21 |
+
"noUncheckedSideEffectImports": true
|
22 |
+
},
|
23 |
+
"include": ["vite.config.ts"]
|
24 |
+
}
|
demo/vite.config.ts
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
+
define: {
|
13 |
+
__API_BASE_URL__: JSON.stringify(process.env.VITE_API_BASE_URL || "http://localhost:5174"),
|
14 |
+
},
|
15 |
+
server: {
|
16 |
+
host: "0.0.0.0",
|
17 |
+
port: 7860,
|
18 |
+
},
|
19 |
+
})
|