// Image generation service using GeminiGen.ai (via backend proxy) import type { CharacterAge, CharacterGender } from "../types"; const API_BASE = import.meta.env.VITE_API_URL || "http://localhost:3001"; const DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions"; const getDeepSeekKey = () => import.meta.env.VITE_DEEPSEEK_API_KEY || ""; interface GenerateAvatarOptions { description: string; name?: string; age?: CharacterAge; gender?: CharacterGender; customPrompt?: string; // Если задан - используется напрямую isNsfw?: boolean; } const AGE_PROMPTS: Record = { child: "young child, cute, innocent", teenager: "teenager, young, youthful", adult: "adult, mature", elderly: "elderly, old, wise, wrinkled", }; const GENDER_PROMPTS: Record = { male: "male, man, boy", female: "female, woman, girl", }; /** * Translates Russian text to English using DeepSeek API */ async function translateToEnglish(text: string): Promise { const apiKey = getDeepSeekKey(); if (!apiKey) { console.warn("No DeepSeek API key for translation"); return text; } try { const response = await fetch(DEEPSEEK_API_URL, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ model: "deepseek-chat", messages: [ { role: "system", content: "Translate to English for image generation. Output ONLY the translation, nothing else. Keep character names as-is. Be concise.", }, { role: "user", content: text, }, ], temperature: 0.1, max_tokens: 150, }), }); if (!response.ok) { console.error("Translation failed:", response.status); return text; } const data = await response.json(); const translated = data.choices?.[0]?.message?.content?.trim(); console.log("Translated prompt:", translated); return translated || text; } catch (error) { console.error("Translation error:", error); return text; } } /** * Generates an image from a prompt (portrait format) * Returns image URL */ async function generateImageFromPrompt(prompt: string): Promise { console.log("Generating image with prompt:", prompt); const response = await fetch(`${API_BASE}/api/generate-image`, { method: "POST", headers: { "Content-Type": "application/json", }, credentials: "include", body: JSON.stringify({ prompt }), }); if (!response.ok) { const error = await response.text(); console.error("Grok API error:", error); throw new Error(`Failed to generate image: ${response.status}`); } const data = await response.json(); console.log("GeminiGen response:", data); if (data.url) { return data.url; } if (data.pending && data.uuid) { return await pollForResult(data.uuid); } throw new Error("Unexpected response from image generation API"); } /** * Generates an avatar image using Grok via GeminiGen.ai * If customPrompt is provided, uses it directly * Otherwise builds prompt from description, age, gender * Returns image URL */ export async function generateAvatarUrl( options: GenerateAvatarOptions, ): Promise { const { description, name, age = "adult", gender = "female", customPrompt, isNsfw } = options; // If custom prompt provided - use it directly if (customPrompt && customPrompt.trim()) { return generateImageFromPrompt(customPrompt.trim()); } // Build automatic prompt const firstSentence = description .split(/[.!?。]/)[0] .replace(/\{user\}/gi, name || "character") .trim() .slice(0, 200); const textToTranslate = name ? `${name} - ${firstSentence}` : firstSentence; const englishDesc = await translateToEnglish(textToTranslate); const ageDesc = AGE_PROMPTS[age] || AGE_PROMPTS.adult; const genderDesc = GENDER_PROMPTS[gender] || GENDER_PROMPTS.female; const nsfwTag = isNsfw ? "nsfw, explicit, " : ""; const prompt = `${nsfwTag}anime illustration, ${englishDesc}, ${genderDesc}, ${ageDesc}, full body from head to toe, entire body visible, standing pose, feet visible on ground, wide shot, masterpiece, best quality, highly detailed, vibrant colors`; return generateImageFromPrompt(prompt); } /** * Poll for image generation result */ async function pollForResult(uuid: string): Promise { const maxAttempts = 60; // 2 minutes max const pollInterval = 2000; // 2 seconds for (let i = 0; i < maxAttempts; i++) { await new Promise((resolve) => setTimeout(resolve, pollInterval)); const response = await fetch( `${API_BASE}/api/generate-image/status/${uuid}`, { credentials: "include", }, ); if (!response.ok) { throw new Error("Failed to check generation status"); } const data = await response.json(); console.log("Generation status:", data); if (data.done && data.url) { return data.url; } if (data.error) { throw new Error(data.error); } // Still pending, continue polling } throw new Error("Image generation timed out"); } /** * Pre-fetches an image to check if it loads correctly */ export async function validateImageUrl(url: string): Promise { if (url.startsWith("data:")) { return true; // Base64 is always valid } return new Promise((resolve) => { const img = new Image(); img.onload = () => resolve(true); img.onerror = () => resolve(false); setTimeout(() => resolve(false), 60000); img.src = url; }); }