Spaces:
Paused
Paused
Julian Bilcke
commited on
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 |
+
}
|