Spaces:
Paused
Paused
Commit
·
65ee86e
0
Parent(s):
initial commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .env +2 -0
- .eslintrc.json +3 -0
- .gitignore +35 -0
- .nvmrc +1 -0
- Dockerfile +65 -0
- README.md +13 -0
- components.json +16 -0
- next.config.js +10 -0
- package-lock.json +0 -0
- package.json +63 -0
- postcss.config.js +6 -0
- public/next.svg +1 -0
- public/vercel.svg +1 -0
- src/app/engine/predict.ts +56 -0
- src/app/engine/see.ts +54 -0
- src/app/engine/think.ts +60 -0
- src/app/favicon.ico +0 -0
- src/app/globals.css +27 -0
- src/app/interface/progress/index.tsx +56 -0
- src/app/interface/progress/progress-bar.tsx +58 -0
- src/app/interface/top-menu/index.tsx +26 -0
- src/app/layout.tsx +24 -0
- src/app/main.tsx +104 -0
- src/app/observer.tsx +134 -0
- src/app/page.tsx +28 -0
- src/components/icons/full-screen.tsx +16 -0
- src/components/ui/accordion.tsx +60 -0
- src/components/ui/alert.tsx +59 -0
- src/components/ui/avatar.tsx +50 -0
- src/components/ui/badge.tsx +36 -0
- src/components/ui/button.tsx +56 -0
- src/components/ui/card.tsx +79 -0
- src/components/ui/checkbox.tsx +30 -0
- src/components/ui/collapsible.tsx +11 -0
- src/components/ui/command.tsx +155 -0
- src/components/ui/dialog.tsx +123 -0
- src/components/ui/dropdown-menu.tsx +200 -0
- src/components/ui/input.tsx +25 -0
- src/components/ui/label.tsx +26 -0
- src/components/ui/menubar.tsx +236 -0
- src/components/ui/popover.tsx +31 -0
- src/components/ui/select.tsx +121 -0
- src/components/ui/separator.tsx +31 -0
- src/components/ui/switch.tsx +29 -0
- src/components/ui/table.tsx +114 -0
- src/components/ui/textarea.tsx +24 -0
- src/components/ui/tooltip.tsx +30 -0
- src/lib/createLlamaPrompt.ts +25 -0
- src/lib/pick.ts +2 -0
- 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 |
+
}
|