<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI PROMPT IDE v7.0 // DUAL CORE</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
    
    <style>
        /* --- ESTILOS DARK MODE IDE --- */
        :root {
            --bg: #050505; --panel: #111; --border: #252525;
            --text: #eee; --dim: #666;
            --nano-color: #00f2ff; /* Cian para Nanobana */
            --veo-color: #ff9f1c;  /* Naranja para Veo Video */
            --active-color: var(--nano-color); /* Cambia dinámicamente */
            --font: 'JetBrains Mono', monospace;
        }
        * { box-sizing: border-box; outline: none; }
        body { background: var(--bg); color: var(--text); font-family: var(--font); margin: 0; display: flex; flex-direction: column; height: 100vh; font-size: 12px; }

        /* --- NAVEGACIÓN SUPERIOR --- */
        .top-nav { display: flex; background: #080808; border-bottom: 1px solid var(--border); height: 50px; }
        .nav-item {
            flex: 1; text-align: center; padding: 15px; cursor: pointer; font-weight: bold; letter-spacing: 2px;
            text-transform: uppercase; color: var(--dim); transition: 0.3s; border-bottom: 2px solid transparent;
        }
        .nav-item:hover { color: var(--text); background: #111; }
        .nav-item.active { color: var(--active-color); border-bottom: 2px solid var(--active-color); background: rgba(255,255,255,0.02); }

        /* --- LAYOUT PRINCIPAL --- */
        .main-layout { display: grid; grid-template-columns: 400px 1fr; gap: 0; flex-grow: 1; overflow: hidden; }
        .input-panel { background: var(--panel); border-right: 1px solid var(--border); padding: 20px; overflow-y: auto; display: flex; flex-direction: column; }
        .output-panel { background: #080808; padding: 20px; overflow-y: auto; }

        /* --- COMPONENTES DE INPUT --- */
        h2 { font-size: 11px; letter-spacing: 1px; color: var(--dim); border-bottom: 1px solid var(--border); padding-bottom: 5px; margin: 20px 0 15px 0; font-weight: 700; text-transform: uppercase;}
        label { display: block; color: var(--dim); margin-bottom: 5px; font-size: 10px; font-weight: bold; text-transform: uppercase; }
        input, select, textarea { width: 100%; background: #000; border: 1px solid var(--border); color: var(--text); padding: 10px; font-family: var(--font); font-size: 12px; margin-bottom: 15px; resize: vertical; }
        input:focus, select:focus, textarea:focus { border-color: var(--active-color); }

        /* ZONAS DE IMAGEN DINÁMICAS */
        .mode-section { display: none; }
        .mode-section.active { display: block; }

        /* GESTOR DE ESCENAS (VEO) & PREVIEW (NANO) */
        .preview-box { height: 120px; background: #000; border: 1px dashed var(--border); display: flex; align-items: center; justify-content: center; cursor: pointer; margin-bottom: 15px; position: relative; }
        .preview-box:hover { border-color: var(--active-color); }
        .preview-box img { max-width: 100%; max-height: 100%; display: none; }
        .scene-slot { border: 1px solid var(--border); padding: 10px; margin-bottom: 10px; background: #0a0a0a; }
        .btn-add { background: transparent; border: 1px dashed var(--border); color: var(--dim); width: 100%; padding: 8px; cursor: pointer; }
        .btn-add:hover { border-color: var(--active-color); color: var(--active-color); }
        .btn-del-scene { float: right; cursor: pointer; color: #666; } .btn-del-scene:hover { color: red; }

        /* BOTON RUN */
        .btn-run { background: var(--active-color); color: #000; font-weight: 800; padding: 15px; border: none; width: 100%; margin-top: auto; cursor: pointer; text-transform: uppercase; letter-spacing: 1px; transition: 0.3s; }
        .btn-run:hover { filter: brightness(1.1); box-shadow: 0 0 15px var(--active-color); }
        .btn-run:disabled { background: #333; color: #555; cursor: not-allowed; box-shadow: none; }

        /* --- RESULTADOS (Consola) --- */
        .result-block { border-left: 3px solid var(--active-color); padding-left: 15px; margin-bottom: 25px; background: rgba(255,255,255,0.02); padding: 15px; }
        .result-header { color: var(--active-color); font-weight: bold; margin-bottom: 10px; font-size: 11px; display: flex; justify-content: space-between; }
        .copy-btn { background: #222; border: 1px solid #333; color: var(--dim); padding: 3px 8px; font-size: 10px; cursor: pointer; }
        .copy-btn:hover { color: var(--active-color); border-color: var(--active-color); }
        /* Estilo para JSON */
        pre.json-output { white-space: pre-wrap; color: #a5d6ff; font-size: 11px; }
        .string { color: #ce9178; } .number { color: #b5cea8; } .boolean { color: #569cd6; } .null { color: #569cd6; } .key { color: #9cdcfe; }

    </style>
</head>
<body>

<nav class="top-nav">
    <div class="nav-item active" id="navNano" onclick="switchMode('nano')">NANOBANA (REALISTIC PHOTO)</div>
    <div class="nav-item" id="navVeo" onclick="switchMode('veo')">VEO 3.1 (VIDEO SCENES)</div>
</nav>

<div class="main-layout">
    <div class="input-panel">
        
        <div>
            <label>API KEY (GEMINI)</label>
            <input type="password" id="apiKey" placeholder="Pegar AIza...">
        </div>

        <div>
            <label>INSTRUCCIONES ADICIONALES (TEXTO)</label>
            <textarea id="userInstructions" rows="4" placeholder="Ej: Usa la persona de la imagen, pero ponla en una cafetería de noche lloviendo. Que la iluminación sea dramática."></textarea>
        </div>

        <div>
            <label>FORMATO DE SALIDA</label>
            <select id="outputFormat">
                <option value="text">PROMPT NORMAL (Raw Text)</option>
                <option value="json">ESTRUCTURA JSON (Programático)</option>
            </select>
        </div>

        <div id="sectionNano" class="mode-section active">
            <h2>// REFERENCIA FOTOGRÁFICA</h2>
            <div class="preview-box" onclick="document.getElementById('nanoFile').click()">
                <input type="file" id="nanoFile" accept="image/*" style="display:none" onchange="handleNanoFile(this)">
                <img id="nanoImg">
                <span id="nanoPlaceholder" style="color:var(--dim)">+ SUBIR SUJETO</span>
            </div>
        </div>

        <div id="sectionVeo" class="mode-section">
            <h2>// REFERENCIAS POR ESCENA</h2>
            <div id="scenesContainer"></div>
            <button class="btn-add" onclick="addSceneSlot()">+ AÑADIR ESCENA DE VIDEO</button>
        </div>

        <button id="btnRun" class="btn-run" onclick="runAI()">GENERAR PROMPT</button>
    </div>

    <div class="output-panel" id="consoleOutput">
        <div style="color:var(--dim)">> IDE listo. Selecciona modo y sube referencias...</div>
    </div>
</div>

<script type="module">
    import { GoogleGenerativeAI } from "https://esm.run/@google/generative-ai";

    // --- ESTADO GLOBAL ---
    let currentMode = 'nano';
    let veoScenes = {};
    let sceneCounter = 0;
    let nanoFileRef = null;
    const root = document.documentElement;

    // --- INICIALIZACIÓN ---
    window.onload = () => {
        const k = localStorage.getItem('ide_key');
        if(k) document.getElementById('apiKey').value = k;
        switchMode('nano'); // Iniciar en modo foto
    };

    // --- LÓGICA DE CAMBIO DE MODO ---
    window.switchMode = (mode) => {
        currentMode = mode;
        // Actualizar UI Nav
        document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
        document.getElementById(mode === 'nano' ? 'navNano' : 'navVeo').classList.add('active');
        
        // Actualizar Secciones Visibles
        document.querySelectorAll('.mode-section').forEach(el => el.classList.remove('active'));
        document.getElementById(mode === 'nano' ? 'sectionNano' : 'sectionVeo').classList.add('active');

        // Actualizar Color Variables CSS
        const newColor = mode === 'nano' ? 'var(--nano-color)' : 'var(--veo-color)';
        root.style.setProperty('--active-color', newColor);

        // Resetear estado del botón
        checkRunButtonState();
    };

    // --- GESTIÓN DE ARCHIVOS (NANO) ---
    window.handleNanoFile = (input) => {
        if(input.files[0]) {
            nanoFileRef = input.files[0];
            const r = new FileReader();
            r.onload = e => {
                document.getElementById('nanoImg').src = e.target.result;
                document.getElementById('nanoImg').style.display = 'block';
                document.getElementById('nanoPlaceholder').style.display = 'none';
            };
            r.readAsDataURL(input.files[0]);
            checkRunButtonState();
        }
    };

    // --- GESTIÓN DE ESCENAS (VEO) ---
    window.addSceneSlot = () => {
        sceneCounter++;
        const id = `s${sceneCounter}`;
        const html = `
            <div class="scene-slot" id="slot-${id}">
                <div style="font-weight:bold; margin-bottom:5px; color:var(--veo-color)">ESCENA ${sceneCounter} <span class="btn-del-scene" onclick="delScene('${id}')">×</span></div>
                <div class="preview-box" style="height:80px" onclick="document.getElementById('f-${id}').click()">
                    <input type="file" id="f-${id}" style="display:none" onchange="handleVeoFile(this, '${id}')">
                    <img id="img-${id}">
                    <span id="txt-${id}" style="color:var(--dim); font-size:10px;">+ REF VISUAL</span>
                </div>
            </div>`;
        document.getElementById('scenesContainer').insertAdjacentHTML('beforeend', html);
        checkRunButtonState();
    };

    window.handleVeoFile = (input, id) => {
        if(input.files[0]) {
            veoScenes[id] = input.files[0];
            const r = new FileReader();
            r.onload = e => {
                document.getElementById(`img-${id}`).src = e.target.result;
                document.getElementById(`img-${id}`).style.display = 'block';
                document.getElementById(`txt-${id}`).style.display = 'none';
            };
            r.readAsDataURL(input.files[0]);
            checkRunButtonState();
        }
    };

    window.delScene = (id) => { document.getElementById(`slot-${id}`).remove(); delete veoScenes[id]; checkRunButtonState(); };

    function checkRunButtonState() {
        let ready = false;
        if(currentMode === 'nano') ready = !!nanoFileRef;
        if(currentMode === 'veo') ready = Object.keys(veoScenes).length > 0;
        document.getElementById('btnRun').disabled = !ready;
        document.getElementById('btnRun').innerText = ready ? (currentMode === 'nano' ? 'GENERAR FOTO PROMPT' : 'GENERAR VIDEO PROMPTS') : 'ESPERANDO REFERENCIAS...';
    }

    // --- NÚCLEO DE IA (EL CEREBRO) ---
    window.runAI = async () => {
        const key = document.getElementById('apiKey').value;
        if(!key) return alert("Falta API Key");
        localStorage.setItem('ide_key', key);

        const userInstructions = document.getElementById('userInstructions').value;
        const outputFormat = document.getElementById('outputFormat').value;
        const consoleOut = document.getElementById('consoleOutput');
        const btn = document.getElementById('btnRun');

        btn.disabled = true;
        btn.innerText = "PROCESANDO...";
        consoleOut.innerHTML = `<div style="color:var(--dim)">> Iniciando análisis en modo ${currentMode.toUpperCase()}...</div>`;

        const genAI = new GoogleGenerativeAI(key);
        const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });

        try {
            let prompt = "";
            let imageParts = [];

            // 1. PREPARAR DATOS SEGÚN MODO
            if(currentMode === 'nano') {
                const base64 = await toBase64(nanoFileRef);
                imageParts.push({ inlineData: { data: base64.split(',')[1], mimeType: nanoFileRef.type } });
                
                prompt = `
                    ACT AS: Senior Prompt Engineer for Realistic Photography (Nanobana/SDXL).
                    TASK: Create a detailed prompt based on the image AND user instructions.
                    USER INSTRUCTIONS: "${userInstructions || 'Keep original appearance'}"
                    FOCUS: Forensic facial/body details intact, but apply user environment/action changes.
                    Style: Photorealistic, raw photo, 8k.
                `;
            } else {
                // Modo Veo - Procesamos múltiples imágenes
                prompt = `
                    ACT AS: AI Video Director for Veo 3.1 / Runway Gen-3.
                    TASK: Create sequential video prompts for ${Object.keys(veoScenes).length} scenes.
                    USER GLOBAL INSTRUCTIONS: "${userInstructions || 'Cinematic sequence'}"
                    INSTRUCTION: Analyze each image as a distinct scene reference. Maintain character consistency if the same person appears. apply user instructions to the overall mood/action.
                `;
                 // Nota: Gemini Flash actualmente maneja mejor 1 imagen + prompt complejo.
                 // Para multiescena real, iteramos. Para este ejemplo, enviaremos la primera escena como referencia principal de estilo.
                 const firstSceneKey = Object.keys(veoScenes)[0];
                 if(firstSceneKey) {
                    const file = veoScenes[firstSceneKey];
                    const base64 = await toBase64(file);
                    imageParts.push({ inlineData: { data: base64.split(',')[1], mimeType: file.type } });
                    prompt += "\nNOTE: Use the provided image as the primary visual reference for style and subjects for SCENE 1.";
                 }
            }

            // 2. AÑADIR RESTRICCIÓN DE FORMATO
            if(outputFormat === 'json') {
                prompt += `
                    \nIMPORTANT: OUTPUT MUST BE PURE VALID JSON ONLY. NO MARKDOWN.
                    Structure needed:
                    {
                      "mode": "${currentMode}",
                      "prompts": [
                        { "scene_id": 1, "prompt_text": "..." },
                        { "scene_id": 2, "prompt_text": "..." } // if applicable
                      ],
                      "technical_notes": "..."
                    }
                `;
            } else {
                prompt += `\nOUTPUT FORMAT: Raw text prompts, separated by double newlines if multiple scenes. Start with tag list: (masterpiece, best quality)...`;
            }

            // 3. EJECUTAR
            consoleOut.innerHTML += `<div style="color:var(--dim)">> Enviando a Gemini...</div>`;
            const result = await model.generateContent([prompt, ...imageParts]);
            const text = result.response.text();

            // 4. RENDERIZAR RESULTADO
            renderResult(text, outputFormat);

        } catch (e) {
            consoleOut.innerHTML += `<div style="color:red">> ERROR: ${e.message}</div>`;
        } finally {
            checkRunButtonState();
        }
    };

    function renderResult(text, format) {
        const consoleOut = document.getElementById('consoleOutput');
        let contentHtml = '';

        if(format === 'json') {
            // Intentar formatear bonito el JSON
            try {
                const jsonObj = JSON.parse(text);
                // Función simple para colorear sintaxis JSON
                const jsonHtml = JSON.stringify(jsonObj, null, 2)
                    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
                    .replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
                        let cls = 'number';
                        if (/^"/.test(match)) {
                            if (/:$/.test(match)) { cls = 'key'; } else { cls = 'string'; }
                        } else if (/true|false/.test(match)) { cls = 'boolean'; } else if (/null/.test(match)) { cls = 'null'; }
                        return '<span class="' + cls + '">' + match + '</span>';
                    });
                contentHtml = `<pre class="json-output">${jsonHtml}</pre>`;
            } catch (e) {
                 contentHtml = `<div style="color:red">Error parseando JSON de la IA. Mostrando texto crudo:</div><pre>${text}</pre>`;
            }
        } else {
            contentHtml = `<div class="prompt-content">${text}</div>`;
        }

        const html = `
            <div class="result-block">
                <div class="result-header">
                    SALIDA GENERADA [MODO: ${currentMode.toUpperCase()} | FMT: ${format.toUpperCase()}]
                    <button class="copy-btn" onclick="copiarTexto(this)">COPIAR</button>
                </div>
                ${contentHtml}
            </div>`;
        consoleOut.innerHTML = html;
    }

    window.copiarTexto = (btn) => {
        const content = btn.parentElement.nextElementSibling.innerText;
        navigator.clipboard.writeText(content);
        btn.innerText = "OK!"; setTimeout(()=>btn.innerText="COPIAR", 1000);
    }

    function toBase64(file) { return new Promise((r) => { const reader = new FileReader(); reader.onloadend = () => r(reader.result); reader.readAsDataURL(file); }); }
</script>

</body>
</html>

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *