/**
* @license
* SPDX-License-Identifier: Apache-2.0
*/
import React, { useState, useRef, useEffect } from 'react';
import { GoogleGenAI } from "@google/genai";
import { motion, AnimatePresence } from 'motion/react';
import {
Upload,
Sparkles,
RefreshCw,
Video,
} from 'lucide-react';
// --- Типы ---
declare global {
interface Window {
aistudio?: {
hasSelectedApiKey: () => Promise;
openSelectKey: () => Promise;
};
}
}
interface GenerationState {
status: 'idle' | 'analyzing' | 'generating' | 'polling' | 'completed' | 'error';
message: string;
}
const DANCE_STYLES = [
{ id: 'auto', label: 'Автоматически' },
{ id: 'ballet', label: 'Балет' },
{ id: 'hiphop', label: 'Хип-хоп' },
{ id: 'contemporary', label: 'Контемпорари' },
{ id: 'jazz', label: 'Джаз' },
{ id: 'breakdance', label: 'Брейк-данс' },
{ id: 'latina', label: 'Латина' },
];
export default function App() {
const [videoUrl, setVideoUrl] = useState(null);
const [generatedVideoUrl, setGeneratedVideoUrl] = useState(null);
const [state, setState] = useState({ status: 'idle', message: '' });
const [analysis, setAnalysis] = useState(null);
const [hasKey, setHasKey] = useState(false);
const [selectedStyle, setSelectedStyle] = useState('auto');
const videoRef = useRef(null);
const canvasRef = useRef(null);
useEffect(() => {
const checkKey = async () => {
if (window.aistudio?.hasSelectedApiKey) {
const selected = await window.aistudio.hasSelectedApiKey();
setHasKey(selected);
}
};
checkKey();
}, []);
const handleFileChange = (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (file) {
setVideoUrl(URL.createObjectURL(file));
setGeneratedVideoUrl(null);
setAnalysis(null);
setState({ status: 'idle', message: '' });
}
};
const openKeySelector = async () => {
if (window.aistudio?.openSelectKey) {
await window.aistudio.openSelectKey();
setHasKey(true);
}
};
const extractFrame = (): Promise => {
return new Promise((resolve, reject) => {
if (!videoRef.current || !canvasRef.current) return reject('Refs not ready');
const video = videoRef.current;
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
if (!context) return reject('Context error');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
context.drawImage(video, 0, 0, canvas.width, canvas.height);
resolve(canvas.toDataURL('image/png').split(',')[1]);
});
};
const synchronizeDance = async () => {
if (!hasKey) { await openKeySelector(); return; }
try {
setState({ status: 'analyzing', message: 'АНАЛИЗ...' });
const frameBase64 = await extractFrame();
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY || '' });
const styleContext = selectedStyle !== 'auto'
? `Пользователь указал стиль: ${DANCE_STYLES.find(s => s.id === selectedStyle)?.label}. `
: "";
const analysisResponse = await ai.models.generateContent({
model: "gemini-3-flash-preview",
contents: [{
parts: [
{ inlineData: { data: frameBase64, mimeType: "image/png" } },
{ text: `${styleContext}Опишите стиль танца, количество танцоров и обстановку. Будьте кратки.` }
]
}]
});
const danceDescription = analysisResponse.text || "Танцевальное выступление.";
setAnalysis(danceDescription);
setState({ status: 'generating', message: 'ГЕНЕРАЦИЯ...' });
const veoAi = new GoogleGenAI({ apiKey: process.env.API_KEY || '' });
const stylePrompt = selectedStyle !== 'auto' ? `в стиле ${DANCE_STYLES.find(s => s.id === selectedStyle)?.label}` : "";
let operation = await veoAi.models.generateVideos({
model: 'veo-3.1-fast-generate-preview',
prompt: `Высококачественное видео, 4 танцора ${stylePrompt} в идеальной синхронности. ${danceDescription}. 4k.`,
image: { imageBytes: frameBase64, mimeType: 'image/png' },
config: { numberOfVideos: 1, resolution: '720p', aspectRatio: '16:9' }
});
setState({ status: 'polling', message: 'УТОЧНЕНИЕ...' });
while (!operation.done) {
await new Promise(r => setTimeout(r, 5000));
operation = await veoAi.operations.getVideosOperation({ operation });
}
const downloadLink = operation.response?.generatedVideos?.[0]?.video?.uri;
const videoResponse = await fetch(downloadLink!, { headers: { 'x-goog-api-key': process.env.API_KEY || '' } });
const blob = await videoResponse.blob();
setGeneratedVideoUrl(URL.createObjectURL(blob));
setState({ status: 'completed', message: 'ГОТОВО' });
} catch (e) {
setState({ status: 'error', message: 'ОШИБКА' });
}
};
return (
SYNCDANCE
DANCE
SYNC
{!videoUrl ? (
) : (
)}
{generatedVideoUrl ?
:
Результат
}
);
}
Page Title
Briefly describe your activity. Double-click to modify texts; open the "Content" tab to edit buttons.