'use client'
import { useState } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Progress } from '@/components/ui/progress'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
User,
Upload,
Play,
Pause,
RotateCcw,
Download,
Trash2,
Eye,
Settings,
Zap,
Heart,
Skull,
Crown,
Star,
Sparkles
} from 'lucide-react'
interface VRMAvatar {
id: string
name: string
source: 'vroid' | 'blender' | 'other'
filePath: string
thumbnail: string
status: 'loaded' | 'loading' | 'error'
animations: string[]
morphTargets: string[]
materials: string[]
bones: number
triangles: number
size: number // MB
lastUsed: string
}
const mockVRMAvatars: VRMAvatar[] = [
{
id: 'vroid-001',
name: 'Alice Explorer',
source: 'vroid',
filePath: '/avatars/alice_explorer.vrm',
thumbnail: '/api/placeholder/150/200',
status: 'loaded',
animations: ['walk', 'run', 'jump', 'dance', 'idle'],
morphTargets: ['smile', 'frown', 'surprise', 'blink'],
materials: ['skin', 'hair', 'eyes', 'clothes'],
bones: 89,
triangles: 15420,
size: 2.4,
lastUsed: '2025-12-17 10:30:00'
},
{
id: 'blender-001',
name: 'RoboDog V2',
source: 'blender',
filePath: '/avatars/robodog_v2.vrm',
thumbnail: '/api/placeholder/150/200',
status: 'loaded',
animations: ['walk', 'run', 'sit', 'bark', 'tail_wag'],
morphTargets: ['ear_twist', 'mouth_open', 'eye_blink'],
materials: ['metal', 'plastic', 'rubber', 'LED_eyes'],
bones: 45,
triangles: 8920,
size: 1.8,
lastUsed: '2025-12-17 09:15:00'
},
{
id: 'vroid-002',
name: 'Tech Support Bot',
source: 'vroid',
filePath: '/avatars/tech_support.vrm',
thumbnail: '/api/placeholder/150/200',
status: 'loading',
animations: ['typing', 'pointing', 'nodding', 'thinking'],
morphTargets: ['smile', 'concerned', 'focused'],
materials: ['uniform', 'badge', 'screen'],
bones: 67,
triangles: 12340,
size: 3.1,
lastUsed: '2025-12-16 16:45:00'
}
]
const quickVRMExamples = [
{ name: 'Humanoid Character', icon: User, description: 'Standard bipedal character with full animations' },
{ name: 'Robot Companion', icon: Heart, description: 'Friendly robotic assistant with expressive features' },
{ name: 'Fantasy Creature', icon: Sparkles, description: 'Magical being with enhanced morphing capabilities' },
{ name: 'Professional Avatar', icon: Crown, description: 'Business-ready character for meetings' },
{ name: 'Anime Character', icon: Star, description: 'Stylized character with exaggerated expressions' },
{ name: 'Horror Entity', icon: Skull, description: 'Atmospheric character for immersive experiences' }
]
export default function VRMAvatarsPage() {
const [selectedAvatar, setSelectedAvatar] = useState<VRMAvatar | null>(null)
const [uploadProgress, setUploadProgress] = useState(0)
const [isUploading, setIsUploading] = useState(false)
const handleFileUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (!file) return
setIsUploading(true)
setUploadProgress(0)
// Simulate upload progress
const interval = setInterval(() => {
setUploadProgress(prev => {
if (prev >= 100) {
clearInterval(interval)
setIsUploading(false)
return 100
}
return prev + 10
})
}, 200)
}
const getStatusColor = (status: string) => {
switch (status) {
case 'loaded': return 'bg-green-500'
case 'loading': return 'bg-yellow-500'
case 'error': return 'bg-red-500'
default: return 'bg-gray-500'
}
}
const getSourceIcon = (source: string) => {
switch (source) {
case 'vroid': return <Star className="h-4 w-4 text-purple-500" />
case 'blender': return <Settings className="h-4 w-4 text-orange-500" />
default: return <Upload className="h-4 w-4 text-gray-500" />
}
}
return (
<div className="container mx-auto px-6 py-8 max-w-7xl">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">VRM Avatar Management</h1>
<p className="text-muted-foreground">
Import, manage, and animate VRM avatars from VRoid Studio and Blender.
Perfect for robotic companions, virtual assistants, and immersive experiences.
</p>
</div>
<Tabs defaultValue="library" className="space-y-6">
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="library">Avatar Library</TabsTrigger>
<TabsTrigger value="upload">Upload VRM</TabsTrigger>
<TabsTrigger value="templates">Quick Templates</TabsTrigger>
<TabsTrigger value="settings">VRM Settings</TabsTrigger>
</TabsList>
<TabsContent value="library" className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{mockVRMAvatars.map((avatar) => (
<Card key={avatar.id} className="cursor-pointer hover:shadow-lg transition-shadow"
onClick={() => setSelectedAvatar(avatar)}>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
{getSourceIcon(avatar.source)}
<Badge variant="outline" className="text-xs">
{avatar.source.toUpperCase()}
</Badge>
</div>
<div className={`w-3 h-3 rounded-full ${getStatusColor(avatar.status)}`} />
</div>
<CardTitle className="text-lg">{avatar.name}</CardTitle>
<CardDescription>
{avatar.bones} bones • {avatar.triangles.toLocaleString()} tris • {avatar.size}MB
</CardDescription>
</CardHeader>
<CardContent>
<div className="aspect-[3/4] bg-muted rounded-lg mb-4 flex items-center justify-center">
<User className="h-16 w-16 text-muted-foreground" />
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Animations:</span>
<span className="font-medium">{avatar.animations.length}</span>
</div>
<div className="flex justify-between text-sm">
<span>Morphs:</span>
<span className="font-medium">{avatar.morphTargets.length}</span>
</div>
<div className="text-xs text-muted-foreground">
Last used: {avatar.lastUsed}
</div>
</div>
<div className="flex space-x-2 mt-4">
<Button size="sm" variant="outline" className="flex-1">
<Play className="h-3 w-3 mr-1" />
Animate
</Button>
<Button size="sm" variant="outline">
<Eye className="h-3 w-3" />
</Button>
</div>
</CardContent>
</Card>
))}
</div>
{selectedAvatar && (
<Card className="mt-6">
<CardHeader>
<CardTitle className="flex items-center space-x-2">
<User className="h-5 w-5" />
<span>{selectedAvatar.name} - Details</span>
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="text-center">
<div className="text-2xl font-bold text-blue-600">{selectedAvatar.bones}</div>
<div className="text-sm text-muted-foreground">Bones</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-green-600">{selectedAvatar.triangles.toLocaleString()}</div>
<div className="text-sm text-muted-foreground">Triangles</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-purple-600">{selectedAvatar.size}</div>
<div className="text-sm text-muted-foreground">Size (MB)</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-orange-600">{selectedAvatar.animations.length}</div>
<div className="text-sm text-muted-foreground">Animations</div>
</div>
</div>
<Tabs defaultValue="animations">
<TabsList>
<TabsTrigger value="animations">Animations</TabsTrigger>
<TabsTrigger value="morphs">Morph Targets</TabsTrigger>
<TabsTrigger value="materials">Materials</TabsTrigger>
</TabsList>
<TabsContent value="animations" className="space-y-2">
{selectedAvatar.animations.map((anim) => (
<div key={anim} className="flex items-center justify-between p-2 border rounded">
<span className="capitalize">{anim.replace('_', ' ')}</span>
<Button size="sm" variant="outline">
<Play className="h-3 w-3 mr-1" />
Play
</Button>
</div>
))}
</TabsContent>
<TabsContent value="morphs" className="space-y-2">
{selectedAvatar.morphTargets.map((morph) => (
<div key={morph} className="flex items-center justify-between p-2 border rounded">
<span className="capitalize">{morph.replace('_', ' ')}</span>
<div className="flex space-x-2">
<Button size="sm" variant="outline" disabled>
<RotateCcw className="h-3 w-3" />
</Button>
<input type="range" min="0" max="100" defaultValue="0" className="w-20" />
</div>
</div>
))}
</TabsContent>
<TabsContent value="materials" className="space-y-2">
{selectedAvatar.materials.map((material) => (
<div key={material} className="flex items-center justify-between p-2 border rounded">
<span className="capitalize">{material.replace('_', ' ')}</span>
<Button size="sm" variant="outline">
<Settings className="h-3 w-3 mr-1" />
Edit
</Button>
</div>
))}
</TabsContent>
</Tabs>
</CardContent>
</Card>
)}
</TabsContent>
<TabsContent value="upload" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Upload VRM File</CardTitle>
<CardDescription>
Import VRM files created in VRoid Studio or exported from Blender.
Supported formats: .vrm (recommended), .glb with VRM extensions.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="border-2 border-dashed border-muted-foreground/25 rounded-lg p-8 text-center">
<Upload className="h-12 w-12 mx-auto mb-4 text-muted-foreground" />
<div className="space-y-2">
<Label htmlFor="vrm-upload" className="text-lg font-medium">
Drop VRM file here or click to browse
</Label>
<p className="text-sm text-muted-foreground">
Maximum file size: 50MB. VRM 1.0 specification compliant.
</p>
</div>
<Input
id="vrm-upload"
type="file"
accept=".vrm,.glb"
onChange={handleFileUpload}
className="mt-4"
/>
</div>
{isUploading && (
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Uploading...</span>
<span>{uploadProgress}%</span>
</div>
<Progress value={uploadProgress} />
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="avatar-name">Avatar Name</Label>
<Input id="avatar-name" placeholder="Enter avatar name" />
</div>
<div className="space-y-2">
<Label htmlFor="avatar-source">Source</Label>
<select className="w-full p-2 border rounded" id="avatar-source">
<option value="vroid">VRoid Studio</option>
<option value="blender">Blender</option>
<option value="other">Other</option>
</select>
</div>
</div>
<Button className="w-full" disabled={isUploading}>
<Upload className="h-4 w-4 mr-2" />
Upload and Process VRM
</Button>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="templates" className="space-y-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{quickVRMExamples.map((template, index) => (
<Card key={index} className="cursor-pointer hover:shadow-lg transition-shadow">
<CardHeader className="text-center">
<div className="mx-auto mb-2 p-3 bg-primary/10 rounded-full w-fit">
<template.icon className="h-6 w-6 text-primary" />
</div>
<CardTitle className="text-lg">{template.name}</CardTitle>
<CardDescription>{template.description}</CardDescription>
</CardHeader>
<CardContent>
<Button className="w-full">
<Sparkles className="h-4 w-4 mr-2" />
Generate Template
</Button>
</CardContent>
</Card>
))}
</div>
</TabsContent>
<TabsContent value="settings" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>VRM Processing Settings</CardTitle>
<CardDescription>
Configure how VRM files are processed and optimized for your robotics platform.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
<div>
<Label>Animation Quality</Label>
<select className="w-full p-2 border rounded mt-1">
<option>High (Full animations)</option>
<option>Medium (Essential animations)</option>
<option>Low (Basic movements)</option>
</select>
</div>
<div>
<Label>Texture Resolution</Label>
<select className="w-full p-2 border rounded mt-1">
<option>2048x2048 (High quality)</option>
<option>1024x1024 (Balanced)</option>
<option>512x512 (Performance)</option>
</select>
</div>
<div className="flex items-center space-x-2">
<input type="checkbox" id="optimize-materials" defaultChecked />
<Label htmlFor="optimize-materials">Optimize materials for real-time rendering</Label>
</div>
</div>
<div className="space-y-4">
<div>
<Label>Morph Target Reduction</Label>
<select className="w-full p-2 border rounded mt-1">
<option>Keep all (Full expressiveness)</option>
<option>Essential only (Performance)</option>
<option>Minimal (Maximum performance)</option>
</select>
</div>
<div>
<Label>Bone Optimization</Label>
<select className="w-full p-2 border rounded mt-1">
<option>Preserve all bones</option>
<option>Merge similar bones</option>
<option>Simplify skeleton</option>
</select>
</div>
<div className="flex items-center space-x-2">
<input type="checkbox" id="physics-ready" defaultChecked />
<Label htmlFor="physics-ready">Prepare for physics simulation</Label>
</div>
</div>
</div>
<Button className="w-full">
<Settings className="h-4 w-4 mr-2" />
Save Settings
</Button>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)
}