From b6017794a5e4263d8e146fff4be7e808fe01b9e5 Mon Sep 17 00:00:00 2001 From: James Twose <39407392+jameshtwose@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:45:25 +0100 Subject: [PATCH] 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. --- .gitignore | 24 ++++ App.tsx | 128 +++++++++++++++++ README.md | 25 ++-- components/AvatarCreator.tsx | 113 +++++++++++++++ components/LoadingSpinner.tsx | 11 ++ components/RiggingEditor.tsx | 224 +++++++++++++++++++++++++++++ components/Studio.tsx | 259 ++++++++++++++++++++++++++++++++++ hooks/useFaceTracking.ts | 140 ++++++++++++++++++ index.html | 45 ++++++ index.tsx | 15 ++ metadata.json | 7 + package.json | 23 +++ services/geminiService.ts | 53 +++++++ services/visionService.ts | 128 +++++++++++++++++ tsconfig.json | 29 ++++ types.ts | 40 ++++++ vite.config.ts | 23 +++ 17 files changed, 1279 insertions(+), 8 deletions(-) create mode 100644 .gitignore create mode 100644 App.tsx create mode 100644 components/AvatarCreator.tsx create mode 100644 components/LoadingSpinner.tsx create mode 100644 components/RiggingEditor.tsx create mode 100644 components/Studio.tsx create mode 100644 hooks/useFaceTracking.ts create mode 100644 index.html create mode 100644 index.tsx create mode 100644 metadata.json create mode 100644 package.json create mode 100644 services/geminiService.ts create mode 100644 services/visionService.ts create mode 100644 tsconfig.json create mode 100644 types.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/App.tsx b/App.tsx new file mode 100644 index 0000000..f10f169 --- /dev/null +++ b/App.tsx @@ -0,0 +1,128 @@ + +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.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(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 ( +
+ {appState === AppState.SETUP && ( +
+
+

+ GEMINI V-STUDIO +

+

+ The next-generation browser-based VTuber studio. Generate your persona with AI and animate it with your face. +

+ +
+ +
+
+
+

AI Generation

+

Describe your dream character. Gemini 3 Pro creates high-fidelity sprites in seconds.

+
+
+
📸
+

Face Tracking

+

Powered by MediaPipe. No expensive equipment needed—just your webcam.

+
+
+
🎥
+

Live Animation

+

Your avatar mimics your head movements and speech in real-time.

+
+
+
+ )} + + {appState === AppState.CREATION && ( +
+ +
+ +
+
+ )} + + {appState === AppState.RIGGING && generatedData && ( +
+ + +
+ )} + + {appState === AppState.STUDIO && avatar && ( + setAppState(AppState.SETUP)} + /> + )} +
+ ); +}; + +export default App; diff --git a/README.md b/README.md index 2241000..5cc8ef4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,20 @@
- GHBanner - -

Built with AI Studio

- -

The fastest path from prompt to production with Gemini.

- - Start building -
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/drive/1Di9b15uKTFXVof4InO8oefefCDaW9Q26 + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/components/AvatarCreator.tsx b/components/AvatarCreator.tsx new file mode 100644 index 0000000..92403e1 --- /dev/null +++ b/components/AvatarCreator.tsx @@ -0,0 +1,113 @@ + +import React, { useState } from 'react'; +import { generateAvatarImage } from '../services/geminiService'; +import { analyzeAvatarImage } from '../services/visionService'; +import LoadingSpinner from './LoadingSpinner'; +import { Rect } from '../types'; + +interface AvatarCreatorProps { + onAvatarGenerated: (url: string, name: string, initialData?: { leftEye: Rect, rightEye: Rect, mouth: Rect, skinColor: string }) => void; +} + +const AvatarCreator: React.FC = ({ onAvatarGenerated }) => { + const [prompt, setPrompt] = useState(''); + const [name, setName] = useState(''); + const [status, setStatus] = useState<'idle' | 'generating' | 'analyzing'>('idle'); + const [error, setError] = useState(null); + + const handleGenerate = async () => { + if (!prompt || !name) return; + + setStatus('generating'); + setError(null); + + try { + // 1. Generate Image + const imageUrl = await generateAvatarImage(prompt); + + // 2. Analyze Image for Landmarks (Initial guess) + setStatus('analyzing'); + const analysisData = await analyzeAvatarImage(imageUrl); + + // 3. Pass to parent (to go to Rigging) + if (analysisData) { + onAvatarGenerated(imageUrl, name, analysisData); + } else { + onAvatarGenerated(imageUrl, name); + } + } catch (err) { + console.error(err); + setError("Failed to generate avatar. Please try again."); + } finally { + setStatus('idle'); + } + }; + + return ( +
+
+

+ Design Your Avatar +

+

+ Describe your dream VTuber model and let Gemini bring it to life. +

+
+ +
+
+ + setName(e.target.value)} + placeholder="e.g., Neon Kitsune" + className="w-full bg-slate-900/50 border border-slate-600 rounded-xl px-4 py-3 text-white placeholder-slate-500 focus:ring-2 focus:ring-cyan-500 focus:border-transparent transition-all outline-none" + /> +
+ +
+ +