jbilcke-hf HF Staff commited on
Commit
7948ff4
Β·
0 Parent(s):

initial commit 🎬

Browse files
.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,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18
2
+
3
+
4
+ ARG DEBIAN_FRONTEND=noninteractive
5
+
6
+ RUN apt update
7
+
8
+ RUN apt --yes install ffmpeg
9
+
10
+ # Set up a new user named "user" with user ID 1000
11
+ RUN useradd -o -u 1000 user
12
+
13
+ # Switch to the "user" user
14
+ USER user
15
+
16
+ # Set home to the user's home directory
17
+ ENV HOME=/home/user \
18
+ PATH=/home/user/.local/bin:$PATH
19
+
20
+ # Set the working directory to the user's home directory
21
+ WORKDIR $HOME/app
22
+
23
+ # Install app dependencies
24
+ # A wildcard is used to ensure both package.json AND package-lock.json are copied
25
+ # where available (npm@5+)
26
+ COPY --chown=user package*.json $HOME/app
27
+
28
+ RUN npm install
29
+
30
+ RUN npm build
31
+
32
+ # Copy the current directory contents into the container at $HOME/app setting the owner to the user
33
+ COPY --chown=user . $HOME/app
34
+
35
+ EXPOSE 3000
36
+
37
+ CMD [ "npm", "run", "start" ]
README.md ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: VideoChain-UI
3
+ emoji: 🎬
4
+ colorFrom: "#1a1b1c"
5
+ colorTo: "#acb1b5"
6
+ sdk: docker
7
+ pinned: false
8
+ app_port: 3000
9
+ ---
10
+
11
+ This is the frontend interface to VideoChain-API, a server to generate videos using AI.
12
+
13
+ ## Getting Started
14
+
15
+ First, run the development server:
16
+
17
+ ```bash
18
+ npm run dev
19
+ ```
20
+
21
+ Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
22
+
23
+ You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
24
+
25
+ This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
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": "zinc",
10
+ "cssVariables": false
11
+ },
12
+ "aliases": {
13
+ "components": "@/components",
14
+ "utils": "@/lib/utils"
15
+ }
16
+ }
next.config.js ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {}
3
+
4
+ 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,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "videochain-ui",
3
+ "version": "0.1.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
+ "@gradio/client": "^0.1.4",
13
+ "@huggingface/inference": "^2.6.1",
14
+ "@radix-ui/react-accordion": "^1.1.2",
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-icons": "^1.3.0",
19
+ "@radix-ui/react-menubar": "^1.0.3",
20
+ "@radix-ui/react-slot": "^1.0.2",
21
+ "@silevis/reactgrid": "^4.0.5",
22
+ "@types/fluent-ffmpeg": "^2.1.21",
23
+ "@types/node": "20.4.2",
24
+ "@types/react": "18.2.15",
25
+ "@types/react-dom": "18.2.7",
26
+ "@types/uuid": "^9.0.2",
27
+ "autoprefixer": "10.4.14",
28
+ "class-variance-authority": "^0.6.1",
29
+ "clsx": "^2.0.0",
30
+ "cmdk": "^0.2.0",
31
+ "date-fns": "^2.30.0",
32
+ "eslint": "8.45.0",
33
+ "eslint-config-next": "13.4.10",
34
+ "fluent-ffmpeg": "^2.1.2",
35
+ "fs-extra": "^11.1.1",
36
+ "lucide-react": "^0.260.0",
37
+ "next": "13.4.10",
38
+ "postcss": "8.4.26",
39
+ "puppeteer": "^20.8.2",
40
+ "react": "18.2.0",
41
+ "react-day-picker": "^8.8.0",
42
+ "react-dom": "18.2.0",
43
+ "tailwind-merge": "^1.13.2",
44
+ "tailwindcss": "3.3.3",
45
+ "tailwindcss-animate": "^1.0.6",
46
+ "temp-dir": "^3.0.0",
47
+ "ts-node": "^10.9.1",
48
+ "typescript": "5.1.6",
49
+ "uuid": "^9.0.0",
50
+ "zustand": "^4.3.9"
51
+ }
52
+ }
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
scripts/test.js ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const { promises: fs } = require("node:fs")
2
+
3
+ const main = async () => {
4
+ console.log('generating shot..')
5
+ const response = await fetch("http://localhost:3000/api/shot", {
6
+ method: "POST",
7
+ headers: {
8
+ "Accept": "application/json",
9
+ "Content-Type": "application/json"
10
+ },
11
+ body: JSON.stringify({
12
+ token: process.env.VS_SECRET_ACCESS_TOKEN,
13
+ shotPrompt: "video of a dancing cat"
14
+ })
15
+ });
16
+
17
+ console.log('response:', response)
18
+ const buffer = await response.buffer()
19
+
20
+ fs.writeFile(`./test-juju.mp4`, buffer)
21
+ }
22
+
23
+ main()
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/layout.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import './globals.css'
2
+ import type { Metadata } from 'next'
3
+ import { Inter } from 'next/font/google'
4
+ import Head from 'next/head'
5
+
6
+ const inter = Inter({ subsets: ['latin'] })
7
+
8
+ export const metadata: Metadata = {
9
+ title: 'Create Next App',
10
+ description: 'Generated by create next app',
11
+ }
12
+
13
+ export default function RootLayout({
14
+ children,
15
+ }: {
16
+ children: React.ReactNode
17
+ }) {
18
+ return (
19
+ <html lang="en">
20
+ <body className={inter.className}>{children}</body>
21
+ </html>
22
+ )
23
+ }
src/app/page.tsx ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Head from "next/head"
2
+
3
+ import { Timeline } from "@/components/business/timeline"
4
+
5
+ export default function Index() {
6
+ return (
7
+ <div>
8
+ <Head>
9
+ <meta name="viewport" content="width=device-width, initial-scale=0.86, maximum-scale=5.0, minimum-scale=0.86" />
10
+ </Head>
11
+ <main className="h-screen w-full flex bg-gray-700 text-gray-200">
12
+ <div className="flex flex-col">
13
+ <Timeline />
14
+ </div>
15
+ </main>
16
+ </div>
17
+ )
18
+ }
src/app/types.ts ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface Shot {
2
+ shotId: string
3
+ index: number
4
+ lastGenerationAt: string
5
+ videoPrompt: string
6
+ audioPrompt: string
7
+ duration: number // no more than 3 (we don't have the ressources for it)
8
+ fps: number // typically 8, 12, 24
9
+ }
10
+
11
+ export interface Sequence {
12
+ sequenceId: string
13
+ skip: boolean
14
+ lastGenerationAt: string
15
+ videoPrompt: string
16
+ audioPrompt: string
17
+ channel: string
18
+ tags: string[]
19
+ shots: Shot[]
20
+ }
21
+
22
+ export interface Database {
23
+ version: number
24
+ startAtShotId: string
25
+ sequences: Sequence[]
26
+ }
27
+
28
+
29
+ export interface ShotQuery {
30
+ token: string
31
+ shotPrompt: string
32
+ // inputVideo?: string
33
+
34
+ // describe the background audio (crowd, birds, wind, sea etc..)
35
+ backgroundAudioPrompt?: string
36
+
37
+ // describe the foreground audio (cars revving, footsteps, objects breaking, explosion etc)
38
+ foregroundAudioPrompt?: string
39
+
40
+ // describe the main actor visible in the shot (optional)
41
+ actorPrompt?: string
42
+
43
+ // describe the main actor voice (man, woman, old, young, amused, annoyed.. etc)
44
+ actorVoicePrompt?: string
45
+
46
+ // describe the main actor dialogue line
47
+ actorDialoguePrompt?: string
48
+
49
+ seed?: number
50
+ upscale?: boolean
51
+
52
+ duration?: number
53
+ steps?: number
54
+
55
+ fps?: number // 8, 12, 24, 30, 60
56
+
57
+ resolution?: number // 256, 512, 576, 720, 1080
58
+ }
59
+
60
+ export interface Job {
61
+ startedAt: string
62
+ query: ShotQuery
63
+ }
src/components/business/menu.tsx ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Menubar,
3
+ MenubarCheckboxItem,
4
+ MenubarContent,
5
+ MenubarItem,
6
+ MenubarLabel,
7
+ MenubarMenu,
8
+ MenubarRadioGroup,
9
+ MenubarRadioItem,
10
+ MenubarSeparator,
11
+ MenubarShortcut,
12
+ MenubarSub,
13
+ MenubarSubContent,
14
+ MenubarSubTrigger,
15
+ MenubarTrigger,
16
+ } from "@/components/ui/menubar"
17
+
18
+ export function Menu() {
19
+ return (
20
+ <Menubar className="rounded-none border-b border-none px-2 lg:px-4">
21
+ <MenubarMenu>
22
+ <MenubarTrigger className="font-bold">Music</MenubarTrigger>
23
+ <MenubarContent>
24
+ <MenubarItem>About Music</MenubarItem>
25
+ <MenubarSeparator />
26
+ <MenubarItem>
27
+ Preferences... <MenubarShortcut>⌘,</MenubarShortcut>
28
+ </MenubarItem>
29
+ <MenubarSeparator />
30
+ <MenubarItem>
31
+ Hide Music... <MenubarShortcut>⌘H</MenubarShortcut>
32
+ </MenubarItem>
33
+ <MenubarItem>
34
+ Hide Others... <MenubarShortcut>β‡§βŒ˜H</MenubarShortcut>
35
+ </MenubarItem>
36
+ <MenubarShortcut />
37
+ <MenubarItem>
38
+ Quit Music <MenubarShortcut>⌘Q</MenubarShortcut>
39
+ </MenubarItem>
40
+ </MenubarContent>
41
+ </MenubarMenu>
42
+ <MenubarMenu>
43
+ <MenubarTrigger className="relative">File</MenubarTrigger>
44
+ <MenubarContent>
45
+ <MenubarSub>
46
+ <MenubarSubTrigger>New</MenubarSubTrigger>
47
+ <MenubarSubContent className="w-[230px]">
48
+ <MenubarItem>
49
+ Playlist <MenubarShortcut>⌘N</MenubarShortcut>
50
+ </MenubarItem>
51
+ <MenubarItem disabled>
52
+ Playlist from Selection <MenubarShortcut>β‡§βŒ˜N</MenubarShortcut>
53
+ </MenubarItem>
54
+ <MenubarItem>
55
+ Smart Playlist... <MenubarShortcut>βŒ₯⌘N</MenubarShortcut>
56
+ </MenubarItem>
57
+ <MenubarItem>Playlist Folder</MenubarItem>
58
+ <MenubarItem disabled>Genius Playlist</MenubarItem>
59
+ </MenubarSubContent>
60
+ </MenubarSub>
61
+ <MenubarItem>
62
+ Open Stream URL... <MenubarShortcut>⌘U</MenubarShortcut>
63
+ </MenubarItem>
64
+ <MenubarItem>
65
+ Close Window <MenubarShortcut>⌘W</MenubarShortcut>
66
+ </MenubarItem>
67
+ <MenubarSeparator />
68
+ <MenubarSub>
69
+ <MenubarSubTrigger>Library</MenubarSubTrigger>
70
+ <MenubarSubContent>
71
+ <MenubarItem>Update Cloud Library</MenubarItem>
72
+ <MenubarItem>Update Genius</MenubarItem>
73
+ <MenubarSeparator />
74
+ <MenubarItem>Organize Library...</MenubarItem>
75
+ <MenubarItem>Export Library...</MenubarItem>
76
+ <MenubarSeparator />
77
+ <MenubarItem>Import Playlist...</MenubarItem>
78
+ <MenubarItem disabled>Export Playlist...</MenubarItem>
79
+ <MenubarItem>Show Duplicate Items</MenubarItem>
80
+ <MenubarSeparator />
81
+ <MenubarItem>Get Album Artwork</MenubarItem>
82
+ <MenubarItem disabled>Get Track Names</MenubarItem>
83
+ </MenubarSubContent>
84
+ </MenubarSub>
85
+ <MenubarItem>
86
+ Import... <MenubarShortcut>⌘O</MenubarShortcut>
87
+ </MenubarItem>
88
+ <MenubarItem disabled>Burn Playlist to Disc...</MenubarItem>
89
+ <MenubarSeparator />
90
+ <MenubarItem>
91
+ Show in Finder <MenubarShortcut>β‡§βŒ˜R</MenubarShortcut>{" "}
92
+ </MenubarItem>
93
+ <MenubarItem>Convert</MenubarItem>
94
+ <MenubarSeparator />
95
+ <MenubarItem>Page Setup...</MenubarItem>
96
+ <MenubarItem disabled>
97
+ Print... <MenubarShortcut>⌘P</MenubarShortcut>
98
+ </MenubarItem>
99
+ </MenubarContent>
100
+ </MenubarMenu>
101
+ <MenubarMenu>
102
+ <MenubarTrigger>Edit</MenubarTrigger>
103
+ <MenubarContent>
104
+ <MenubarItem disabled>
105
+ Undo <MenubarShortcut>⌘Z</MenubarShortcut>
106
+ </MenubarItem>
107
+ <MenubarItem disabled>
108
+ Redo <MenubarShortcut>β‡§βŒ˜Z</MenubarShortcut>
109
+ </MenubarItem>
110
+ <MenubarSeparator />
111
+ <MenubarItem disabled>
112
+ Cut <MenubarShortcut>⌘X</MenubarShortcut>
113
+ </MenubarItem>
114
+ <MenubarItem disabled>
115
+ Copy <MenubarShortcut>⌘C</MenubarShortcut>
116
+ </MenubarItem>
117
+ <MenubarItem disabled>
118
+ Paste <MenubarShortcut>⌘V</MenubarShortcut>
119
+ </MenubarItem>
120
+ <MenubarSeparator />
121
+ <MenubarItem>
122
+ Select All <MenubarShortcut>⌘A</MenubarShortcut>
123
+ </MenubarItem>
124
+ <MenubarItem disabled>
125
+ Deselect All <MenubarShortcut>β‡§βŒ˜A</MenubarShortcut>
126
+ </MenubarItem>
127
+ <MenubarSeparator />
128
+ <MenubarItem>
129
+ Smart Dictation...{" "}
130
+ <MenubarShortcut>
131
+ <svg
132
+ xmlns="http://www.w3.org/2000/svg"
133
+ fill="none"
134
+ stroke="currentColor"
135
+ strokeLinecap="round"
136
+ strokeLinejoin="round"
137
+ strokeWidth="2"
138
+ className="h-4 w-4"
139
+ viewBox="0 0 24 24"
140
+ >
141
+ <path d="m12 8-9.04 9.06a2.82 2.82 0 1 0 3.98 3.98L16 12" />
142
+ <circle cx="17" cy="7" r="5" />
143
+ </svg>
144
+ </MenubarShortcut>
145
+ </MenubarItem>
146
+ <MenubarItem>
147
+ Emoji & Symbols{" "}
148
+ <MenubarShortcut>
149
+ <svg
150
+ xmlns="http://www.w3.org/2000/svg"
151
+ fill="none"
152
+ stroke="currentColor"
153
+ strokeLinecap="round"
154
+ strokeLinejoin="round"
155
+ strokeWidth="2"
156
+ className="h-4 w-4"
157
+ viewBox="0 0 24 24"
158
+ >
159
+ <circle cx="12" cy="12" r="10" />
160
+ <path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
161
+ </svg>
162
+ </MenubarShortcut>
163
+ </MenubarItem>
164
+ </MenubarContent>
165
+ </MenubarMenu>
166
+ <MenubarMenu>
167
+ <MenubarTrigger>View</MenubarTrigger>
168
+ <MenubarContent>
169
+ <MenubarCheckboxItem>Show Playing Next</MenubarCheckboxItem>
170
+ <MenubarCheckboxItem checked>Show Lyrics</MenubarCheckboxItem>
171
+ <MenubarSeparator />
172
+ <MenubarItem inset disabled>
173
+ Show Status Bar
174
+ </MenubarItem>
175
+ <MenubarSeparator />
176
+ <MenubarItem inset>Hide Sidebar</MenubarItem>
177
+ <MenubarItem disabled inset>
178
+ Enter Full Screen
179
+ </MenubarItem>
180
+ </MenubarContent>
181
+ </MenubarMenu>
182
+ <MenubarMenu>
183
+ <MenubarTrigger className="hidden md:block">Account</MenubarTrigger>
184
+ <MenubarContent forceMount>
185
+ <MenubarLabel inset>Switch Account</MenubarLabel>
186
+ <MenubarSeparator />
187
+ <MenubarRadioGroup value="benoit">
188
+ <MenubarRadioItem value="andy">Andy</MenubarRadioItem>
189
+ <MenubarRadioItem value="benoit">Benoit</MenubarRadioItem>
190
+ <MenubarRadioItem value="Luis">Luis</MenubarRadioItem>
191
+ </MenubarRadioGroup>
192
+ <MenubarSeparator />
193
+ <MenubarItem inset>Manage Famliy...</MenubarItem>
194
+ <MenubarSeparator />
195
+ <MenubarItem inset>Add Account...</MenubarItem>
196
+ </MenubarContent>
197
+ </MenubarMenu>
198
+ </Menubar>
199
+ )
200
+ }
src/components/business/timeline/index.tsx ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import { ReactGrid, Column, Row } from "@silevis/reactgrid"
4
+ import "@silevis/reactgrid/styles.css"
5
+ import { useState } from "react"
6
+
7
+ type RowData = Record<string, string>
8
+
9
+ const nbColumns = 20
10
+
11
+ const getRowsData = (nbLayers: number, nbShots: number): RowData[] => [
12
+ { name: "Thomas", surname: "Goldman" },
13
+ { name: "Susie", surname: "Quattro" },
14
+ { name: "", surname: "" }
15
+ ];
16
+
17
+ const getColumns = (nbColumns: number): Column[] => {
18
+
19
+ const columns: Column[] = []
20
+ for (let i = 0; i < nbColumns; i++) {
21
+ columns.push({
22
+ columnId: `Shot ${i}`,
23
+ width: 150,
24
+ })
25
+ }
26
+
27
+ return columns
28
+ }
29
+
30
+
31
+
32
+ const getRows = (nbShots: number, rows: RowData[]): Row[] => [
33
+ {
34
+ rowId: 'header',
35
+ cells: [...Array(nbShots)].map((_, i) => ({
36
+ type: "text",
37
+ text: `Shot ${i}`,
38
+ })),
39
+ },
40
+ ...rows.map<Row>((row, idx) => ({
41
+ rowId: idx,
42
+ cells: Object.entries(row).map(([_, value]) => ({
43
+ type: "text",
44
+ text: value
45
+ }))
46
+ }))
47
+ ]
48
+
49
+ export function Timeline() {
50
+
51
+ const nbLayers = 8
52
+ const nbShots = 30
53
+
54
+ const [rowsData] = useState<RowData[]>(getRowsData(nbLayers, nbShots))
55
+
56
+ const rows = getRows(nbShots, rowsData)
57
+ const columns = getColumns(nbShots)
58
+
59
+ return (
60
+ <ReactGrid
61
+ rows={rows}
62
+ columns={columns}
63
+ onCellsChanged={(changes) => {
64
+ const change = changes[0]
65
+ const { columnId, newCell, previousCell, rowId, type } = change
66
+
67
+ console.log('change:', { columnId, newCell, previousCell, rowId, type })
68
+ }}
69
+ />
70
+ )
71
+ }
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-zinc-200 p-4 [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-zinc-950 dark:border-zinc-800 dark:[&>svg]:text-zinc-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-white text-zinc-950 dark:bg-zinc-950 dark:text-zinc-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/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-zinc-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-zinc-400 focus:ring-offset-2 dark:border-zinc-800 dark:focus:ring-zinc-800",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-zinc-900 text-zinc-50 hover:bg-zinc-900/80 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/80",
13
+ secondary:
14
+ "border-transparent bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
15
+ destructive:
16
+ "border-transparent bg-red-500 text-zinc-50 hover:bg-red-500/80 dark:bg-red-900 dark:text-red-50 dark:hover:bg-red-900/80",
17
+ outline: "text-zinc-950 dark:text-zinc-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-zinc-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-800",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-zinc-900 text-zinc-50 hover:bg-zinc-900/90 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50/90",
13
+ destructive:
14
+ "bg-red-500 text-zinc-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-zinc-200 bg-white hover:bg-zinc-100 hover:text-zinc-900 dark:border-zinc-800 dark:bg-zinc-950 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
17
+ secondary:
18
+ "bg-zinc-100 text-zinc-900 hover:bg-zinc-100/80 dark:bg-zinc-800 dark:text-zinc-50 dark:hover:bg-zinc-800/80",
19
+ ghost: "hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50",
20
+ link: "text-zinc-900 underline-offset-4 hover:underline dark:text-zinc-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/calendar.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ChevronLeft, ChevronRight } from "lucide-react"
5
+ import { DayPicker } from "react-day-picker"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { buttonVariants } from "@/components/ui/button"
9
+
10
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>
11
+
12
+ function Calendar({
13
+ className,
14
+ classNames,
15
+ showOutsideDays = true,
16
+ ...props
17
+ }: CalendarProps) {
18
+ return (
19
+ <DayPicker
20
+ showOutsideDays={showOutsideDays}
21
+ className={cn("p-3", className)}
22
+ classNames={{
23
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
24
+ month: "space-y-4",
25
+ caption: "flex justify-center pt-1 relative items-center",
26
+ caption_label: "text-sm font-medium",
27
+ nav: "space-x-1 flex items-center",
28
+ nav_button: cn(
29
+ buttonVariants({ variant: "outline" }),
30
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
31
+ ),
32
+ nav_button_previous: "absolute left-1",
33
+ nav_button_next: "absolute right-1",
34
+ table: "w-full border-collapse space-y-1",
35
+ head_row: "flex",
36
+ head_cell:
37
+ "text-zinc-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-zinc-400",
38
+ row: "flex w-full mt-2",
39
+ cell: "text-center text-sm p-0 relative [&:has([aria-selected])]:bg-zinc-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected])]:bg-zinc-800",
40
+ day: cn(
41
+ buttonVariants({ variant: "ghost" }),
42
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
43
+ ),
44
+ day_selected:
45
+ "bg-zinc-900 text-zinc-50 hover:bg-zinc-900 hover:text-zinc-50 focus:bg-zinc-900 focus:text-zinc-50 dark:bg-zinc-50 dark:text-zinc-900 dark:hover:bg-zinc-50 dark:hover:text-zinc-900 dark:focus:bg-zinc-50 dark:focus:text-zinc-900",
46
+ day_today: "bg-zinc-100 text-zinc-900 dark:bg-zinc-800 dark:text-zinc-50",
47
+ day_outside: "text-zinc-500 opacity-50 dark:text-zinc-400",
48
+ day_disabled: "text-zinc-500 opacity-50 dark:text-zinc-400",
49
+ day_range_middle:
50
+ "aria-selected:bg-zinc-100 aria-selected:text-zinc-900 dark:aria-selected:bg-zinc-800 dark:aria-selected:text-zinc-50",
51
+ day_hidden: "invisible",
52
+ ...classNames,
53
+ }}
54
+ components={{
55
+ IconLeft: ({ ...props }) => <ChevronLeft className="h-4 w-4" />,
56
+ IconRight: ({ ...props }) => <ChevronRight className="h-4 w-4" />,
57
+ }}
58
+ {...props}
59
+ />
60
+ )
61
+ }
62
+ Calendar.displayName = "Calendar"
63
+
64
+ export { Calendar }
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-zinc-200 bg-white text-zinc-950 shadow-sm dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-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-zinc-500 dark:text-zinc-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-zinc-200 border-zinc-900 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-zinc-900 data-[state=checked]:text-zinc-50 dark:border-zinc-800 dark:border-zinc-50 dark:ring-offset-zinc-950 dark:focus-visible:ring-zinc-800 dark:data-[state=checked]:bg-zinc-50 dark:data-[state=checked]:text-zinc-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-zinc-950 dark:bg-zinc-950 dark:text-zinc-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-zinc-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-zinc-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-zinc-500 disabled:cursor-not-allowed disabled:opacity-50 dark:placeholder:text-zinc-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-zinc-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-zinc-500 dark:text-zinc-50 dark:[&_[cmdk-group-heading]]:text-zinc-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-zinc-200 dark:bg-zinc-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-zinc-100 aria-selected:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:aria-selected:bg-zinc-800 dark:aria-selected:text-zinc-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-zinc-500 dark:text-zinc-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-zinc-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-zinc-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-zinc-800 dark:bg-zinc-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-zinc-400 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-zinc-100 data-[state=open]:text-zinc-500 dark:ring-offset-zinc-950 dark:focus:ring-zinc-800 dark:data-[state=open]:bg-zinc-800 dark:data-[state=open]:text-zinc-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-zinc-500 dark:text-zinc-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/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-zinc-200 bg-white p-1 dark:border-zinc-800 dark:bg-zinc-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-zinc-100 focus:text-zinc-900 data-[state=open]:bg-zinc-100 data-[state=open]:text-zinc-900 dark:focus:bg-zinc-800 dark:focus:text-zinc-50 dark:data-[state=open]:bg-zinc-800 dark:data-[state=open]:text-zinc-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-zinc-100 focus:text-zinc-900 data-[state=open]:bg-zinc-100 data-[state=open]:text-zinc-900 dark:focus:bg-zinc-800 dark:focus:text-zinc-50 dark:data-[state=open]:bg-zinc-800 dark:data-[state=open]:text-zinc-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-zinc-200 bg-white p-1 text-zinc-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-zinc-800 dark:bg-zinc-950 dark:text-zinc-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-zinc-200 bg-white p-1 text-zinc-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-zinc-800 dark:bg-zinc-950 dark:text-zinc-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-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-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-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-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-zinc-100 focus:text-zinc-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-zinc-800 dark:focus:text-zinc-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-zinc-100 dark:bg-zinc-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-zinc-500 dark:text-zinc-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/core/goodWords.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { promises as fs } from "node:fs"
2
+
3
+ type GoodWordsDB = Record<string, Record<string, string[]>>
4
+
5
+ // import ngrams from "talisman/tokenizers/ngrams"
6
+
7
+ let db: GoodWordsDB = {}
8
+
9
+ export let goodWords: Set<string> = new Set()
10
+ export let goodWordsList: string[] = []
11
+
12
+ export const getGoodWords = async () =>Β {
13
+
14
+ if (!Object.entries(db)) {
15
+ const dbFileContent = await fs.readFile("./data/good_words.json", "utf8")
16
+
17
+ db = JSON.parse(dbFileContent) as GoodWordsDB
18
+
19
+
20
+ // we don't want those categories to be part of the acceptable, fair use words
21
+ const unwantedCategories = {
22
+ celeb: true
23
+ } as Record<string, boolean>
24
+
25
+ for (const [category, words] of Object.entries(db)) {
26
+ if (unwantedCategories[category]) {
27
+ continue
28
+ }
29
+
30
+ for (const word in words) {
31
+ const normalizedWord = word.trim().toLowerCase()
32
+ goodWords.add(normalizedWord)
33
+ goodWordsList.push(normalizedWord)
34
+ }
35
+ }
36
+ }
37
+
38
+ return { db, goodWords, goodWordsList }
39
+ }
src/core/hasBadWords.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // as the tool will be public, we need some level of moderation to bred unfair use
3
+ // note: another possibility could be to use the moderation api from openai,
4
+ // but it is paying
5
+ const config = JSON.parse(`${process.env.VS_MODERATION_FILTER_PATTERN}`)
6
+
7
+ export const hasBadWords = (userPrompt: string) => {
8
+ const normalized = userPrompt.trim().toLowerCase()
9
+ return !!config[normalized]
10
+ }
src/core/interpolateVideo.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { promises as fs } from "node:fs"
2
+ import path from "node:path"
3
+ import { Blob } from "buffer"
4
+ // import { blobFrom } from "fetch-blob"
5
+
6
+ import { client } from "@gradio/client"
7
+ import tmpDir from "temp-dir"
8
+
9
+ import { downloadVideo } from "./downloadVideo"
10
+
11
+ const instances: string[] = [
12
+ `${process.env.VS_VIDEO_INTERPOLATION_SPACE_API_URL}`
13
+ ]
14
+
15
+ export const interpolateVideo = async (fileName: string) => {
16
+
17
+ const inputFilePath = path.join(tmpDir, fileName)
18
+
19
+ const instance = instances.shift()!
20
+ instances.push(instance)
21
+
22
+ const api = await client(instance)
23
+
24
+ const video = await fs.readFile(inputFilePath)
25
+
26
+ const blob = new Blob([video], { type: 'video/mp4' })
27
+ // const blob = blobFrom(filePath)
28
+ const result = await api.predict(1, [
29
+ blob, // blob in 'parameter_5' Video component
30
+ 1, // number (numeric value between 1 and 4) in 'Interpolation Steps' Slider component
31
+ 24, // string in 'FPS output' Radio component
32
+ ])
33
+
34
+ const data = (result as any).data[0]
35
+ console.log('raw data:', data)
36
+ const { orig_name, data: remoteFilePath } = data
37
+ const remoteUrl = `${instance}/file=${remoteFilePath}`
38
+ console.log("remoteUrl:", remoteUrl)
39
+ await downloadVideo(remoteUrl, fileName)
40
+ }
src/core/isGoodWord.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { getGoodWords } from "./goodWords"
2
+
3
+ //
4
+ export const isGoodWord = async (word: string) => {
5
+ const { goodWords } = await getGoodWords()
6
+ return goodWords.has(word.trim().toLowerCase())
7
+ }
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
+ }
tailwind.config.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ darkMode: ["class"],
4
+ content: [
5
+ './pages/**/*.{ts,tsx}',
6
+ './components/**/*.{ts,tsx}',
7
+ './app/**/*.{ts,tsx}',
8
+ './src/**/*.{ts,tsx}',
9
+ ],
10
+ theme: {
11
+ container: {
12
+ center: true,
13
+ padding: "2rem",
14
+ screens: {
15
+ "2xl": "1400px",
16
+ },
17
+ },
18
+ extend: {
19
+ keyframes: {
20
+ "accordion-down": {
21
+ from: { height: 0 },
22
+ to: { height: "var(--radix-accordion-content-height)" },
23
+ },
24
+ "accordion-up": {
25
+ from: { height: "var(--radix-accordion-content-height)" },
26
+ to: { height: 0 },
27
+ },
28
+ },
29
+ animation: {
30
+ "accordion-down": "accordion-down 0.2s ease-out",
31
+ "accordion-up": "accordion-up 0.2s ease-out",
32
+ },
33
+ },
34
+ },
35
+ plugins: [require("tailwindcss-animate")],
36
+ }
tsconfig.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noEmit": true,
10
+ "esModuleInterop": true,
11
+ "module": "esnext",
12
+ "moduleResolution": "node",
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "jsx": "preserve",
16
+ "incremental": true,
17
+ "plugins": [
18
+ {
19
+ "name": "next"
20
+ }
21
+ ],
22
+ "paths": {
23
+ "@/*": ["./src/*"]
24
+ }
25
+ },
26
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27
+ "exclude": ["node_modules"]
28
+ }