/** * @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 ?
); }
Портфолио демонстрирует талант и преданность искусству балерины, её путь от первых шагов в балете до выступлений на престижных сценах. Откройте для себя вдохновляющую историю успеха и глубокого погружения в мир балета.
About our company
Professional photography, set design, top-notch photo editing and retouching
My name is Courtney, I am a photographer based in Berlin. I specialize in portraits, studio photography, and documentary photography.
Портфолио балерины
Познакомьтесь с творчеством Ольги Марченковой
Посмотрите фото и видео работы известной балерины, Примы Большого театра России.
Напишите Ольге Марченковой и поделитесь своими мыслями и вопросами.
Преимущества просмотра художественного портфолио
Откройте для себя лучшие моменты выступлений известной балерины через фотографии и видео.
  • Великолепные фотографии
  • Запечатлённые видеовыступления
  • Известные моменты карьеры
  • Эмоциональная глубина выступлений
  • Высокое качество визуального контента
  • Возможность пережить моменты заново
  • Вдохновение для любителей балета
  • Приобщение к искусству
Вдохновляющие образы современной балетной хореографии
Свяжитесь с нами
Телефон: +7 ХХХ ХХХ-ХХ-ХХ
Электронная почта: example@example.ru
Мы всегда рады ответить на ваши вопросы.
Галерея балетных фотографий и видео
PHOTO CREDITS: Bernard Spragg. NZ, Art Gallery ErgsArt - by ErgSap, Pedro Ribeiro Simões


Все фотографии, тексты и видеоматериалы принадлежат их владельцам и использованы для демонстрации. Пожалуйста, не используйте контент шаблона в коммерческих целях.