commit 88133f643d12f54b840002d177fdf2adf28b5bf1 Author: 北枳 <7854742+wang_rumeng@user.noreply.gitee.com> Date: Thu Jun 19 17:15:03 2025 +0800 init diff --git a/.bolt/config.json b/.bolt/config.json new file mode 100644 index 0000000..f236591 --- /dev/null +++ b/.bolt/config.json @@ -0,0 +1,3 @@ +{ + "template": "nextjs-shadcn" +} diff --git a/.bolt/ignore b/.bolt/ignore new file mode 100644 index 0000000..bbe3a15 --- /dev/null +++ b/.bolt/ignore @@ -0,0 +1,2 @@ +components/ui/* +hooks/use-toast.ts diff --git a/.bolt/prompt b/.bolt/prompt new file mode 100644 index 0000000..88d020b --- /dev/null +++ b/.bolt/prompt @@ -0,0 +1,9 @@ +For all designs I ask you to make, have them be beautiful, not cookie cutter. Make webpages that are fully featured and worthy for production. + +When using client-side hooks (useState and useEffect) in a component that's being treated as a Server Component by Next.js, always add the "use client" directive at the top of the file. + +Do not write code that will trigger this error: "Warning: Extra attributes from the server: %s%s""class,style" + +By default, this template supports JSX syntax with Tailwind CSS classes, the shadcn/ui library, React hooks, and Lucide React for icons. Do not install other packages for UI themes, icons, etc unless absolutely necessary or I request them. + +Use icons from lucide-react for logos. diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b1913e --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/app/actors/page.tsx b/app/actors/page.tsx new file mode 100644 index 0000000..cb00c48 --- /dev/null +++ b/app/actors/page.tsx @@ -0,0 +1,10 @@ +import { DashboardLayout } from '@/components/layout/dashboard-layout'; +import { ActorsLibraryPage } from '@/components/pages/actors-library-page'; + +export default function ActorsPage() { + return ( + + + + ); +} \ No newline at end of file diff --git a/app/create/page.tsx b/app/create/page.tsx new file mode 100644 index 0000000..016734b --- /dev/null +++ b/app/create/page.tsx @@ -0,0 +1,10 @@ +import { DashboardLayout } from '@/components/layout/dashboard-layout'; +import { CreateVideoWorkflow } from '@/components/pages/create-video-workflow'; + +export default function CreatePage() { + return ( + + + + ); +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..52f5692 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,86 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + } +} + +.hide-scrollbar::-webkit-scrollbar { + display: none; +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/app/history/page.tsx b/app/history/page.tsx new file mode 100644 index 0000000..300dd4d --- /dev/null +++ b/app/history/page.tsx @@ -0,0 +1,10 @@ +import { DashboardLayout } from '@/components/layout/dashboard-layout'; +import { HistoryPage } from '@/components/pages/history-page'; + +export default function History() { + return ( + + + + ); +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..61e4a5e --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,34 @@ +import './globals.css'; +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import { ThemeProvider } from '@/components/theme-provider'; +import { Toaster } from '@/components/ui/sonner'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata: Metadata = { + title: 'AI Video Studio - Create Amazing Videos with AI', + description: 'Professional AI-powered video creation platform with advanced editing tools', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + {children} + + + + + ); +} \ No newline at end of file diff --git a/app/media/page.tsx b/app/media/page.tsx new file mode 100644 index 0000000..f951411 --- /dev/null +++ b/app/media/page.tsx @@ -0,0 +1,10 @@ +import { DashboardLayout } from '@/components/layout/dashboard-layout'; +import { MediaLibraryPage } from '@/components/pages/media-library-page'; + +export default function MediaPage() { + return ( + + + + ); +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..872a03e --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,10 @@ +import { DashboardLayout } from '@/components/layout/dashboard-layout'; +import { HomePage } from '@/components/pages/home-page'; + +export default function Home() { + return ( + + + + ); +} \ No newline at end of file diff --git a/components.json b/components.json new file mode 100644 index 0000000..c597462 --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/components/layout/dashboard-layout.tsx b/components/layout/dashboard-layout.tsx new file mode 100644 index 0000000..81f93d3 --- /dev/null +++ b/components/layout/dashboard-layout.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useState } from 'react'; +import { Sidebar } from './sidebar'; +import { TopBar } from './top-bar'; + +interface DashboardLayoutProps { + children: React.ReactNode; +} + +export function DashboardLayout({ children }: DashboardLayoutProps) { + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); + + return ( +
+ +
+ +
+ {children} +
+
+
+ ); +} \ No newline at end of file diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx new file mode 100644 index 0000000..e37b9fd --- /dev/null +++ b/components/layout/sidebar.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useState } from 'react'; +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Separator } from '@/components/ui/separator'; +import { + Home, + FolderOpen, + Users, + Type, + Image, + History, + ChevronLeft, + ChevronRight, + Video, +} from 'lucide-react'; + +interface SidebarProps { + collapsed: boolean; + onToggle: (collapsed: boolean) => void; +} + +const navigationItems = [ + { + title: 'Main', + items: [ + { name: 'Home', href: '/', icon: Home }, + { name: 'Media Library', href: '/media', icon: FolderOpen }, + { name: 'Actors Library', href: '/actors', icon: Users }, + ], + }, + { + title: 'Plugins', + items: [ + { name: 'Text to Clip', href: '/plugins/text-to-clip', icon: Type }, + { name: 'Text to Image', href: '/plugins/text-to-image', icon: Image }, + ], + }, + { + title: 'History', + items: [ + { name: 'Task History', href: '/history', icon: History }, + ], + }, +]; + +export function Sidebar({ collapsed, onToggle }: SidebarProps) { + const pathname = usePathname(); + + return ( +
+
+ {/* Logo */} +
+ {!collapsed && ( +
+
+ )} + +
+ + {/* Navigation */} +
+ {navigationItems.map((section, index) => ( +
+ {!collapsed && ( +

+ {section.title} +

+ )} +
+ {section.items.map((item) => { + const isActive = pathname === item.href; + return ( + + + + ); + })} +
+ {index < navigationItems.length - 1 && ( + + )} +
+ ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx new file mode 100644 index 0000000..c18d8fc --- /dev/null +++ b/components/layout/top-bar.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { useTheme } from 'next-themes'; +import { + Sun, + Moon, + User, + Settings, + LogOut, + Bell, +} from 'lucide-react'; + +export function TopBar() { + const { theme, setTheme } = useTheme(); + + return ( +
+
+
+

AI Video Studio

+
+ +
+ {/* Notifications */} + + + {/* Theme Toggle */} + + + {/* User Menu */} + + + + + + + + Settings + + + + + Log out + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/components/pages/actors-library-page.tsx b/components/pages/actors-library-page.tsx new file mode 100644 index 0000000..5963fd1 --- /dev/null +++ b/components/pages/actors-library-page.tsx @@ -0,0 +1,360 @@ +"use client"; + +import { useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Badge } from '@/components/ui/badge'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@/components/ui/dialog'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + Plus, + Upload, + Search, + MoreHorizontal, + Play, + Pause, + Wand2, + Edit, + Trash2, + Volume2, + User, +} from 'lucide-react'; + +const mockActors = [ + { + id: 1, + name: 'Sarah Chen', + description: 'Professional corporate presenter with clear articulation', + avatar: 'https://images.pexels.com/photos/774909/pexels-photo-774909.jpeg?auto=compress&cs=tinysrgb&w=200', + voice: { + type: 'generated', + name: 'Professional Female', + sample: 'Hello, I\'m Sarah and I\'ll be your guide through this presentation.', + }, + tags: ['corporate', 'professional', 'female'], + createdAt: '2024-01-15', + usageCount: 12, + }, + { + id: 2, + name: 'Dr. Marcus Webb', + description: 'Expert educator with authoritative voice for technical content', + avatar: 'https://images.pexels.com/photos/1222271/pexels-photo-1222271.jpeg?auto=compress&cs=tinysrgb&w=200', + voice: { + type: 'generated', + name: 'Expert Male', + sample: 'Welcome to today\'s lesson on advanced artificial intelligence.', + }, + tags: ['education', 'expert', 'male'], + createdAt: '2024-01-12', + usageCount: 8, + }, + { + id: 3, + name: 'Alex Rivera', + description: 'Energetic host perfect for engaging, casual content', + avatar: 'https://images.pexels.com/photos/1239291/pexels-photo-1239291.jpeg?auto=compress&cs=tinysrgb&w=200', + voice: { + type: 'uploaded', + name: 'Custom Voice', + sample: 'Hey everyone! Ready to dive into something amazing?', + }, + tags: ['casual', 'energetic', 'neutral'], + createdAt: '2024-01-10', + usageCount: 5, + }, +]; + +const voiceTypes = [ + 'Professional Female', + 'Professional Male', + 'Casual Female', + 'Casual Male', + 'Expert Female', + 'Expert Male', + 'Enthusiastic Neutral', +]; + +export function ActorsLibraryPage() { + const [searchQuery, setSearchQuery] = useState(''); + const [playingVoice, setPlayingVoice] = useState(null); + const [newActorName, setNewActorName] = useState(''); + const [newActorDescription, setNewActorDescription] = useState(''); + const [newActorPrompt, setNewActorPrompt] = useState(''); + + const filteredActors = mockActors.filter(actor => + actor.name.toLowerCase().includes(searchQuery.toLowerCase()) || + actor.description.toLowerCase().includes(searchQuery.toLowerCase()) || + actor.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())) + ); + + const toggleVoicePlayback = (actorId: number) => { + setPlayingVoice(playingVoice === actorId ? null : actorId); + }; + + return ( +
+ {/* Header */} +
+
+

Actors Library

+

+ Create and manage AI actors with custom voices for your videos +

+
+ + + + + + + Create New Actor + + + + Upload Image + AI Generated + + + +
+
+ +
+
+
+ + +
+
+ +