vtube-studio/App.tsx
James Twose b6017794a5 feat: Initialize Gemini V-Studio project setup
Sets up the foundational project structure, including:
- Vite for build tooling.
- React for the UI.
- Tailwind CSS for styling.
- MediaPipe for face tracking capabilities.
- Gemini API integration for avatar generation.
- Basic configuration files (package.json, vite.config.ts, tsconfig.json).
- Initial README with local run instructions.
- Core types and a basic Gemini service for image generation.
2025-11-20 20:45:25 +01:00

129 lines
5.3 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 }) => {
if (generatedData) {
setAvatar({
imageUrl: generatedData.url,
name: generatedData.name,
description: '',
leftEye: data.leftEye,
rightEye: data.rightEye,
mouth: data.mouth,
skinColor: data.skinColor
});
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 neededjust 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;