jbilcke-hf HF Staff commited on
Commit
65ee86e
·
0 Parent(s):

initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env +2 -0
  2. .eslintrc.json +3 -0
  3. .gitignore +35 -0
  4. .nvmrc +1 -0
  5. Dockerfile +65 -0
  6. README.md +13 -0
  7. components.json +16 -0
  8. next.config.js +10 -0
  9. package-lock.json +0 -0
  10. package.json +63 -0
  11. postcss.config.js +6 -0
  12. public/next.svg +1 -0
  13. public/vercel.svg +1 -0
  14. src/app/engine/predict.ts +56 -0
  15. src/app/engine/see.ts +54 -0
  16. src/app/engine/think.ts +60 -0
  17. src/app/favicon.ico +0 -0
  18. src/app/globals.css +27 -0
  19. src/app/interface/progress/index.tsx +56 -0
  20. src/app/interface/progress/progress-bar.tsx +58 -0
  21. src/app/interface/top-menu/index.tsx +26 -0
  22. src/app/layout.tsx +24 -0
  23. src/app/main.tsx +104 -0
  24. src/app/observer.tsx +134 -0
  25. src/app/page.tsx +28 -0
  26. src/components/icons/full-screen.tsx +16 -0
  27. src/components/ui/accordion.tsx +60 -0
  28. src/components/ui/alert.tsx +59 -0
  29. src/components/ui/avatar.tsx +50 -0
  30. src/components/ui/badge.tsx +36 -0
  31. src/components/ui/button.tsx +56 -0
  32. src/components/ui/card.tsx +79 -0
  33. src/components/ui/checkbox.tsx +30 -0
  34. src/components/ui/collapsible.tsx +11 -0
  35. src/components/ui/command.tsx +155 -0
  36. src/components/ui/dialog.tsx +123 -0
  37. src/components/ui/dropdown-menu.tsx +200 -0
  38. src/components/ui/input.tsx +25 -0
  39. src/components/ui/label.tsx +26 -0
  40. src/components/ui/menubar.tsx +236 -0
  41. src/components/ui/popover.tsx +31 -0
  42. src/components/ui/select.tsx +121 -0
  43. src/components/ui/separator.tsx +31 -0
  44. src/components/ui/switch.tsx +29 -0
  45. src/components/ui/table.tsx +114 -0
  46. src/components/ui/textarea.tsx +24 -0
  47. src/components/ui/tooltip.tsx +30 -0
  48. src/lib/createLlamaPrompt.ts +25 -0
  49. src/lib/pick.ts +2 -0
  50. src/lib/utils.ts +6 -0
.env ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ NEXT_PUBLIC_BASE_URL=https://jbilcke-hf-webcam-to-idefics.hf.space
2
+ RENDERING_ENGINE_API=https://jbilcke-hf-videochain-api.hf.space
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitignore ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+
8
+ # testing
9
+ /coverage
10
+
11
+ # next.js
12
+ /.next/
13
+ /out/
14
+
15
+ # production
16
+ /build
17
+
18
+ # misc
19
+ .DS_Store
20
+ *.pem
21
+
22
+ # debug
23
+ npm-debug.log*
24
+ yarn-debug.log*
25
+ yarn-error.log*
26
+
27
+ # local env files
28
+ .env*.local
29
+
30
+ # vercel
31
+ .vercel
32
+
33
+ # typescript
34
+ *.tsbuildinfo
35
+ next-env.d.ts
.nvmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ v18.16.0
Dockerfile ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine AS base
2
+
3
+ # Install dependencies only when needed
4
+ FROM base AS deps
5
+ # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
6
+ RUN apk add --no-cache libc6-compat
7
+ WORKDIR /app
8
+
9
+ # Install dependencies based on the preferred package manager
10
+ COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
11
+ RUN \
12
+ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
13
+ elif [ -f package-lock.json ]; then npm ci; \
14
+ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
15
+ else echo "Lockfile not found." && exit 1; \
16
+ fi
17
+
18
+ # Uncomment the following lines if you want to use a secret at buildtime,
19
+ # for example to access your private npm packages
20
+ # RUN --mount=type=secret,id=HF_EXAMPLE_SECRET,mode=0444,required=true \
21
+ # $(cat /run/secrets/HF_EXAMPLE_SECRET)
22
+
23
+ # Rebuild the source code only when needed
24
+ FROM base AS builder
25
+ WORKDIR /app
26
+ COPY --from=deps /app/node_modules ./node_modules
27
+ COPY . .
28
+
29
+ # Next.js collects completely anonymous telemetry data about general usage.
30
+ # Learn more here: https://nextjs.org/telemetry
31
+ # Uncomment the following line in case you want to disable telemetry during the build.
32
+ # ENV NEXT_TELEMETRY_DISABLED 1
33
+
34
+ # RUN yarn build
35
+
36
+ # If you use yarn, comment out this line and use the line above
37
+ RUN npm run build
38
+
39
+ # Production image, copy all the files and run next
40
+ FROM base AS runner
41
+ WORKDIR /app
42
+
43
+ ENV NODE_ENV production
44
+ # Uncomment the following line in case you want to disable telemetry during runtime.
45
+ # ENV NEXT_TELEMETRY_DISABLED 1
46
+
47
+ RUN addgroup --system --gid 1001 nodejs
48
+ RUN adduser --system --uid 1001 nextjs
49
+
50
+ COPY --from=builder /app/public ./public
51
+
52
+ # Automatically leverage output traces to reduce image size
53
+ # https://nextjs.org/docs/advanced-features/output-file-tracing
54
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
55
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
56
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/cache ./.next/cache
57
+ # COPY --from=builder --chown=nextjs:nodejs /app/.next/cache/fetch-cache ./.next/cache/fetch-cache
58
+
59
+ USER nextjs
60
+
61
+ EXPOSE 3000
62
+
63
+ ENV PORT 3000
64
+
65
+ CMD ["node", "server.js"]
README.md ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Observer
3
+ emoji: 🐶🎥
4
+ colorFrom: red
5
+ colorTo: yellow
6
+ sdk: docker
7
+ pinned: true
8
+ app_port: 3000
9
+ ---
10
+
11
+ Your webcam, sent to Idefics every ~12-15 sec, then interpreted by Llama-2 👀
12
+
13
+ So it's an agent that can look at things (but not do much)
components.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.js",
8
+ "css": "app/globals.css",
9
+ "baseColor": "stone",
10
+ "cssVariables": false
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils"
15
+ }
16
+ }
next.config.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ output: 'standalone',
4
+
5
+ experimental: {
6
+ serverActions: true,
7
+ },
8
+ }
9
+
10
+ module.exports = nextConfig
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "@jbilcke/observer",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@huggingface/inference": "^2.6.1",
13
+ "@radix-ui/react-accordion": "^1.1.2",
14
+ "@radix-ui/react-avatar": "^1.0.3",
15
+ "@radix-ui/react-checkbox": "^1.0.4",
16
+ "@radix-ui/react-collapsible": "^1.0.3",
17
+ "@radix-ui/react-dialog": "^1.0.4",
18
+ "@radix-ui/react-dropdown-menu": "^2.0.5",
19
+ "@radix-ui/react-icons": "^1.3.0",
20
+ "@radix-ui/react-label": "^2.0.2",
21
+ "@radix-ui/react-menubar": "^1.0.3",
22
+ "@radix-ui/react-popover": "^1.0.6",
23
+ "@radix-ui/react-select": "^1.2.2",
24
+ "@radix-ui/react-separator": "^1.0.3",
25
+ "@radix-ui/react-slot": "^1.0.2",
26
+ "@radix-ui/react-switch": "^1.0.3",
27
+ "@radix-ui/react-tooltip": "^1.0.6",
28
+ "@react-pdf/renderer": "^3.1.12",
29
+ "@types/node": "20.4.2",
30
+ "@types/react": "18.2.15",
31
+ "@types/react-dom": "18.2.7",
32
+ "@types/uuid": "^9.0.2",
33
+ "autoprefixer": "10.4.14",
34
+ "class-variance-authority": "^0.6.1",
35
+ "clsx": "^2.0.0",
36
+ "cmdk": "^0.2.0",
37
+ "cookies-next": "^2.1.2",
38
+ "date-fns": "^2.30.0",
39
+ "eslint": "8.45.0",
40
+ "eslint-config-next": "13.4.10",
41
+ "lucide-react": "^0.260.0",
42
+ "next": "13.4.10",
43
+ "pick": "^0.0.1",
44
+ "postcss": "8.4.26",
45
+ "react": "18.2.0",
46
+ "react-circular-progressbar": "^2.1.0",
47
+ "react-dom": "18.2.0",
48
+ "react-virtualized-auto-sizer": "^1.0.20",
49
+ "react-webcam": "^7.1.1",
50
+ "sbd": "^1.0.19",
51
+ "styled-components": "^6.0.7",
52
+ "tailwind-merge": "^1.13.2",
53
+ "tailwindcss": "3.3.3",
54
+ "tailwindcss-animate": "^1.0.6",
55
+ "ts-node": "^10.9.1",
56
+ "typescript": "5.1.6",
57
+ "usehooks-ts": "^2.9.1",
58
+ "uuid": "^9.0.0"
59
+ },
60
+ "devDependencies": {
61
+ "@types/sbd": "^1.0.3"
62
+ }
63
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/next.svg ADDED
public/vercel.svg ADDED
src/app/engine/predict.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { HfInference } from "@huggingface/inference"
4
+
5
+ const hfi = new HfInference(process.env.HF_API_TOKEN)
6
+ const hf = hfi.endpoint(`${process.env.HF_INFERENCE_ENDPOINT_URL || ""}`)
7
+
8
+ export async function predict(inputs: string) {
9
+
10
+ console.log(`predict: `, inputs)
11
+
12
+ let instructions = ""
13
+ try {
14
+ for await (const output of hf.textGenerationStream({
15
+ inputs,
16
+ parameters: {
17
+ do_sample: true,
18
+
19
+ // hard limit for max_new_tokens is 1512
20
+ max_new_tokens: 200, // 1150,
21
+ return_full_text: false,
22
+ }
23
+ })) {
24
+ instructions += output.token.text
25
+ process.stdout.write(output.token.text)
26
+ if (
27
+ instructions.includes("</s>") ||
28
+ instructions.includes("<s>") ||
29
+ instructions.includes("[INST]") ||
30
+ instructions.includes("[/INST]") ||
31
+ instructions.includes("<SYS>") ||
32
+ instructions.includes("</SYS>") ||
33
+ instructions.includes("<|end|>") ||
34
+ instructions.includes("<|assistant|>")
35
+ ) {
36
+ break
37
+ }
38
+ }
39
+ } catch (err) {
40
+ console.error(`error during generation: ${err}`)
41
+ }
42
+
43
+ // need to do some cleanup of the garbage the LLM might have gave us
44
+ return (
45
+ instructions
46
+ .replaceAll("<|end|>", "")
47
+ .replaceAll("<s>", "")
48
+ .replaceAll("</s>", "")
49
+ .replaceAll("[INST]", "")
50
+ .replaceAll("[/INST]", "")
51
+ .replaceAll("<SYS>", "")
52
+ .replaceAll("</SYS>", "")
53
+ .replaceAll("<|assistant|>", "")
54
+ .replaceAll('""', '"')
55
+ )
56
+ }
src/app/engine/see.ts ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import { ImageAnalysisRequest, ImageAnalysisResponse } from "@/types"
4
+
5
+ const apiUrl = `${process.env.RENDERING_ENGINE_API || ""}`
6
+
7
+ export async function see({
8
+ prompt,
9
+ imageBase64
10
+ }: {
11
+ prompt: string
12
+ imageBase64: string
13
+ }): Promise<string> {
14
+ if (!prompt) {
15
+ console.error(`cannot call the API without an image, aborting..`)
16
+ throw new Error(`cannot call the API without an image, aborting..`)
17
+ }
18
+
19
+ try {
20
+ const request = {
21
+ prompt,
22
+ image: imageBase64
23
+
24
+ } as ImageAnalysisRequest
25
+
26
+ console.log(`calling ${apiUrl}/analyze called with: `, {
27
+ prompt: request.prompt,
28
+ image: request.image.slice(0, 20)
29
+ })
30
+
31
+ const res = await fetch(`${apiUrl}/analyze`, {
32
+ method: "POST",
33
+ headers: {
34
+ Accept: "application/json",
35
+ "Content-Type": "application/json",
36
+ // Authorization: `Bearer ${process.env.VC_SECRET_ACCESS_TOKEN}`,
37
+ },
38
+ body: JSON.stringify(request),
39
+ cache: 'no-store',
40
+ // we can also use this (see https://vercel.com/blog/vercel-cache-api-nextjs-cache)
41
+ // next: { revalidate: 1 }
42
+ })
43
+
44
+ if (res.status !== 200) {
45
+ throw new Error('Failed to fetch data')
46
+ }
47
+
48
+ const response = (await res.json()) as ImageAnalysisResponse
49
+ return response.result
50
+ } catch (err) {
51
+ console.error(err)
52
+ return ""
53
+ }
54
+ }
src/app/engine/think.ts ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sbd from "sbd"
2
+ import { format } from "date-fns"
3
+
4
+ import { createLlamaPrompt } from "@/lib/createLlamaPrompt"
5
+
6
+ import { predict } from "./predict"
7
+
8
+ export const think = async ({
9
+ event = "",
10
+ observation = "",
11
+ history = "",
12
+ }: {
13
+ event: string;
14
+ observation: string;
15
+ history: string;
16
+ }): Promise<string> => {
17
+ if (!event) {
18
+ throw new Error("missing event")
19
+ }
20
+ const prompt = createLlamaPrompt([
21
+ {
22
+ role: "system",
23
+ content: [
24
+ `You are a companion robot, very friendly, curious about the world.`,
25
+
26
+ // TODO: put the history here (from most recent to oldest)
27
+ `You have been presented some situation in the past, but you lost your memory.`,
28
+
29
+ `Today's date is ${format(new Date(), 'yyyy-MM-dd at HH:mm (d)')}.`,
30
+ , `You are currently observing this: ${observation}`,
31
+ ].filter(item => item).join("\n")
32
+ },
33
+ {
34
+ role: "user",
35
+ content: event,
36
+ }
37
+ ])
38
+
39
+
40
+ let result = ""
41
+ try {
42
+ result = await predict(prompt)
43
+ if (!result.trim().length) {
44
+ throw new Error("no response")
45
+ }
46
+ } catch (err) {
47
+ console.log(`prediction of the response..`)
48
+ try {
49
+ result = await predict(prompt+".")
50
+ } catch (err) {
51
+ console.error(`prediction of the response failed again!`)
52
+ throw new Error(`failed to generate the response ${err}`)
53
+ }
54
+ }
55
+
56
+ // llama-2 is too chatty, let's keep 3 sentences at most
57
+ const sentences = sbd.sentences(result).slice(0, 3).join(" ").trim()
58
+
59
+ return sentences
60
+ }
src/app/favicon.ico ADDED
src/app/globals.css ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ --foreground-rgb: 0, 0, 0;
7
+ --background-start-rgb: 214, 219, 220;
8
+ --background-end-rgb: 255, 255, 255;
9
+ }
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root {
13
+ --foreground-rgb: 255, 255, 255;
14
+ --background-start-rgb: 0, 0, 0;
15
+ --background-end-rgb: 0, 0, 0;
16
+ }
17
+ }
18
+
19
+ body {
20
+ color: rgb(var(--foreground-rgb));
21
+ background: linear-gradient(
22
+ to bottom,
23
+ transparent,
24
+ rgb(var(--background-end-rgb))
25
+ )
26
+ rgb(var(--background-start-rgb));
27
+ }
src/app/interface/progress/index.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useEffect, useRef, useState } from "react"
2
+
3
+ import { ProgressBar } from "./progress-bar"
4
+ import { cn } from "@/lib/utils"
5
+
6
+ export function Progress({
7
+ isLoading,
8
+ resetKey = "", // when this key change, this will re-spawn the progress bar
9
+ className = "",
10
+ }: {
11
+ isLoading: boolean
12
+ resetKey: string
13
+ className?: string
14
+ }) {
15
+ const timeoutRef = useRef<any>()
16
+ const [progressPercent, setProcessPercent] = useState(0)
17
+ const progressRef = useRef(0)
18
+ const isLoadingRef = useRef(isLoading)
19
+
20
+ const updateProgressBar = () => {
21
+ const duration = 1000 // 1 sec
22
+ const frequency = 200 // 200ms
23
+ const nbUpdatesPerSec = duration / frequency // 5x per second
24
+
25
+ // normally it takes 45, and we will try to go below,
26
+ // but to be safe let's set the counter a 1 min
27
+ const nbSeconds = 32 // 1 min
28
+ const amountInPercent = 100 / (nbUpdatesPerSec * nbSeconds) // 0.333
29
+
30
+ progressRef.current = Math.min(100, progressRef.current + amountInPercent)
31
+ setProcessPercent(progressRef.current)
32
+ }
33
+
34
+ useEffect(() => {
35
+ clearInterval(timeoutRef.current)
36
+ isLoadingRef.current = isLoading
37
+ progressRef.current = 0
38
+ setProcessPercent(0)
39
+ if (isLoading) {
40
+ timeoutRef.current = setInterval(updateProgressBar, 200)
41
+ }
42
+ }, [isLoading, resetKey])
43
+
44
+ return (
45
+ <div className={cn(
46
+ `fixed flex w-16 h-16 top-16 right-6 z-50`,
47
+ `animation-all duration-300 text-md`,
48
+ isLoading
49
+ ? `scale-100 opacity-100`
50
+ : `scale-0 opacity-0`,
51
+ className
52
+ )}>
53
+ <ProgressBar progressPercentage={progressPercent} />
54
+ </div>
55
+ )
56
+ }
src/app/interface/progress/progress-bar.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { CircularProgressbar, buildStyles } from "react-circular-progressbar"
4
+ import "react-circular-progressbar/dist/styles.css"
5
+
6
+ export function ProgressBar ({
7
+ className,
8
+ progressPercentage,
9
+ text
10
+ }: {
11
+ className?: string
12
+ progressPercentage?: number
13
+ text?: string
14
+ }) {
15
+ return (
16
+ <div className={className}>
17
+ <CircularProgressbar
18
+ // doc: https://www.npmjs.com/package/react-circular-progressbar
19
+
20
+ value={progressPercentage || 0}
21
+
22
+ // Text to display inside progressbar. Default: ''.
23
+ text={text || ""}
24
+
25
+ // Width of circular line relative to total width of component, a value from 0-100. Default: 8.
26
+ strokeWidth={10}
27
+
28
+
29
+ // As a convenience, you can use buildStyles to configure the most common style changes:
30
+
31
+ styles={buildStyles({
32
+ // Rotation of path and trail, in number of turns (0-1)
33
+ rotation: 0,
34
+
35
+ // Whether to use rounded or flat corners on the ends - can use 'butt' or 'round'
36
+ strokeLinecap: 'round',
37
+
38
+ // Text size
39
+ textSize: '20px',
40
+
41
+ // How long animation takes to go from one percentage to another, in seconds
42
+ pathTransitionDuration: 0.1,
43
+
44
+ // Can specify path transition in more detail, or remove it entirely
45
+ // pathTransition: 'none',
46
+
47
+ // Colors
48
+ // pathColor: `rgba(62, 152, 199, ${percentage / 100})`,
49
+ textColor: '#f88',
50
+ trailColor: '#c6c6c6',
51
+ pathColor: '#d46300',
52
+ backgroundColor: '#fcba03',
53
+ })}
54
+
55
+ />
56
+ </div>
57
+ )
58
+ }
src/app/interface/top-menu/index.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export function TopMenu() {
6
+ return (
7
+ <div className={cn(
8
+ `z-10 fixed top-0 left-0 right-0`,
9
+ `flex flex-row w-full justify-between items-center`,
10
+ `backdrop-blur-xl`,
11
+ `px-2 py-2 border-b-1 border-gray-50 dark:border-gray-50`,
12
+ `bg-stone-900/70 dark:bg-stone-900/70 text-gray-50 dark:text-gray-50`,
13
+ `space-x-6`
14
+ )}>
15
+ <div className="flex flex-row items-center space-x-3 font-mono">
16
+ TODO
17
+ </div>
18
+ <div className="flex flex-row flex-grow items-center space-x-3 font-mono">
19
+ TODO
20
+ </div>
21
+ <div className="flex flex-row items-center space-x-3 font-mono">
22
+ TODO
23
+ </div>
24
+ </div>
25
+ )
26
+ }
src/app/layout.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './globals.css'
2
+ import type { Metadata } from 'next'
3
+ import { Inter } from 'next/font/google'
4
+
5
+ const inter = Inter({ subsets: ['latin'] })
6
+
7
+ export const metadata: Metadata = {
8
+ title: 'Idfx',
9
+ description: 'Idfx',
10
+ }
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode
16
+ }) {
17
+ return (
18
+ <html lang="en">
19
+ <body className={inter.className}>
20
+ {children}
21
+ </body>
22
+ </html>
23
+ )
24
+ }
src/app/main.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useState, useTransition } from "react"
4
+ import { format } from "date-fns"
5
+
6
+ import Observer from "./observer"
7
+ import { cn } from "@/lib/utils"
8
+ import { think } from "./engine/think"
9
+ import { Progress } from "./interface/progress"
10
+
11
+ export default function Main() {
12
+ const [_isPending, startTransition] = useTransition()
13
+ const [lastImage, setLastImage] = useState<string>("")
14
+ const [lastRawObservation, setLastRawObservation] = useState<string>("")
15
+ const [isLoadingAction, setLoadingAction] = useState(false)
16
+
17
+ const [observations, setObservations] = useState<string[]>([])
18
+ const [action, setAction] = useState<string>("Nothing to say yet.")
19
+
20
+ // receive a new observation from what the agent is looking at
21
+ const handleObservation = (observation: string, image: string) => {
22
+ setLastRawObservation(observation)
23
+ setLastImage(image)
24
+
25
+ // last comes first
26
+ setObservations([
27
+ `On ${format(new Date(), 'yyyy-MM-dd at HH:mm (d)')}, you saw: \"${observation}\".`
28
+ ].concat(observations))
29
+
30
+ // TODO: use llama-2 to summarize previous observations
31
+ const history = observations.slice(0, 3).join("\n")
32
+
33
+
34
+ startTransition(async () => {
35
+ setLoadingAction(true)
36
+ const action = await think({
37
+ history,
38
+ observation,
39
+ event: "Please react in a natural way to the current situation: comment on what's happening, ask questions etc.",
40
+ })
41
+
42
+ setAction(action)
43
+ setLoadingAction(false)
44
+ })
45
+ }
46
+
47
+ return (
48
+ <div className="w-screen h-screen bg-zinc-100">
49
+
50
+ <div className="fixed z-10 left-0 right-0 flex flex-col items-center justify-center">
51
+ <div className={cn(
52
+ `flex flex-col md:flex-row`,
53
+ `items-center justify-between`,
54
+ `w-full md:w-[90%] lg:w-[80%]`,
55
+ `p-2 mt-0 md:p-4 md:mt-8`,
56
+ `bg-zinc-100 md:rounded-xl`,
57
+ `shadow-2xl text-xs md:text-sm`
58
+ )}>
59
+ <div className="flex flex-row space-x-4 w-full md:w-1/2 p-2 md:p-4">
60
+ <div className="flex w-[112px]">
61
+ {lastImage ?
62
+ <div className="w-28 aspect-video">
63
+ <img
64
+ src={lastImage}
65
+ alt="screenshot"
66
+ className="rounded-lg shadow-xl border border-zinc-500"
67
+ />
68
+ </div> : null}
69
+ </div>
70
+
71
+ <div className="text-lg flex-grow italic">
72
+ <span className="text-zinc-700 text-lg">
73
+ {lastRawObservation}
74
+ </span>
75
+ </div>
76
+ </div>
77
+
78
+
79
+ <div className="flex flex-row w-full md:w-1/2 p-2 md:p-4">
80
+
81
+ <div className="w-full text-zinc-800 text-lg">
82
+ {action}
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <Observer onObserve={handleObservation} />
89
+
90
+ <Progress
91
+ isLoading={isLoadingAction}
92
+ resetKey=""
93
+ className="left-6 right-0"
94
+ />
95
+
96
+ <div className="fixed z-10 left-0 right-0 bottom-0 flex flex-col items-center justify-center">
97
+ <div className="full md:w-[80%] lg:w-[70%] mb-0 md:p-4 md:mb-8 bg-zinc-100 md:rounded-xl p-4 shadow-2xl text-xs md:text-sm">
98
+ <p>🅿️ <span className="font-semibold">Informations: </span> This demo uses <a href="https://huggingface.co/HuggingFaceM4/idefics-80b#bias-evaluation" target="_blank">IDEFICS</a>, and is provided for demonstration and research purposes.</p>
99
+ <p>⛔️ <span className="font-semibold">Limitations: </span> This demo is provided as-is, with no guarantee of factually correct results. In some cases, the model may return hallucinated or innapropriate responses.</p>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ )
104
+ }
src/app/observer.tsx ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { useCallback, useEffect, useRef, useState, useTransition } from "react"
4
+ import { useInterval } from "usehooks-ts"
5
+ import Webcam from "react-webcam"
6
+ import AutoSizer from "react-virtualized-auto-sizer"
7
+
8
+ import { see } from "./engine/see"
9
+ import { Progress } from "./interface/progress"
10
+
11
+ export default function Observer({
12
+ onObserve,
13
+ }: {
14
+ onObserve: (observation: string, image: string) => void
15
+ }) {
16
+ const [_isPending, startTransition] = useTransition()
17
+ const [img, setImg] = useState<string>("")
18
+ const webcamRef = useRef<Webcam>(null)
19
+ const [isInitialized, setInitialized] = useState(false)
20
+ const [frameNumber, setFrameNumber] = useState(0)
21
+ const [isBusy, setBusy] = useState(false)
22
+ const [lastObservation, setLastObservation] = useState("Nothing to see yet.")
23
+ const [lastObservedAt, setLastObservedAt] = useState(Date.now())
24
+
25
+ const defaultWidth = 1280
26
+ const defaultHeight = 1024 // 720
27
+
28
+ // minimum wait time between calls
29
+ const minimumWaitTimeInSec = 10
30
+
31
+ // in case we need to record a video, check the last part of
32
+ // https://blog.openreplay.com/capture-real-time-images-and-videos-with-react-webcam/
33
+ const capture = useCallback(() => {
34
+ if (!webcamRef.current) { return }
35
+ const imageSrc = webcamRef.current.getScreenshot()
36
+ if (!imageSrc) { return }
37
+ setImg(imageSrc)
38
+ setFrameNumber(frameNumber + 1)
39
+
40
+ return imageSrc
41
+ }, [webcamRef])
42
+
43
+ // note: for some strange reason, the webcam (at least on macOS)
44
+ // has a "fade in effect", which means in the first few seconds,
45
+ // eg. if we capture at 800ms, if will be darker than normal
46
+
47
+ useEffect(() => {
48
+ if (webcamRef.current && img && !isInitialized) {
49
+ setInitialized(true)
50
+ }
51
+ }, [webcamRef.current, img, isInitialized])
52
+
53
+ const observe = () => {
54
+ if (isBusy) {
55
+ // console.log("we are already predicting: skippping turn")
56
+ return
57
+ }
58
+
59
+ const currentTimeInMs = Date.now()
60
+ const elapsedTimeInMs = currentTimeInMs - lastObservedAt
61
+ const elapsedTimeInSec = elapsedTimeInMs / 1000
62
+ if (elapsedTimeInSec < minimumWaitTimeInSec) {
63
+ // console.log("minimum wait time between calls not reached: skipping turn")
64
+ return
65
+ }
66
+
67
+ setBusy(true)
68
+
69
+ console.log("Capturing new frame..")
70
+
71
+ startTransition(async () => {
72
+ const imageBase64 = capture()
73
+ if (!imageBase64) {
74
+ console.log("Failed to capture a new frame")
75
+ setTimeout(() => {
76
+ setBusy(false)
77
+ setLastObservedAt(Date.now())
78
+ }, 2000)
79
+ return
80
+ }
81
+ const prompt = `What do you see here?`
82
+
83
+ console.log("Calling IDEFICS..")
84
+ const newObservation = "Nothing to see here!!" // await see({ prompt, imageBase64 })
85
+
86
+ console.log("New observation: ", newObservation)
87
+ if (newObservation !== lastObservation) {
88
+ console.log("update!")
89
+ setLastObservation(newObservation || "")
90
+ onObserve(newObservation || "", imageBase64)
91
+ }
92
+ setLastObservedAt(Date.now())
93
+
94
+ // commented, because we don't want to spam the prod server
95
+ setBusy(false)
96
+ })
97
+
98
+ console.log("observation ended!")
99
+ }
100
+
101
+ useInterval(() => {
102
+ observe()
103
+ }, 1000)
104
+
105
+ return (
106
+ <AutoSizer>
107
+ {({ height, width }) => (
108
+ <>
109
+ <Webcam
110
+ ref={webcamRef}
111
+ className="fixed top-0 left-0 right-0 w-screen"
112
+ screenshotFormat='image/jpeg'
113
+ // screenshotFormat="image/webp"
114
+ mirrored={true}
115
+ videoConstraints={{
116
+ width: { min: defaultWidth },
117
+ height: { min: defaultHeight },
118
+ aspectRatio: defaultWidth / defaultHeight,
119
+ facingMode: "user",
120
+
121
+ // if the device allows it, we can use the back camera
122
+ // facingMode: { exact: "environment" }
123
+ } as MediaTrackConstraints}
124
+ />
125
+ <Progress
126
+ isLoading={isBusy}
127
+ resetKey=""
128
+ className="right-6"
129
+ />
130
+ </>
131
+ )}
132
+ </AutoSizer>
133
+ )
134
+ }
src/app/page.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use server"
2
+
3
+ import Head from "next/head"
4
+
5
+ import Main from "./main"
6
+ import { TooltipProvider } from "@/components/ui/tooltip"
7
+
8
+ // https://nextjs.org/docs/pages/building-your-application/optimizing/fonts
9
+
10
+ export default async function IndexPage({ params: { ownerId } }: { params: { ownerId: string }}) {
11
+ return (
12
+ <>
13
+ <Head>
14
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
15
+ <link rel="preconnect" href="https://fonts.googleapis.com" crossOrigin="anonymous" />
16
+ <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
17
+ </Head>
18
+ <main className={
19
+ `light fixed inset-0 w-screen h-screen flex flex-col items-center
20
+ bg-zinc-200 text-stone-800 overflow-y-scroll
21
+ `}>
22
+ <TooltipProvider delayDuration={100}>
23
+ <Main />
24
+ </TooltipProvider>
25
+ </main>
26
+ </>
27
+ )
28
+ }
src/components/icons/full-screen.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function FullScreenIcon() {
2
+ return (
3
+ <svg version="1.1" viewBox="0 0 14 14" width="24px" height="24px" xmlns="http://www.w3.org/2000/svg">
4
+ <title/>
5
+ <desc/>
6
+ <defs/>
7
+ <g fill="none" fill-rule="evenodd" id="Page-1" stroke="none" stroke-width="1">
8
+ <g fill="currentColor" id="Core" transform="translate(-215.000000, -257.000000)">
9
+ <g id="fullscreen" transform="translate(215.000000, 257.000000)">
10
+ <path d="M2,9 L0,9 L0,14 L5,14 L5,12 L2,12 L2,9 L2,9 Z M0,5 L2,5 L2,2 L5,2 L5,0 L0,0 L0,5 L0,5 Z M12,12 L9,12 L9,14 L14,14 L14,9 L12,9 L12,12 L12,12 Z M9,0 L9,2 L12,2 L12,5 L14,5 L14,0 L9,0 L9,0 Z" id="Shape"/>
11
+ </g>
12
+ </g>
13
+ </g>
14
+ </svg>
15
+ )
16
+ }
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
5
+ import { ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Accordion = AccordionPrimitive.Root
10
+
11
+ const AccordionItem = React.forwardRef<
12
+ React.ElementRef<typeof AccordionPrimitive.Item>,
13
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
14
+ >(({ className, ...props }, ref) => (
15
+ <AccordionPrimitive.Item
16
+ ref={ref}
17
+ className={cn("border-b", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ AccordionItem.displayName = "AccordionItem"
22
+
23
+ const AccordionTrigger = React.forwardRef<
24
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
25
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
26
+ >(({ className, children, ...props }, ref) => (
27
+ <AccordionPrimitive.Header className="flex">
28
+ <AccordionPrimitive.Trigger
29
+ ref={ref}
30
+ className={cn(
31
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
38
+ </AccordionPrimitive.Trigger>
39
+ </AccordionPrimitive.Header>
40
+ ))
41
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42
+
43
+ const AccordionContent = React.forwardRef<
44
+ React.ElementRef<typeof AccordionPrimitive.Content>,
45
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
46
+ >(({ className, children, ...props }, ref) => (
47
+ <AccordionPrimitive.Content
48
+ ref={ref}
49
+ className={cn(
50
+ "overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down",
51
+ className
52
+ )}
53
+ {...props}
54
+ >
55
+ <div className="pb-4 pt-0">{children}</div>
56
+ </AccordionPrimitive.Content>
57
+ ))
58
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
59
+
60
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border border-stone-200 p-4 [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-stone-950 dark:border-stone-800 dark:[&>svg]:text-stone-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-white text-stone-950 dark:bg-stone-950 dark:text-stone-50",
12
+ destructive:
13
+ "border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Avatar = React.forwardRef<
9
+ React.ElementRef<typeof AvatarPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <AvatarPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ ))
21
+ Avatar.displayName = AvatarPrimitive.Root.displayName
22
+
23
+ const AvatarImage = React.forwardRef<
24
+ React.ElementRef<typeof AvatarPrimitive.Image>,
25
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
26
+ >(({ className, ...props }, ref) => (
27
+ <AvatarPrimitive.Image
28
+ ref={ref}
29
+ className={cn("aspect-square h-full w-full", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
34
+
35
+ const AvatarFallback = React.forwardRef<
36
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
37
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
38
+ >(({ className, ...props }, ref) => (
39
+ <AvatarPrimitive.Fallback
40
+ ref={ref}
41
+ className={cn(
42
+ "flex h-full w-full items-center justify-center rounded-full bg-stone-100 dark:bg-stone-800",
43
+ className
44
+ )}
45
+ {...props}
46
+ />
47
+ ))
48
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49
+
50
+ export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border border-stone-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-stone-400 focus:ring-offset-2 dark:border-stone-800 dark:focus:ring-stone-800",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-stone-900 text-stone-50 hover:bg-stone-900/80 dark:bg-stone-50 dark:text-stone-900 dark:hover:bg-stone-50/80",
13
+ secondary:
14
+ "border-transparent bg-stone-100 text-stone-900 hover:bg-stone-100/80 dark:bg-stone-800 dark:text-stone-50 dark:hover:bg-stone-800/80",
15
+ destructive:
16
+ "border-transparent bg-red-500 text-stone-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/80",
17
+ outline: "text-stone-950 dark:text-stone-50",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
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 rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-stone-950 dark:focus-visible:ring-stone-800",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-stone-900 text-stone-50 hover:bg-stone-900/90 dark:bg-stone-50 dark:text-stone-900 dark:hover:bg-stone-50/90",
13
+ destructive:
14
+ "bg-red-500 text-stone-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/90",
15
+ outline:
16
+ "border border-stone-200 bg-white hover:bg-stone-100 hover:text-stone-900 dark:border-stone-800 dark:bg-stone-950 dark:hover:bg-stone-800 dark:hover:text-stone-50",
17
+ secondary:
18
+ "bg-stone-100 text-stone-900 hover:bg-stone-100/80 dark:bg-stone-800 dark:text-stone-50 dark:hover:bg-stone-800/80",
19
+ ghost: "hover:bg-stone-100 hover:text-stone-900 dark:hover:bg-stone-800 dark:hover:text-stone-50",
20
+ link: "text-stone-900 underline-offset-4 hover:underline dark:text-stone-50",
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 }
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 border-stone-200 bg-white text-stone-950 shadow-sm dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
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
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
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
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-stone-500 dark:text-stone-400", 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 }
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5
+ import { Check } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ "peer h-4 w-4 shrink-0 rounded-sm border border-stone-200 border-stone-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-stone-900 data-[state=checked]:text-stone-50 dark:border-stone-800 dark:border-stone-50 dark:ring-offset-stone-950 dark:focus-visible:ring-stone-800 dark:data-[state=checked]:bg-stone-50 dark:data-[state=checked]:text-stone-900",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ className={cn("flex items-center justify-center text-current")}
23
+ >
24
+ <Check className="h-4 w-4" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ ))
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
29
+
30
+ export { Checkbox }
src/components/ui/collapsible.tsx ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ const Collapsible = CollapsiblePrimitive.Root
6
+
7
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8
+
9
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10
+
11
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
src/components/ui/command.tsx ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { DialogProps } from "@radix-ui/react-dialog"
5
+ import { Command as CommandPrimitive } from "cmdk"
6
+ import { Search } from "lucide-react"
7
+
8
+ import { cn } from "@/lib/utils"
9
+ import { Dialog, DialogContent } from "@/components/ui/dialog"
10
+
11
+ const Command = React.forwardRef<
12
+ React.ElementRef<typeof CommandPrimitive>,
13
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
14
+ >(({ className, ...props }, ref) => (
15
+ <CommandPrimitive
16
+ ref={ref}
17
+ className={cn(
18
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-white text-stone-950 dark:bg-stone-950 dark:text-stone-50",
19
+ className
20
+ )}
21
+ {...props}
22
+ />
23
+ ))
24
+ Command.displayName = CommandPrimitive.displayName
25
+
26
+ interface CommandDialogProps extends DialogProps {}
27
+
28
+ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
29
+ return (
30
+ <Dialog {...props}>
31
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
32
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-stone-500 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 dark:[&_[cmdk-group-heading]]:text-stone-400">
33
+ {children}
34
+ </Command>
35
+ </DialogContent>
36
+ </Dialog>
37
+ )
38
+ }
39
+
40
+ const CommandInput = React.forwardRef<
41
+ React.ElementRef<typeof CommandPrimitive.Input>,
42
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
43
+ >(({ className, ...props }, ref) => (
44
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
45
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
46
+ <CommandPrimitive.Input
47
+ ref={ref}
48
+ className={cn(
49
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-stone-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-stone-400",
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ </div>
55
+ ))
56
+
57
+ CommandInput.displayName = CommandPrimitive.Input.displayName
58
+
59
+ const CommandList = React.forwardRef<
60
+ React.ElementRef<typeof CommandPrimitive.List>,
61
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
62
+ >(({ className, ...props }, ref) => (
63
+ <CommandPrimitive.List
64
+ ref={ref}
65
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
66
+ {...props}
67
+ />
68
+ ))
69
+
70
+ CommandList.displayName = CommandPrimitive.List.displayName
71
+
72
+ const CommandEmpty = React.forwardRef<
73
+ React.ElementRef<typeof CommandPrimitive.Empty>,
74
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
75
+ >((props, ref) => (
76
+ <CommandPrimitive.Empty
77
+ ref={ref}
78
+ className="py-6 text-center text-sm"
79
+ {...props}
80
+ />
81
+ ))
82
+
83
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
84
+
85
+ const CommandGroup = React.forwardRef<
86
+ React.ElementRef<typeof CommandPrimitive.Group>,
87
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
88
+ >(({ className, ...props }, ref) => (
89
+ <CommandPrimitive.Group
90
+ ref={ref}
91
+ className={cn(
92
+ "overflow-hidden p-1 text-stone-950 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-stone-500 dark:text-stone-50 dark:[&_[cmdk-group-heading]]:text-stone-400",
93
+ className
94
+ )}
95
+ {...props}
96
+ />
97
+ ))
98
+
99
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
100
+
101
+ const CommandSeparator = React.forwardRef<
102
+ React.ElementRef<typeof CommandPrimitive.Separator>,
103
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
104
+ >(({ className, ...props }, ref) => (
105
+ <CommandPrimitive.Separator
106
+ ref={ref}
107
+ className={cn("-mx-1 h-px bg-stone-200 dark:bg-stone-800", className)}
108
+ {...props}
109
+ />
110
+ ))
111
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
112
+
113
+ const CommandItem = React.forwardRef<
114
+ React.ElementRef<typeof CommandPrimitive.Item>,
115
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
116
+ >(({ className, ...props }, ref) => (
117
+ <CommandPrimitive.Item
118
+ ref={ref}
119
+ className={cn(
120
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-stone-100 aria-selected:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:aria-selected:bg-stone-800 dark:aria-selected:text-stone-50",
121
+ className
122
+ )}
123
+ {...props}
124
+ />
125
+ ))
126
+
127
+ CommandItem.displayName = CommandPrimitive.Item.displayName
128
+
129
+ const CommandShortcut = ({
130
+ className,
131
+ ...props
132
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
133
+ return (
134
+ <span
135
+ className={cn(
136
+ "ml-auto text-xs tracking-widest text-stone-500 dark:text-stone-400",
137
+ className
138
+ )}
139
+ {...props}
140
+ />
141
+ )
142
+ }
143
+ CommandShortcut.displayName = "CommandShortcut"
144
+
145
+ export {
146
+ Command,
147
+ CommandDialog,
148
+ CommandInput,
149
+ CommandList,
150
+ CommandEmpty,
151
+ CommandGroup,
152
+ CommandItem,
153
+ CommandShortcut,
154
+ CommandSeparator,
155
+ }
src/components/ui/dialog.tsx ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { X } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Dialog = DialogPrimitive.Root
10
+
11
+ const DialogTrigger = DialogPrimitive.Trigger
12
+
13
+ const DialogPortal = ({
14
+ className,
15
+ ...props
16
+ }: DialogPrimitive.DialogPortalProps) => (
17
+ <DialogPrimitive.Portal className={cn(className)} {...props} />
18
+ )
19
+ DialogPortal.displayName = DialogPrimitive.Portal.displayName
20
+
21
+ const DialogOverlay = React.forwardRef<
22
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
23
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
24
+ >(({ className, ...props }, ref) => (
25
+ <DialogPrimitive.Overlay
26
+ ref={ref}
27
+ className={cn(
28
+ "fixed inset-0 z-50 bg-white/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 dark:bg-stone-950/80",
29
+ className
30
+ )}
31
+ {...props}
32
+ />
33
+ ))
34
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
35
+
36
+ const DialogContent = React.forwardRef<
37
+ React.ElementRef<typeof DialogPrimitive.Content>,
38
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
39
+ >(({ className, children, ...props }, ref) => (
40
+ <DialogPortal>
41
+ <DialogOverlay />
42
+ <DialogPrimitive.Content
43
+ ref={ref}
44
+ className={cn(
45
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-stone-200 bg-white p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full dark:border-stone-800 dark:bg-stone-950",
46
+ className
47
+ )}
48
+ {...props}
49
+ >
50
+ {children}
51
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-stone-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-stone-100 data-[state=open]:text-stone-500 dark:ring-offset-stone-950 dark:focus:ring-stone-800 dark:data-[state=open]:bg-stone-800 dark:data-[state=open]:text-stone-400">
52
+ <X className="h-4 w-4" />
53
+ <span className="sr-only">Close</span>
54
+ </DialogPrimitive.Close>
55
+ </DialogPrimitive.Content>
56
+ </DialogPortal>
57
+ ))
58
+ DialogContent.displayName = DialogPrimitive.Content.displayName
59
+
60
+ const DialogHeader = ({
61
+ className,
62
+ ...props
63
+ }: React.HTMLAttributes<HTMLDivElement>) => (
64
+ <div
65
+ className={cn(
66
+ "flex flex-col space-y-1.5 text-center sm:text-left",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ )
72
+ DialogHeader.displayName = "DialogHeader"
73
+
74
+ const DialogFooter = ({
75
+ className,
76
+ ...props
77
+ }: React.HTMLAttributes<HTMLDivElement>) => (
78
+ <div
79
+ className={cn(
80
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
81
+ className
82
+ )}
83
+ {...props}
84
+ />
85
+ )
86
+ DialogFooter.displayName = "DialogFooter"
87
+
88
+ const DialogTitle = React.forwardRef<
89
+ React.ElementRef<typeof DialogPrimitive.Title>,
90
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
91
+ >(({ className, ...props }, ref) => (
92
+ <DialogPrimitive.Title
93
+ ref={ref}
94
+ className={cn(
95
+ "text-lg font-semibold leading-none tracking-tight",
96
+ className
97
+ )}
98
+ {...props}
99
+ />
100
+ ))
101
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
102
+
103
+ const DialogDescription = React.forwardRef<
104
+ React.ElementRef<typeof DialogPrimitive.Description>,
105
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
106
+ >(({ className, ...props }, ref) => (
107
+ <DialogPrimitive.Description
108
+ ref={ref}
109
+ className={cn("text-sm text-stone-500 dark:text-stone-400", className)}
110
+ {...props}
111
+ />
112
+ ))
113
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
114
+
115
+ export {
116
+ Dialog,
117
+ DialogTrigger,
118
+ DialogContent,
119
+ DialogHeader,
120
+ DialogFooter,
121
+ DialogTitle,
122
+ DialogDescription,
123
+ }
src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5
+ import { Check, ChevronRight, Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-stone-100 data-[state=open]:bg-stone-100 dark:focus:bg-stone-800 dark:data-[state=open]:bg-stone-800",
31
+ inset && "pl-8",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRight className="ml-auto h-4 w-4" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ))
40
+ DropdownMenuSubTrigger.displayName =
41
+ DropdownMenuPrimitive.SubTrigger.displayName
42
+
43
+ const DropdownMenuSubContent = React.forwardRef<
44
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
45
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
46
+ >(({ className, ...props }, ref) => (
47
+ <DropdownMenuPrimitive.SubContent
48
+ ref={ref}
49
+ className={cn(
50
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-stone-200 bg-white p-1 text-stone-950 shadow-lg 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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ ))
56
+ DropdownMenuSubContent.displayName =
57
+ DropdownMenuPrimitive.SubContent.displayName
58
+
59
+ const DropdownMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
62
+ >(({ className, sideOffset = 4, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Portal>
64
+ <DropdownMenuPrimitive.Content
65
+ ref={ref}
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-stone-200 bg-white p-1 text-stone-950 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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ </DropdownMenuPrimitive.Portal>
74
+ ))
75
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76
+
77
+ const DropdownMenuItem = React.forwardRef<
78
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
80
+ inset?: boolean
81
+ }
82
+ >(({ className, inset, ...props }, ref) => (
83
+ <DropdownMenuPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
87
+ inset && "pl-8",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ ))
93
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94
+
95
+ const DropdownMenuCheckboxItem = React.forwardRef<
96
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
97
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
98
+ >(({ className, children, checked, ...props }, ref) => (
99
+ <DropdownMenuPrimitive.CheckboxItem
100
+ ref={ref}
101
+ className={cn(
102
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
103
+ className
104
+ )}
105
+ checked={checked}
106
+ {...props}
107
+ >
108
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
109
+ <DropdownMenuPrimitive.ItemIndicator>
110
+ <Check className="h-4 w-4" />
111
+ </DropdownMenuPrimitive.ItemIndicator>
112
+ </span>
113
+ {children}
114
+ </DropdownMenuPrimitive.CheckboxItem>
115
+ ))
116
+ DropdownMenuCheckboxItem.displayName =
117
+ DropdownMenuPrimitive.CheckboxItem.displayName
118
+
119
+ const DropdownMenuRadioItem = React.forwardRef<
120
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
121
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
122
+ >(({ className, children, ...props }, ref) => (
123
+ <DropdownMenuPrimitive.RadioItem
124
+ ref={ref}
125
+ className={cn(
126
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
127
+ className
128
+ )}
129
+ {...props}
130
+ >
131
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
132
+ <DropdownMenuPrimitive.ItemIndicator>
133
+ <Circle className="h-2 w-2 fill-current" />
134
+ </DropdownMenuPrimitive.ItemIndicator>
135
+ </span>
136
+ {children}
137
+ </DropdownMenuPrimitive.RadioItem>
138
+ ))
139
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140
+
141
+ const DropdownMenuLabel = React.forwardRef<
142
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
143
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
144
+ inset?: boolean
145
+ }
146
+ >(({ className, inset, ...props }, ref) => (
147
+ <DropdownMenuPrimitive.Label
148
+ ref={ref}
149
+ className={cn(
150
+ "px-2 py-1.5 text-sm font-semibold",
151
+ inset && "pl-8",
152
+ className
153
+ )}
154
+ {...props}
155
+ />
156
+ ))
157
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158
+
159
+ const DropdownMenuSeparator = React.forwardRef<
160
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
161
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
162
+ >(({ className, ...props }, ref) => (
163
+ <DropdownMenuPrimitive.Separator
164
+ ref={ref}
165
+ className={cn("-mx-1 my-1 h-px bg-stone-100 dark:bg-stone-800", className)}
166
+ {...props}
167
+ />
168
+ ))
169
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170
+
171
+ const DropdownMenuShortcut = ({
172
+ className,
173
+ ...props
174
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
175
+ return (
176
+ <span
177
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
178
+ {...props}
179
+ />
180
+ )
181
+ }
182
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183
+
184
+ export {
185
+ DropdownMenu,
186
+ DropdownMenuTrigger,
187
+ DropdownMenuContent,
188
+ DropdownMenuItem,
189
+ DropdownMenuCheckboxItem,
190
+ DropdownMenuRadioItem,
191
+ DropdownMenuLabel,
192
+ DropdownMenuSeparator,
193
+ DropdownMenuShortcut,
194
+ DropdownMenuGroup,
195
+ DropdownMenuPortal,
196
+ DropdownMenuSub,
197
+ DropdownMenuSubContent,
198
+ DropdownMenuSubTrigger,
199
+ DropdownMenuRadioGroup,
200
+ }
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-stone-200 bg-white px-3 py-2 text-sm ring-offset-white file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-stone-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-stone-800 dark:bg-stone-950 dark:ring-offset-stone-950 dark:placeholder:text-stone-400 dark:focus-visible:ring-stone-800",
15
+ className
16
+ )}
17
+ ref={ref}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+ )
23
+ Input.displayName = "Input"
24
+
25
+ export { Input }
src/components/ui/label.tsx ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const labelVariants = cva(
10
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11
+ )
12
+
13
+ const Label = React.forwardRef<
14
+ React.ElementRef<typeof LabelPrimitive.Root>,
15
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
16
+ VariantProps<typeof labelVariants>
17
+ >(({ className, ...props }, ref) => (
18
+ <LabelPrimitive.Root
19
+ ref={ref}
20
+ className={cn(labelVariants(), className)}
21
+ {...props}
22
+ />
23
+ ))
24
+ Label.displayName = LabelPrimitive.Root.displayName
25
+
26
+ export { Label }
src/components/ui/menubar.tsx ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as MenubarPrimitive from "@radix-ui/react-menubar"
5
+ import { Check, ChevronRight, Circle } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const MenubarMenu = MenubarPrimitive.Menu
10
+
11
+ const MenubarGroup = MenubarPrimitive.Group
12
+
13
+ const MenubarPortal = MenubarPrimitive.Portal
14
+
15
+ const MenubarSub = MenubarPrimitive.Sub
16
+
17
+ const MenubarRadioGroup = MenubarPrimitive.RadioGroup
18
+
19
+ const Menubar = React.forwardRef<
20
+ React.ElementRef<typeof MenubarPrimitive.Root>,
21
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
22
+ >(({ className, ...props }, ref) => (
23
+ <MenubarPrimitive.Root
24
+ ref={ref}
25
+ className={cn(
26
+ "flex h-10 items-center space-x-1 rounded-md border border-stone-200 bg-white p-1 dark:border-stone-800 dark:bg-stone-950",
27
+ className
28
+ )}
29
+ {...props}
30
+ />
31
+ ))
32
+ Menubar.displayName = MenubarPrimitive.Root.displayName
33
+
34
+ const MenubarTrigger = React.forwardRef<
35
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
36
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
37
+ >(({ className, ...props }, ref) => (
38
+ <MenubarPrimitive.Trigger
39
+ ref={ref}
40
+ className={cn(
41
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-stone-100 focus:text-stone-900 data-[state=open]:bg-stone-100 data-[state=open]:text-stone-900 dark:focus:bg-stone-800 dark:focus:text-stone-50 dark:data-[state=open]:bg-stone-800 dark:data-[state=open]:text-stone-50",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ ))
47
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
48
+
49
+ const MenubarSubTrigger = React.forwardRef<
50
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
51
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
52
+ inset?: boolean
53
+ }
54
+ >(({ className, inset, children, ...props }, ref) => (
55
+ <MenubarPrimitive.SubTrigger
56
+ ref={ref}
57
+ className={cn(
58
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-stone-100 focus:text-stone-900 data-[state=open]:bg-stone-100 data-[state=open]:text-stone-900 dark:focus:bg-stone-800 dark:focus:text-stone-50 dark:data-[state=open]:bg-stone-800 dark:data-[state=open]:text-stone-50",
59
+ inset && "pl-8",
60
+ className
61
+ )}
62
+ {...props}
63
+ >
64
+ {children}
65
+ <ChevronRight className="ml-auto h-4 w-4" />
66
+ </MenubarPrimitive.SubTrigger>
67
+ ))
68
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
69
+
70
+ const MenubarSubContent = React.forwardRef<
71
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
72
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
73
+ >(({ className, ...props }, ref) => (
74
+ <MenubarPrimitive.SubContent
75
+ ref={ref}
76
+ className={cn(
77
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border border-stone-200 bg-white p-1 text-stone-950 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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
78
+ className
79
+ )}
80
+ {...props}
81
+ />
82
+ ))
83
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
84
+
85
+ const MenubarContent = React.forwardRef<
86
+ React.ElementRef<typeof MenubarPrimitive.Content>,
87
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
88
+ >(
89
+ (
90
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
91
+ ref
92
+ ) => (
93
+ <MenubarPrimitive.Portal>
94
+ <MenubarPrimitive.Content
95
+ ref={ref}
96
+ align={align}
97
+ alignOffset={alignOffset}
98
+ sideOffset={sideOffset}
99
+ className={cn(
100
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border border-stone-200 bg-white p-1 text-stone-950 shadow-md data-[state=open]:animate-in 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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
101
+ className
102
+ )}
103
+ {...props}
104
+ />
105
+ </MenubarPrimitive.Portal>
106
+ )
107
+ )
108
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName
109
+
110
+ const MenubarItem = React.forwardRef<
111
+ React.ElementRef<typeof MenubarPrimitive.Item>,
112
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
113
+ inset?: boolean
114
+ }
115
+ >(({ className, inset, ...props }, ref) => (
116
+ <MenubarPrimitive.Item
117
+ ref={ref}
118
+ className={cn(
119
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
120
+ inset && "pl-8",
121
+ className
122
+ )}
123
+ {...props}
124
+ />
125
+ ))
126
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName
127
+
128
+ const MenubarCheckboxItem = React.forwardRef<
129
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
130
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
131
+ >(({ className, children, checked, ...props }, ref) => (
132
+ <MenubarPrimitive.CheckboxItem
133
+ ref={ref}
134
+ className={cn(
135
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
136
+ className
137
+ )}
138
+ checked={checked}
139
+ {...props}
140
+ >
141
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
142
+ <MenubarPrimitive.ItemIndicator>
143
+ <Check className="h-4 w-4" />
144
+ </MenubarPrimitive.ItemIndicator>
145
+ </span>
146
+ {children}
147
+ </MenubarPrimitive.CheckboxItem>
148
+ ))
149
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
150
+
151
+ const MenubarRadioItem = React.forwardRef<
152
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
153
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
154
+ >(({ className, children, ...props }, ref) => (
155
+ <MenubarPrimitive.RadioItem
156
+ ref={ref}
157
+ className={cn(
158
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
164
+ <MenubarPrimitive.ItemIndicator>
165
+ <Circle className="h-2 w-2 fill-current" />
166
+ </MenubarPrimitive.ItemIndicator>
167
+ </span>
168
+ {children}
169
+ </MenubarPrimitive.RadioItem>
170
+ ))
171
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
172
+
173
+ const MenubarLabel = React.forwardRef<
174
+ React.ElementRef<typeof MenubarPrimitive.Label>,
175
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
176
+ inset?: boolean
177
+ }
178
+ >(({ className, inset, ...props }, ref) => (
179
+ <MenubarPrimitive.Label
180
+ ref={ref}
181
+ className={cn(
182
+ "px-2 py-1.5 text-sm font-semibold",
183
+ inset && "pl-8",
184
+ className
185
+ )}
186
+ {...props}
187
+ />
188
+ ))
189
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName
190
+
191
+ const MenubarSeparator = React.forwardRef<
192
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
193
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
194
+ >(({ className, ...props }, ref) => (
195
+ <MenubarPrimitive.Separator
196
+ ref={ref}
197
+ className={cn("-mx-1 my-1 h-px bg-stone-100 dark:bg-stone-800", className)}
198
+ {...props}
199
+ />
200
+ ))
201
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
202
+
203
+ const MenubarShortcut = ({
204
+ className,
205
+ ...props
206
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
207
+ return (
208
+ <span
209
+ className={cn(
210
+ "ml-auto text-xs tracking-widest text-stone-500 dark:text-stone-400",
211
+ className
212
+ )}
213
+ {...props}
214
+ />
215
+ )
216
+ }
217
+ MenubarShortcut.displayname = "MenubarShortcut"
218
+
219
+ export {
220
+ Menubar,
221
+ MenubarMenu,
222
+ MenubarTrigger,
223
+ MenubarContent,
224
+ MenubarItem,
225
+ MenubarSeparator,
226
+ MenubarLabel,
227
+ MenubarCheckboxItem,
228
+ MenubarRadioGroup,
229
+ MenubarRadioItem,
230
+ MenubarPortal,
231
+ MenubarSubContent,
232
+ MenubarSubTrigger,
233
+ MenubarGroup,
234
+ MenubarSub,
235
+ MenubarShortcut,
236
+ }
src/components/ui/popover.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Popover = PopoverPrimitive.Root
9
+
10
+ const PopoverTrigger = PopoverPrimitive.Trigger
11
+
12
+ const PopoverContent = React.forwardRef<
13
+ React.ElementRef<typeof PopoverPrimitive.Content>,
14
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
15
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16
+ <PopoverPrimitive.Portal>
17
+ <PopoverPrimitive.Content
18
+ ref={ref}
19
+ align={align}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 w-72 rounded-md border border-stone-200 bg-white p-4 text-stone-950 shadow-md outline-none 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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ </PopoverPrimitive.Portal>
28
+ ))
29
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
30
+
31
+ export { Popover, PopoverTrigger, PopoverContent }
src/components/ui/select.tsx ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+
9
+ const Select = SelectPrimitive.Root
10
+
11
+ const SelectGroup = SelectPrimitive.Group
12
+
13
+ const SelectValue = SelectPrimitive.Value
14
+
15
+ const SelectTrigger = React.forwardRef<
16
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
17
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
18
+ >(({ className, children, ...props }, ref) => (
19
+ <SelectPrimitive.Trigger
20
+ ref={ref}
21
+ className={cn(
22
+ "flex h-10 w-full items-center justify-between rounded-md border border-stone-200 border-stone-200 bg-transparent px-3 py-2 text-sm ring-offset-white placeholder:text-stone-500 focus:outline-none focus:ring-2 focus:ring-stone-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-stone-800 dark:border-stone-800 dark:ring-offset-stone-950 dark:placeholder:text-stone-400 dark:focus:ring-stone-800",
23
+ className
24
+ )}
25
+ {...props}
26
+ >
27
+ {children}
28
+ <SelectPrimitive.Icon asChild>
29
+ <ChevronDown className="h-4 w-4 opacity-50" />
30
+ </SelectPrimitive.Icon>
31
+ </SelectPrimitive.Trigger>
32
+ ))
33
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34
+
35
+ const SelectContent = React.forwardRef<
36
+ React.ElementRef<typeof SelectPrimitive.Content>,
37
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
38
+ >(({ className, children, position = "popper", ...props }, ref) => (
39
+ <SelectPrimitive.Portal>
40
+ <SelectPrimitive.Content
41
+ ref={ref}
42
+ className={cn(
43
+ "relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-stone-200 bg-white text-stone-950 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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
44
+ position === "popper" &&
45
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
46
+ className
47
+ )}
48
+ position={position}
49
+ {...props}
50
+ >
51
+ <SelectPrimitive.Viewport
52
+ className={cn(
53
+ "p-1",
54
+ position === "popper" &&
55
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
56
+ )}
57
+ >
58
+ {children}
59
+ </SelectPrimitive.Viewport>
60
+ </SelectPrimitive.Content>
61
+ </SelectPrimitive.Portal>
62
+ ))
63
+ SelectContent.displayName = SelectPrimitive.Content.displayName
64
+
65
+ const SelectLabel = React.forwardRef<
66
+ React.ElementRef<typeof SelectPrimitive.Label>,
67
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
68
+ >(({ className, ...props }, ref) => (
69
+ <SelectPrimitive.Label
70
+ ref={ref}
71
+ className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
72
+ {...props}
73
+ />
74
+ ))
75
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
76
+
77
+ const SelectItem = React.forwardRef<
78
+ React.ElementRef<typeof SelectPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
80
+ >(({ className, children, ...props }, ref) => (
81
+ <SelectPrimitive.Item
82
+ ref={ref}
83
+ className={cn(
84
+ "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-stone-100 focus:text-stone-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-stone-800 dark:focus:text-stone-50",
85
+ className
86
+ )}
87
+ {...props}
88
+ >
89
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
90
+ <SelectPrimitive.ItemIndicator>
91
+ <Check className="h-4 w-4" />
92
+ </SelectPrimitive.ItemIndicator>
93
+ </span>
94
+
95
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
96
+ </SelectPrimitive.Item>
97
+ ))
98
+ SelectItem.displayName = SelectPrimitive.Item.displayName
99
+
100
+ const SelectSeparator = React.forwardRef<
101
+ React.ElementRef<typeof SelectPrimitive.Separator>,
102
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
103
+ >(({ className, ...props }, ref) => (
104
+ <SelectPrimitive.Separator
105
+ ref={ref}
106
+ className={cn("-mx-1 my-1 h-px bg-stone-100 dark:bg-stone-800", className)}
107
+ {...props}
108
+ />
109
+ ))
110
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
111
+
112
+ export {
113
+ Select,
114
+ SelectGroup,
115
+ SelectValue,
116
+ SelectTrigger,
117
+ SelectContent,
118
+ SelectLabel,
119
+ SelectItem,
120
+ SelectSeparator,
121
+ }
src/components/ui/separator.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SeparatorPrimitive from "@radix-ui/react-separator"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Separator = React.forwardRef<
9
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
11
+ >(
12
+ (
13
+ { className, orientation = "horizontal", decorative = true, ...props },
14
+ ref
15
+ ) => (
16
+ <SeparatorPrimitive.Root
17
+ ref={ref}
18
+ decorative={decorative}
19
+ orientation={orientation}
20
+ className={cn(
21
+ "shrink-0 bg-stone-200 dark:bg-stone-800",
22
+ orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ )
29
+ Separator.displayName = SeparatorPrimitive.Root.displayName
30
+
31
+ export { Separator }
src/components/ui/switch.tsx ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SwitchPrimitives from "@radix-ui/react-switch"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const Switch = React.forwardRef<
9
+ React.ElementRef<typeof SwitchPrimitives.Root>,
10
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
11
+ >(({ className, ...props }, ref) => (
12
+ <SwitchPrimitives.Root
13
+ className={cn(
14
+ "peer inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-400 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-stone-900 data-[state=unchecked]:bg-stone-200 dark:focus-visible:ring-stone-800 dark:focus-visible:ring-offset-stone-950 dark:data-[state=checked]:bg-stone-50 dark:data-[state=unchecked]:bg-stone-800",
15
+ className
16
+ )}
17
+ {...props}
18
+ ref={ref}
19
+ >
20
+ <SwitchPrimitives.Thumb
21
+ className={cn(
22
+ "pointer-events-none block h-5 w-5 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0 dark:bg-stone-950"
23
+ )}
24
+ />
25
+ </SwitchPrimitives.Root>
26
+ ))
27
+ Switch.displayName = SwitchPrimitives.Root.displayName
28
+
29
+ export { Switch }
src/components/ui/table.tsx ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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="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("bg-stone-900 font-medium text-stone-50 dark:bg-stone-50 dark:text-stone-900", className)}
46
+ {...props}
47
+ />
48
+ ))
49
+ TableFooter.displayName = "TableFooter"
50
+
51
+ const TableRow = React.forwardRef<
52
+ HTMLTableRowElement,
53
+ React.HTMLAttributes<HTMLTableRowElement>
54
+ >(({ className, ...props }, ref) => (
55
+ <tr
56
+ ref={ref}
57
+ className={cn(
58
+ "border-b transition-colors hover:bg-stone-100/50 data-[state=selected]:bg-stone-100 dark:hover:bg-stone-800/50 dark:data-[state=selected]:bg-stone-800",
59
+ className
60
+ )}
61
+ {...props}
62
+ />
63
+ ))
64
+ TableRow.displayName = "TableRow"
65
+
66
+ const TableHead = React.forwardRef<
67
+ HTMLTableCellElement,
68
+ React.ThHTMLAttributes<HTMLTableCellElement>
69
+ >(({ className, ...props }, ref) => (
70
+ <th
71
+ ref={ref}
72
+ className={cn(
73
+ "h-12 px-4 text-left align-middle font-medium text-stone-500 [&:has([role=checkbox])]:pr-0 dark:text-stone-400",
74
+ className
75
+ )}
76
+ {...props}
77
+ />
78
+ ))
79
+ TableHead.displayName = "TableHead"
80
+
81
+ const TableCell = React.forwardRef<
82
+ HTMLTableCellElement,
83
+ React.TdHTMLAttributes<HTMLTableCellElement>
84
+ >(({ className, ...props }, ref) => (
85
+ <td
86
+ ref={ref}
87
+ className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
88
+ {...props}
89
+ />
90
+ ))
91
+ TableCell.displayName = "TableCell"
92
+
93
+ const TableCaption = React.forwardRef<
94
+ HTMLTableCaptionElement,
95
+ React.HTMLAttributes<HTMLTableCaptionElement>
96
+ >(({ className, ...props }, ref) => (
97
+ <caption
98
+ ref={ref}
99
+ className={cn("mt-4 text-sm text-stone-500 dark:text-stone-400", className)}
100
+ {...props}
101
+ />
102
+ ))
103
+ TableCaption.displayName = "TableCaption"
104
+
105
+ export {
106
+ Table,
107
+ TableHeader,
108
+ TableBody,
109
+ TableFooter,
110
+ TableHead,
111
+ TableRow,
112
+ TableCell,
113
+ TableCaption,
114
+ }
src/components/ui/textarea.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ export interface TextareaProps
6
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
7
+
8
+ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
9
+ ({ className, ...props }, ref) => {
10
+ return (
11
+ <textarea
12
+ className={cn(
13
+ "flex min-h-[80px] w-full rounded-md border border-stone-200 border-stone-200 bg-transparent px-3 py-2 text-sm ring-offset-white placeholder:text-stone-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-stone-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-stone-800 dark:border-stone-800 dark:ring-offset-stone-950 dark:placeholder:text-stone-400 dark:focus-visible:ring-stone-800",
14
+ className
15
+ )}
16
+ ref={ref}
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+ )
22
+ Textarea.displayName = "Textarea"
23
+
24
+ export { Textarea }
src/components/ui/tooltip.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const TooltipProvider = TooltipPrimitive.Provider
9
+
10
+ const Tooltip = TooltipPrimitive.Root
11
+
12
+ const TooltipTrigger = TooltipPrimitive.Trigger
13
+
14
+ const TooltipContent = React.forwardRef<
15
+ React.ElementRef<typeof TooltipPrimitive.Content>,
16
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
17
+ >(({ className, sideOffset = 4, ...props }, ref) => (
18
+ <TooltipPrimitive.Content
19
+ ref={ref}
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 overflow-hidden rounded-md border border-stone-200 bg-white px-3 py-1.5 text-sm text-stone-950 shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 dark:border-stone-800 dark:bg-stone-950 dark:text-stone-50",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
29
+
30
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
src/lib/createLlamaPrompt.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // adapted from https://huggingface.co/TheBloke/Llama-2-13B-chat-GPTQ/discussions/5
2
+ export function createLlamaPrompt(messages: Array<{ role: string, content: string }>) {
3
+ const B_INST = "[INST]", E_INST = "[/INST]";
4
+ const B_SYS = "<<SYS>>\n", E_SYS = "\n<</SYS>>\n\n";
5
+ const BOS = "<s>", EOS = "</s>";
6
+ const DEFAULT_SYSTEM_PROMPT = "You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Please ensure that your responses are socially unbiased and positive in nature. If a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.";
7
+
8
+ if (messages[0].role != "system"){
9
+ messages = [
10
+ {role: "system", content: DEFAULT_SYSTEM_PROMPT}
11
+ ].concat(messages);
12
+ }
13
+ messages = [{role: messages[1].role, content: B_SYS + messages[0].content + E_SYS + messages[1].content}].concat(messages.slice(2));
14
+
15
+ let messages_list = messages.map((value, index, array) => {
16
+ if (index % 2 == 0 && index + 1 < array.length){
17
+ return `${BOS}${B_INST} ${array[index].content.trim()} ${E_INST} ${array[index+1].content.trim()} ${EOS}`
18
+ }
19
+ return '';
20
+ })
21
+
22
+ messages_list.push(`${BOS}${B_INST} ${messages[messages.length-1].content.trim()} ${E_INST}`)
23
+
24
+ return messages_list.join('');
25
+ }
src/lib/pick.ts ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+
2
+ export const pick = (items: string[]) => items[Math.floor(Math.random()*items.length)]
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
+ }