### **1. GREENS (Left Half: 0–500px Width)** - **Flower Area (Triangle)**: - **Corners**: (100,700) → (400,700) → (250,900). - **Label**: "FLOWERS" centered at (250,800). - **Small Lake**: - **Circle**: Center at (400,300), radius 80px. - **Label**: "LAKE" at (400,200). - **Grass**: Solid green fill from (0,0) to (500,1000). --- ### **2. HOME (Center-Right Vertical Strip: 500–700px Width)** - **Villa (HOME)**: - **Rectangle**: (550,400) to (700,600). - **Label**: "HOME" at (625,500). - **Garage**: - **Rectangle**: (700,450) to (750,550). - **Label**: "GARAGE" at (725,500). - **Gray Car Path (LANE)**: - **Road**: (500,950) to (700,400), width 60px. - **Fountain (Blue Circle)**: Center at (600,675), radius 30px. - **Label**: "MAIN ENTRANCE" at (600,975), "LANE" at (600,750). - **Stepping Stone Paths**: - **To Pool**: Dotted line from (625,600) → (450,500). - **To Gazebo**: Dotted line from (675,600) → (800,300). --- ### **3. AGRICULTURE (Right Half: 700–1000px Width)** - **Agricultural Field**: - **Rectangle**: (700,0) to (1000,1000). - **Label**: "AGRICULTURAL FIELD" at (850,50). - **Animal Enclosures**: - **Rectangles**: 1. (720,100) to (820,250). 2. (720,260) to (820,400). - **Label**: "ANIMAL ENCLOSURES" at (770,50). - **Vegetable Garden**: - **Rectangle**: (700,450) to (1000,550). - **Label**: "VEGETABLE GARDEN" at (850,500). --- ### **4. Trees & Boundaries** - **Road Trees**: Line both sides of the gray path from (500,950) to (700,400), spaced 50px apart. - **Field Boundary Trees**: Vertical line from (700,400) to (700,600), separating HOME and AGRICULTURE. --- ### **5. Gazebo & Pool** - **Gazebo (Pentagon)**: - **Corners**: (800,250) → (850,200) → (900,250) → (875,300) → (825,300). - **Label**: "GAZEBO" at (850,350). - **Pool**: Unlabeled oval at (450,450) to (550,550). --- ### **Style Instructions** - **2D Flat Design**: No shadows, gradients, or perspective. - **Colors**: - Gray path: `#808080` - Green zones: `#90EE90` - Yellow agriculture: `#FFFF00` - Blue fountain/lake: `#4682B4` - Orange enclosures: `#FFA500`
Over-the-Shoulder Shot (Action/Dialogue Implied) Ultra-realistic, dynamic over-the-shoulder (OTS) cinematic shot of the same man in a black tuxedo. Face match 100.9999%. Aspect Ratio: 16:9. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/100, ISO 400, tungsten white balance. Composition: The camera is positioned behind an unseen, heavily blurred second figure's shoulder (only the dark blur is visible in the foreground), focusing on the main subject who is standing in profile or three-quarter view, looking intently at the blurred figure. His hand is gesturing slightly, mid-sentence. Mood & Tone: Intense, focused, and suggestive of a high-stakes conversation. Shallow depth of field ensures the focus is purely on the subject's reaction. Color Palette: Cinematic warm and cool tones playing on the faces, heavy Portra 400 glow. Shot Detail: Intense eye contact, blurred foreground object adds a layer of secrecy/eavesdropping
He optimizado tu código para lograr una modulación vocal continua y fluida basada en los sliders, con caché de audio, timeouts y mejor manejo del estado. Ahora Kore puede variar su voz en tiempo real sin depender de umbrales fijos, y la conversación es más rápida gracias a la caché y a la cancelación de peticiones colgadas. ```javascript import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, Mic, MicOff, Settings2, Activity, Loader2, X, GripHorizontal, LayoutGrid, Zap, AlertCircle } from 'lucide-react'; // --- CONSTANTES --- const SILENT_WAV = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; const TTS_TIMEOUT = 5000; // 5 segundos máximo para la síntesis const DEFAULT_API_KEY = 'AIzaSyBlkvy_Op-XlzSMSDDl9ip42dMFZX28MAA'; // ⚠️ Cámbiala por tu propia clave // --- UTILIDADES --- const base64ToWavBlob = (base64Data, sampleRate = 24000) => { const binaryString = window.atob(base64Data); const pcmData = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) pcmData[i] = binaryString.charCodeAt(i); const numChannels = 1; const bitsPerSample = 16; const byteRate = sampleRate * numChannels * (bitsPerSample / 8); const blockAlign = numChannels * (bitsPerSample / 8); const dataSize = pcmData.length; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < dataSize; i++) view.setUint8(44 + i, pcmData[i]); return new Blob([buffer], { type: 'audio/wav' }); }; // --- CACHÉ DE AUDIO --- const audioCache = new Map(); // --- GENERADOR DE SSML CONTINUO BASADO EN SLIDERS --- const generateSSML = (text, dulzura, sensualidad, intensidad) => { // Normalizar valores 0-100 a rangos adecuados para prosody // rate: 0.5 a 2.0 (1.0 es normal) const rate = 0.8 + (intensidad / 100) * 1.2; // 0.8 (lento) a 2.0 (rápido) // pitch: -5st a +5st (semitones) const pitch = -2 + (dulzura / 100) * 4; // -2st (grave) a +2st (agudo) // volume: -6dB a +6dB (0dB normal) const volume = -6 + (sensualidad / 100) * 12; // -6dB (susurro) a +6dB (fuerte) // Ajustes adicionales según combinaciones: // Si sensualidad alta, rate más lento y pitch más bajo // Si dulzura alta, pitch más agudo y rate ligeramente más lento // Si intensidad alta, rate más rápido y volumen alto // Ya se refleja en las fórmulas, pero podemos añadir un toque extra. const ssml = `<speak> <prosody rate="${rate.toFixed(2)}" pitch="${pitch.toFixed(0)}st" volume="${volume.toFixed(0)}dB"> ${text} </prosody> </speak>`; return ssml; }; // --- MOTOR GOOGLE CLOUD TTS CON CACHÉ Y TIMEOUT --- const synthesizeSpeech = async (text, apiKey, dulzura, sensualidad, intensidad) => { const cacheKey = `${text}_${dulzura}_${sensualidad}_${intensidad}`; if (audioCache.has(cacheKey)) { console.log('🎯 Usando audio cacheado'); return audioCache.get(cacheKey); } const ssml = generateSSML(text, dulzura, sensualidad, intensidad); const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`; const body = { input: { ssml }, voice: { languageCode: 'es-ES', name: 'es-ES-Neural2-F', ssmlGender: 'FEMALE' }, audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 24000 } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TTS_TIMEOUT); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`TTS error: ${res.status}`); const data = await res.json(); audioCache.set(cacheKey, data.audioContent); return data.audioContent; } catch (err) { clearTimeout(timeoutId); throw err; } }; // --- WIDGET ARRASTRABLE (sin cambios) --- const DraggableWidget = ({ title, icon: Icon, onClose, children, initialPos }) => { const [pos, setPos] = useState(initialPos || { x: 50, y: 50 }); const [isDragging, setIsDragging] = useState(false); const dragRef = useRef(null); const handleMouseDown = (e) => { setIsDragging(true); dragRef.current = { startX: e.clientX, startY: e.clientY, initialX: pos.x, initialY: pos.y }; }; const handleMouseMove = (e) => { if (!isDragging) return; setPos({ x: Math.max(0, dragRef.current.initialX + (e.clientX - dragRef.current.startX)), y: Math.max(0, dragRef.current.initialY + (e.clientY - dragRef.current.startY)) }); }; const handleMouseUp = () => setIsDragging(false); useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div style={{ left: `${pos.x}px`, top: `${pos.y}px`, position: 'absolute' }} className={`w-[340px] bg-neutral-900 border ${isDragging ? 'border-emerald-500 shadow-emerald-900/20' : 'border-neutral-700'} rounded-xl shadow-2xl flex flex-col overflow-hidden transition-shadow duration-200 z-50`} > <div onMouseDown={handleMouseDown} className="bg-neutral-950 px-3 py-2 flex items-center justify-between cursor-move select-none border-b border-neutral-800"> <div className="flex items-center gap-2 text-neutral-400"> <GripHorizontal size={14} className="opacity-50" /> {Icon && <Icon size={14} className="text-emerald-500" />} <span className="text-xs font-bold tracking-wider">{title}</span> </div> <button onClick={onClose} className="text-neutral-500 hover:text-red-400 transition-colors"><X size={16} /></button> </div> <div className="p-4 flex-1 overflow-y-auto">{children}</div> </div> ); }; // --- WIDGET PRINCIPAL: MODULADOR VOCAL KORE (MEJORADO) --- const VoiceModulatorWidget = () => { const [text, setText] = useState(''); const [apiKey, setApiKey] = useState(DEFAULT_API_KEY); const [dulzura, setDulzura] = useState(50); const [sensualidad, setSensualidad] = useState(50); const [intensidad, setIntensidad] = useState(50); const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isHandsFree, setIsHandsFree] = useState(false); const [statusMsg, setStatusMsg] = useState('Enlace 1.5 Flash + GCP TTS Establecido.'); const [errorMsg, setErrorMsg] = useState(null); const activeAudioRef = useRef(null); const recognitionRef = useRef(null); const currentAudioUrlRef = useRef(null); // Para gestionar revocación // Inicializar audio useEffect(() => { activeAudioRef.current = new Audio(); activeAudioRef.current.preload = "auto"; return () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } } if (recognitionRef.current) recognitionRef.current.stop(); }; }, []); // Configurar reconocimiento de voz useEffect(() => { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { setErrorMsg('Reconocimiento de voz no soportado.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.lang = 'es-ES'; recognitionRef.current.interimResults = false; recognitionRef.current.continuous = false; recognitionRef.current.onresult = async (event) => { const transcript = event.results[0][0].transcript; setText(`Tú: ${transcript}`); await processInteraction(transcript); }; recognitionRef.current.onerror = (event) => { if (event.error !== 'no-speech') { setErrorMsg(`Micrófono: ${event.error}`); setIsHandsFree(false); } }; recognitionRef.current.onend = () => { if (isHandsFree && !isPlaying && !isLoading) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; // Limpiar al desmontar return () => { if (recognitionRef.current) recognitionRef.current.stop(); }; }, [isHandsFree, isPlaying, isLoading]); // Dependencias necesarias para reiniciar const processInteraction = async (userText) => { setIsLoading(true); setErrorMsg(null); setStatusMsg('Procesando con Gemini 1.5 Flash...'); const key = apiKey.trim(); try { // 1. Llamada a Gemini con timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const payloadText = { contents: [{ parts: [{ text: userText }] }], systemInstruction: { parts: [{ text: `Eres Kore, 28 años, Huesca. Labia, directa, sensual, disruptiva. Responde en español, máx 20 palabras. REGLA INNEGOCIABLE: CERO PREGUNTAS.` }] } }; const resText = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payloadText), signal: controller.signal } ); clearTimeout(timeoutId); if (!resText.ok) throw new Error(`Gemini error: ${resText.status}`); const dataText = await resText.json(); const aiText = dataText.candidates?.[0]?.content?.parts?.[0]?.text || "Mmm... vale."; setText(`Kore: ${aiText}`); // 2. Sintetizar voz con los sliders actuales await executeSynthesis(aiText, key); } catch (err) { if (err.name === 'AbortError') { setErrorMsg('Gemini timeout (5s)'); } else { setErrorMsg(err.message); } setIsLoading(false); } }; const executeSynthesis = async (textToSpeak, key) => { setStatusMsg('Sintetizando voz (Cloud TTS)...'); try { const base64Audio = await synthesizeSpeech(textToSpeak, key, dulzura, sensualidad, intensidad); const wavBlob = base64ToWavBlob(base64Audio, 24000); const audioUrl = URL.createObjectURL(wavBlob); // Revocar URL anterior si existe if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } currentAudioUrlRef.current = audioUrl; activeAudioRef.current.src = audioUrl; activeAudioRef.current.onended = () => { setIsPlaying(false); setStatusMsg('Transmisión completada.'); if (isHandsFree) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; setStatusMsg('Transmitiendo...'); setIsPlaying(true); setIsLoading(false); await activeAudioRef.current.play().catch(err => { throw new Error(`Autoplay bloqueado: ${err.message}`); }); } catch (error) { throw new Error(`Fallo TTS: ${error.message}`); } }; const handleManualPlay = async () => { if (!text.trim()) return setErrorMsg('Escribe algo primero.'); // Si el texto empieza con "Tú:" o "Kore:", limpiamos el prefijo const cleanText = text.replace(/^(Tú:|Kore:)\s*/, ''); if (!cleanText.trim()) return setErrorMsg('Texto vacío después de limpiar.'); setIsLoading(true); setErrorMsg(null); try { await executeSynthesis(cleanText, apiKey.trim()); } catch (err) { setErrorMsg(err.message); setIsLoading(false); } }; const toggleHandsFree = () => { if (!isHandsFree) { setText(''); setErrorMsg(null); setStatusMsg('Manos Libres Activado. Habla...'); // Desbloquear audio en algunos navegadores if (activeAudioRef.current) { activeAudioRef.current.src = SILENT_WAV; activeAudioRef.current.play().catch(() => {}); } try { recognitionRef.current.start(); } catch (e) {} } else { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Sistemas en pausa.'); if (recognitionRef.current) recognitionRef.current.stop(); } setIsHandsFree(!isHandsFree); }; const stopAudio = () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Señal interrumpida.'); }; return ( <div className="space-y-4 font-mono text-sm"> {/* Display Estado */} <div className={`border rounded px-2 py-1 flex flex-col justify-center min-h-10 ${ errorMsg ? 'bg-red-950/50 border-red-900' : isHandsFree ? 'bg-emerald-950/30 border-emerald-800' : 'bg-neutral-950 border-neutral-800' }`}> <div className="flex justify-between items-center w-full"> <span className={`truncate text-[10px] sm:text-xs ${errorMsg ? 'text-red-500' : 'text-emerald-500'}`}> > {errorMsg || statusMsg} </span> {isPlaying && !errorMsg && <Activity size={14} className="text-emerald-500 animate-pulse ml-2 flex-shrink-0" />} {isLoading && !errorMsg && <Zap size={14} className="text-amber-500 animate-pulse ml-2 flex-shrink-0" />} {isHandsFree && !isPlaying && !isLoading && !errorMsg && <Mic size={14} className="text-red-500 animate-pulse ml-2 flex-shrink-0" />} </div> </div> {/* Input Texto / Log */} <textarea value={text} onChange={(e) => setText(e.target.value)} className="w-full bg-neutral-950/50 border border-neutral-700 rounded p-2 text-xs text-neutral-300 focus:outline-none focus:border-emerald-500 resize-none h-20" placeholder={isHandsFree ? "Escuchando transcripción en tiempo real..." : "Escribe texto directo o activa Manos Libres..."} readOnly={isHandsFree || isLoading} /> {/* Sliders continuos (controlan SSML en tiempo real) */} <div className="space-y-3 bg-neutral-950/30 p-3 rounded border border-neutral-800"> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Agresiva</span><span className="text-emerald-400">Dulzura [{dulzura}]</span><span>Dulce</span> </div> <input type="range" min="0" max="100" value={dulzura} onChange={(e)=>setDulzura(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-emerald-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Robótica</span><span className="text-pink-400">Aura [{sensualidad}]</span><span>Sensual</span> </div> <input type="range" min="0" max="100" value={sensualidad} onChange={(e)=>setSensualidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-pink-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Atenuada</span><span className="text-amber-400">Intensidad [{intensidad}]</span><span>Fuerte</span> </div> <input type="range" min="0" max="100" value={intensidad} onChange={(e)=>setIntensidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-amber-500 cursor-pointer" /> </div> </div> {/* Botones de Control */} <div className="flex flex-col sm:flex-row gap-2"> <button onClick={toggleHandsFree} disabled={isLoading} className={`flex-1 py-2 rounded text-xs font-bold flex items-center justify-center gap-2 transition-colors border ${ isHandsFree ? 'bg-red-900/20 text-red-400 border-red-900/50 hover:bg-red-900/40 shadow-[0_0_10px_rgba(239,68,68,0.2)]' : 'bg-indigo-900/20 text-indigo-400 border-indigo-900/50 hover:bg-indigo-900/40' }`} > {isHandsFree ? <MicOff size={14} /> : <Mic size={14} />} {isHandsFree ? 'Detener Escucha' : 'Manos Libres'} </button> <div className="flex gap-2 flex-1"> <button onClick={handleManualPlay} disabled={isLoading || isPlaying || isHandsFree} className="flex-1 bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 border border-emerald-600/50 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center gap-1 transition-colors" > {isLoading ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />} Sintetizar </button> <button onClick={stopAudio} disabled={!isPlaying && !isHandsFree} className="px-4 bg-neutral-800 hover:bg-neutral-700 text-neutral-400 border border-neutral-700 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center transition-colors" > <Square size={14} /> </button> </div> </div> {/* Botón para limpiar caché (opcional) */} <div className="text-right"> <button onClick={() => audioCache.clear()} className="text-[8px] text-neutral-600 hover:text-neutral-400 underline" > limpiar caché de audio </button> </div> </div> ); }; // --- ENTORNO ESCRITORIO (sin cambios) --- export default function App() { const [widgets, setWidgets] = useState({ voice: { isOpen: true, pos: { x: window.innerWidth > 768 ? window.innerWidth / 2 - 170 : 20, y: 40 } } }); const toggleWidget = (id) => { setWidgets(prev => ({ ...prev, [id]: { ...prev[id], isOpen: !prev[id].isOpen } })); }; return ( <div className="w-full h-screen bg-neutral-950 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.1),rgba(0,0,0,1))] overflow-hidden relative font-sans text-neutral-200"> <div className="absolute inset-0 flex items-center justify-center opacity-[0.02] pointer-events-none"><Settings2 size={500} /></div> {widgets.voice.isOpen && ( <DraggableWidget title="MODULADOR VOCAL KORE" icon={Zap} initialPos={widgets.voice.pos} onClose={() => toggleWidget('voice')}> <VoiceModulatorWidget /> </DraggableWidget> )} <div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-neutral-900/80 backdrop-blur-md border border-neutral-700/50 p-2 rounded-2xl shadow-2xl flex gap-2 z-[100]"> <div className="px-3 flex items-center border-r border-neutral-700/50 text-neutral-500"><LayoutGrid size={20} /></div> <button onClick={() => toggleWidget('voice')} className={`px-4 py-2 rounded-xl flex items-center gap-2 text-sm font-medium transition-all ${
https://s.mj.run/1150bpD5hFw Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1.8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --ar 4:5 --style raw --stylize 400 --c 50
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.8> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8>
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.6> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8> <lora:3DMM_V12:0.8>
Create a movie-level ultra-realistic photo of a upload image of a man. The face matches the reference image 100.9999% 35mm Kodak Portra 400 film, cinematic portrait shot during golden hour. Aspect ratio 9:16 Captured with a Canon AE-1 using 50mm f/1.4 lens, shutter speed 1/250s, white balance 5200K (daylight), ISO 400. A mid-shot portrait of a man sitting in a vintage convertible car, leaning on the door with his left arm resting casually. He wears a light beige linen blazer, striped open-collar shirt, and leather bracelets. His gaze is directed toward the horizon, expressing contemplation and calm confidence. The composition uses soft, natural sunlight that illuminates his face from the side, creating gentle golden highlights and smooth shadows across his profile and the car’s curves. The focus is on the man’s thoughtful expression, with shallow depth of field (f/1.4) blurring the background sky for cinematic separation. Mood & Tone: nostalgic, warm, introspective, timeless elegance. Color Palette: soft amber, golden beige, charcoal black, muted sky blue. Film Aesthetic: subtle grain, creamy highlights, balanced contrast typical of Portra 400. keep face features original
a woman sitting on a rock next to a dragon in the woods with her head turned to the side, Clint Cearley, epic fantasy character art, a character portrait, fantasy art, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera
Adjustment of Cufflink (Detailed Mid-Shot) Ultra-realistic, mid-shot portrait focusing on a precise detail. Face match 100.9999%. Aspect Ratio: 4:5. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/125, ISO 400, tungsten white balance. Composition: Tight mid-shot where the man is looking down, intensely focused on adjusting a simple, elegant cufflink. His face is partially obscured by his hand, but the concentration in his brow is visible. Mood & Tone: Obsessive perfection, quiet preparation, focused intensity. Lighting: A single, small directional light source highlights the wrist, the cufflink, and the texture of the crisp white shirt cuff. Color Palette: High contrast between the dark tuxedo and the bright, yet warm-toned white shirt. Shot Detail: Hyper-detailed focus on the metal texture of the cufflink and the fabric fibers, very shallow depth of field
Fashion editorial style grand photograph, hair light, Polaroid, F/5, dramatic lighting, Samsung Galaxy, F/1.8, rim light, Iphone X, L USM, side light, film camera, 800mm lens, waning light, Nikon d3300, 35mm, Proud, key light, compact camera, telephoto lens, Bloom light, Kodak portra 800, Depth of field 270mm, Black lighting, Kodak gold 200, F/2.8, soft light, Phase One XF IQ4 150MP, F/14, Muted Colors, Motion blur, Canon 5d mark 4, Circular polarizer, (young woman, very thin, small breasts, abs, supermodel in post modern environment :1.3) with Purple skin, Bedouin hair, Direct light, Nikon d850, macro lens, flat lighting, Canon RF, F/8, cinematic lighting, Fujifilm XT3, 80mm, Gel lighting, Ilford HP5, Zoom lens, Low Contrast, studio lighting, Fuji superia 400, Low shutter, Neon Light, Nikon Z9, Depth of field 100mm, specular lighting, Kodak portra 400, 50mm, Accent lighting, dslr, Fish-eye Lens, Nostalgic lighting, Canon eos 5d mark 4, Selective focus, two colors . High fashion, trendy, stylish, editorial, magazine style, professional, highly detailed
animal look, https://s.mj.run/1150bpD5hFw Smiling and looking to the camera, Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1. 8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --style raw --stylize 400 --c 50 --ar 4:5
At this time, Heaven first had a foundation. 5,400 years later, in the middle of Phase l, the light and pure roseupwards, and sun, moon, stars, and constellations were created. These were called the Four Images. Hence thesaying that heaven began in I. Another 5.400 years later, when Phase I was nearing its end and Phase Il was imminent, things graduallysolidified. As the Book of Changes says, "Great is the Positive; far-reaching is the Negative! All things areendowed and born in accordance with Heaven." This was when the earth began to congeal. After 5,400 morevears came the height of Phase Il. when the heavy and impure solidified. and water, fire, mountains. stoneand Earth came into being. These five were called the Five Movers. Therefore it is said that the Earth wascreated in Phase Il. After a further 5,400 years, at the end of Phase Il and the beginning of the Phase Ill, living beings werecreated. In the words of the Book of the Calendar, "The essence of the sky came down and the essence ofearth went up. Heaven and Earth intermingled, and all creatures were born." Then Heaven was bright andEarth was fresh, and the Positive intermingled with the Negative. 5.400 years later, when Phase Il was at itsheight, men, birds and beasts were created. Thus the Three Powers--Heaven, Earth and Man--now had theitset places. Therefore it is said that man was created in Phase Ill. Moved by Pan Cu's creation, the Three Emperors put the world in order and the Five Rulers laid down themoral code. The world was then divided into four great continents: The Eastern Continent of Superior Body.the Western Continent of Cattle-gift, the Southern Continent of Jambu and the Northern Continent of KuruThis book deals only with the Eastern Continent of Superior Body. Beyond the seas there is a country calledAolai. This country is next to an ocean, and in the middle of the ocean is a famous island called the Mountainof Flowers and Fruit. This mountain is the ancestral artery of the Ten Continents, the oripin of the Threeslands: it was formed when the clear and impure were separated and the Enormous Vagueness was divided. Itis a really splendid mountain and there are some verses to prove it:
Over-the-Shoulder Shot (Action/Dialogue Implied) Ultra-realistic, dynamic over-the-shoulder (OTS) cinematic shot of the same man in a black tuxedo. Face match 100.9999%. Aspect Ratio: 16:9. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/100, ISO 400, tungsten white balance. Composition: The camera is positioned behind an unseen, heavily blurred second figure's shoulder (only the dark blur is visible in the foreground), focusing on the main subject who is standing in profile or three-quarter view, looking intently at the blurred figure. His hand is gesturing slightly, mid-sentence. Mood & Tone: Intense, focused, and suggestive of a high-stakes conversation. Shallow depth of field ensures the focus is purely on the subject's reaction. Color Palette: Cinematic warm and cool tones playing on the faces, heavy Portra 400 glow. Shot Detail: Intense eye contact, blurred foreground object adds a layer of secrecy/eavesdropping
He optimizado tu código para lograr una modulación vocal continua y fluida basada en los sliders, con caché de audio, timeouts y mejor manejo del estado. Ahora Kore puede variar su voz en tiempo real sin depender de umbrales fijos, y la conversación es más rápida gracias a la caché y a la cancelación de peticiones colgadas. ```javascript import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, Mic, MicOff, Settings2, Activity, Loader2, X, GripHorizontal, LayoutGrid, Zap, AlertCircle } from 'lucide-react'; // --- CONSTANTES --- const SILENT_WAV = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; const TTS_TIMEOUT = 5000; // 5 segundos máximo para la síntesis const DEFAULT_API_KEY = 'AIzaSyBlkvy_Op-XlzSMSDDl9ip42dMFZX28MAA'; // ⚠️ Cámbiala por tu propia clave // --- UTILIDADES --- const base64ToWavBlob = (base64Data, sampleRate = 24000) => { const binaryString = window.atob(base64Data); const pcmData = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) pcmData[i] = binaryString.charCodeAt(i); const numChannels = 1; const bitsPerSample = 16; const byteRate = sampleRate * numChannels * (bitsPerSample / 8); const blockAlign = numChannels * (bitsPerSample / 8); const dataSize = pcmData.length; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < dataSize; i++) view.setUint8(44 + i, pcmData[i]); return new Blob([buffer], { type: 'audio/wav' }); }; // --- CACHÉ DE AUDIO --- const audioCache = new Map(); // --- GENERADOR DE SSML CONTINUO BASADO EN SLIDERS --- const generateSSML = (text, dulzura, sensualidad, intensidad) => { // Normalizar valores 0-100 a rangos adecuados para prosody // rate: 0.5 a 2.0 (1.0 es normal) const rate = 0.8 + (intensidad / 100) * 1.2; // 0.8 (lento) a 2.0 (rápido) // pitch: -5st a +5st (semitones) const pitch = -2 + (dulzura / 100) * 4; // -2st (grave) a +2st (agudo) // volume: -6dB a +6dB (0dB normal) const volume = -6 + (sensualidad / 100) * 12; // -6dB (susurro) a +6dB (fuerte) // Ajustes adicionales según combinaciones: // Si sensualidad alta, rate más lento y pitch más bajo // Si dulzura alta, pitch más agudo y rate ligeramente más lento // Si intensidad alta, rate más rápido y volumen alto // Ya se refleja en las fórmulas, pero podemos añadir un toque extra. const ssml = `<speak> <prosody rate="${rate.toFixed(2)}" pitch="${pitch.toFixed(0)}st" volume="${volume.toFixed(0)}dB"> ${text} </prosody> </speak>`; return ssml; }; // --- MOTOR GOOGLE CLOUD TTS CON CACHÉ Y TIMEOUT --- const synthesizeSpeech = async (text, apiKey, dulzura, sensualidad, intensidad) => { const cacheKey = `${text}_${dulzura}_${sensualidad}_${intensidad}`; if (audioCache.has(cacheKey)) { console.log('🎯 Usando audio cacheado'); return audioCache.get(cacheKey); } const ssml = generateSSML(text, dulzura, sensualidad, intensidad); const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`; const body = { input: { ssml }, voice: { languageCode: 'es-ES', name: 'es-ES-Neural2-F', ssmlGender: 'FEMALE' }, audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 24000 } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TTS_TIMEOUT); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`TTS error: ${res.status}`); const data = await res.json(); audioCache.set(cacheKey, data.audioContent); return data.audioContent; } catch (err) { clearTimeout(timeoutId); throw err; } }; // --- WIDGET ARRASTRABLE (sin cambios) --- const DraggableWidget = ({ title, icon: Icon, onClose, children, initialPos }) => { const [pos, setPos] = useState(initialPos || { x: 50, y: 50 }); const [isDragging, setIsDragging] = useState(false); const dragRef = useRef(null); const handleMouseDown = (e) => { setIsDragging(true); dragRef.current = { startX: e.clientX, startY: e.clientY, initialX: pos.x, initialY: pos.y }; }; const handleMouseMove = (e) => { if (!isDragging) return; setPos({ x: Math.max(0, dragRef.current.initialX + (e.clientX - dragRef.current.startX)), y: Math.max(0, dragRef.current.initialY + (e.clientY - dragRef.current.startY)) }); }; const handleMouseUp = () => setIsDragging(false); useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div style={{ left: `${pos.x}px`, top: `${pos.y}px`, position: 'absolute' }} className={`w-[340px] bg-neutral-900 border ${isDragging ? 'border-emerald-500 shadow-emerald-900/20' : 'border-neutral-700'} rounded-xl shadow-2xl flex flex-col overflow-hidden transition-shadow duration-200 z-50`} > <div onMouseDown={handleMouseDown} className="bg-neutral-950 px-3 py-2 flex items-center justify-between cursor-move select-none border-b border-neutral-800"> <div className="flex items-center gap-2 text-neutral-400"> <GripHorizontal size={14} className="opacity-50" /> {Icon && <Icon size={14} className="text-emerald-500" />} <span className="text-xs font-bold tracking-wider">{title}</span> </div> <button onClick={onClose} className="text-neutral-500 hover:text-red-400 transition-colors"><X size={16} /></button> </div> <div className="p-4 flex-1 overflow-y-auto">{children}</div> </div> ); }; // --- WIDGET PRINCIPAL: MODULADOR VOCAL KORE (MEJORADO) --- const VoiceModulatorWidget = () => { const [text, setText] = useState(''); const [apiKey, setApiKey] = useState(DEFAULT_API_KEY); const [dulzura, setDulzura] = useState(50); const [sensualidad, setSensualidad] = useState(50); const [intensidad, setIntensidad] = useState(50); const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isHandsFree, setIsHandsFree] = useState(false); const [statusMsg, setStatusMsg] = useState('Enlace 1.5 Flash + GCP TTS Establecido.'); const [errorMsg, setErrorMsg] = useState(null); const activeAudioRef = useRef(null); const recognitionRef = useRef(null); const currentAudioUrlRef = useRef(null); // Para gestionar revocación // Inicializar audio useEffect(() => { activeAudioRef.current = new Audio(); activeAudioRef.current.preload = "auto"; return () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } } if (recognitionRef.current) recognitionRef.current.stop(); }; }, []); // Configurar reconocimiento de voz useEffect(() => { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { setErrorMsg('Reconocimiento de voz no soportado.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.lang = 'es-ES'; recognitionRef.current.interimResults = false; recognitionRef.current.continuous = false; recognitionRef.current.onresult = async (event) => { const transcript = event.results[0][0].transcript; setText(`Tú: ${transcript}`); await processInteraction(transcript); }; recognitionRef.current.onerror = (event) => { if (event.error !== 'no-speech') { setErrorMsg(`Micrófono: ${event.error}`); setIsHandsFree(false); } }; recognitionRef.current.onend = () => { if (isHandsFree && !isPlaying && !isLoading) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; // Limpiar al desmontar return () => { if (recognitionRef.current) recognitionRef.current.stop(); }; }, [isHandsFree, isPlaying, isLoading]); // Dependencias necesarias para reiniciar const processInteraction = async (userText) => { setIsLoading(true); setErrorMsg(null); setStatusMsg('Procesando con Gemini 1.5 Flash...'); const key = apiKey.trim(); try { // 1. Llamada a Gemini con timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const payloadText = { contents: [{ parts: [{ text: userText }] }], systemInstruction: { parts: [{ text: `Eres Kore, 28 años, Huesca. Labia, directa, sensual, disruptiva. Responde en español, máx 20 palabras. REGLA INNEGOCIABLE: CERO PREGUNTAS.` }] } }; const resText = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payloadText), signal: controller.signal } ); clearTimeout(timeoutId); if (!resText.ok) throw new Error(`Gemini error: ${resText.status}`); const dataText = await resText.json(); const aiText = dataText.candidates?.[0]?.content?.parts?.[0]?.text || "Mmm... vale."; setText(`Kore: ${aiText}`); // 2. Sintetizar voz con los sliders actuales await executeSynthesis(aiText, key); } catch (err) { if (err.name === 'AbortError') { setErrorMsg('Gemini timeout (5s)'); } else { setErrorMsg(err.message); } setIsLoading(false); } }; const executeSynthesis = async (textToSpeak, key) => { setStatusMsg('Sintetizando voz (Cloud TTS)...'); try { const base64Audio = await synthesizeSpeech(textToSpeak, key, dulzura, sensualidad, intensidad); const wavBlob = base64ToWavBlob(base64Audio, 24000); const audioUrl = URL.createObjectURL(wavBlob); // Revocar URL anterior si existe if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } currentAudioUrlRef.current = audioUrl; activeAudioRef.current.src = audioUrl; activeAudioRef.current.onended = () => { setIsPlaying(false); setStatusMsg('Transmisión completada.'); if (isHandsFree) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; setStatusMsg('Transmitiendo...'); setIsPlaying(true); setIsLoading(false); await activeAudioRef.current.play().catch(err => { throw new Error(`Autoplay bloqueado: ${err.message}`); }); } catch (error) { throw new Error(`Fallo TTS: ${error.message}`); } }; const handleManualPlay = async () => { if (!text.trim()) return setErrorMsg('Escribe algo primero.'); // Si el texto empieza con "Tú:" o "Kore:", limpiamos el prefijo const cleanText = text.replace(/^(Tú:|Kore:)\s*/, ''); if (!cleanText.trim()) return setErrorMsg('Texto vacío después de limpiar.'); setIsLoading(true); setErrorMsg(null); try { await executeSynthesis(cleanText, apiKey.trim()); } catch (err) { setErrorMsg(err.message); setIsLoading(false); } }; const toggleHandsFree = () => { if (!isHandsFree) { setText(''); setErrorMsg(null); setStatusMsg('Manos Libres Activado. Habla...'); // Desbloquear audio en algunos navegadores if (activeAudioRef.current) { activeAudioRef.current.src = SILENT_WAV; activeAudioRef.current.play().catch(() => {}); } try { recognitionRef.current.start(); } catch (e) {} } else { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Sistemas en pausa.'); if (recognitionRef.current) recognitionRef.current.stop(); } setIsHandsFree(!isHandsFree); }; const stopAudio = () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Señal interrumpida.'); }; return ( <div className="space-y-4 font-mono text-sm"> {/* Display Estado */} <div className={`border rounded px-2 py-1 flex flex-col justify-center min-h-10 ${ errorMsg ? 'bg-red-950/50 border-red-900' : isHandsFree ? 'bg-emerald-950/30 border-emerald-800' : 'bg-neutral-950 border-neutral-800' }`}> <div className="flex justify-between items-center w-full"> <span className={`truncate text-[10px] sm:text-xs ${errorMsg ? 'text-red-500' : 'text-emerald-500'}`}> > {errorMsg || statusMsg} </span> {isPlaying && !errorMsg && <Activity size={14} className="text-emerald-500 animate-pulse ml-2 flex-shrink-0" />} {isLoading && !errorMsg && <Zap size={14} className="text-amber-500 animate-pulse ml-2 flex-shrink-0" />} {isHandsFree && !isPlaying && !isLoading && !errorMsg && <Mic size={14} className="text-red-500 animate-pulse ml-2 flex-shrink-0" />} </div> </div> {/* Input Texto / Log */} <textarea value={text} onChange={(e) => setText(e.target.value)} className="w-full bg-neutral-950/50 border border-neutral-700 rounded p-2 text-xs text-neutral-300 focus:outline-none focus:border-emerald-500 resize-none h-20" placeholder={isHandsFree ? "Escuchando transcripción en tiempo real..." : "Escribe texto directo o activa Manos Libres..."} readOnly={isHandsFree || isLoading} /> {/* Sliders continuos (controlan SSML en tiempo real) */} <div className="space-y-3 bg-neutral-950/30 p-3 rounded border border-neutral-800"> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Agresiva</span><span className="text-emerald-400">Dulzura [{dulzura}]</span><span>Dulce</span> </div> <input type="range" min="0" max="100" value={dulzura} onChange={(e)=>setDulzura(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-emerald-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Robótica</span><span className="text-pink-400">Aura [{sensualidad}]</span><span>Sensual</span> </div> <input type="range" min="0" max="100" value={sensualidad} onChange={(e)=>setSensualidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-pink-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Atenuada</span><span className="text-amber-400">Intensidad [{intensidad}]</span><span>Fuerte</span> </div> <input type="range" min="0" max="100" value={intensidad} onChange={(e)=>setIntensidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-amber-500 cursor-pointer" /> </div> </div> {/* Botones de Control */} <div className="flex flex-col sm:flex-row gap-2"> <button onClick={toggleHandsFree} disabled={isLoading} className={`flex-1 py-2 rounded text-xs font-bold flex items-center justify-center gap-2 transition-colors border ${ isHandsFree ? 'bg-red-900/20 text-red-400 border-red-900/50 hover:bg-red-900/40 shadow-[0_0_10px_rgba(239,68,68,0.2)]' : 'bg-indigo-900/20 text-indigo-400 border-indigo-900/50 hover:bg-indigo-900/40' }`} > {isHandsFree ? <MicOff size={14} /> : <Mic size={14} />} {isHandsFree ? 'Detener Escucha' : 'Manos Libres'} </button> <div className="flex gap-2 flex-1"> <button onClick={handleManualPlay} disabled={isLoading || isPlaying || isHandsFree} className="flex-1 bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 border border-emerald-600/50 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center gap-1 transition-colors" > {isLoading ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />} Sintetizar </button> <button onClick={stopAudio} disabled={!isPlaying && !isHandsFree} className="px-4 bg-neutral-800 hover:bg-neutral-700 text-neutral-400 border border-neutral-700 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center transition-colors" > <Square size={14} /> </button> </div> </div> {/* Botón para limpiar caché (opcional) */} <div className="text-right"> <button onClick={() => audioCache.clear()} className="text-[8px] text-neutral-600 hover:text-neutral-400 underline" > limpiar caché de audio </button> </div> </div> ); }; // --- ENTORNO ESCRITORIO (sin cambios) --- export default function App() { const [widgets, setWidgets] = useState({ voice: { isOpen: true, pos: { x: window.innerWidth > 768 ? window.innerWidth / 2 - 170 : 20, y: 40 } } }); const toggleWidget = (id) => { setWidgets(prev => ({ ...prev, [id]: { ...prev[id], isOpen: !prev[id].isOpen } })); }; return ( <div className="w-full h-screen bg-neutral-950 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.1),rgba(0,0,0,1))] overflow-hidden relative font-sans text-neutral-200"> <div className="absolute inset-0 flex items-center justify-center opacity-[0.02] pointer-events-none"><Settings2 size={500} /></div> {widgets.voice.isOpen && ( <DraggableWidget title="MODULADOR VOCAL KORE" icon={Zap} initialPos={widgets.voice.pos} onClose={() => toggleWidget('voice')}> <VoiceModulatorWidget /> </DraggableWidget> )} <div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-neutral-900/80 backdrop-blur-md border border-neutral-700/50 p-2 rounded-2xl shadow-2xl flex gap-2 z-[100]"> <div className="px-3 flex items-center border-r border-neutral-700/50 text-neutral-500"><LayoutGrid size={20} /></div> <button onClick={() => toggleWidget('voice')} className={`px-4 py-2 rounded-xl flex items-center gap-2 text-sm font-medium transition-all ${
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.8> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8>
Create a movie-level ultra-realistic photo of a upload image of a man. The face matches the reference image 100.9999% 35mm Kodak Portra 400 film, cinematic portrait shot during golden hour. Aspect ratio 9:16 Captured with a Canon AE-1 using 50mm f/1.4 lens, shutter speed 1/250s, white balance 5200K (daylight), ISO 400. A mid-shot portrait of a man sitting in a vintage convertible car, leaning on the door with his left arm resting casually. He wears a light beige linen blazer, striped open-collar shirt, and leather bracelets. His gaze is directed toward the horizon, expressing contemplation and calm confidence. The composition uses soft, natural sunlight that illuminates his face from the side, creating gentle golden highlights and smooth shadows across his profile and the car’s curves. The focus is on the man’s thoughtful expression, with shallow depth of field (f/1.4) blurring the background sky for cinematic separation. Mood & Tone: nostalgic, warm, introspective, timeless elegance. Color Palette: soft amber, golden beige, charcoal black, muted sky blue. Film Aesthetic: subtle grain, creamy highlights, balanced contrast typical of Portra 400. keep face features original
a woman sitting on a rock next to a dragon in the woods with her head turned to the side, Clint Cearley, epic fantasy character art, a character portrait, fantasy art, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera
animal look, https://s.mj.run/1150bpD5hFw Smiling and looking to the camera, Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1. 8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --style raw --stylize 400 --c 50 --ar 4:5
At this time, Heaven first had a foundation. 5,400 years later, in the middle of Phase l, the light and pure roseupwards, and sun, moon, stars, and constellations were created. These were called the Four Images. Hence thesaying that heaven began in I. Another 5.400 years later, when Phase I was nearing its end and Phase Il was imminent, things graduallysolidified. As the Book of Changes says, "Great is the Positive; far-reaching is the Negative! All things areendowed and born in accordance with Heaven." This was when the earth began to congeal. After 5,400 morevears came the height of Phase Il. when the heavy and impure solidified. and water, fire, mountains. stoneand Earth came into being. These five were called the Five Movers. Therefore it is said that the Earth wascreated in Phase Il. After a further 5,400 years, at the end of Phase Il and the beginning of the Phase Ill, living beings werecreated. In the words of the Book of the Calendar, "The essence of the sky came down and the essence ofearth went up. Heaven and Earth intermingled, and all creatures were born." Then Heaven was bright andEarth was fresh, and the Positive intermingled with the Negative. 5.400 years later, when Phase Il was at itsheight, men, birds and beasts were created. Thus the Three Powers--Heaven, Earth and Man--now had theitset places. Therefore it is said that man was created in Phase Ill. Moved by Pan Cu's creation, the Three Emperors put the world in order and the Five Rulers laid down themoral code. The world was then divided into four great continents: The Eastern Continent of Superior Body.the Western Continent of Cattle-gift, the Southern Continent of Jambu and the Northern Continent of KuruThis book deals only with the Eastern Continent of Superior Body. Beyond the seas there is a country calledAolai. This country is next to an ocean, and in the middle of the ocean is a famous island called the Mountainof Flowers and Fruit. This mountain is the ancestral artery of the Ten Continents, the oripin of the Threeslands: it was formed when the clear and impure were separated and the Enormous Vagueness was divided. Itis a really splendid mountain and there are some verses to prove it:
### **1. GREENS (Left Half: 0–500px Width)** - **Flower Area (Triangle)**: - **Corners**: (100,700) → (400,700) → (250,900). - **Label**: "FLOWERS" centered at (250,800). - **Small Lake**: - **Circle**: Center at (400,300), radius 80px. - **Label**: "LAKE" at (400,200). - **Grass**: Solid green fill from (0,0) to (500,1000). --- ### **2. HOME (Center-Right Vertical Strip: 500–700px Width)** - **Villa (HOME)**: - **Rectangle**: (550,400) to (700,600). - **Label**: "HOME" at (625,500). - **Garage**: - **Rectangle**: (700,450) to (750,550). - **Label**: "GARAGE" at (725,500). - **Gray Car Path (LANE)**: - **Road**: (500,950) to (700,400), width 60px. - **Fountain (Blue Circle)**: Center at (600,675), radius 30px. - **Label**: "MAIN ENTRANCE" at (600,975), "LANE" at (600,750). - **Stepping Stone Paths**: - **To Pool**: Dotted line from (625,600) → (450,500). - **To Gazebo**: Dotted line from (675,600) → (800,300). --- ### **3. AGRICULTURE (Right Half: 700–1000px Width)** - **Agricultural Field**: - **Rectangle**: (700,0) to (1000,1000). - **Label**: "AGRICULTURAL FIELD" at (850,50). - **Animal Enclosures**: - **Rectangles**: 1. (720,100) to (820,250). 2. (720,260) to (820,400). - **Label**: "ANIMAL ENCLOSURES" at (770,50). - **Vegetable Garden**: - **Rectangle**: (700,450) to (1000,550). - **Label**: "VEGETABLE GARDEN" at (850,500). --- ### **4. Trees & Boundaries** - **Road Trees**: Line both sides of the gray path from (500,950) to (700,400), spaced 50px apart. - **Field Boundary Trees**: Vertical line from (700,400) to (700,600), separating HOME and AGRICULTURE. --- ### **5. Gazebo & Pool** - **Gazebo (Pentagon)**: - **Corners**: (800,250) → (850,200) → (900,250) → (875,300) → (825,300). - **Label**: "GAZEBO" at (850,350). - **Pool**: Unlabeled oval at (450,450) to (550,550). --- ### **Style Instructions** - **2D Flat Design**: No shadows, gradients, or perspective. - **Colors**: - Gray path: `#808080` - Green zones: `#90EE90` - Yellow agriculture: `#FFFF00` - Blue fountain/lake: `#4682B4` - Orange enclosures: `#FFA500`
https://s.mj.run/1150bpD5hFw Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1.8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --ar 4:5 --style raw --stylize 400 --c 50
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.6> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8> <lora:3DMM_V12:0.8>
Adjustment of Cufflink (Detailed Mid-Shot) Ultra-realistic, mid-shot portrait focusing on a precise detail. Face match 100.9999%. Aspect Ratio: 4:5. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/125, ISO 400, tungsten white balance. Composition: Tight mid-shot where the man is looking down, intensely focused on adjusting a simple, elegant cufflink. His face is partially obscured by his hand, but the concentration in his brow is visible. Mood & Tone: Obsessive perfection, quiet preparation, focused intensity. Lighting: A single, small directional light source highlights the wrist, the cufflink, and the texture of the crisp white shirt cuff. Color Palette: High contrast between the dark tuxedo and the bright, yet warm-toned white shirt. Shot Detail: Hyper-detailed focus on the metal texture of the cufflink and the fabric fibers, very shallow depth of field
Fashion editorial style grand photograph, hair light, Polaroid, F/5, dramatic lighting, Samsung Galaxy, F/1.8, rim light, Iphone X, L USM, side light, film camera, 800mm lens, waning light, Nikon d3300, 35mm, Proud, key light, compact camera, telephoto lens, Bloom light, Kodak portra 800, Depth of field 270mm, Black lighting, Kodak gold 200, F/2.8, soft light, Phase One XF IQ4 150MP, F/14, Muted Colors, Motion blur, Canon 5d mark 4, Circular polarizer, (young woman, very thin, small breasts, abs, supermodel in post modern environment :1.3) with Purple skin, Bedouin hair, Direct light, Nikon d850, macro lens, flat lighting, Canon RF, F/8, cinematic lighting, Fujifilm XT3, 80mm, Gel lighting, Ilford HP5, Zoom lens, Low Contrast, studio lighting, Fuji superia 400, Low shutter, Neon Light, Nikon Z9, Depth of field 100mm, specular lighting, Kodak portra 400, 50mm, Accent lighting, dslr, Fish-eye Lens, Nostalgic lighting, Canon eos 5d mark 4, Selective focus, two colors . High fashion, trendy, stylish, editorial, magazine style, professional, highly detailed
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.6> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8> <lora:3DMM_V12:0.8>
Adjustment of Cufflink (Detailed Mid-Shot) Ultra-realistic, mid-shot portrait focusing on a precise detail. Face match 100.9999%. Aspect Ratio: 4:5. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/125, ISO 400, tungsten white balance. Composition: Tight mid-shot where the man is looking down, intensely focused on adjusting a simple, elegant cufflink. His face is partially obscured by his hand, but the concentration in his brow is visible. Mood & Tone: Obsessive perfection, quiet preparation, focused intensity. Lighting: A single, small directional light source highlights the wrist, the cufflink, and the texture of the crisp white shirt cuff. Color Palette: High contrast between the dark tuxedo and the bright, yet warm-toned white shirt. Shot Detail: Hyper-detailed focus on the metal texture of the cufflink and the fabric fibers, very shallow depth of field
animal look, https://s.mj.run/1150bpD5hFw Smiling and looking to the camera, Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1. 8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --style raw --stylize 400 --c 50 --ar 4:5
### **1. GREENS (Left Half: 0–500px Width)** - **Flower Area (Triangle)**: - **Corners**: (100,700) → (400,700) → (250,900). - **Label**: "FLOWERS" centered at (250,800). - **Small Lake**: - **Circle**: Center at (400,300), radius 80px. - **Label**: "LAKE" at (400,200). - **Grass**: Solid green fill from (0,0) to (500,1000). --- ### **2. HOME (Center-Right Vertical Strip: 500–700px Width)** - **Villa (HOME)**: - **Rectangle**: (550,400) to (700,600). - **Label**: "HOME" at (625,500). - **Garage**: - **Rectangle**: (700,450) to (750,550). - **Label**: "GARAGE" at (725,500). - **Gray Car Path (LANE)**: - **Road**: (500,950) to (700,400), width 60px. - **Fountain (Blue Circle)**: Center at (600,675), radius 30px. - **Label**: "MAIN ENTRANCE" at (600,975), "LANE" at (600,750). - **Stepping Stone Paths**: - **To Pool**: Dotted line from (625,600) → (450,500). - **To Gazebo**: Dotted line from (675,600) → (800,300). --- ### **3. AGRICULTURE (Right Half: 700–1000px Width)** - **Agricultural Field**: - **Rectangle**: (700,0) to (1000,1000). - **Label**: "AGRICULTURAL FIELD" at (850,50). - **Animal Enclosures**: - **Rectangles**: 1. (720,100) to (820,250). 2. (720,260) to (820,400). - **Label**: "ANIMAL ENCLOSURES" at (770,50). - **Vegetable Garden**: - **Rectangle**: (700,450) to (1000,550). - **Label**: "VEGETABLE GARDEN" at (850,500). --- ### **4. Trees & Boundaries** - **Road Trees**: Line both sides of the gray path from (500,950) to (700,400), spaced 50px apart. - **Field Boundary Trees**: Vertical line from (700,400) to (700,600), separating HOME and AGRICULTURE. --- ### **5. Gazebo & Pool** - **Gazebo (Pentagon)**: - **Corners**: (800,250) → (850,200) → (900,250) → (875,300) → (825,300). - **Label**: "GAZEBO" at (850,350). - **Pool**: Unlabeled oval at (450,450) to (550,550). --- ### **Style Instructions** - **2D Flat Design**: No shadows, gradients, or perspective. - **Colors**: - Gray path: `#808080` - Green zones: `#90EE90` - Yellow agriculture: `#FFFF00` - Blue fountain/lake: `#4682B4` - Orange enclosures: `#FFA500`
He optimizado tu código para lograr una modulación vocal continua y fluida basada en los sliders, con caché de audio, timeouts y mejor manejo del estado. Ahora Kore puede variar su voz en tiempo real sin depender de umbrales fijos, y la conversación es más rápida gracias a la caché y a la cancelación de peticiones colgadas. ```javascript import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, Mic, MicOff, Settings2, Activity, Loader2, X, GripHorizontal, LayoutGrid, Zap, AlertCircle } from 'lucide-react'; // --- CONSTANTES --- const SILENT_WAV = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; const TTS_TIMEOUT = 5000; // 5 segundos máximo para la síntesis const DEFAULT_API_KEY = 'AIzaSyBlkvy_Op-XlzSMSDDl9ip42dMFZX28MAA'; // ⚠️ Cámbiala por tu propia clave // --- UTILIDADES --- const base64ToWavBlob = (base64Data, sampleRate = 24000) => { const binaryString = window.atob(base64Data); const pcmData = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) pcmData[i] = binaryString.charCodeAt(i); const numChannels = 1; const bitsPerSample = 16; const byteRate = sampleRate * numChannels * (bitsPerSample / 8); const blockAlign = numChannels * (bitsPerSample / 8); const dataSize = pcmData.length; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < dataSize; i++) view.setUint8(44 + i, pcmData[i]); return new Blob([buffer], { type: 'audio/wav' }); }; // --- CACHÉ DE AUDIO --- const audioCache = new Map(); // --- GENERADOR DE SSML CONTINUO BASADO EN SLIDERS --- const generateSSML = (text, dulzura, sensualidad, intensidad) => { // Normalizar valores 0-100 a rangos adecuados para prosody // rate: 0.5 a 2.0 (1.0 es normal) const rate = 0.8 + (intensidad / 100) * 1.2; // 0.8 (lento) a 2.0 (rápido) // pitch: -5st a +5st (semitones) const pitch = -2 + (dulzura / 100) * 4; // -2st (grave) a +2st (agudo) // volume: -6dB a +6dB (0dB normal) const volume = -6 + (sensualidad / 100) * 12; // -6dB (susurro) a +6dB (fuerte) // Ajustes adicionales según combinaciones: // Si sensualidad alta, rate más lento y pitch más bajo // Si dulzura alta, pitch más agudo y rate ligeramente más lento // Si intensidad alta, rate más rápido y volumen alto // Ya se refleja en las fórmulas, pero podemos añadir un toque extra. const ssml = `<speak> <prosody rate="${rate.toFixed(2)}" pitch="${pitch.toFixed(0)}st" volume="${volume.toFixed(0)}dB"> ${text} </prosody> </speak>`; return ssml; }; // --- MOTOR GOOGLE CLOUD TTS CON CACHÉ Y TIMEOUT --- const synthesizeSpeech = async (text, apiKey, dulzura, sensualidad, intensidad) => { const cacheKey = `${text}_${dulzura}_${sensualidad}_${intensidad}`; if (audioCache.has(cacheKey)) { console.log('🎯 Usando audio cacheado'); return audioCache.get(cacheKey); } const ssml = generateSSML(text, dulzura, sensualidad, intensidad); const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`; const body = { input: { ssml }, voice: { languageCode: 'es-ES', name: 'es-ES-Neural2-F', ssmlGender: 'FEMALE' }, audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 24000 } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TTS_TIMEOUT); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`TTS error: ${res.status}`); const data = await res.json(); audioCache.set(cacheKey, data.audioContent); return data.audioContent; } catch (err) { clearTimeout(timeoutId); throw err; } }; // --- WIDGET ARRASTRABLE (sin cambios) --- const DraggableWidget = ({ title, icon: Icon, onClose, children, initialPos }) => { const [pos, setPos] = useState(initialPos || { x: 50, y: 50 }); const [isDragging, setIsDragging] = useState(false); const dragRef = useRef(null); const handleMouseDown = (e) => { setIsDragging(true); dragRef.current = { startX: e.clientX, startY: e.clientY, initialX: pos.x, initialY: pos.y }; }; const handleMouseMove = (e) => { if (!isDragging) return; setPos({ x: Math.max(0, dragRef.current.initialX + (e.clientX - dragRef.current.startX)), y: Math.max(0, dragRef.current.initialY + (e.clientY - dragRef.current.startY)) }); }; const handleMouseUp = () => setIsDragging(false); useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div style={{ left: `${pos.x}px`, top: `${pos.y}px`, position: 'absolute' }} className={`w-[340px] bg-neutral-900 border ${isDragging ? 'border-emerald-500 shadow-emerald-900/20' : 'border-neutral-700'} rounded-xl shadow-2xl flex flex-col overflow-hidden transition-shadow duration-200 z-50`} > <div onMouseDown={handleMouseDown} className="bg-neutral-950 px-3 py-2 flex items-center justify-between cursor-move select-none border-b border-neutral-800"> <div className="flex items-center gap-2 text-neutral-400"> <GripHorizontal size={14} className="opacity-50" /> {Icon && <Icon size={14} className="text-emerald-500" />} <span className="text-xs font-bold tracking-wider">{title}</span> </div> <button onClick={onClose} className="text-neutral-500 hover:text-red-400 transition-colors"><X size={16} /></button> </div> <div className="p-4 flex-1 overflow-y-auto">{children}</div> </div> ); }; // --- WIDGET PRINCIPAL: MODULADOR VOCAL KORE (MEJORADO) --- const VoiceModulatorWidget = () => { const [text, setText] = useState(''); const [apiKey, setApiKey] = useState(DEFAULT_API_KEY); const [dulzura, setDulzura] = useState(50); const [sensualidad, setSensualidad] = useState(50); const [intensidad, setIntensidad] = useState(50); const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isHandsFree, setIsHandsFree] = useState(false); const [statusMsg, setStatusMsg] = useState('Enlace 1.5 Flash + GCP TTS Establecido.'); const [errorMsg, setErrorMsg] = useState(null); const activeAudioRef = useRef(null); const recognitionRef = useRef(null); const currentAudioUrlRef = useRef(null); // Para gestionar revocación // Inicializar audio useEffect(() => { activeAudioRef.current = new Audio(); activeAudioRef.current.preload = "auto"; return () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } } if (recognitionRef.current) recognitionRef.current.stop(); }; }, []); // Configurar reconocimiento de voz useEffect(() => { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { setErrorMsg('Reconocimiento de voz no soportado.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.lang = 'es-ES'; recognitionRef.current.interimResults = false; recognitionRef.current.continuous = false; recognitionRef.current.onresult = async (event) => { const transcript = event.results[0][0].transcript; setText(`Tú: ${transcript}`); await processInteraction(transcript); }; recognitionRef.current.onerror = (event) => { if (event.error !== 'no-speech') { setErrorMsg(`Micrófono: ${event.error}`); setIsHandsFree(false); } }; recognitionRef.current.onend = () => { if (isHandsFree && !isPlaying && !isLoading) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; // Limpiar al desmontar return () => { if (recognitionRef.current) recognitionRef.current.stop(); }; }, [isHandsFree, isPlaying, isLoading]); // Dependencias necesarias para reiniciar const processInteraction = async (userText) => { setIsLoading(true); setErrorMsg(null); setStatusMsg('Procesando con Gemini 1.5 Flash...'); const key = apiKey.trim(); try { // 1. Llamada a Gemini con timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const payloadText = { contents: [{ parts: [{ text: userText }] }], systemInstruction: { parts: [{ text: `Eres Kore, 28 años, Huesca. Labia, directa, sensual, disruptiva. Responde en español, máx 20 palabras. REGLA INNEGOCIABLE: CERO PREGUNTAS.` }] } }; const resText = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payloadText), signal: controller.signal } ); clearTimeout(timeoutId); if (!resText.ok) throw new Error(`Gemini error: ${resText.status}`); const dataText = await resText.json(); const aiText = dataText.candidates?.[0]?.content?.parts?.[0]?.text || "Mmm... vale."; setText(`Kore: ${aiText}`); // 2. Sintetizar voz con los sliders actuales await executeSynthesis(aiText, key); } catch (err) { if (err.name === 'AbortError') { setErrorMsg('Gemini timeout (5s)'); } else { setErrorMsg(err.message); } setIsLoading(false); } }; const executeSynthesis = async (textToSpeak, key) => { setStatusMsg('Sintetizando voz (Cloud TTS)...'); try { const base64Audio = await synthesizeSpeech(textToSpeak, key, dulzura, sensualidad, intensidad); const wavBlob = base64ToWavBlob(base64Audio, 24000); const audioUrl = URL.createObjectURL(wavBlob); // Revocar URL anterior si existe if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } currentAudioUrlRef.current = audioUrl; activeAudioRef.current.src = audioUrl; activeAudioRef.current.onended = () => { setIsPlaying(false); setStatusMsg('Transmisión completada.'); if (isHandsFree) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; setStatusMsg('Transmitiendo...'); setIsPlaying(true); setIsLoading(false); await activeAudioRef.current.play().catch(err => { throw new Error(`Autoplay bloqueado: ${err.message}`); }); } catch (error) { throw new Error(`Fallo TTS: ${error.message}`); } }; const handleManualPlay = async () => { if (!text.trim()) return setErrorMsg('Escribe algo primero.'); // Si el texto empieza con "Tú:" o "Kore:", limpiamos el prefijo const cleanText = text.replace(/^(Tú:|Kore:)\s*/, ''); if (!cleanText.trim()) return setErrorMsg('Texto vacío después de limpiar.'); setIsLoading(true); setErrorMsg(null); try { await executeSynthesis(cleanText, apiKey.trim()); } catch (err) { setErrorMsg(err.message); setIsLoading(false); } }; const toggleHandsFree = () => { if (!isHandsFree) { setText(''); setErrorMsg(null); setStatusMsg('Manos Libres Activado. Habla...'); // Desbloquear audio en algunos navegadores if (activeAudioRef.current) { activeAudioRef.current.src = SILENT_WAV; activeAudioRef.current.play().catch(() => {}); } try { recognitionRef.current.start(); } catch (e) {} } else { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Sistemas en pausa.'); if (recognitionRef.current) recognitionRef.current.stop(); } setIsHandsFree(!isHandsFree); }; const stopAudio = () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Señal interrumpida.'); }; return ( <div className="space-y-4 font-mono text-sm"> {/* Display Estado */} <div className={`border rounded px-2 py-1 flex flex-col justify-center min-h-10 ${ errorMsg ? 'bg-red-950/50 border-red-900' : isHandsFree ? 'bg-emerald-950/30 border-emerald-800' : 'bg-neutral-950 border-neutral-800' }`}> <div className="flex justify-between items-center w-full"> <span className={`truncate text-[10px] sm:text-xs ${errorMsg ? 'text-red-500' : 'text-emerald-500'}`}> > {errorMsg || statusMsg} </span> {isPlaying && !errorMsg && <Activity size={14} className="text-emerald-500 animate-pulse ml-2 flex-shrink-0" />} {isLoading && !errorMsg && <Zap size={14} className="text-amber-500 animate-pulse ml-2 flex-shrink-0" />} {isHandsFree && !isPlaying && !isLoading && !errorMsg && <Mic size={14} className="text-red-500 animate-pulse ml-2 flex-shrink-0" />} </div> </div> {/* Input Texto / Log */} <textarea value={text} onChange={(e) => setText(e.target.value)} className="w-full bg-neutral-950/50 border border-neutral-700 rounded p-2 text-xs text-neutral-300 focus:outline-none focus:border-emerald-500 resize-none h-20" placeholder={isHandsFree ? "Escuchando transcripción en tiempo real..." : "Escribe texto directo o activa Manos Libres..."} readOnly={isHandsFree || isLoading} /> {/* Sliders continuos (controlan SSML en tiempo real) */} <div className="space-y-3 bg-neutral-950/30 p-3 rounded border border-neutral-800"> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Agresiva</span><span className="text-emerald-400">Dulzura [{dulzura}]</span><span>Dulce</span> </div> <input type="range" min="0" max="100" value={dulzura} onChange={(e)=>setDulzura(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-emerald-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Robótica</span><span className="text-pink-400">Aura [{sensualidad}]</span><span>Sensual</span> </div> <input type="range" min="0" max="100" value={sensualidad} onChange={(e)=>setSensualidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-pink-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Atenuada</span><span className="text-amber-400">Intensidad [{intensidad}]</span><span>Fuerte</span> </div> <input type="range" min="0" max="100" value={intensidad} onChange={(e)=>setIntensidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-amber-500 cursor-pointer" /> </div> </div> {/* Botones de Control */} <div className="flex flex-col sm:flex-row gap-2"> <button onClick={toggleHandsFree} disabled={isLoading} className={`flex-1 py-2 rounded text-xs font-bold flex items-center justify-center gap-2 transition-colors border ${ isHandsFree ? 'bg-red-900/20 text-red-400 border-red-900/50 hover:bg-red-900/40 shadow-[0_0_10px_rgba(239,68,68,0.2)]' : 'bg-indigo-900/20 text-indigo-400 border-indigo-900/50 hover:bg-indigo-900/40' }`} > {isHandsFree ? <MicOff size={14} /> : <Mic size={14} />} {isHandsFree ? 'Detener Escucha' : 'Manos Libres'} </button> <div className="flex gap-2 flex-1"> <button onClick={handleManualPlay} disabled={isLoading || isPlaying || isHandsFree} className="flex-1 bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 border border-emerald-600/50 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center gap-1 transition-colors" > {isLoading ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />} Sintetizar </button> <button onClick={stopAudio} disabled={!isPlaying && !isHandsFree} className="px-4 bg-neutral-800 hover:bg-neutral-700 text-neutral-400 border border-neutral-700 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center transition-colors" > <Square size={14} /> </button> </div> </div> {/* Botón para limpiar caché (opcional) */} <div className="text-right"> <button onClick={() => audioCache.clear()} className="text-[8px] text-neutral-600 hover:text-neutral-400 underline" > limpiar caché de audio </button> </div> </div> ); }; // --- ENTORNO ESCRITORIO (sin cambios) --- export default function App() { const [widgets, setWidgets] = useState({ voice: { isOpen: true, pos: { x: window.innerWidth > 768 ? window.innerWidth / 2 - 170 : 20, y: 40 } } }); const toggleWidget = (id) => { setWidgets(prev => ({ ...prev, [id]: { ...prev[id], isOpen: !prev[id].isOpen } })); }; return ( <div className="w-full h-screen bg-neutral-950 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.1),rgba(0,0,0,1))] overflow-hidden relative font-sans text-neutral-200"> <div className="absolute inset-0 flex items-center justify-center opacity-[0.02] pointer-events-none"><Settings2 size={500} /></div> {widgets.voice.isOpen && ( <DraggableWidget title="MODULADOR VOCAL KORE" icon={Zap} initialPos={widgets.voice.pos} onClose={() => toggleWidget('voice')}> <VoiceModulatorWidget /> </DraggableWidget> )} <div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-neutral-900/80 backdrop-blur-md border border-neutral-700/50 p-2 rounded-2xl shadow-2xl flex gap-2 z-[100]"> <div className="px-3 flex items-center border-r border-neutral-700/50 text-neutral-500"><LayoutGrid size={20} /></div> <button onClick={() => toggleWidget('voice')} className={`px-4 py-2 rounded-xl flex items-center gap-2 text-sm font-medium transition-all ${
Create a movie-level ultra-realistic photo of a upload image of a man. The face matches the reference image 100.9999% 35mm Kodak Portra 400 film, cinematic portrait shot during golden hour. Aspect ratio 9:16 Captured with a Canon AE-1 using 50mm f/1.4 lens, shutter speed 1/250s, white balance 5200K (daylight), ISO 400. A mid-shot portrait of a man sitting in a vintage convertible car, leaning on the door with his left arm resting casually. He wears a light beige linen blazer, striped open-collar shirt, and leather bracelets. His gaze is directed toward the horizon, expressing contemplation and calm confidence. The composition uses soft, natural sunlight that illuminates his face from the side, creating gentle golden highlights and smooth shadows across his profile and the car’s curves. The focus is on the man’s thoughtful expression, with shallow depth of field (f/1.4) blurring the background sky for cinematic separation. Mood & Tone: nostalgic, warm, introspective, timeless elegance. Color Palette: soft amber, golden beige, charcoal black, muted sky blue. Film Aesthetic: subtle grain, creamy highlights, balanced contrast typical of Portra 400. keep face features original
a woman sitting on a rock next to a dragon in the woods with her head turned to the side, Clint Cearley, epic fantasy character art, a character portrait, fantasy art, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera
Over-the-Shoulder Shot (Action/Dialogue Implied) Ultra-realistic, dynamic over-the-shoulder (OTS) cinematic shot of the same man in a black tuxedo. Face match 100.9999%. Aspect Ratio: 16:9. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/100, ISO 400, tungsten white balance. Composition: The camera is positioned behind an unseen, heavily blurred second figure's shoulder (only the dark blur is visible in the foreground), focusing on the main subject who is standing in profile or three-quarter view, looking intently at the blurred figure. His hand is gesturing slightly, mid-sentence. Mood & Tone: Intense, focused, and suggestive of a high-stakes conversation. Shallow depth of field ensures the focus is purely on the subject's reaction. Color Palette: Cinematic warm and cool tones playing on the faces, heavy Portra 400 glow. Shot Detail: Intense eye contact, blurred foreground object adds a layer of secrecy/eavesdropping
https://s.mj.run/1150bpD5hFw Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1.8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --ar 4:5 --style raw --stylize 400 --c 50
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.8> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8>
Fashion editorial style grand photograph, hair light, Polaroid, F/5, dramatic lighting, Samsung Galaxy, F/1.8, rim light, Iphone X, L USM, side light, film camera, 800mm lens, waning light, Nikon d3300, 35mm, Proud, key light, compact camera, telephoto lens, Bloom light, Kodak portra 800, Depth of field 270mm, Black lighting, Kodak gold 200, F/2.8, soft light, Phase One XF IQ4 150MP, F/14, Muted Colors, Motion blur, Canon 5d mark 4, Circular polarizer, (young woman, very thin, small breasts, abs, supermodel in post modern environment :1.3) with Purple skin, Bedouin hair, Direct light, Nikon d850, macro lens, flat lighting, Canon RF, F/8, cinematic lighting, Fujifilm XT3, 80mm, Gel lighting, Ilford HP5, Zoom lens, Low Contrast, studio lighting, Fuji superia 400, Low shutter, Neon Light, Nikon Z9, Depth of field 100mm, specular lighting, Kodak portra 400, 50mm, Accent lighting, dslr, Fish-eye Lens, Nostalgic lighting, Canon eos 5d mark 4, Selective focus, two colors . High fashion, trendy, stylish, editorial, magazine style, professional, highly detailed
At this time, Heaven first had a foundation. 5,400 years later, in the middle of Phase l, the light and pure roseupwards, and sun, moon, stars, and constellations were created. These were called the Four Images. Hence thesaying that heaven began in I. Another 5.400 years later, when Phase I was nearing its end and Phase Il was imminent, things graduallysolidified. As the Book of Changes says, "Great is the Positive; far-reaching is the Negative! All things areendowed and born in accordance with Heaven." This was when the earth began to congeal. After 5,400 morevears came the height of Phase Il. when the heavy and impure solidified. and water, fire, mountains. stoneand Earth came into being. These five were called the Five Movers. Therefore it is said that the Earth wascreated in Phase Il. After a further 5,400 years, at the end of Phase Il and the beginning of the Phase Ill, living beings werecreated. In the words of the Book of the Calendar, "The essence of the sky came down and the essence ofearth went up. Heaven and Earth intermingled, and all creatures were born." Then Heaven was bright andEarth was fresh, and the Positive intermingled with the Negative. 5.400 years later, when Phase Il was at itsheight, men, birds and beasts were created. Thus the Three Powers--Heaven, Earth and Man--now had theitset places. Therefore it is said that man was created in Phase Ill. Moved by Pan Cu's creation, the Three Emperors put the world in order and the Five Rulers laid down themoral code. The world was then divided into four great continents: The Eastern Continent of Superior Body.the Western Continent of Cattle-gift, the Southern Continent of Jambu and the Northern Continent of KuruThis book deals only with the Eastern Continent of Superior Body. Beyond the seas there is a country calledAolai. This country is next to an ocean, and in the middle of the ocean is a famous island called the Mountainof Flowers and Fruit. This mountain is the ancestral artery of the Ten Continents, the oripin of the Threeslands: it was formed when the clear and impure were separated and the Enormous Vagueness was divided. Itis a really splendid mountain and there are some verses to prove it:
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.6> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8> <lora:3DMM_V12:0.8>
a woman sitting on a rock next to a dragon in the woods with her head turned to the side, Clint Cearley, epic fantasy character art, a character portrait, fantasy art, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera
animal look, https://s.mj.run/1150bpD5hFw Smiling and looking to the camera, Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1. 8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --style raw --stylize 400 --c 50 --ar 4:5
At this time, Heaven first had a foundation. 5,400 years later, in the middle of Phase l, the light and pure roseupwards, and sun, moon, stars, and constellations were created. These were called the Four Images. Hence thesaying that heaven began in I. Another 5.400 years later, when Phase I was nearing its end and Phase Il was imminent, things graduallysolidified. As the Book of Changes says, "Great is the Positive; far-reaching is the Negative! All things areendowed and born in accordance with Heaven." This was when the earth began to congeal. After 5,400 morevears came the height of Phase Il. when the heavy and impure solidified. and water, fire, mountains. stoneand Earth came into being. These five were called the Five Movers. Therefore it is said that the Earth wascreated in Phase Il. After a further 5,400 years, at the end of Phase Il and the beginning of the Phase Ill, living beings werecreated. In the words of the Book of the Calendar, "The essence of the sky came down and the essence ofearth went up. Heaven and Earth intermingled, and all creatures were born." Then Heaven was bright andEarth was fresh, and the Positive intermingled with the Negative. 5.400 years later, when Phase Il was at itsheight, men, birds and beasts were created. Thus the Three Powers--Heaven, Earth and Man--now had theitset places. Therefore it is said that man was created in Phase Ill. Moved by Pan Cu's creation, the Three Emperors put the world in order and the Five Rulers laid down themoral code. The world was then divided into four great continents: The Eastern Continent of Superior Body.the Western Continent of Cattle-gift, the Southern Continent of Jambu and the Northern Continent of KuruThis book deals only with the Eastern Continent of Superior Body. Beyond the seas there is a country calledAolai. This country is next to an ocean, and in the middle of the ocean is a famous island called the Mountainof Flowers and Fruit. This mountain is the ancestral artery of the Ten Continents, the oripin of the Threeslands: it was formed when the clear and impure were separated and the Enormous Vagueness was divided. Itis a really splendid mountain and there are some verses to prove it:
### **1. GREENS (Left Half: 0–500px Width)** - **Flower Area (Triangle)**: - **Corners**: (100,700) → (400,700) → (250,900). - **Label**: "FLOWERS" centered at (250,800). - **Small Lake**: - **Circle**: Center at (400,300), radius 80px. - **Label**: "LAKE" at (400,200). - **Grass**: Solid green fill from (0,0) to (500,1000). --- ### **2. HOME (Center-Right Vertical Strip: 500–700px Width)** - **Villa (HOME)**: - **Rectangle**: (550,400) to (700,600). - **Label**: "HOME" at (625,500). - **Garage**: - **Rectangle**: (700,450) to (750,550). - **Label**: "GARAGE" at (725,500). - **Gray Car Path (LANE)**: - **Road**: (500,950) to (700,400), width 60px. - **Fountain (Blue Circle)**: Center at (600,675), radius 30px. - **Label**: "MAIN ENTRANCE" at (600,975), "LANE" at (600,750). - **Stepping Stone Paths**: - **To Pool**: Dotted line from (625,600) → (450,500). - **To Gazebo**: Dotted line from (675,600) → (800,300). --- ### **3. AGRICULTURE (Right Half: 700–1000px Width)** - **Agricultural Field**: - **Rectangle**: (700,0) to (1000,1000). - **Label**: "AGRICULTURAL FIELD" at (850,50). - **Animal Enclosures**: - **Rectangles**: 1. (720,100) to (820,250). 2. (720,260) to (820,400). - **Label**: "ANIMAL ENCLOSURES" at (770,50). - **Vegetable Garden**: - **Rectangle**: (700,450) to (1000,550). - **Label**: "VEGETABLE GARDEN" at (850,500). --- ### **4. Trees & Boundaries** - **Road Trees**: Line both sides of the gray path from (500,950) to (700,400), spaced 50px apart. - **Field Boundary Trees**: Vertical line from (700,400) to (700,600), separating HOME and AGRICULTURE. --- ### **5. Gazebo & Pool** - **Gazebo (Pentagon)**: - **Corners**: (800,250) → (850,200) → (900,250) → (875,300) → (825,300). - **Label**: "GAZEBO" at (850,350). - **Pool**: Unlabeled oval at (450,450) to (550,550). --- ### **Style Instructions** - **2D Flat Design**: No shadows, gradients, or perspective. - **Colors**: - Gray path: `#808080` - Green zones: `#90EE90` - Yellow agriculture: `#FFFF00` - Blue fountain/lake: `#4682B4` - Orange enclosures: `#FFA500`
https://s.mj.run/1150bpD5hFw Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1.8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --ar 4:5 --style raw --stylize 400 --c 50
Create a movie-level ultra-realistic photo of a upload image of a man. The face matches the reference image 100.9999% 35mm Kodak Portra 400 film, cinematic portrait shot during golden hour. Aspect ratio 9:16 Captured with a Canon AE-1 using 50mm f/1.4 lens, shutter speed 1/250s, white balance 5200K (daylight), ISO 400. A mid-shot portrait of a man sitting in a vintage convertible car, leaning on the door with his left arm resting casually. He wears a light beige linen blazer, striped open-collar shirt, and leather bracelets. His gaze is directed toward the horizon, expressing contemplation and calm confidence. The composition uses soft, natural sunlight that illuminates his face from the side, creating gentle golden highlights and smooth shadows across his profile and the car’s curves. The focus is on the man’s thoughtful expression, with shallow depth of field (f/1.4) blurring the background sky for cinematic separation. Mood & Tone: nostalgic, warm, introspective, timeless elegance. Color Palette: soft amber, golden beige, charcoal black, muted sky blue. Film Aesthetic: subtle grain, creamy highlights, balanced contrast typical of Portra 400. keep face features original
Adjustment of Cufflink (Detailed Mid-Shot) Ultra-realistic, mid-shot portrait focusing on a precise detail. Face match 100.9999%. Aspect Ratio: 4:5. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/125, ISO 400, tungsten white balance. Composition: Tight mid-shot where the man is looking down, intensely focused on adjusting a simple, elegant cufflink. His face is partially obscured by his hand, but the concentration in his brow is visible. Mood & Tone: Obsessive perfection, quiet preparation, focused intensity. Lighting: A single, small directional light source highlights the wrist, the cufflink, and the texture of the crisp white shirt cuff. Color Palette: High contrast between the dark tuxedo and the bright, yet warm-toned white shirt. Shot Detail: Hyper-detailed focus on the metal texture of the cufflink and the fabric fibers, very shallow depth of field
Over-the-Shoulder Shot (Action/Dialogue Implied) Ultra-realistic, dynamic over-the-shoulder (OTS) cinematic shot of the same man in a black tuxedo. Face match 100.9999%. Aspect Ratio: 16:9. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/100, ISO 400, tungsten white balance. Composition: The camera is positioned behind an unseen, heavily blurred second figure's shoulder (only the dark blur is visible in the foreground), focusing on the main subject who is standing in profile or three-quarter view, looking intently at the blurred figure. His hand is gesturing slightly, mid-sentence. Mood & Tone: Intense, focused, and suggestive of a high-stakes conversation. Shallow depth of field ensures the focus is purely on the subject's reaction. Color Palette: Cinematic warm and cool tones playing on the faces, heavy Portra 400 glow. Shot Detail: Intense eye contact, blurred foreground object adds a layer of secrecy/eavesdropping
He optimizado tu código para lograr una modulación vocal continua y fluida basada en los sliders, con caché de audio, timeouts y mejor manejo del estado. Ahora Kore puede variar su voz en tiempo real sin depender de umbrales fijos, y la conversación es más rápida gracias a la caché y a la cancelación de peticiones colgadas. ```javascript import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, Mic, MicOff, Settings2, Activity, Loader2, X, GripHorizontal, LayoutGrid, Zap, AlertCircle } from 'lucide-react'; // --- CONSTANTES --- const SILENT_WAV = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; const TTS_TIMEOUT = 5000; // 5 segundos máximo para la síntesis const DEFAULT_API_KEY = 'AIzaSyBlkvy_Op-XlzSMSDDl9ip42dMFZX28MAA'; // ⚠️ Cámbiala por tu propia clave // --- UTILIDADES --- const base64ToWavBlob = (base64Data, sampleRate = 24000) => { const binaryString = window.atob(base64Data); const pcmData = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) pcmData[i] = binaryString.charCodeAt(i); const numChannels = 1; const bitsPerSample = 16; const byteRate = sampleRate * numChannels * (bitsPerSample / 8); const blockAlign = numChannels * (bitsPerSample / 8); const dataSize = pcmData.length; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < dataSize; i++) view.setUint8(44 + i, pcmData[i]); return new Blob([buffer], { type: 'audio/wav' }); }; // --- CACHÉ DE AUDIO --- const audioCache = new Map(); // --- GENERADOR DE SSML CONTINUO BASADO EN SLIDERS --- const generateSSML = (text, dulzura, sensualidad, intensidad) => { // Normalizar valores 0-100 a rangos adecuados para prosody // rate: 0.5 a 2.0 (1.0 es normal) const rate = 0.8 + (intensidad / 100) * 1.2; // 0.8 (lento) a 2.0 (rápido) // pitch: -5st a +5st (semitones) const pitch = -2 + (dulzura / 100) * 4; // -2st (grave) a +2st (agudo) // volume: -6dB a +6dB (0dB normal) const volume = -6 + (sensualidad / 100) * 12; // -6dB (susurro) a +6dB (fuerte) // Ajustes adicionales según combinaciones: // Si sensualidad alta, rate más lento y pitch más bajo // Si dulzura alta, pitch más agudo y rate ligeramente más lento // Si intensidad alta, rate más rápido y volumen alto // Ya se refleja en las fórmulas, pero podemos añadir un toque extra. const ssml = `<speak> <prosody rate="${rate.toFixed(2)}" pitch="${pitch.toFixed(0)}st" volume="${volume.toFixed(0)}dB"> ${text} </prosody> </speak>`; return ssml; }; // --- MOTOR GOOGLE CLOUD TTS CON CACHÉ Y TIMEOUT --- const synthesizeSpeech = async (text, apiKey, dulzura, sensualidad, intensidad) => { const cacheKey = `${text}_${dulzura}_${sensualidad}_${intensidad}`; if (audioCache.has(cacheKey)) { console.log('🎯 Usando audio cacheado'); return audioCache.get(cacheKey); } const ssml = generateSSML(text, dulzura, sensualidad, intensidad); const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`; const body = { input: { ssml }, voice: { languageCode: 'es-ES', name: 'es-ES-Neural2-F', ssmlGender: 'FEMALE' }, audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 24000 } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TTS_TIMEOUT); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`TTS error: ${res.status}`); const data = await res.json(); audioCache.set(cacheKey, data.audioContent); return data.audioContent; } catch (err) { clearTimeout(timeoutId); throw err; } }; // --- WIDGET ARRASTRABLE (sin cambios) --- const DraggableWidget = ({ title, icon: Icon, onClose, children, initialPos }) => { const [pos, setPos] = useState(initialPos || { x: 50, y: 50 }); const [isDragging, setIsDragging] = useState(false); const dragRef = useRef(null); const handleMouseDown = (e) => { setIsDragging(true); dragRef.current = { startX: e.clientX, startY: e.clientY, initialX: pos.x, initialY: pos.y }; }; const handleMouseMove = (e) => { if (!isDragging) return; setPos({ x: Math.max(0, dragRef.current.initialX + (e.clientX - dragRef.current.startX)), y: Math.max(0, dragRef.current.initialY + (e.clientY - dragRef.current.startY)) }); }; const handleMouseUp = () => setIsDragging(false); useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div style={{ left: `${pos.x}px`, top: `${pos.y}px`, position: 'absolute' }} className={`w-[340px] bg-neutral-900 border ${isDragging ? 'border-emerald-500 shadow-emerald-900/20' : 'border-neutral-700'} rounded-xl shadow-2xl flex flex-col overflow-hidden transition-shadow duration-200 z-50`} > <div onMouseDown={handleMouseDown} className="bg-neutral-950 px-3 py-2 flex items-center justify-between cursor-move select-none border-b border-neutral-800"> <div className="flex items-center gap-2 text-neutral-400"> <GripHorizontal size={14} className="opacity-50" /> {Icon && <Icon size={14} className="text-emerald-500" />} <span className="text-xs font-bold tracking-wider">{title}</span> </div> <button onClick={onClose} className="text-neutral-500 hover:text-red-400 transition-colors"><X size={16} /></button> </div> <div className="p-4 flex-1 overflow-y-auto">{children}</div> </div> ); }; // --- WIDGET PRINCIPAL: MODULADOR VOCAL KORE (MEJORADO) --- const VoiceModulatorWidget = () => { const [text, setText] = useState(''); const [apiKey, setApiKey] = useState(DEFAULT_API_KEY); const [dulzura, setDulzura] = useState(50); const [sensualidad, setSensualidad] = useState(50); const [intensidad, setIntensidad] = useState(50); const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isHandsFree, setIsHandsFree] = useState(false); const [statusMsg, setStatusMsg] = useState('Enlace 1.5 Flash + GCP TTS Establecido.'); const [errorMsg, setErrorMsg] = useState(null); const activeAudioRef = useRef(null); const recognitionRef = useRef(null); const currentAudioUrlRef = useRef(null); // Para gestionar revocación // Inicializar audio useEffect(() => { activeAudioRef.current = new Audio(); activeAudioRef.current.preload = "auto"; return () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } } if (recognitionRef.current) recognitionRef.current.stop(); }; }, []); // Configurar reconocimiento de voz useEffect(() => { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { setErrorMsg('Reconocimiento de voz no soportado.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.lang = 'es-ES'; recognitionRef.current.interimResults = false; recognitionRef.current.continuous = false; recognitionRef.current.onresult = async (event) => { const transcript = event.results[0][0].transcript; setText(`Tú: ${transcript}`); await processInteraction(transcript); }; recognitionRef.current.onerror = (event) => { if (event.error !== 'no-speech') { setErrorMsg(`Micrófono: ${event.error}`); setIsHandsFree(false); } }; recognitionRef.current.onend = () => { if (isHandsFree && !isPlaying && !isLoading) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; // Limpiar al desmontar return () => { if (recognitionRef.current) recognitionRef.current.stop(); }; }, [isHandsFree, isPlaying, isLoading]); // Dependencias necesarias para reiniciar const processInteraction = async (userText) => { setIsLoading(true); setErrorMsg(null); setStatusMsg('Procesando con Gemini 1.5 Flash...'); const key = apiKey.trim(); try { // 1. Llamada a Gemini con timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const payloadText = { contents: [{ parts: [{ text: userText }] }], systemInstruction: { parts: [{ text: `Eres Kore, 28 años, Huesca. Labia, directa, sensual, disruptiva. Responde en español, máx 20 palabras. REGLA INNEGOCIABLE: CERO PREGUNTAS.` }] } }; const resText = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payloadText), signal: controller.signal } ); clearTimeout(timeoutId); if (!resText.ok) throw new Error(`Gemini error: ${resText.status}`); const dataText = await resText.json(); const aiText = dataText.candidates?.[0]?.content?.parts?.[0]?.text || "Mmm... vale."; setText(`Kore: ${aiText}`); // 2. Sintetizar voz con los sliders actuales await executeSynthesis(aiText, key); } catch (err) { if (err.name === 'AbortError') { setErrorMsg('Gemini timeout (5s)'); } else { setErrorMsg(err.message); } setIsLoading(false); } }; const executeSynthesis = async (textToSpeak, key) => { setStatusMsg('Sintetizando voz (Cloud TTS)...'); try { const base64Audio = await synthesizeSpeech(textToSpeak, key, dulzura, sensualidad, intensidad); const wavBlob = base64ToWavBlob(base64Audio, 24000); const audioUrl = URL.createObjectURL(wavBlob); // Revocar URL anterior si existe if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } currentAudioUrlRef.current = audioUrl; activeAudioRef.current.src = audioUrl; activeAudioRef.current.onended = () => { setIsPlaying(false); setStatusMsg('Transmisión completada.'); if (isHandsFree) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; setStatusMsg('Transmitiendo...'); setIsPlaying(true); setIsLoading(false); await activeAudioRef.current.play().catch(err => { throw new Error(`Autoplay bloqueado: ${err.message}`); }); } catch (error) { throw new Error(`Fallo TTS: ${error.message}`); } }; const handleManualPlay = async () => { if (!text.trim()) return setErrorMsg('Escribe algo primero.'); // Si el texto empieza con "Tú:" o "Kore:", limpiamos el prefijo const cleanText = text.replace(/^(Tú:|Kore:)\s*/, ''); if (!cleanText.trim()) return setErrorMsg('Texto vacío después de limpiar.'); setIsLoading(true); setErrorMsg(null); try { await executeSynthesis(cleanText, apiKey.trim()); } catch (err) { setErrorMsg(err.message); setIsLoading(false); } }; const toggleHandsFree = () => { if (!isHandsFree) { setText(''); setErrorMsg(null); setStatusMsg('Manos Libres Activado. Habla...'); // Desbloquear audio en algunos navegadores if (activeAudioRef.current) { activeAudioRef.current.src = SILENT_WAV; activeAudioRef.current.play().catch(() => {}); } try { recognitionRef.current.start(); } catch (e) {} } else { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Sistemas en pausa.'); if (recognitionRef.current) recognitionRef.current.stop(); } setIsHandsFree(!isHandsFree); }; const stopAudio = () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Señal interrumpida.'); }; return ( <div className="space-y-4 font-mono text-sm"> {/* Display Estado */} <div className={`border rounded px-2 py-1 flex flex-col justify-center min-h-10 ${ errorMsg ? 'bg-red-950/50 border-red-900' : isHandsFree ? 'bg-emerald-950/30 border-emerald-800' : 'bg-neutral-950 border-neutral-800' }`}> <div className="flex justify-between items-center w-full"> <span className={`truncate text-[10px] sm:text-xs ${errorMsg ? 'text-red-500' : 'text-emerald-500'}`}> > {errorMsg || statusMsg} </span> {isPlaying && !errorMsg && <Activity size={14} className="text-emerald-500 animate-pulse ml-2 flex-shrink-0" />} {isLoading && !errorMsg && <Zap size={14} className="text-amber-500 animate-pulse ml-2 flex-shrink-0" />} {isHandsFree && !isPlaying && !isLoading && !errorMsg && <Mic size={14} className="text-red-500 animate-pulse ml-2 flex-shrink-0" />} </div> </div> {/* Input Texto / Log */} <textarea value={text} onChange={(e) => setText(e.target.value)} className="w-full bg-neutral-950/50 border border-neutral-700 rounded p-2 text-xs text-neutral-300 focus:outline-none focus:border-emerald-500 resize-none h-20" placeholder={isHandsFree ? "Escuchando transcripción en tiempo real..." : "Escribe texto directo o activa Manos Libres..."} readOnly={isHandsFree || isLoading} /> {/* Sliders continuos (controlan SSML en tiempo real) */} <div className="space-y-3 bg-neutral-950/30 p-3 rounded border border-neutral-800"> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Agresiva</span><span className="text-emerald-400">Dulzura [{dulzura}]</span><span>Dulce</span> </div> <input type="range" min="0" max="100" value={dulzura} onChange={(e)=>setDulzura(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-emerald-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Robótica</span><span className="text-pink-400">Aura [{sensualidad}]</span><span>Sensual</span> </div> <input type="range" min="0" max="100" value={sensualidad} onChange={(e)=>setSensualidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-pink-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Atenuada</span><span className="text-amber-400">Intensidad [{intensidad}]</span><span>Fuerte</span> </div> <input type="range" min="0" max="100" value={intensidad} onChange={(e)=>setIntensidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-amber-500 cursor-pointer" /> </div> </div> {/* Botones de Control */} <div className="flex flex-col sm:flex-row gap-2"> <button onClick={toggleHandsFree} disabled={isLoading} className={`flex-1 py-2 rounded text-xs font-bold flex items-center justify-center gap-2 transition-colors border ${ isHandsFree ? 'bg-red-900/20 text-red-400 border-red-900/50 hover:bg-red-900/40 shadow-[0_0_10px_rgba(239,68,68,0.2)]' : 'bg-indigo-900/20 text-indigo-400 border-indigo-900/50 hover:bg-indigo-900/40' }`} > {isHandsFree ? <MicOff size={14} /> : <Mic size={14} />} {isHandsFree ? 'Detener Escucha' : 'Manos Libres'} </button> <div className="flex gap-2 flex-1"> <button onClick={handleManualPlay} disabled={isLoading || isPlaying || isHandsFree} className="flex-1 bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 border border-emerald-600/50 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center gap-1 transition-colors" > {isLoading ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />} Sintetizar </button> <button onClick={stopAudio} disabled={!isPlaying && !isHandsFree} className="px-4 bg-neutral-800 hover:bg-neutral-700 text-neutral-400 border border-neutral-700 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center transition-colors" > <Square size={14} /> </button> </div> </div> {/* Botón para limpiar caché (opcional) */} <div className="text-right"> <button onClick={() => audioCache.clear()} className="text-[8px] text-neutral-600 hover:text-neutral-400 underline" > limpiar caché de audio </button> </div> </div> ); }; // --- ENTORNO ESCRITORIO (sin cambios) --- export default function App() { const [widgets, setWidgets] = useState({ voice: { isOpen: true, pos: { x: window.innerWidth > 768 ? window.innerWidth / 2 - 170 : 20, y: 40 } } }); const toggleWidget = (id) => { setWidgets(prev => ({ ...prev, [id]: { ...prev[id], isOpen: !prev[id].isOpen } })); }; return ( <div className="w-full h-screen bg-neutral-950 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.1),rgba(0,0,0,1))] overflow-hidden relative font-sans text-neutral-200"> <div className="absolute inset-0 flex items-center justify-center opacity-[0.02] pointer-events-none"><Settings2 size={500} /></div> {widgets.voice.isOpen && ( <DraggableWidget title="MODULADOR VOCAL KORE" icon={Zap} initialPos={widgets.voice.pos} onClose={() => toggleWidget('voice')}> <VoiceModulatorWidget /> </DraggableWidget> )} <div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-neutral-900/80 backdrop-blur-md border border-neutral-700/50 p-2 rounded-2xl shadow-2xl flex gap-2 z-[100]"> <div className="px-3 flex items-center border-r border-neutral-700/50 text-neutral-500"><LayoutGrid size={20} /></div> <button onClick={() => toggleWidget('voice')} className={`px-4 py-2 rounded-xl flex items-center gap-2 text-sm font-medium transition-all ${
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.8> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8>
Fashion editorial style grand photograph, hair light, Polaroid, F/5, dramatic lighting, Samsung Galaxy, F/1.8, rim light, Iphone X, L USM, side light, film camera, 800mm lens, waning light, Nikon d3300, 35mm, Proud, key light, compact camera, telephoto lens, Bloom light, Kodak portra 800, Depth of field 270mm, Black lighting, Kodak gold 200, F/2.8, soft light, Phase One XF IQ4 150MP, F/14, Muted Colors, Motion blur, Canon 5d mark 4, Circular polarizer, (young woman, very thin, small breasts, abs, supermodel in post modern environment :1.3) with Purple skin, Bedouin hair, Direct light, Nikon d850, macro lens, flat lighting, Canon RF, F/8, cinematic lighting, Fujifilm XT3, 80mm, Gel lighting, Ilford HP5, Zoom lens, Low Contrast, studio lighting, Fuji superia 400, Low shutter, Neon Light, Nikon Z9, Depth of field 100mm, specular lighting, Kodak portra 400, 50mm, Accent lighting, dslr, Fish-eye Lens, Nostalgic lighting, Canon eos 5d mark 4, Selective focus, two colors . High fashion, trendy, stylish, editorial, magazine style, professional, highly detailed
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.8> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8>
### **1. GREENS (Left Half: 0–500px Width)** - **Flower Area (Triangle)**: - **Corners**: (100,700) → (400,700) → (250,900). - **Label**: "FLOWERS" centered at (250,800). - **Small Lake**: - **Circle**: Center at (400,300), radius 80px. - **Label**: "LAKE" at (400,200). - **Grass**: Solid green fill from (0,0) to (500,1000). --- ### **2. HOME (Center-Right Vertical Strip: 500–700px Width)** - **Villa (HOME)**: - **Rectangle**: (550,400) to (700,600). - **Label**: "HOME" at (625,500). - **Garage**: - **Rectangle**: (700,450) to (750,550). - **Label**: "GARAGE" at (725,500). - **Gray Car Path (LANE)**: - **Road**: (500,950) to (700,400), width 60px. - **Fountain (Blue Circle)**: Center at (600,675), radius 30px. - **Label**: "MAIN ENTRANCE" at (600,975), "LANE" at (600,750). - **Stepping Stone Paths**: - **To Pool**: Dotted line from (625,600) → (450,500). - **To Gazebo**: Dotted line from (675,600) → (800,300). --- ### **3. AGRICULTURE (Right Half: 700–1000px Width)** - **Agricultural Field**: - **Rectangle**: (700,0) to (1000,1000). - **Label**: "AGRICULTURAL FIELD" at (850,50). - **Animal Enclosures**: - **Rectangles**: 1. (720,100) to (820,250). 2. (720,260) to (820,400). - **Label**: "ANIMAL ENCLOSURES" at (770,50). - **Vegetable Garden**: - **Rectangle**: (700,450) to (1000,550). - **Label**: "VEGETABLE GARDEN" at (850,500). --- ### **4. Trees & Boundaries** - **Road Trees**: Line both sides of the gray path from (500,950) to (700,400), spaced 50px apart. - **Field Boundary Trees**: Vertical line from (700,400) to (700,600), separating HOME and AGRICULTURE. --- ### **5. Gazebo & Pool** - **Gazebo (Pentagon)**: - **Corners**: (800,250) → (850,200) → (900,250) → (875,300) → (825,300). - **Label**: "GAZEBO" at (850,350). - **Pool**: Unlabeled oval at (450,450) to (550,550). --- ### **Style Instructions** - **2D Flat Design**: No shadows, gradients, or perspective. - **Colors**: - Gray path: `#808080` - Green zones: `#90EE90` - Yellow agriculture: `#FFFF00` - Blue fountain/lake: `#4682B4` - Orange enclosures: `#FFA500`
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.6> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8> <lora:3DMM_V12:0.8>
Adjustment of Cufflink (Detailed Mid-Shot) Ultra-realistic, mid-shot portrait focusing on a precise detail. Face match 100.9999%. Aspect Ratio: 4:5. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/125, ISO 400, tungsten white balance. Composition: Tight mid-shot where the man is looking down, intensely focused on adjusting a simple, elegant cufflink. His face is partially obscured by his hand, but the concentration in his brow is visible. Mood & Tone: Obsessive perfection, quiet preparation, focused intensity. Lighting: A single, small directional light source highlights the wrist, the cufflink, and the texture of the crisp white shirt cuff. Color Palette: High contrast between the dark tuxedo and the bright, yet warm-toned white shirt. Shot Detail: Hyper-detailed focus on the metal texture of the cufflink and the fabric fibers, very shallow depth of field
At this time, Heaven first had a foundation. 5,400 years later, in the middle of Phase l, the light and pure roseupwards, and sun, moon, stars, and constellations were created. These were called the Four Images. Hence thesaying that heaven began in I. Another 5.400 years later, when Phase I was nearing its end and Phase Il was imminent, things graduallysolidified. As the Book of Changes says, "Great is the Positive; far-reaching is the Negative! All things areendowed and born in accordance with Heaven." This was when the earth began to congeal. After 5,400 morevears came the height of Phase Il. when the heavy and impure solidified. and water, fire, mountains. stoneand Earth came into being. These five were called the Five Movers. Therefore it is said that the Earth wascreated in Phase Il. After a further 5,400 years, at the end of Phase Il and the beginning of the Phase Ill, living beings werecreated. In the words of the Book of the Calendar, "The essence of the sky came down and the essence ofearth went up. Heaven and Earth intermingled, and all creatures were born." Then Heaven was bright andEarth was fresh, and the Positive intermingled with the Negative. 5.400 years later, when Phase Il was at itsheight, men, birds and beasts were created. Thus the Three Powers--Heaven, Earth and Man--now had theitset places. Therefore it is said that man was created in Phase Ill. Moved by Pan Cu's creation, the Three Emperors put the world in order and the Five Rulers laid down themoral code. The world was then divided into four great continents: The Eastern Continent of Superior Body.the Western Continent of Cattle-gift, the Southern Continent of Jambu and the Northern Continent of KuruThis book deals only with the Eastern Continent of Superior Body. Beyond the seas there is a country calledAolai. This country is next to an ocean, and in the middle of the ocean is a famous island called the Mountainof Flowers and Fruit. This mountain is the ancestral artery of the Ten Continents, the oripin of the Threeslands: it was formed when the clear and impure were separated and the Enormous Vagueness was divided. Itis a really splendid mountain and there are some verses to prove it:
Over-the-Shoulder Shot (Action/Dialogue Implied) Ultra-realistic, dynamic over-the-shoulder (OTS) cinematic shot of the same man in a black tuxedo. Face match 100.9999%. Aspect Ratio: 16:9. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/100, ISO 400, tungsten white balance. Composition: The camera is positioned behind an unseen, heavily blurred second figure's shoulder (only the dark blur is visible in the foreground), focusing on the main subject who is standing in profile or three-quarter view, looking intently at the blurred figure. His hand is gesturing slightly, mid-sentence. Mood & Tone: Intense, focused, and suggestive of a high-stakes conversation. Shallow depth of field ensures the focus is purely on the subject's reaction. Color Palette: Cinematic warm and cool tones playing on the faces, heavy Portra 400 glow. Shot Detail: Intense eye contact, blurred foreground object adds a layer of secrecy/eavesdropping
https://s.mj.run/1150bpD5hFw Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1.8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --ar 4:5 --style raw --stylize 400 --c 50
a woman sitting on a rock next to a dragon in the woods with her head turned to the side, Clint Cearley, epic fantasy character art, a character portrait, fantasy art, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera
He optimizado tu código para lograr una modulación vocal continua y fluida basada en los sliders, con caché de audio, timeouts y mejor manejo del estado. Ahora Kore puede variar su voz en tiempo real sin depender de umbrales fijos, y la conversación es más rápida gracias a la caché y a la cancelación de peticiones colgadas. ```javascript import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, Mic, MicOff, Settings2, Activity, Loader2, X, GripHorizontal, LayoutGrid, Zap, AlertCircle } from 'lucide-react'; // --- CONSTANTES --- const SILENT_WAV = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; const TTS_TIMEOUT = 5000; // 5 segundos máximo para la síntesis const DEFAULT_API_KEY = 'AIzaSyBlkvy_Op-XlzSMSDDl9ip42dMFZX28MAA'; // ⚠️ Cámbiala por tu propia clave // --- UTILIDADES --- const base64ToWavBlob = (base64Data, sampleRate = 24000) => { const binaryString = window.atob(base64Data); const pcmData = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) pcmData[i] = binaryString.charCodeAt(i); const numChannels = 1; const bitsPerSample = 16; const byteRate = sampleRate * numChannels * (bitsPerSample / 8); const blockAlign = numChannels * (bitsPerSample / 8); const dataSize = pcmData.length; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < dataSize; i++) view.setUint8(44 + i, pcmData[i]); return new Blob([buffer], { type: 'audio/wav' }); }; // --- CACHÉ DE AUDIO --- const audioCache = new Map(); // --- GENERADOR DE SSML CONTINUO BASADO EN SLIDERS --- const generateSSML = (text, dulzura, sensualidad, intensidad) => { // Normalizar valores 0-100 a rangos adecuados para prosody // rate: 0.5 a 2.0 (1.0 es normal) const rate = 0.8 + (intensidad / 100) * 1.2; // 0.8 (lento) a 2.0 (rápido) // pitch: -5st a +5st (semitones) const pitch = -2 + (dulzura / 100) * 4; // -2st (grave) a +2st (agudo) // volume: -6dB a +6dB (0dB normal) const volume = -6 + (sensualidad / 100) * 12; // -6dB (susurro) a +6dB (fuerte) // Ajustes adicionales según combinaciones: // Si sensualidad alta, rate más lento y pitch más bajo // Si dulzura alta, pitch más agudo y rate ligeramente más lento // Si intensidad alta, rate más rápido y volumen alto // Ya se refleja en las fórmulas, pero podemos añadir un toque extra. const ssml = `<speak> <prosody rate="${rate.toFixed(2)}" pitch="${pitch.toFixed(0)}st" volume="${volume.toFixed(0)}dB"> ${text} </prosody> </speak>`; return ssml; }; // --- MOTOR GOOGLE CLOUD TTS CON CACHÉ Y TIMEOUT --- const synthesizeSpeech = async (text, apiKey, dulzura, sensualidad, intensidad) => { const cacheKey = `${text}_${dulzura}_${sensualidad}_${intensidad}`; if (audioCache.has(cacheKey)) { console.log('🎯 Usando audio cacheado'); return audioCache.get(cacheKey); } const ssml = generateSSML(text, dulzura, sensualidad, intensidad); const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`; const body = { input: { ssml }, voice: { languageCode: 'es-ES', name: 'es-ES-Neural2-F', ssmlGender: 'FEMALE' }, audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 24000 } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TTS_TIMEOUT); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`TTS error: ${res.status}`); const data = await res.json(); audioCache.set(cacheKey, data.audioContent); return data.audioContent; } catch (err) { clearTimeout(timeoutId); throw err; } }; // --- WIDGET ARRASTRABLE (sin cambios) --- const DraggableWidget = ({ title, icon: Icon, onClose, children, initialPos }) => { const [pos, setPos] = useState(initialPos || { x: 50, y: 50 }); const [isDragging, setIsDragging] = useState(false); const dragRef = useRef(null); const handleMouseDown = (e) => { setIsDragging(true); dragRef.current = { startX: e.clientX, startY: e.clientY, initialX: pos.x, initialY: pos.y }; }; const handleMouseMove = (e) => { if (!isDragging) return; setPos({ x: Math.max(0, dragRef.current.initialX + (e.clientX - dragRef.current.startX)), y: Math.max(0, dragRef.current.initialY + (e.clientY - dragRef.current.startY)) }); }; const handleMouseUp = () => setIsDragging(false); useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div style={{ left: `${pos.x}px`, top: `${pos.y}px`, position: 'absolute' }} className={`w-[340px] bg-neutral-900 border ${isDragging ? 'border-emerald-500 shadow-emerald-900/20' : 'border-neutral-700'} rounded-xl shadow-2xl flex flex-col overflow-hidden transition-shadow duration-200 z-50`} > <div onMouseDown={handleMouseDown} className="bg-neutral-950 px-3 py-2 flex items-center justify-between cursor-move select-none border-b border-neutral-800"> <div className="flex items-center gap-2 text-neutral-400"> <GripHorizontal size={14} className="opacity-50" /> {Icon && <Icon size={14} className="text-emerald-500" />} <span className="text-xs font-bold tracking-wider">{title}</span> </div> <button onClick={onClose} className="text-neutral-500 hover:text-red-400 transition-colors"><X size={16} /></button> </div> <div className="p-4 flex-1 overflow-y-auto">{children}</div> </div> ); }; // --- WIDGET PRINCIPAL: MODULADOR VOCAL KORE (MEJORADO) --- const VoiceModulatorWidget = () => { const [text, setText] = useState(''); const [apiKey, setApiKey] = useState(DEFAULT_API_KEY); const [dulzura, setDulzura] = useState(50); const [sensualidad, setSensualidad] = useState(50); const [intensidad, setIntensidad] = useState(50); const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isHandsFree, setIsHandsFree] = useState(false); const [statusMsg, setStatusMsg] = useState('Enlace 1.5 Flash + GCP TTS Establecido.'); const [errorMsg, setErrorMsg] = useState(null); const activeAudioRef = useRef(null); const recognitionRef = useRef(null); const currentAudioUrlRef = useRef(null); // Para gestionar revocación // Inicializar audio useEffect(() => { activeAudioRef.current = new Audio(); activeAudioRef.current.preload = "auto"; return () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } } if (recognitionRef.current) recognitionRef.current.stop(); }; }, []); // Configurar reconocimiento de voz useEffect(() => { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { setErrorMsg('Reconocimiento de voz no soportado.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.lang = 'es-ES'; recognitionRef.current.interimResults = false; recognitionRef.current.continuous = false; recognitionRef.current.onresult = async (event) => { const transcript = event.results[0][0].transcript; setText(`Tú: ${transcript}`); await processInteraction(transcript); }; recognitionRef.current.onerror = (event) => { if (event.error !== 'no-speech') { setErrorMsg(`Micrófono: ${event.error}`); setIsHandsFree(false); } }; recognitionRef.current.onend = () => { if (isHandsFree && !isPlaying && !isLoading) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; // Limpiar al desmontar return () => { if (recognitionRef.current) recognitionRef.current.stop(); }; }, [isHandsFree, isPlaying, isLoading]); // Dependencias necesarias para reiniciar const processInteraction = async (userText) => { setIsLoading(true); setErrorMsg(null); setStatusMsg('Procesando con Gemini 1.5 Flash...'); const key = apiKey.trim(); try { // 1. Llamada a Gemini con timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const payloadText = { contents: [{ parts: [{ text: userText }] }], systemInstruction: { parts: [{ text: `Eres Kore, 28 años, Huesca. Labia, directa, sensual, disruptiva. Responde en español, máx 20 palabras. REGLA INNEGOCIABLE: CERO PREGUNTAS.` }] } }; const resText = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payloadText), signal: controller.signal } ); clearTimeout(timeoutId); if (!resText.ok) throw new Error(`Gemini error: ${resText.status}`); const dataText = await resText.json(); const aiText = dataText.candidates?.[0]?.content?.parts?.[0]?.text || "Mmm... vale."; setText(`Kore: ${aiText}`); // 2. Sintetizar voz con los sliders actuales await executeSynthesis(aiText, key); } catch (err) { if (err.name === 'AbortError') { setErrorMsg('Gemini timeout (5s)'); } else { setErrorMsg(err.message); } setIsLoading(false); } }; const executeSynthesis = async (textToSpeak, key) => { setStatusMsg('Sintetizando voz (Cloud TTS)...'); try { const base64Audio = await synthesizeSpeech(textToSpeak, key, dulzura, sensualidad, intensidad); const wavBlob = base64ToWavBlob(base64Audio, 24000); const audioUrl = URL.createObjectURL(wavBlob); // Revocar URL anterior si existe if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } currentAudioUrlRef.current = audioUrl; activeAudioRef.current.src = audioUrl; activeAudioRef.current.onended = () => { setIsPlaying(false); setStatusMsg('Transmisión completada.'); if (isHandsFree) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; setStatusMsg('Transmitiendo...'); setIsPlaying(true); setIsLoading(false); await activeAudioRef.current.play().catch(err => { throw new Error(`Autoplay bloqueado: ${err.message}`); }); } catch (error) { throw new Error(`Fallo TTS: ${error.message}`); } }; const handleManualPlay = async () => { if (!text.trim()) return setErrorMsg('Escribe algo primero.'); // Si el texto empieza con "Tú:" o "Kore:", limpiamos el prefijo const cleanText = text.replace(/^(Tú:|Kore:)\s*/, ''); if (!cleanText.trim()) return setErrorMsg('Texto vacío después de limpiar.'); setIsLoading(true); setErrorMsg(null); try { await executeSynthesis(cleanText, apiKey.trim()); } catch (err) { setErrorMsg(err.message); setIsLoading(false); } }; const toggleHandsFree = () => { if (!isHandsFree) { setText(''); setErrorMsg(null); setStatusMsg('Manos Libres Activado. Habla...'); // Desbloquear audio en algunos navegadores if (activeAudioRef.current) { activeAudioRef.current.src = SILENT_WAV; activeAudioRef.current.play().catch(() => {}); } try { recognitionRef.current.start(); } catch (e) {} } else { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Sistemas en pausa.'); if (recognitionRef.current) recognitionRef.current.stop(); } setIsHandsFree(!isHandsFree); }; const stopAudio = () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Señal interrumpida.'); }; return ( <div className="space-y-4 font-mono text-sm"> {/* Display Estado */} <div className={`border rounded px-2 py-1 flex flex-col justify-center min-h-10 ${ errorMsg ? 'bg-red-950/50 border-red-900' : isHandsFree ? 'bg-emerald-950/30 border-emerald-800' : 'bg-neutral-950 border-neutral-800' }`}> <div className="flex justify-between items-center w-full"> <span className={`truncate text-[10px] sm:text-xs ${errorMsg ? 'text-red-500' : 'text-emerald-500'}`}> > {errorMsg || statusMsg} </span> {isPlaying && !errorMsg && <Activity size={14} className="text-emerald-500 animate-pulse ml-2 flex-shrink-0" />} {isLoading && !errorMsg && <Zap size={14} className="text-amber-500 animate-pulse ml-2 flex-shrink-0" />} {isHandsFree && !isPlaying && !isLoading && !errorMsg && <Mic size={14} className="text-red-500 animate-pulse ml-2 flex-shrink-0" />} </div> </div> {/* Input Texto / Log */} <textarea value={text} onChange={(e) => setText(e.target.value)} className="w-full bg-neutral-950/50 border border-neutral-700 rounded p-2 text-xs text-neutral-300 focus:outline-none focus:border-emerald-500 resize-none h-20" placeholder={isHandsFree ? "Escuchando transcripción en tiempo real..." : "Escribe texto directo o activa Manos Libres..."} readOnly={isHandsFree || isLoading} /> {/* Sliders continuos (controlan SSML en tiempo real) */} <div className="space-y-3 bg-neutral-950/30 p-3 rounded border border-neutral-800"> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Agresiva</span><span className="text-emerald-400">Dulzura [{dulzura}]</span><span>Dulce</span> </div> <input type="range" min="0" max="100" value={dulzura} onChange={(e)=>setDulzura(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-emerald-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Robótica</span><span className="text-pink-400">Aura [{sensualidad}]</span><span>Sensual</span> </div> <input type="range" min="0" max="100" value={sensualidad} onChange={(e)=>setSensualidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-pink-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Atenuada</span><span className="text-amber-400">Intensidad [{intensidad}]</span><span>Fuerte</span> </div> <input type="range" min="0" max="100" value={intensidad} onChange={(e)=>setIntensidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-amber-500 cursor-pointer" /> </div> </div> {/* Botones de Control */} <div className="flex flex-col sm:flex-row gap-2"> <button onClick={toggleHandsFree} disabled={isLoading} className={`flex-1 py-2 rounded text-xs font-bold flex items-center justify-center gap-2 transition-colors border ${ isHandsFree ? 'bg-red-900/20 text-red-400 border-red-900/50 hover:bg-red-900/40 shadow-[0_0_10px_rgba(239,68,68,0.2)]' : 'bg-indigo-900/20 text-indigo-400 border-indigo-900/50 hover:bg-indigo-900/40' }`} > {isHandsFree ? <MicOff size={14} /> : <Mic size={14} />} {isHandsFree ? 'Detener Escucha' : 'Manos Libres'} </button> <div className="flex gap-2 flex-1"> <button onClick={handleManualPlay} disabled={isLoading || isPlaying || isHandsFree} className="flex-1 bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 border border-emerald-600/50 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center gap-1 transition-colors" > {isLoading ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />} Sintetizar </button> <button onClick={stopAudio} disabled={!isPlaying && !isHandsFree} className="px-4 bg-neutral-800 hover:bg-neutral-700 text-neutral-400 border border-neutral-700 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center transition-colors" > <Square size={14} /> </button> </div> </div> {/* Botón para limpiar caché (opcional) */} <div className="text-right"> <button onClick={() => audioCache.clear()} className="text-[8px] text-neutral-600 hover:text-neutral-400 underline" > limpiar caché de audio </button> </div> </div> ); }; // --- ENTORNO ESCRITORIO (sin cambios) --- export default function App() { const [widgets, setWidgets] = useState({ voice: { isOpen: true, pos: { x: window.innerWidth > 768 ? window.innerWidth / 2 - 170 : 20, y: 40 } } }); const toggleWidget = (id) => { setWidgets(prev => ({ ...prev, [id]: { ...prev[id], isOpen: !prev[id].isOpen } })); }; return ( <div className="w-full h-screen bg-neutral-950 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.1),rgba(0,0,0,1))] overflow-hidden relative font-sans text-neutral-200"> <div className="absolute inset-0 flex items-center justify-center opacity-[0.02] pointer-events-none"><Settings2 size={500} /></div> {widgets.voice.isOpen && ( <DraggableWidget title="MODULADOR VOCAL KORE" icon={Zap} initialPos={widgets.voice.pos} onClose={() => toggleWidget('voice')}> <VoiceModulatorWidget /> </DraggableWidget> )} <div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-neutral-900/80 backdrop-blur-md border border-neutral-700/50 p-2 rounded-2xl shadow-2xl flex gap-2 z-[100]"> <div className="px-3 flex items-center border-r border-neutral-700/50 text-neutral-500"><LayoutGrid size={20} /></div> <button onClick={() => toggleWidget('voice')} className={`px-4 py-2 rounded-xl flex items-center gap-2 text-sm font-medium transition-all ${
animal look, https://s.mj.run/1150bpD5hFw Smiling and looking to the camera, Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1. 8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --style raw --stylize 400 --c 50 --ar 4:5
Create a movie-level ultra-realistic photo of a upload image of a man. The face matches the reference image 100.9999% 35mm Kodak Portra 400 film, cinematic portrait shot during golden hour. Aspect ratio 9:16 Captured with a Canon AE-1 using 50mm f/1.4 lens, shutter speed 1/250s, white balance 5200K (daylight), ISO 400. A mid-shot portrait of a man sitting in a vintage convertible car, leaning on the door with his left arm resting casually. He wears a light beige linen blazer, striped open-collar shirt, and leather bracelets. His gaze is directed toward the horizon, expressing contemplation and calm confidence. The composition uses soft, natural sunlight that illuminates his face from the side, creating gentle golden highlights and smooth shadows across his profile and the car’s curves. The focus is on the man’s thoughtful expression, with shallow depth of field (f/1.4) blurring the background sky for cinematic separation. Mood & Tone: nostalgic, warm, introspective, timeless elegance. Color Palette: soft amber, golden beige, charcoal black, muted sky blue. Film Aesthetic: subtle grain, creamy highlights, balanced contrast typical of Portra 400. keep face features original
Fashion editorial style grand photograph, hair light, Polaroid, F/5, dramatic lighting, Samsung Galaxy, F/1.8, rim light, Iphone X, L USM, side light, film camera, 800mm lens, waning light, Nikon d3300, 35mm, Proud, key light, compact camera, telephoto lens, Bloom light, Kodak portra 800, Depth of field 270mm, Black lighting, Kodak gold 200, F/2.8, soft light, Phase One XF IQ4 150MP, F/14, Muted Colors, Motion blur, Canon 5d mark 4, Circular polarizer, (young woman, very thin, small breasts, abs, supermodel in post modern environment :1.3) with Purple skin, Bedouin hair, Direct light, Nikon d850, macro lens, flat lighting, Canon RF, F/8, cinematic lighting, Fujifilm XT3, 80mm, Gel lighting, Ilford HP5, Zoom lens, Low Contrast, studio lighting, Fuji superia 400, Low shutter, Neon Light, Nikon Z9, Depth of field 100mm, specular lighting, Kodak portra 400, 50mm, Accent lighting, dslr, Fish-eye Lens, Nostalgic lighting, Canon eos 5d mark 4, Selective focus, two colors . High fashion, trendy, stylish, editorial, magazine style, professional, highly detailed
Create a movie-level ultra-realistic photo of a upload image of a man. The face matches the reference image 100.9999% 35mm Kodak Portra 400 film, cinematic portrait shot during golden hour. Aspect ratio 9:16 Captured with a Canon AE-1 using 50mm f/1.4 lens, shutter speed 1/250s, white balance 5200K (daylight), ISO 400. A mid-shot portrait of a man sitting in a vintage convertible car, leaning on the door with his left arm resting casually. He wears a light beige linen blazer, striped open-collar shirt, and leather bracelets. His gaze is directed toward the horizon, expressing contemplation and calm confidence. The composition uses soft, natural sunlight that illuminates his face from the side, creating gentle golden highlights and smooth shadows across his profile and the car’s curves. The focus is on the man’s thoughtful expression, with shallow depth of field (f/1.4) blurring the background sky for cinematic separation. Mood & Tone: nostalgic, warm, introspective, timeless elegance. Color Palette: soft amber, golden beige, charcoal black, muted sky blue. Film Aesthetic: subtle grain, creamy highlights, balanced contrast typical of Portra 400. keep face features original
Fashion editorial style grand photograph, hair light, Polaroid, F/5, dramatic lighting, Samsung Galaxy, F/1.8, rim light, Iphone X, L USM, side light, film camera, 800mm lens, waning light, Nikon d3300, 35mm, Proud, key light, compact camera, telephoto lens, Bloom light, Kodak portra 800, Depth of field 270mm, Black lighting, Kodak gold 200, F/2.8, soft light, Phase One XF IQ4 150MP, F/14, Muted Colors, Motion blur, Canon 5d mark 4, Circular polarizer, (young woman, very thin, small breasts, abs, supermodel in post modern environment :1.3) with Purple skin, Bedouin hair, Direct light, Nikon d850, macro lens, flat lighting, Canon RF, F/8, cinematic lighting, Fujifilm XT3, 80mm, Gel lighting, Ilford HP5, Zoom lens, Low Contrast, studio lighting, Fuji superia 400, Low shutter, Neon Light, Nikon Z9, Depth of field 100mm, specular lighting, Kodak portra 400, 50mm, Accent lighting, dslr, Fish-eye Lens, Nostalgic lighting, Canon eos 5d mark 4, Selective focus, two colors . High fashion, trendy, stylish, editorial, magazine style, professional, highly detailed
### **1. GREENS (Left Half: 0–500px Width)** - **Flower Area (Triangle)**: - **Corners**: (100,700) → (400,700) → (250,900). - **Label**: "FLOWERS" centered at (250,800). - **Small Lake**: - **Circle**: Center at (400,300), radius 80px. - **Label**: "LAKE" at (400,200). - **Grass**: Solid green fill from (0,0) to (500,1000). --- ### **2. HOME (Center-Right Vertical Strip: 500–700px Width)** - **Villa (HOME)**: - **Rectangle**: (550,400) to (700,600). - **Label**: "HOME" at (625,500). - **Garage**: - **Rectangle**: (700,450) to (750,550). - **Label**: "GARAGE" at (725,500). - **Gray Car Path (LANE)**: - **Road**: (500,950) to (700,400), width 60px. - **Fountain (Blue Circle)**: Center at (600,675), radius 30px. - **Label**: "MAIN ENTRANCE" at (600,975), "LANE" at (600,750). - **Stepping Stone Paths**: - **To Pool**: Dotted line from (625,600) → (450,500). - **To Gazebo**: Dotted line from (675,600) → (800,300). --- ### **3. AGRICULTURE (Right Half: 700–1000px Width)** - **Agricultural Field**: - **Rectangle**: (700,0) to (1000,1000). - **Label**: "AGRICULTURAL FIELD" at (850,50). - **Animal Enclosures**: - **Rectangles**: 1. (720,100) to (820,250). 2. (720,260) to (820,400). - **Label**: "ANIMAL ENCLOSURES" at (770,50). - **Vegetable Garden**: - **Rectangle**: (700,450) to (1000,550). - **Label**: "VEGETABLE GARDEN" at (850,500). --- ### **4. Trees & Boundaries** - **Road Trees**: Line both sides of the gray path from (500,950) to (700,400), spaced 50px apart. - **Field Boundary Trees**: Vertical line from (700,400) to (700,600), separating HOME and AGRICULTURE. --- ### **5. Gazebo & Pool** - **Gazebo (Pentagon)**: - **Corners**: (800,250) → (850,200) → (900,250) → (875,300) → (825,300). - **Label**: "GAZEBO" at (850,350). - **Pool**: Unlabeled oval at (450,450) to (550,550). --- ### **Style Instructions** - **2D Flat Design**: No shadows, gradients, or perspective. - **Colors**: - Gray path: `#808080` - Green zones: `#90EE90` - Yellow agriculture: `#FFFF00` - Blue fountain/lake: `#4682B4` - Orange enclosures: `#FFA500`
He optimizado tu código para lograr una modulación vocal continua y fluida basada en los sliders, con caché de audio, timeouts y mejor manejo del estado. Ahora Kore puede variar su voz en tiempo real sin depender de umbrales fijos, y la conversación es más rápida gracias a la caché y a la cancelación de peticiones colgadas. ```javascript import React, { useState, useRef, useEffect, useCallback } from 'react'; import { Play, Square, Mic, MicOff, Settings2, Activity, Loader2, X, GripHorizontal, LayoutGrid, Zap, AlertCircle } from 'lucide-react'; // --- CONSTANTES --- const SILENT_WAV = "data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA"; const TTS_TIMEOUT = 5000; // 5 segundos máximo para la síntesis const DEFAULT_API_KEY = 'AIzaSyBlkvy_Op-XlzSMSDDl9ip42dMFZX28MAA'; // ⚠️ Cámbiala por tu propia clave // --- UTILIDADES --- const base64ToWavBlob = (base64Data, sampleRate = 24000) => { const binaryString = window.atob(base64Data); const pcmData = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) pcmData[i] = binaryString.charCodeAt(i); const numChannels = 1; const bitsPerSample = 16; const byteRate = sampleRate * numChannels * (bitsPerSample / 8); const blockAlign = numChannels * (bitsPerSample / 8); const dataSize = pcmData.length; const buffer = new ArrayBuffer(44 + dataSize); const view = new DataView(buffer); const writeString = (view, offset, string) => { for (let i = 0; i < string.length; i++) view.setUint8(offset + i, string.charCodeAt(i)); }; writeString(view, 0, 'RIFF'); view.setUint32(4, 36 + dataSize, true); writeString(view, 8, 'WAVE'); writeString(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, numChannels, true); view.setUint32(24, sampleRate, true); view.setUint32(28, byteRate, true); view.setUint16(32, blockAlign, true); view.setUint16(34, bitsPerSample, true); writeString(view, 36, 'data'); view.setUint32(40, dataSize, true); for (let i = 0; i < dataSize; i++) view.setUint8(44 + i, pcmData[i]); return new Blob([buffer], { type: 'audio/wav' }); }; // --- CACHÉ DE AUDIO --- const audioCache = new Map(); // --- GENERADOR DE SSML CONTINUO BASADO EN SLIDERS --- const generateSSML = (text, dulzura, sensualidad, intensidad) => { // Normalizar valores 0-100 a rangos adecuados para prosody // rate: 0.5 a 2.0 (1.0 es normal) const rate = 0.8 + (intensidad / 100) * 1.2; // 0.8 (lento) a 2.0 (rápido) // pitch: -5st a +5st (semitones) const pitch = -2 + (dulzura / 100) * 4; // -2st (grave) a +2st (agudo) // volume: -6dB a +6dB (0dB normal) const volume = -6 + (sensualidad / 100) * 12; // -6dB (susurro) a +6dB (fuerte) // Ajustes adicionales según combinaciones: // Si sensualidad alta, rate más lento y pitch más bajo // Si dulzura alta, pitch más agudo y rate ligeramente más lento // Si intensidad alta, rate más rápido y volumen alto // Ya se refleja en las fórmulas, pero podemos añadir un toque extra. const ssml = `<speak> <prosody rate="${rate.toFixed(2)}" pitch="${pitch.toFixed(0)}st" volume="${volume.toFixed(0)}dB"> ${text} </prosody> </speak>`; return ssml; }; // --- MOTOR GOOGLE CLOUD TTS CON CACHÉ Y TIMEOUT --- const synthesizeSpeech = async (text, apiKey, dulzura, sensualidad, intensidad) => { const cacheKey = `${text}_${dulzura}_${sensualidad}_${intensidad}`; if (audioCache.has(cacheKey)) { console.log('🎯 Usando audio cacheado'); return audioCache.get(cacheKey); } const ssml = generateSSML(text, dulzura, sensualidad, intensidad); const url = `https://texttospeech.googleapis.com/v1/text:synthesize?key=${apiKey}`; const body = { input: { ssml }, voice: { languageCode: 'es-ES', name: 'es-ES-Neural2-F', ssmlGender: 'FEMALE' }, audioConfig: { audioEncoding: 'LINEAR16', sampleRateHertz: 24000 } }; const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), TTS_TIMEOUT); try { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: controller.signal }); clearTimeout(timeoutId); if (!res.ok) throw new Error(`TTS error: ${res.status}`); const data = await res.json(); audioCache.set(cacheKey, data.audioContent); return data.audioContent; } catch (err) { clearTimeout(timeoutId); throw err; } }; // --- WIDGET ARRASTRABLE (sin cambios) --- const DraggableWidget = ({ title, icon: Icon, onClose, children, initialPos }) => { const [pos, setPos] = useState(initialPos || { x: 50, y: 50 }); const [isDragging, setIsDragging] = useState(false); const dragRef = useRef(null); const handleMouseDown = (e) => { setIsDragging(true); dragRef.current = { startX: e.clientX, startY: e.clientY, initialX: pos.x, initialY: pos.y }; }; const handleMouseMove = (e) => { if (!isDragging) return; setPos({ x: Math.max(0, dragRef.current.initialX + (e.clientX - dragRef.current.startX)), y: Math.max(0, dragRef.current.initialY + (e.clientY - dragRef.current.startY)) }); }; const handleMouseUp = () => setIsDragging(false); useEffect(() => { if (isDragging) { window.addEventListener('mousemove', handleMouseMove); window.addEventListener('mouseup', handleMouseUp); } return () => { window.removeEventListener('mousemove', handleMouseMove); window.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging]); return ( <div style={{ left: `${pos.x}px`, top: `${pos.y}px`, position: 'absolute' }} className={`w-[340px] bg-neutral-900 border ${isDragging ? 'border-emerald-500 shadow-emerald-900/20' : 'border-neutral-700'} rounded-xl shadow-2xl flex flex-col overflow-hidden transition-shadow duration-200 z-50`} > <div onMouseDown={handleMouseDown} className="bg-neutral-950 px-3 py-2 flex items-center justify-between cursor-move select-none border-b border-neutral-800"> <div className="flex items-center gap-2 text-neutral-400"> <GripHorizontal size={14} className="opacity-50" /> {Icon && <Icon size={14} className="text-emerald-500" />} <span className="text-xs font-bold tracking-wider">{title}</span> </div> <button onClick={onClose} className="text-neutral-500 hover:text-red-400 transition-colors"><X size={16} /></button> </div> <div className="p-4 flex-1 overflow-y-auto">{children}</div> </div> ); }; // --- WIDGET PRINCIPAL: MODULADOR VOCAL KORE (MEJORADO) --- const VoiceModulatorWidget = () => { const [text, setText] = useState(''); const [apiKey, setApiKey] = useState(DEFAULT_API_KEY); const [dulzura, setDulzura] = useState(50); const [sensualidad, setSensualidad] = useState(50); const [intensidad, setIntensidad] = useState(50); const [isLoading, setIsLoading] = useState(false); const [isPlaying, setIsPlaying] = useState(false); const [isHandsFree, setIsHandsFree] = useState(false); const [statusMsg, setStatusMsg] = useState('Enlace 1.5 Flash + GCP TTS Establecido.'); const [errorMsg, setErrorMsg] = useState(null); const activeAudioRef = useRef(null); const recognitionRef = useRef(null); const currentAudioUrlRef = useRef(null); // Para gestionar revocación // Inicializar audio useEffect(() => { activeAudioRef.current = new Audio(); activeAudioRef.current.preload = "auto"; return () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } } if (recognitionRef.current) recognitionRef.current.stop(); }; }, []); // Configurar reconocimiento de voz useEffect(() => { if (!('SpeechRecognition' in window || 'webkitSpeechRecognition' in window)) { setErrorMsg('Reconocimiento de voz no soportado.'); return; } const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; recognitionRef.current = new SpeechRecognition(); recognitionRef.current.lang = 'es-ES'; recognitionRef.current.interimResults = false; recognitionRef.current.continuous = false; recognitionRef.current.onresult = async (event) => { const transcript = event.results[0][0].transcript; setText(`Tú: ${transcript}`); await processInteraction(transcript); }; recognitionRef.current.onerror = (event) => { if (event.error !== 'no-speech') { setErrorMsg(`Micrófono: ${event.error}`); setIsHandsFree(false); } }; recognitionRef.current.onend = () => { if (isHandsFree && !isPlaying && !isLoading) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; // Limpiar al desmontar return () => { if (recognitionRef.current) recognitionRef.current.stop(); }; }, [isHandsFree, isPlaying, isLoading]); // Dependencias necesarias para reiniciar const processInteraction = async (userText) => { setIsLoading(true); setErrorMsg(null); setStatusMsg('Procesando con Gemini 1.5 Flash...'); const key = apiKey.trim(); try { // 1. Llamada a Gemini con timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const payloadText = { contents: [{ parts: [{ text: userText }] }], systemInstruction: { parts: [{ text: `Eres Kore, 28 años, Huesca. Labia, directa, sensual, disruptiva. Responde en español, máx 20 palabras. REGLA INNEGOCIABLE: CERO PREGUNTAS.` }] } }; const resText = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=${key}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payloadText), signal: controller.signal } ); clearTimeout(timeoutId); if (!resText.ok) throw new Error(`Gemini error: ${resText.status}`); const dataText = await resText.json(); const aiText = dataText.candidates?.[0]?.content?.parts?.[0]?.text || "Mmm... vale."; setText(`Kore: ${aiText}`); // 2. Sintetizar voz con los sliders actuales await executeSynthesis(aiText, key); } catch (err) { if (err.name === 'AbortError') { setErrorMsg('Gemini timeout (5s)'); } else { setErrorMsg(err.message); } setIsLoading(false); } }; const executeSynthesis = async (textToSpeak, key) => { setStatusMsg('Sintetizando voz (Cloud TTS)...'); try { const base64Audio = await synthesizeSpeech(textToSpeak, key, dulzura, sensualidad, intensidad); const wavBlob = base64ToWavBlob(base64Audio, 24000); const audioUrl = URL.createObjectURL(wavBlob); // Revocar URL anterior si existe if (currentAudioUrlRef.current) { URL.revokeObjectURL(currentAudioUrlRef.current); } currentAudioUrlRef.current = audioUrl; activeAudioRef.current.src = audioUrl; activeAudioRef.current.onended = () => { setIsPlaying(false); setStatusMsg('Transmisión completada.'); if (isHandsFree) { try { recognitionRef.current.start(); setStatusMsg('Escuchando...'); } catch (e) {} } }; setStatusMsg('Transmitiendo...'); setIsPlaying(true); setIsLoading(false); await activeAudioRef.current.play().catch(err => { throw new Error(`Autoplay bloqueado: ${err.message}`); }); } catch (error) { throw new Error(`Fallo TTS: ${error.message}`); } }; const handleManualPlay = async () => { if (!text.trim()) return setErrorMsg('Escribe algo primero.'); // Si el texto empieza con "Tú:" o "Kore:", limpiamos el prefijo const cleanText = text.replace(/^(Tú:|Kore:)\s*/, ''); if (!cleanText.trim()) return setErrorMsg('Texto vacío después de limpiar.'); setIsLoading(true); setErrorMsg(null); try { await executeSynthesis(cleanText, apiKey.trim()); } catch (err) { setErrorMsg(err.message); setIsLoading(false); } }; const toggleHandsFree = () => { if (!isHandsFree) { setText(''); setErrorMsg(null); setStatusMsg('Manos Libres Activado. Habla...'); // Desbloquear audio en algunos navegadores if (activeAudioRef.current) { activeAudioRef.current.src = SILENT_WAV; activeAudioRef.current.play().catch(() => {}); } try { recognitionRef.current.start(); } catch (e) {} } else { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Sistemas en pausa.'); if (recognitionRef.current) recognitionRef.current.stop(); } setIsHandsFree(!isHandsFree); }; const stopAudio = () => { if (activeAudioRef.current) { activeAudioRef.current.pause(); activeAudioRef.current.currentTime = 0; } setIsPlaying(false); setStatusMsg('Señal interrumpida.'); }; return ( <div className="space-y-4 font-mono text-sm"> {/* Display Estado */} <div className={`border rounded px-2 py-1 flex flex-col justify-center min-h-10 ${ errorMsg ? 'bg-red-950/50 border-red-900' : isHandsFree ? 'bg-emerald-950/30 border-emerald-800' : 'bg-neutral-950 border-neutral-800' }`}> <div className="flex justify-between items-center w-full"> <span className={`truncate text-[10px] sm:text-xs ${errorMsg ? 'text-red-500' : 'text-emerald-500'}`}> > {errorMsg || statusMsg} </span> {isPlaying && !errorMsg && <Activity size={14} className="text-emerald-500 animate-pulse ml-2 flex-shrink-0" />} {isLoading && !errorMsg && <Zap size={14} className="text-amber-500 animate-pulse ml-2 flex-shrink-0" />} {isHandsFree && !isPlaying && !isLoading && !errorMsg && <Mic size={14} className="text-red-500 animate-pulse ml-2 flex-shrink-0" />} </div> </div> {/* Input Texto / Log */} <textarea value={text} onChange={(e) => setText(e.target.value)} className="w-full bg-neutral-950/50 border border-neutral-700 rounded p-2 text-xs text-neutral-300 focus:outline-none focus:border-emerald-500 resize-none h-20" placeholder={isHandsFree ? "Escuchando transcripción en tiempo real..." : "Escribe texto directo o activa Manos Libres..."} readOnly={isHandsFree || isLoading} /> {/* Sliders continuos (controlan SSML en tiempo real) */} <div className="space-y-3 bg-neutral-950/30 p-3 rounded border border-neutral-800"> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Agresiva</span><span className="text-emerald-400">Dulzura [{dulzura}]</span><span>Dulce</span> </div> <input type="range" min="0" max="100" value={dulzura} onChange={(e)=>setDulzura(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-emerald-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Robótica</span><span className="text-pink-400">Aura [{sensualidad}]</span><span>Sensual</span> </div> <input type="range" min="0" max="100" value={sensualidad} onChange={(e)=>setSensualidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-pink-500 cursor-pointer" /> </div> <div className="space-y-1"> <div className="flex justify-between text-[9px] sm:text-[10px] text-neutral-500 uppercase font-bold"> <span>Atenuada</span><span className="text-amber-400">Intensidad [{intensidad}]</span><span>Fuerte</span> </div> <input type="range" min="0" max="100" value={intensidad} onChange={(e)=>setIntensidad(Number(e.target.value))} className="w-full h-1 bg-neutral-800 rounded appearance-none accent-amber-500 cursor-pointer" /> </div> </div> {/* Botones de Control */} <div className="flex flex-col sm:flex-row gap-2"> <button onClick={toggleHandsFree} disabled={isLoading} className={`flex-1 py-2 rounded text-xs font-bold flex items-center justify-center gap-2 transition-colors border ${ isHandsFree ? 'bg-red-900/20 text-red-400 border-red-900/50 hover:bg-red-900/40 shadow-[0_0_10px_rgba(239,68,68,0.2)]' : 'bg-indigo-900/20 text-indigo-400 border-indigo-900/50 hover:bg-indigo-900/40' }`} > {isHandsFree ? <MicOff size={14} /> : <Mic size={14} />} {isHandsFree ? 'Detener Escucha' : 'Manos Libres'} </button> <div className="flex gap-2 flex-1"> <button onClick={handleManualPlay} disabled={isLoading || isPlaying || isHandsFree} className="flex-1 bg-emerald-600/20 hover:bg-emerald-600/40 text-emerald-400 border border-emerald-600/50 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center gap-1 transition-colors" > {isLoading ? <Loader2 size={14} className="animate-spin" /> : <Play size={14} />} Sintetizar </button> <button onClick={stopAudio} disabled={!isPlaying && !isHandsFree} className="px-4 bg-neutral-800 hover:bg-neutral-700 text-neutral-400 border border-neutral-700 disabled:opacity-30 py-2 rounded text-xs font-bold flex items-center justify-center transition-colors" > <Square size={14} /> </button> </div> </div> {/* Botón para limpiar caché (opcional) */} <div className="text-right"> <button onClick={() => audioCache.clear()} className="text-[8px] text-neutral-600 hover:text-neutral-400 underline" > limpiar caché de audio </button> </div> </div> ); }; // --- ENTORNO ESCRITORIO (sin cambios) --- export default function App() { const [widgets, setWidgets] = useState({ voice: { isOpen: true, pos: { x: window.innerWidth > 768 ? window.innerWidth / 2 - 170 : 20, y: 40 } } }); const toggleWidget = (id) => { setWidgets(prev => ({ ...prev, [id]: { ...prev[id], isOpen: !prev[id].isOpen } })); }; return ( <div className="w-full h-screen bg-neutral-950 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.1),rgba(0,0,0,1))] overflow-hidden relative font-sans text-neutral-200"> <div className="absolute inset-0 flex items-center justify-center opacity-[0.02] pointer-events-none"><Settings2 size={500} /></div> {widgets.voice.isOpen && ( <DraggableWidget title="MODULADOR VOCAL KORE" icon={Zap} initialPos={widgets.voice.pos} onClose={() => toggleWidget('voice')}> <VoiceModulatorWidget /> </DraggableWidget> )} <div className="absolute bottom-6 left-1/2 transform -translate-x-1/2 bg-neutral-900/80 backdrop-blur-md border border-neutral-700/50 p-2 rounded-2xl shadow-2xl flex gap-2 z-[100]"> <div className="px-3 flex items-center border-r border-neutral-700/50 text-neutral-500"><LayoutGrid size={20} /></div> <button onClick={() => toggleWidget('voice')} className={`px-4 py-2 rounded-xl flex items-center gap-2 text-sm font-medium transition-all ${
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.6> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8> <lora:3DMM_V12:0.8>
Over-the-Shoulder Shot (Action/Dialogue Implied) Ultra-realistic, dynamic over-the-shoulder (OTS) cinematic shot of the same man in a black tuxedo. Face match 100.9999%. Aspect Ratio: 16:9. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/100, ISO 400, tungsten white balance. Composition: The camera is positioned behind an unseen, heavily blurred second figure's shoulder (only the dark blur is visible in the foreground), focusing on the main subject who is standing in profile or three-quarter view, looking intently at the blurred figure. His hand is gesturing slightly, mid-sentence. Mood & Tone: Intense, focused, and suggestive of a high-stakes conversation. Shallow depth of field ensures the focus is purely on the subject's reaction. Color Palette: Cinematic warm and cool tones playing on the faces, heavy Portra 400 glow. Shot Detail: Intense eye contact, blurred foreground object adds a layer of secrecy/eavesdropping
epic photograph, Sun Rays, dslr, Zoom lens, spotlight, film camera, 50mm, dramatic lighting, Kodak portra 800, Depth of field 270mm, Moonlit, Ilford HP5, telephoto lens, Rembrandt lighting, Kodak portra 400, F/14, loop lighting, Canon 5d mark 4, F/5, short lighting, Polaroid, 35mm, a woman, skinny, abs, flat chested, small breasts of [Chaos|Hate] Panther hybrid, Red Lush hairstyle, studio lighting, Fuji superia 400, Selective focus, Frightening, spotlit, Phase One XF IQ4 150MP, F/2.8, Lens Flare, Samsung Galaxy, Depth of field 100mm <lyco:GoodHands-beta2:0.8> <lyco:DarkHoodies:0.8> <lora:Niji_Visual_Kei:0.8>
Adjustment of Cufflink (Detailed Mid-Shot) Ultra-realistic, mid-shot portrait focusing on a precise detail. Face match 100.9999%. Aspect Ratio: 4:5. Film Stock: Kodak Portra 400. Camera: Leica M6 35mm film camera, Lens: 50mm f/1.4 Summilux. Camera Settings: f/1.4, 1/125, ISO 400, tungsten white balance. Composition: Tight mid-shot where the man is looking down, intensely focused on adjusting a simple, elegant cufflink. His face is partially obscured by his hand, but the concentration in his brow is visible. Mood & Tone: Obsessive perfection, quiet preparation, focused intensity. Lighting: A single, small directional light source highlights the wrist, the cufflink, and the texture of the crisp white shirt cuff. Color Palette: High contrast between the dark tuxedo and the bright, yet warm-toned white shirt. Shot Detail: Hyper-detailed focus on the metal texture of the cufflink and the fabric fibers, very shallow depth of field
a woman sitting on a rock next to a dragon in the woods with her head turned to the side, Clint Cearley, epic fantasy character art, a character portrait, fantasy art, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera, RAW candid cinema, 16mm, color graded portra 400 film, remarkable color, ultra realistic, textured skin, remarkable detailed pupils, realistic dull skin noise, visible skin detail, skin fuzz, dry skin, shot with cinematic camera
animal look, https://s.mj.run/1150bpD5hFw Smiling and looking to the camera, Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1. 8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --style raw --stylize 400 --c 50 --ar 4:5
https://s.mj.run/1150bpD5hFw Portrait Photography, A highly detailed close-up of a content woman looking left, with prominent, bright eyes that sparkle with sharpness and contrast against a subtle background, Her skin is flawlessly smooth, An award-winning capture that accentuates her eye color vividly, Use an 85mm lens with ISO 400 for a pinpoint focus on her distinct facial characteristics, Time value set at 1/500 and focal aperture at f/1.8 to bring her eyes and hair into striking relief with a narrow depth of field, aiming for a very realistic and high-resolution image --ar 4:5 --style raw --stylize 400 --c 50
At this time, Heaven first had a foundation. 5,400 years later, in the middle of Phase l, the light and pure roseupwards, and sun, moon, stars, and constellations were created. These were called the Four Images. Hence thesaying that heaven began in I. Another 5.400 years later, when Phase I was nearing its end and Phase Il was imminent, things graduallysolidified. As the Book of Changes says, "Great is the Positive; far-reaching is the Negative! All things areendowed and born in accordance with Heaven." This was when the earth began to congeal. After 5,400 morevears came the height of Phase Il. when the heavy and impure solidified. and water, fire, mountains. stoneand Earth came into being. These five were called the Five Movers. Therefore it is said that the Earth wascreated in Phase Il. After a further 5,400 years, at the end of Phase Il and the beginning of the Phase Ill, living beings werecreated. In the words of the Book of the Calendar, "The essence of the sky came down and the essence ofearth went up. Heaven and Earth intermingled, and all creatures were born." Then Heaven was bright andEarth was fresh, and the Positive intermingled with the Negative. 5.400 years later, when Phase Il was at itsheight, men, birds and beasts were created. Thus the Three Powers--Heaven, Earth and Man--now had theitset places. Therefore it is said that man was created in Phase Ill. Moved by Pan Cu's creation, the Three Emperors put the world in order and the Five Rulers laid down themoral code. The world was then divided into four great continents: The Eastern Continent of Superior Body.the Western Continent of Cattle-gift, the Southern Continent of Jambu and the Northern Continent of KuruThis book deals only with the Eastern Continent of Superior Body. Beyond the seas there is a country calledAolai. This country is next to an ocean, and in the middle of the ocean is a famous island called the Mountainof Flowers and Fruit. This mountain is the ancestral artery of the Ten Continents, the oripin of the Threeslands: it was formed when the clear and impure were separated and the Enormous Vagueness was divided. Itis a really splendid mountain and there are some verses to prove it: