Enables users to upload custom avatar assets and automatically remove the background from the generated image. New features: - Avatar creation now supports uploading base, blink, and talk textures. - Added ability to define the main body bounding box during rigging. - Vision service now includes image segmentation for background removal. - Studio component dynamically processes the avatar image for background removal if chroma key is enabled.
137 lines
5.6 KiB
TypeScript
137 lines
5.6 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|
import { AppState, AvatarConfig, Rect } from './types';
|
|
import AvatarCreator from './components/AvatarCreator';
|
|
import RiggingEditor from './components/RiggingEditor';
|
|
import Studio from './components/Studio';
|
|
|
|
const App: React.FC = () => {
|
|
const [appState, setAppState] = useState<AppState>(AppState.SETUP);
|
|
// Temp storage for the generated image before rigging
|
|
const [generatedData, setGeneratedData] = useState<{url: string, name: string, initialData?: any} | null>(null);
|
|
const [avatar, setAvatar] = useState<AvatarConfig | null>(null);
|
|
|
|
const handleStartCreation = async () => {
|
|
try {
|
|
if (window.aistudio) {
|
|
const hasKey = await window.aistudio.hasSelectedApiKey();
|
|
if (!hasKey) {
|
|
await window.aistudio.openSelectKey();
|
|
}
|
|
}
|
|
setAppState(AppState.CREATION);
|
|
} catch (error) {
|
|
console.error("Error during API key selection:", error);
|
|
setAppState(AppState.CREATION);
|
|
}
|
|
};
|
|
|
|
const handleAvatarGenerated = (url: string, name: string, initialData?: any) => {
|
|
setGeneratedData({ url, name, initialData });
|
|
setAppState(AppState.RIGGING);
|
|
};
|
|
|
|
const handleRiggingComplete = (data: {
|
|
leftEye: Rect, rightEye: Rect, mouth: Rect, skinColor: string,
|
|
textureClosedEye: Rect, textureOpenMouth: Rect,
|
|
mainBody: Rect, chromaKeyColor: string
|
|
}) => {
|
|
if (generatedData) {
|
|
setAvatar({
|
|
imageUrl: generatedData.url,
|
|
name: generatedData.name,
|
|
description: '',
|
|
leftEye: data.leftEye,
|
|
rightEye: data.rightEye,
|
|
mouth: data.mouth,
|
|
skinColor: data.skinColor,
|
|
textureClosedEye: data.textureClosedEye,
|
|
textureOpenMouth: data.textureOpenMouth,
|
|
mainBody: data.mainBody,
|
|
chromaKeyColor: data.chromaKeyColor
|
|
});
|
|
setAppState(AppState.STUDIO);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-slate-900 text-white">
|
|
{appState === AppState.SETUP && (
|
|
<div className="container mx-auto px-4 py-12 flex flex-col items-center justify-center min-h-screen">
|
|
<div className="text-center mb-12 space-y-4">
|
|
<h1 className="text-6xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 via-blue-500 to-purple-600 brand-font tracking-tighter">
|
|
GEMINI V-STUDIO
|
|
</h1>
|
|
<p className="text-xl text-slate-400 max-w-2xl mx-auto">
|
|
The next-generation browser-based VTuber studio. Generate your persona with AI and animate it with your face.
|
|
</p>
|
|
<button
|
|
onClick={handleStartCreation}
|
|
className="mt-8 px-8 py-4 bg-white text-slate-900 rounded-full font-bold hover:bg-cyan-50 transition-colors shadow-[0_0_20px_rgba(255,255,255,0.3)]"
|
|
>
|
|
Start Creation
|
|
</button>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 w-full max-w-5xl">
|
|
<div className="p-6 bg-slate-800/50 rounded-xl border border-slate-700 backdrop-blur-sm">
|
|
<div className="h-12 w-12 bg-cyan-500/10 rounded-lg flex items-center justify-center mb-4 text-2xl">✨</div>
|
|
<h3 className="text-xl font-bold mb-2">AI Generation</h3>
|
|
<p className="text-slate-400">Describe your dream character. Gemini 3 Pro creates high-fidelity sprites in seconds.</p>
|
|
</div>
|
|
<div className="p-6 bg-slate-800/50 rounded-xl border border-slate-700 backdrop-blur-sm">
|
|
<div className="h-12 w-12 bg-purple-500/10 rounded-lg flex items-center justify-center mb-4 text-2xl">📸</div>
|
|
<h3 className="text-xl font-bold mb-2">Face Tracking</h3>
|
|
<p className="text-slate-400">Powered by MediaPipe. No expensive equipment needed—just your webcam.</p>
|
|
</div>
|
|
<div className="p-6 bg-slate-800/50 rounded-xl border border-slate-700 backdrop-blur-sm">
|
|
<div className="h-12 w-12 bg-pink-500/10 rounded-lg flex items-center justify-center mb-4 text-2xl">🎥</div>
|
|
<h3 className="text-xl font-bold mb-2">Live Animation</h3>
|
|
<p className="text-slate-400">Your avatar mimics your head movements and speech in real-time.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{appState === AppState.CREATION && (
|
|
<div className="container mx-auto px-4 py-12 min-h-screen flex flex-col">
|
|
<button
|
|
onClick={() => setAppState(AppState.SETUP)}
|
|
className="self-start mb-8 px-4 py-2 text-slate-400 hover:text-white transition-colors"
|
|
>
|
|
← Back to Home
|
|
</button>
|
|
<div className="flex-1 flex items-center justify-center">
|
|
<AvatarCreator onAvatarGenerated={handleAvatarGenerated} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{appState === AppState.RIGGING && generatedData && (
|
|
<div className="container mx-auto px-4 py-8 min-h-screen flex flex-col">
|
|
<button
|
|
onClick={() => setAppState(AppState.CREATION)}
|
|
className="self-start mb-4 px-4 py-2 text-slate-400 hover:text-white transition-colors"
|
|
>
|
|
← Back to Generator
|
|
</button>
|
|
<RiggingEditor
|
|
imageUrl={generatedData.url}
|
|
initialData={generatedData.initialData}
|
|
onComplete={handleRiggingComplete}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{appState === AppState.STUDIO && avatar && (
|
|
<Studio
|
|
avatar={avatar}
|
|
onBack={() => setAppState(AppState.SETUP)}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App;
|