import { useState, useRef, useCallback } from "react"; const STAB_KEY = "sk-mjCcsKaFNWSiePwT3T7TwQ3D8WkiCRn4jsOMz7fPnWX6TxYT"; const REP_KEY = "r8_B4u3yWtkTVu3KpbMjC9wJVEUIjIBxfg1sd0x9"; const VIDEO_MODELS = [ { id: "wavespeedai/wan-2.1-t2v-480p", name: "⚡ Wan 2.1 Fast", desc: "480p · ~40с · дёшево" }, { id: "wavespeedai/wan-2.1-t2v-720p", name: "🎬 Wan 2.1 HD", desc: "720p · ~2мин" }, { id: "minimax/video-01", name: "🌊 Minimax", desc: "720p · кино" }, ]; const STYLES = [ { label: "Без стиля", suf: "" }, { label: "📷 Фото", suf: ", photorealistic, 8k, detailed" }, { label: "⛩ Аниме", suf: ", anime style, vibrant colors" }, { label: "💻 Цифровой",suf: ", digital art, trending artstation" }, { label: "🖌 Масло", suf: ", oil painting, masterpiece" }, { label: "🧊 3D", suf: ", 3d render, octane, cinematic" }, ]; const SIZES = [ { label: "1:1 Квадрат", val: "1:1" }, { label: "16:9 Широкий", val: "16:9" }, { label: "9:16 Портрет", val: "9:16" }, { label: "4:3 Пейзаж", val: "4:3" }, ]; export default function AIStudio() { const [tab, setTab] = useState("image"); const [prompt, setPrompt] = useState(""); const [negPrompt, setNegPrompt] = useState(""); const [styleIdx, setStyleIdx] = useState(0); const [sizeIdx, setSizeIdx] = useState(0); const [imgModel, setImgModel] = useState("core"); const [imgCount, setImgCount] = useState(1); const [vidModel, setVidModel] = useState(0); const [status, setStatus] = useState("idle"); // idle | loading | polling | done | error const [loadMsg, setLoadMsg] = useState(""); const [progress, setProgress] = useState(0); const [errorMsg, setErrorMsg] = useState(""); const [results, setResults] = useState([]); // [{type,url}] const [gallery, setGallery] = useState([]); const [modal, setModal] = useState(null); const pollRef = useRef(null); const cancelled = useRef(false); // ── GENERATE ── const generate = useCallback(async () => { if (!prompt.trim()) return; if (tab === "image") await genImage(); else await genVideo(); }, [tab, prompt, negPrompt, styleIdx, sizeIdx, imgModel, imgCount, vidModel]); // ── IMAGE: Stability AI ── async function genImage() { setStatus("loading"); setProgress(0); setResults([]); setErrorMsg(""); const msgs = ["Отправляю запрос...", "Генерирую изображение...", "Применяю стиль...", "Финальная обработка..."]; let mi = 0; const t = setInterval(() => { if (mi < msgs.length) setLoadMsg(msgs[mi++]); }, 1500); const prog = setInterval(() => setProgress(p => Math.min(p + 2, 90)), 200); const fullPrompt = prompt + STYLES[styleIdx].suf; const ar = SIZES[sizeIdx].val; const endpoint = imgModel === "ultra" ? "https://api.stability.ai/v2beta/stable-image/generate/ultra" : "https://api.stability.ai/v2beta/stable-image/generate/core"; const jobs = Array.from({ length: imgCount }, async () => { try { const fd = new FormData(); fd.append("prompt", fullPrompt); if (negPrompt) fd.append("negative_prompt", negPrompt); fd.append("output_format", "png"); fd.append("aspect_ratio", ar); const r = await fetch(endpoint, { method: "POST", headers: { Authorization: "Bearer " + STAB_KEY, Accept: "image/*" }, body: fd, }); if (!r.ok) { const j = await r.json().catch(() => ({})); let msg = j.errors?.[0] || j.message || `HTTP ${r.status}`; if (r.status === 401) msg = "Неверный Stability AI ключ"; if (r.status === 402) msg = "Нет кредитов на Stability AI"; return { error: msg }; } const blob = await r.blob(); return { url: URL.createObjectURL(blob), type: "image" }; } catch (e) { return { error: e.message }; } }); const res = await Promise.all(jobs); clearInterval(t); clearInterval(prog); setProgress(100); const ok = res.filter(r => r.url); const errs = res.filter(r => r.error); if (!ok.length) { setStatus("error"); setErrorMsg(errs[0]?.error || "Ошибка"); return; } setResults(ok); setStatus("done"); if (errs.length) setErrorMsg(`⚠️ ${errs.length}/${imgCount} не удалось`); ok.forEach(r => addToGallery({ type: "image", url: r.url, prompt })); } // ── VIDEO: Replicate ── async function genVideo() { setStatus("loading"); setProgress(0); setResults([]); setErrorMsg(""); setLoadMsg("Создаю задачу..."); cancelled.current = false; const model = VIDEO_MODELS[vidModel].id; try { const r = await fetch(`https://api.replicate.com/v1/models/${model}/predictions`, { method: "POST", headers: { Authorization: "Bearer " + REP_KEY, "Content-Type": "application/json", Prefer: "wait=5", }, body: JSON.stringify({ input: { prompt, ...(negPrompt ? { negative_prompt: negPrompt } : {}) } }), }); if (!r.ok) { const j = await r.json().catch(() => ({})); let msg = j.detail || j.error || `HTTP ${r.status}`; if (r.status === 401) msg = "Неверный Replicate ключ"; if (r.status === 402) msg = "Нет кредитов на Replicate"; throw new Error(msg); } const pred = await r.json(); setStatus("polling"); setProgress(5); setLoadMsg("Видео генерируется..."); pollVideo(pred.id, prompt); } catch (e) { setStatus("error"); setErrorMsg("Ошибка: " + e.message); } } function pollVideo(predId, promptText) { let attempts = 0; const MAX = 150; const tick = async () => { if (cancelled.current) return; attempts++; setProgress(Math.min(5 + (attempts / MAX) * 88, 93)); setLoadMsg(`Обрабатывается... (~${Math.round(attempts * 2)}с)`); if (attempts > MAX) { setStatus("error"); setErrorMsg("Время ожидания истекло"); return; } try { const r = await fetch(`https://api.replicate.com/v1/predictions/${predId}`, { headers: { Authorization: "Bearer " + REP_KEY }, }); const data = await r.json(); if (data.status === "succeeded") { let url = Array.isArray(data.output) ? data.output[0] : data.output; if (!url) throw new Error("URL не найден в ответе"); setProgress(100); setResults([{ type: "video", url }]); setStatus("done"); addToGallery({ type: "video", url, prompt: promptText }); } else if (data.status === "failed" || data.status === "canceled") { throw new Error(data.error || "Генерация завершилась ошибкой"); } else { pollRef.current = setTimeout(tick, 2000); } } catch (e) { setStatus("error"); setErrorMsg("Ошибка: " + e.message); } }; pollRef.current = setTimeout(tick, 3000); } function cancelPoll() { cancelled.current = true; if (pollRef.current) clearTimeout(pollRef.current); setStatus("idle"); } function addToGallery(item) { setGallery(g => [item, ...g].slice(0, 32)); } const isbusy = status === "loading" || status === "polling"; return (
{/* BG glow */}
{/* ── HEADER ── */}
AIGEN
ИИ подключён и готов
{/* ── HERO ── */}
✦ Всё готово — просто опиши и создай
Создай{" "} настоящее
Реальные картинки через Stable Diffusion и видео через Wan/Minimax
{/* ── TABS ── */}
{[["image","🖼 Изображение"],["video","🎬 Видео"]].map(([t, label]) => ( ))}
{/* ── WORKSPACE ── */}
{/* LEFT: SETTINGS */}
{tab === "image" ? "НАСТРОЙКИ ИЗОБРАЖЕНИЯ" : "НАСТРОЙКИ ВИДЕО"}
{tab === "image" ? "Stable Diffusion" : "Replicate"}