├── src ├── vite-env.d.ts ├── components │ ├── ui │ │ ├── input │ │ │ ├── index.ts │ │ │ └── Input.vue │ │ ├── label │ │ │ ├── index.ts │ │ │ └── Label.vue │ │ ├── slider │ │ │ ├── index.ts │ │ │ └── Slider.vue │ │ ├── sonner │ │ │ ├── index.ts │ │ │ └── Sonner.vue │ │ ├── switch │ │ │ ├── index.ts │ │ │ └── Switch.vue │ │ ├── skeleton │ │ │ ├── index.ts │ │ │ └── Skeleton.vue │ │ ├── textarea │ │ │ ├── index.ts │ │ │ └── Textarea.vue │ │ ├── separator │ │ │ ├── index.ts │ │ │ └── Separator.vue │ │ ├── scroll-area │ │ │ ├── index.ts │ │ │ ├── ScrollArea.vue │ │ │ └── ScrollBar.vue │ │ ├── radio-group │ │ │ ├── index.ts │ │ │ ├── RadioGroup.vue │ │ │ └── RadioGroupItem.vue │ │ ├── form │ │ │ ├── injectionKeys.ts │ │ │ ├── index.ts │ │ │ ├── FormControl.vue │ │ │ ├── FormDescription.vue │ │ │ ├── FormItem.vue │ │ │ ├── FormMessage.vue │ │ │ ├── FormLabel.vue │ │ │ └── useFormField.ts │ │ ├── avatar │ │ │ ├── index.ts │ │ │ ├── AvatarImage.vue │ │ │ ├── Avatar.vue │ │ │ └── AvatarFallback.vue │ │ ├── tabs │ │ │ ├── index.ts │ │ │ ├── TabsContent.vue │ │ │ ├── TabsList.vue │ │ │ ├── Tabs.vue │ │ │ └── TabsTrigger.vue │ │ ├── tooltip │ │ │ ├── index.ts │ │ │ ├── TooltipProvider.vue │ │ │ ├── TooltipTrigger.vue │ │ │ ├── Tooltip.vue │ │ │ └── TooltipContent.vue │ │ ├── sheet │ │ │ ├── SheetDescription.vue │ │ │ ├── SheetTitle.vue │ │ │ ├── SheetHeader.vue │ │ │ ├── SheetFooter.vue │ │ │ ├── SheetClose.vue │ │ │ ├── SheetTrigger.vue │ │ │ ├── index.ts │ │ │ ├── Sheet.vue │ │ │ └── SheetContent.vue │ │ ├── table │ │ │ ├── utils.ts │ │ │ ├── TableHeader.vue │ │ │ ├── TableBody.vue │ │ │ ├── TableCaption.vue │ │ │ ├── TableFooter.vue │ │ │ ├── TableRow.vue │ │ │ ├── Table.vue │ │ │ ├── TableHead.vue │ │ │ ├── TableCell.vue │ │ │ ├── index.ts │ │ │ └── TableEmpty.vue │ │ ├── dialog │ │ │ ├── DialogClose.vue │ │ │ ├── DialogTrigger.vue │ │ │ ├── DialogFooter.vue │ │ │ ├── DialogHeader.vue │ │ │ ├── Dialog.vue │ │ │ ├── index.ts │ │ │ ├── DialogTitle.vue │ │ │ ├── DialogDescription.vue │ │ │ ├── DialogOverlay.vue │ │ │ ├── DialogContent.vue │ │ │ └── DialogScrollContent.vue │ │ ├── select │ │ │ ├── SelectGroup.vue │ │ │ ├── SelectValue.vue │ │ │ ├── SelectItemText.vue │ │ │ ├── SelectLabel.vue │ │ │ ├── Select.vue │ │ │ ├── SelectSeparator.vue │ │ │ ├── index.ts │ │ │ ├── SelectScrollUpButton.vue │ │ │ ├── SelectScrollDownButton.vue │ │ │ ├── SelectItem.vue │ │ │ ├── SelectTrigger.vue │ │ │ └── SelectContent.vue │ │ ├── alert-dialog │ │ │ ├── AlertDialogTrigger.vue │ │ │ ├── AlertDialogHeader.vue │ │ │ ├── AlertDialog.vue │ │ │ ├── AlertDialogFooter.vue │ │ │ ├── index.ts │ │ │ ├── AlertDialogAction.vue │ │ │ ├── AlertDialogTitle.vue │ │ │ ├── AlertDialogDescription.vue │ │ │ ├── AlertDialogCancel.vue │ │ │ └── AlertDialogContent.vue │ │ ├── number-field │ │ │ ├── index.ts │ │ │ ├── NumberFieldContent.vue │ │ │ ├── NumberFieldInput.vue │ │ │ ├── NumberField.vue │ │ │ ├── NumberFieldDecrement.vue │ │ │ └── NumberFieldIncrement.vue │ │ ├── card │ │ │ ├── CardContent.vue │ │ │ ├── CardTitle.vue │ │ │ ├── CardDescription.vue │ │ │ ├── CardFooter.vue │ │ │ ├── index.ts │ │ │ ├── CardAction.vue │ │ │ ├── Card.vue │ │ │ └── CardHeader.vue │ │ ├── dropdown-menu │ │ │ ├── DropdownMenuGroup.vue │ │ │ ├── DropdownMenuShortcut.vue │ │ │ ├── DropdownMenuTrigger.vue │ │ │ ├── DropdownMenu.vue │ │ │ ├── DropdownMenuSub.vue │ │ │ ├── DropdownMenuRadioGroup.vue │ │ │ ├── DropdownMenuSeparator.vue │ │ │ ├── DropdownMenuLabel.vue │ │ │ ├── index.ts │ │ │ ├── DropdownMenuSubTrigger.vue │ │ │ ├── DropdownMenuSubContent.vue │ │ │ ├── DropdownMenuItem.vue │ │ │ ├── DropdownMenuRadioItem.vue │ │ │ ├── DropdownMenuCheckboxItem.vue │ │ │ └── DropdownMenuContent.vue │ │ ├── alert │ │ │ ├── AlertTitle.vue │ │ │ ├── AlertDescription.vue │ │ │ ├── Alert.vue │ │ │ └── index.ts │ │ ├── pagination │ │ │ ├── index.ts │ │ │ ├── PaginationContent.vue │ │ │ ├── Pagination.vue │ │ │ ├── PaginationEllipsis.vue │ │ │ ├── PaginationItem.vue │ │ │ ├── PaginationLast.vue │ │ │ ├── PaginationNext.vue │ │ │ ├── PaginationFirst.vue │ │ │ └── PaginationPrevious.vue │ │ ├── button │ │ │ ├── Button.vue │ │ │ └── index.ts │ │ └── badge │ │ │ ├── Badge.vue │ │ │ └── index.ts │ ├── AppLayout.vue │ ├── Navbar.vue │ ├── ClearCacheButton.vue │ ├── ApiKeyInput.vue │ ├── NavbarSheet.vue │ └── image-generator │ │ ├── ImageSizeRadioGroup.vue │ │ └── ImageDisplay.vue ├── lib │ ├── utils.ts │ ├── models │ │ ├── registry.ts │ │ └── flux │ │ │ └── text-to-text.ts │ ├── supabase.ts │ └── types.ts ├── main.ts ├── composables │ └── useDebounce.ts ├── App.vue ├── assets │ └── images │ │ └── paintings │ │ ├── image-size-1-2.svg │ │ ├── image-size-16-9.svg │ │ ├── image-size-3-2.svg │ │ ├── image-size-3-4.svg │ │ ├── image-size-9-16.svg │ │ └── image-size-1-1.svg ├── router │ ├── models.ts │ └── index.ts ├── views │ ├── FluxModelPage.vue │ ├── ModelsPage.vue │ └── Home.vue ├── types │ └── flux.ts ├── services │ ├── generate-image.ts │ ├── api-key-manager.ts │ └── generation-history.ts └── style.css ├── .vercelignore ├── vercel-build.sh ├── .editorconfig ├── public └── logo.svg ├── tsconfig.json ├── .gitignore ├── .env.example ├── index.html ├── vite.config.ts ├── components.json ├── tsconfig.app.json ├── tsconfig.node.json ├── tailwind.config.ts ├── vercel.json ├── package.json ├── api └── blob-keys.js ├── CHANGELOG.md └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/components/ui/input/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Input } from './Input.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/label/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Label } from './Label.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/slider/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Slider } from './Slider.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/sonner/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Toaster } from './Sonner.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/switch/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Switch } from './Switch.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/skeleton/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Skeleton } from './Skeleton.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/textarea/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Textarea } from './Textarea.vue' 2 | -------------------------------------------------------------------------------- /src/components/ui/separator/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Separator } from './Separator.vue' 2 | -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .git 3 | .github 4 | .vscode 5 | .idea 6 | *.log 7 | .env 8 | .env.* 9 | !.env.example 10 | -------------------------------------------------------------------------------- /vercel-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 安装 pnpm 4 | npm install -g pnpm 5 | 6 | # 安装依赖 7 | pnpm install 8 | 9 | # 构建项目 10 | pnpm build 11 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ScrollArea } from './ScrollArea.vue' 2 | export { default as ScrollBar } from './ScrollBar.vue' 3 | -------------------------------------------------------------------------------- /src/components/ui/radio-group/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RadioGroup } from './RadioGroup.vue' 2 | export { default as RadioGroupItem } from './RadioGroupItem.vue' 3 | -------------------------------------------------------------------------------- /src/components/ui/form/injectionKeys.ts: -------------------------------------------------------------------------------- 1 | import type { InjectionKey } from 'vue' 2 | 3 | export const FORM_ITEM_INJECTION_KEY 4 | = Symbol() as InjectionKey 5 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 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 | } 7 | -------------------------------------------------------------------------------- /src/components/ui/avatar/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Avatar } from './Avatar.vue' 2 | export { default as AvatarFallback } from './AvatarFallback.vue' 3 | export { default as AvatarImage } from './AvatarImage.vue' 4 | -------------------------------------------------------------------------------- /src/components/ui/tabs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tabs } from './Tabs.vue' 2 | export { default as TabsContent } from './TabsContent.vue' 3 | export { default as TabsList } from './TabsList.vue' 4 | export { default as TabsTrigger } from './TabsTrigger.vue' 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = false -------------------------------------------------------------------------------- /src/components/ui/tooltip/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Tooltip } from './Tooltip.vue' 2 | export { default as TooltipContent } from './TooltipContent.vue' 3 | export { default as TooltipProvider } from './TooltipProvider.vue' 4 | export { default as TooltipTrigger } from './TooltipTrigger.vue' 5 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import { createPinia } from 'pinia' 3 | import router from './router' 4 | import './style.css' 5 | import App from './App.vue' 6 | 7 | const app = createApp(App) 8 | app.use(createPinia()) 9 | app.use(router) 10 | app.mount('#app') 11 | -------------------------------------------------------------------------------- /src/components/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/components/ui/sheet/SheetDescription.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /src/components/ui/sheet/SheetTitle.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /src/components/ui/sheet/SheetHeader.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/ui/table/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Updater } from '@tanstack/vue-table' 2 | import type { Ref } from 'vue' 3 | 4 | export function valueUpdater>(updaterOrValue: T, ref: Ref) { 5 | ref.value 6 | = typeof updaterOrValue === 'function' 7 | ? updaterOrValue(ref.value) 8 | : updaterOrValue 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ui/sheet/SheetFooter.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ], 7 | "compilerOptions": { 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": ["./src/*"] 11 | }, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/composables/useDebounce.ts: -------------------------------------------------------------------------------- 1 | export function useDebounce(fn: Function, delay: number) { 2 | let timeout: ReturnType | null = null; 3 | 4 | return function(...args: any[]) { 5 | if (timeout) { 6 | clearTimeout(timeout); 7 | } 8 | timeout = setTimeout(() => { 9 | fn(...args); 10 | }, delay); 11 | }; 12 | } -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogClose.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectGroup.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectValue.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogTrigger.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env 26 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectItemText.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/components/ui/tooltip/TooltipProvider.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /src/components/ui/tooltip/TooltipTrigger.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogTrigger.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/components/ui/number-field/index.ts: -------------------------------------------------------------------------------- 1 | export { default as NumberField } from './NumberField.vue' 2 | export { default as NumberFieldContent } from './NumberFieldContent.vue' 3 | export { default as NumberFieldDecrement } from './NumberFieldDecrement.vue' 4 | export { default as NumberFieldIncrement } from './NumberFieldIncrement.vue' 5 | export { default as NumberFieldInput } from './NumberFieldInput.vue' 6 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # FAL.AI API密钥 2 | # 可以配置多个密钥,用逗号分隔,例如:VITE_FAL_API_KEYS=xxxxxx,yyyyyy 3 | VITE_FAL_API_KEYS= 4 | 5 | # Vercel Blob Token - 服务端使用,用于获取预置的API密钥 6 | # 只有在localStorage中不存在API密钥时才会从Blob存储获取 7 | # 注意:这个Token应该在Vercel部署时配置为服务端环境变量 8 | BLOB_READ_WRITE_TOKEN=vercel_blob_rw_xxxxxxxxxx 9 | 10 | # Supabase配置 11 | VITE_SUPABASE_URL=https://your-project-id.supabase.co 12 | VITE_SUPABASE_ANON_KEY=your-anon-key 13 | -------------------------------------------------------------------------------- /src/components/ui/card/CardContent.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuGroup.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fal.AI Studio 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/ui/sheet/SheetClose.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/components/ui/sheet/SheetTrigger.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /src/components/ui/card/CardTitle.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/table/TableHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { defineConfig } from 'vite' 3 | import vue from '@vitejs/plugin-vue' 4 | import tailwindcss from '@tailwindcss/vite' 5 | 6 | // https://vite.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | tailwindcss(), 11 | ], 12 | resolve: { 13 | alias: { 14 | '@': path.resolve(__dirname, './src'), 15 | }, 16 | }, 17 | }) 18 | -------------------------------------------------------------------------------- /src/components/ui/avatar/AvatarImage.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /src/components/ui/table/TableBody.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/card/CardDescription.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/card/CardFooter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/card/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Card } from './Card.vue' 2 | export { default as CardAction } from './CardAction.vue' 3 | export { default as CardContent } from './CardContent.vue' 4 | export { default as CardDescription } from './CardDescription.vue' 5 | export { default as CardFooter } from './CardFooter.vue' 6 | export { default as CardHeader } from './CardHeader.vue' 7 | export { default as CardTitle } from './CardTitle.vue' 8 | -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogFooter.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/components/ui/table/TableCaption.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/skeleton/Skeleton.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /src/components/ui/alert/AlertTitle.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/card/CardAction.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/table/TableFooter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/table/TableRow.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/form/index.ts: -------------------------------------------------------------------------------- 1 | export { default as FormControl } from './FormControl.vue' 2 | export { default as FormDescription } from './FormDescription.vue' 3 | export { default as FormItem } from './FormItem.vue' 4 | export { default as FormLabel } from './FormLabel.vue' 5 | export { default as FormMessage } from './FormMessage.vue' 6 | export { FORM_ITEM_INJECTION_KEY } from './injectionKeys' 7 | export { Form, Field as FormField, FieldArray as FormFieldArray } from 'vee-validate' 8 | -------------------------------------------------------------------------------- /src/components/ui/number-field/NumberFieldContent.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuShortcut.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/sonner/Sonner.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | -------------------------------------------------------------------------------- /src/components/ui/table/Table.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 17 | -------------------------------------------------------------------------------- /src/components/ui/dialog/Dialog.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuTrigger.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/components/ui/alert/AlertDescription.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/avatar/Avatar.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialog.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | -------------------------------------------------------------------------------- /src/components/ui/tooltip/Tooltip.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/sheet/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Sheet } from './Sheet.vue' 2 | export { default as SheetTrigger } from './SheetTrigger.vue' 3 | export { default as SheetClose } from './SheetClose.vue' 4 | export { default as SheetContent } from './SheetContent.vue' 5 | export { default as SheetHeader } from './SheetHeader.vue' 6 | export { default as SheetTitle } from './SheetTitle.vue' 7 | export { default as SheetDescription } from './SheetDescription.vue' 8 | export { default as SheetFooter } from './SheetFooter.vue' 9 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectLabel.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/components/ui/select/Select.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | -------------------------------------------------------------------------------- /src/components/ui/card/Card.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /src/components/ui/form/FormControl.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://shadcn-vue.com/schema.json", 3 | "style": "new-york", 4 | "typescript": true, 5 | "tailwind": { 6 | "config": "tailwind.config.ts", 7 | "css": "src/style.css", 8 | "baseColor": "neutral", 9 | "cssVariables": true, 10 | "prefix": "" 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "composables": "@/composables", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib" 18 | }, 19 | "iconLibrary": "lucide" 20 | } -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogFooter.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /src/components/ui/alert/Alert.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /src/components/ui/table/TableHead.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/card/CardHeader.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenu.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 18 | -------------------------------------------------------------------------------- /src/components/ui/table/TableCell.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /src/components/ui/table/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Table } from './Table.vue' 2 | export { default as TableBody } from './TableBody.vue' 3 | export { default as TableCaption } from './TableCaption.vue' 4 | export { default as TableCell } from './TableCell.vue' 5 | export { default as TableEmpty } from './TableEmpty.vue' 6 | export { default as TableFooter } from './TableFooter.vue' 7 | export { default as TableHead } from './TableHead.vue' 8 | export { default as TableHeader } from './TableHeader.vue' 9 | export { default as TableRow } from './TableRow.vue' 10 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuSub.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /src/components/ui/form/FormDescription.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 22 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 5 | 6 | /* Linting */ 7 | "strict": true, 8 | "noUnusedLocals": true, 9 | "noUnusedParameters": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noUncheckedSideEffectImports": true, 12 | 13 | "baseUrl": ".", 14 | "paths": { 15 | "@/*": [ 16 | "./src/*" 17 | ] 18 | } 19 | }, 20 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] 21 | } 22 | -------------------------------------------------------------------------------- /src/components/ui/form/FormItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /src/components/ui/pagination/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Pagination } from './Pagination.vue' 2 | export { default as PaginationContent } from './PaginationContent.vue' 3 | export { default as PaginationEllipsis } from './PaginationEllipsis.vue' 4 | export { default as PaginationFirst } from './PaginationFirst.vue' 5 | export { default as PaginationItem } from './PaginationItem.vue' 6 | export { default as PaginationLast } from './PaginationLast.vue' 7 | export { default as PaginationNext } from './PaginationNext.vue' 8 | export { default as PaginationPrevious } from './PaginationPrevious.vue' 9 | -------------------------------------------------------------------------------- /src/lib/models/registry.ts: -------------------------------------------------------------------------------- 1 | import type { Model } from "@/types/flux"; 2 | import * as textToText from "./flux/text-to-text"; 3 | 4 | // Get all exported models from text-to-text 5 | const textToTextModels = Object.values(textToText).filter( 6 | (value): value is Model => { 7 | return ( 8 | typeof value === "object" && 9 | value !== null && 10 | "name" in value && 11 | "id" in value && 12 | "inputSchema" in value && 13 | "outputSchema" in value 14 | ); 15 | } 16 | ); 17 | 18 | // Export all models in a single array 19 | export const allModels = [...textToTextModels]; 20 | -------------------------------------------------------------------------------- /src/components/ui/form/FormMessage.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 23 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuRadioGroup.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | -------------------------------------------------------------------------------- /src/components/ui/dialog/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Dialog } from './Dialog.vue' 2 | export { default as DialogClose } from './DialogClose.vue' 3 | export { default as DialogContent } from './DialogContent.vue' 4 | export { default as DialogDescription } from './DialogDescription.vue' 5 | export { default as DialogFooter } from './DialogFooter.vue' 6 | export { default as DialogHeader } from './DialogHeader.vue' 7 | export { default as DialogOverlay } from './DialogOverlay.vue' 8 | export { default as DialogScrollContent } from './DialogScrollContent.vue' 9 | export { default as DialogTitle } from './DialogTitle.vue' 10 | export { default as DialogTrigger } from './DialogTrigger.vue' 11 | -------------------------------------------------------------------------------- /src/components/ui/tabs/TabsContent.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectSeparator.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AlertDialog } from './AlertDialog.vue' 2 | export { default as AlertDialogAction } from './AlertDialogAction.vue' 3 | export { default as AlertDialogCancel } from './AlertDialogCancel.vue' 4 | export { default as AlertDialogContent } from './AlertDialogContent.vue' 5 | export { default as AlertDialogDescription } from './AlertDialogDescription.vue' 6 | export { default as AlertDialogFooter } from './AlertDialogFooter.vue' 7 | export { default as AlertDialogHeader } from './AlertDialogHeader.vue' 8 | export { default as AlertDialogTitle } from './AlertDialogTitle.vue' 9 | export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue' 10 | -------------------------------------------------------------------------------- /src/components/ui/number-field/NumberFieldInput.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 17 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ESNext"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /src/assets/images/paintings/image-size-1-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/paintings/image-size-16-9.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/paintings/image-size-3-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/paintings/image-size-3-4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/paintings/image-size-9-16.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/images/paintings/image-size-1-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogAction.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 21 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogTitle.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/components/ui/form/FormLabel.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /src/components/ui/avatar/AvatarFallback.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuSeparator.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 27 | -------------------------------------------------------------------------------- /src/components/ui/select/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Select } from './Select.vue' 2 | export { default as SelectContent } from './SelectContent.vue' 3 | export { default as SelectGroup } from './SelectGroup.vue' 4 | export { default as SelectItem } from './SelectItem.vue' 5 | export { default as SelectItemText } from './SelectItemText.vue' 6 | export { default as SelectLabel } from './SelectLabel.vue' 7 | export { default as SelectScrollDownButton } from './SelectScrollDownButton.vue' 8 | export { default as SelectScrollUpButton } from './SelectScrollUpButton.vue' 9 | export { default as SelectSeparator } from './SelectSeparator.vue' 10 | export { default as SelectTrigger } from './SelectTrigger.vue' 11 | export { default as SelectValue } from './SelectValue.vue' 12 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationContent.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | -------------------------------------------------------------------------------- /src/components/ui/tabs/TabsList.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | -------------------------------------------------------------------------------- /src/components/ui/button/Button.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 28 | -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogTitle.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /src/components/ui/tabs/Tabs.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogDescription.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /src/components/ui/badge/Badge.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 29 | -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogDescription.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 26 | -------------------------------------------------------------------------------- /src/components/ui/alert-dialog/AlertDialogCancel.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 28 | -------------------------------------------------------------------------------- /src/components/ui/dialog/DialogOverlay.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/components/ui/number-field/NumberField.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /src/components/ui/radio-group/RadioGroup.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuLabel.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/components/ui/label/Label.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 29 | -------------------------------------------------------------------------------- /src/router/models.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router'; 2 | import { allModels } from '@/lib/models/registry'; 3 | 4 | // 为每个模型创建路由配置 5 | export const modelRoutes: RouteRecordRaw[] = allModels.map(model => { 6 | const routeId = model.id.replace(/\//g, '-'); 7 | return { 8 | path: `/models/${routeId}`, 9 | name: `Model-${routeId}`, 10 | component: () => import('@/views/FluxModelPage.vue'), 11 | props: { modelId: routeId }, 12 | meta: { 13 | title: `${model.name} - FAL.AI`, 14 | model 15 | } 16 | }; 17 | }); 18 | 19 | // 创建模型分类 20 | export const modelCategories = [ 21 | { 22 | title: 'Flux 模型', 23 | models: allModels.filter(model => model.id.includes('flux')) 24 | } 25 | ]; 26 | 27 | // 获取模型信息的辅助函数 28 | export function getModelById(id: string) { 29 | return allModels.find(model => model.id.replace(/\//g, '-') === id); 30 | } 31 | -------------------------------------------------------------------------------- /src/components/ui/pagination/Pagination.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 26 | -------------------------------------------------------------------------------- /src/components/ui/separator/Separator.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 29 | -------------------------------------------------------------------------------- /src/components/ui/sheet/Sheet.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationEllipsis.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 28 | -------------------------------------------------------------------------------- /src/lib/supabase.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from '@supabase/supabase-js'; 2 | import { v4 as uuidv4 } from 'uuid'; 3 | 4 | // 从环境变量获取Supabase配置 5 | const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || ''; 6 | const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || ''; 7 | 8 | // 创建Supabase客户端 9 | export const supabase = createClient(supabaseUrl, supabaseAnonKey); 10 | 11 | // 获取或创建用户ID 12 | export const getUserId = (): string => { 13 | // 尝试从localStorage获取用户ID 14 | let userId = localStorage.getItem('fal-ai-user-id'); 15 | 16 | // 如果不存在,创建一个新的UUID并保存 17 | if (!userId) { 18 | userId = uuidv4(); 19 | localStorage.setItem('fal-ai-user-id', userId); 20 | } 21 | 22 | return userId; 23 | }; 24 | 25 | // 当前用户ID 26 | export const currentUserId = getUserId(); 27 | 28 | // 检查是否是当前用户的记录 29 | export const isCurrentUserRecord = (userId: string): boolean => { 30 | return userId === currentUserId; 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectScrollUpButton.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /src/components/ui/table/TableEmpty.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 38 | -------------------------------------------------------------------------------- /src/components/ui/number-field/NumberFieldDecrement.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /src/components/ui/number-field/NumberFieldIncrement.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 26 | -------------------------------------------------------------------------------- /src/components/ui/alert/index.ts: -------------------------------------------------------------------------------- 1 | import { cva, type VariantProps } from 'class-variance-authority' 2 | 3 | export { default as Alert } from './Alert.vue' 4 | export { default as AlertDescription } from './AlertDescription.vue' 5 | export { default as AlertTitle } from './AlertTitle.vue' 6 | 7 | export const alertVariants = cva( 8 | 'relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current', 9 | { 10 | variants: { 11 | variant: { 12 | default: 'bg-card text-card-foreground', 13 | destructive: 14 | 'text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90', 15 | }, 16 | }, 17 | defaultVariants: { 18 | variant: 'default', 19 | }, 20 | }, 21 | ) 22 | 23 | export type AlertVariants = VariantProps 24 | -------------------------------------------------------------------------------- /src/components/ui/select/SelectScrollDownButton.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /src/components/ui/form/useFormField.ts: -------------------------------------------------------------------------------- 1 | import { FieldContextKey, useFieldError, useIsFieldDirty, useIsFieldTouched, useIsFieldValid } from 'vee-validate' 2 | import { inject } from 'vue' 3 | import { FORM_ITEM_INJECTION_KEY } from './injectionKeys' 4 | 5 | export function useFormField() { 6 | const fieldContext = inject(FieldContextKey) 7 | const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY) 8 | 9 | if (!fieldContext) 10 | throw new Error('useFormField should be used within ') 11 | 12 | const { name } = fieldContext 13 | const id = fieldItemContext 14 | 15 | const fieldState = { 16 | valid: useIsFieldValid(name), 17 | isDirty: useIsFieldDirty(name), 18 | isTouched: useIsFieldTouched(name), 19 | error: useFieldError(name), 20 | } 21 | 22 | return { 23 | id, 24 | name, 25 | formItemId: `${id}-form-item`, 26 | formDescriptionId: `${id}-form-item-description`, 27 | formMessageId: `${id}-form-item-message`, 28 | ...fieldState, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationItem.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 33 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area/ScrollArea.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 37 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationLast.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationNext.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationFirst.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /src/components/ui/pagination/PaginationPrevious.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 33 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DropdownMenu } from './DropdownMenu.vue' 2 | 3 | export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue' 4 | export { default as DropdownMenuContent } from './DropdownMenuContent.vue' 5 | export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue' 6 | export { default as DropdownMenuItem } from './DropdownMenuItem.vue' 7 | export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue' 8 | export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue' 9 | export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue' 10 | export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue' 11 | export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue' 12 | export { default as DropdownMenuSub } from './DropdownMenuSub.vue' 13 | export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue' 14 | export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue' 15 | export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue' 16 | export { DropdownMenuPortal } from 'reka-ui' 17 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area/ScrollBar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 35 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu/DropdownMenuSubTrigger.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /src/components/ui/textarea/Textarea.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 |