alextreumer

PromptGen V1 - WEB

Sep 20th, 2026 (edited)
67
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 18.76 KB | Source Code | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6.     <title>Prompt Generator Web</title>
  7.     <style>
  8.         :root { --bg: #1e1e2e; --surface: #2b2b36; --text: #cdd6f4; --primary: #89b4fa; --hover: #b4befe; --border: #45475a; }
  9.         body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: var(--bg); color: var(--text); margin: 0; padding: 20px; display: flex; flex-direction: column; height: 100vh; box-sizing: border-box; }
  10.         h1 { font-size: 1.5rem; margin-top: 0; }
  11.         .container { display: flex; gap: 20px; flex: 1; min-height: 0; }
  12.         .left-panel, .right-panel { background: var(--surface); padding: 15px; border-radius: 8px; display: flex; flex-direction: column; gap: 10px; overflow: hidden; border: 1px solid var(--border); }
  13.         .left-panel { flex: 1; }
  14.         .right-panel { flex: 1; }
  15.         .toolbar { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
  16.         button, input[type="number"] { background: var(--primary); color: #111; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-weight: bold; font-size: 0.9rem; }
  17.         button:hover { background: var(--hover); }
  18.         .btn-secondary { background: #45475a; color: var(--text); }
  19.         .btn-secondary:hover { background: #585b70; }
  20.         table { width: 100%; border-collapse: collapse; margin-bottom: 10px; display: block; overflow-y: auto; max-height: 400px; }
  21.         th, td { border: 1px solid var(--border); padding: 8px; text-align: left; }
  22.         th { background: var(--border); position: sticky; top: 0; }
  23.         td[contenteditable="true"] { background: rgba(255,255,255,0.05); outline: none; }
  24.         td[contenteditable="true"]:focus { background: rgba(255,255,255,0.1); }
  25.         .row-select { width: 30px; text-align: center; cursor: pointer; }
  26.         textarea { width: 100%; flex: 1; background: rgba(0,0,0,0.2); color: var(--text); border: 1px solid var(--border); border-radius: 4px; padding: 10px; font-family: monospace; resize: none; box-sizing: border-box; font-size: 14px; }
  27.         label { font-weight: bold; }
  28.        
  29.         /* Context Menu Styles */
  30.         #contextMenu { position: absolute; background: var(--surface); border: 1px solid var(--border); border-radius: 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); display: none; flex-direction: column; min-width: 150px; z-index: 1000; }
  31.         #contextMenu div { padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border); }
  32.         #contextMenu div:last-child { border-bottom: none; }
  33.         #contextMenu div:hover { background: var(--primary); color: #111; }
  34.        
  35.         .hidden { display: none; }
  36.     </style>
  37. </head>
  38. <body>
  39.  
  40.     <h1>Prompt Generator</h1>
  41.  
  42.     <div class="toolbar">
  43.         <button class="btn-secondary" onclick="document.getElementById('fileInput').click()">📁 Load Config</button>
  44.         <button class="btn-secondary" onclick="saveConfig()">💾 Save Config</button>
  45.         <input type="file" id="fileInput" class="hidden" accept=".cfg,.txt,.json" onchange="loadConfig(event)">
  46.     </div>
  47.  
  48.     <div class="container">
  49.         <!-- LEFT PANEL: Table -->
  50.         <div class="left-panel">
  51.             <label>1. Define Variables (Select for Variation)</label>
  52.             <table id="grid">
  53.                 <thead>
  54.                     <tr>
  55.                         <th>Var</th>
  56.                         <th>Key Name</th>
  57.                         <th>Values (separated by /) (Weight with : e.g. Blonde:3)</th>
  58.                         <th>Action</th>
  59.                     </tr>
  60.                 </thead>
  61.                 <tbody id="gridBody">
  62.                     <!-- Standard Rows -->
  63.                 </tbody>
  64.             </table>
  65.             <button class="btn-secondary" onclick="addRow('', '')">+ Add Row</button>
  66.            
  67.             <div style="margin-top: 20px; display: flex; gap: 10px; align-items: center;">
  68.                 <button onclick="variationBatch()">Variation Batch (Selected Key)</button>
  69.             </div>
  70.         </div>
  71.  
  72.         <!-- RIGHT PANEL: Text fields -->
  73.         <div class="right-panel">
  74.             <label>2. Template (Right-click for Variables Menu)</label>
  75.             <textarea id="memoTemplate"><Cameraangle> <Lights> photo of a <Age> old man with <*Haircolor> Hair standing next to his <Age> year old wife, also with <*Haircolor> Hair in <Location>. He is wearing a <Clothing>, she is wearing a <Clothing>.</textarea>
  76.  
  77.             <div style="display: flex; gap: 10px; align-items: center; margin: 10px 0;">
  78.                 <button onclick="generateSingle()">Generate Text</button>
  79.                 <button class="btn-secondary" onclick="saveOutput('RANDO.txt', document.getElementById('memoTemplate').value)">Save Template</button>
  80.                 <span style="margin-left:auto;">Batch Count:</span>
  81.                 <input type="number" id="spinEdit" value="500" min="1" max="20000" style="width: 70px;">
  82.                 <button onclick="generateBatch()">Generate Batch</button>
  83.             </div>
  84.  
  85.             <label>3. Output</label>
  86.             <textarea id="memoOutput" readonly></textarea>
  87.         </div>
  88.     </div>
  89.  
  90.     <!-- Custom Context Menu -->
  91.     <div id="contextMenu"></div>
  92.  
  93.     <script>
  94.         // --- INITIALIZATION ---
  95.         window.onload = () => {
  96.             addRow('Haircolor', 'Blue/Blonde:3/Brown:0.5');
  97.             addRow('Clothing', 'Shirt/Sweater/T-Shirt');
  98.             addRow('Age', '20/25/30/35');
  99.             addRow('Location', 'Park/Pool/Bedroom/Stage/Supermarket');
  100.             addRow('Cameraangle', 'High-Angle/Low-Angle/Medium-Shot/Close-Up');
  101.             addRow('Lights', 'soft morning daylight/golden hour sunlight/harsh midday sun/afternoon glow/warm sunset');
  102.             selectRow(0); // Select first row
  103.         };
  104.  
  105.         let selectedRowIndex = -1;
  106.  
  107.         // --- TABLE LOGIC ---
  108.         function addRow(key, values) {
  109.             const tbody = document.getElementById('gridBody');
  110.             const rowCount = tbody.children.length;
  111.             const tr = document.createElement('tr');
  112.            
  113.             tr.innerHTML = `
  114.                 <td class="row-select" onclick="selectRow(${rowCount})"><input type="radio" name="rowSelector" id="radio_${rowCount}"></td>
  115.                 <td contenteditable="true" class="cell-key">${key}</td>
  116.                 <td contenteditable="true" class="cell-val">${values}</td>
  117.                 <td><button class="btn-secondary" style="padding: 2px 6px;" onclick="this.parentElement.parentElement.remove()">X</button></td>
  118.             `;
  119.             tbody.appendChild(tr);
  120.         }
  121.  
  122.         function selectRow(index) {
  123.             selectedRowIndex = index;
  124.             const radio = document.getElementById(`radio_${index}`);
  125.             if (radio) radio.checked = true;
  126.         }
  127.  
  128.         function getGridData() {
  129.             const rows = document.querySelectorAll('#gridBody tr');
  130.             const data = [];
  131.             rows.forEach(tr => {
  132.                 const key = tr.querySelector('.cell-key').innerText.trim();
  133.                 const val = tr.querySelector('.cell-val').innerText.trim();
  134.                 if (key !== '') {
  135.                     data.push({ key, val });
  136.                 }
  137.             });
  138.             return data;
  139.         }
  140.  
  141.         // --- ROULETTE WHEEL SELECTION (Weighted Random Selection) ---
  142.         function getRandomValue(valuesStr) {
  143.             if (!valuesStr || valuesStr.trim() === '') return '';
  144.            
  145.             const parts = valuesStr.split('/');
  146.             let items = [];
  147.             let totalWeight = 0;
  148.  
  149.             for (let part of parts) {
  150.                 part = part.trim();
  151.                 let textVal = part;
  152.                 let weight = 1.0;
  153.  
  154.                 const splitPos = part.indexOf(':');
  155.                 if (splitPos !== -1) {
  156.                     textVal = part.substring(0, splitPos).trim();
  157.                     let weightStr = part.substring(splitPos + 1).trim().replace(',', '.');
  158.                     let parsedWeight = parseFloat(weightStr);
  159.                     if (!isNaN(parsedWeight) && parsedWeight >= 0) {
  160.                        weight = parsedWeight;
  161.                     }
  162.                 }
  163.                 items.push({ text: textVal, weight: weight });
  164.                 totalWeight += weight;
  165.             }
  166.  
  167.             if (totalWeight <= 0) {
  168.                return items[Math.floor(Math.random() * items.length)].text;
  169.            }
  170.  
  171.            let randomChoice = Math.random() * totalWeight;
  172.            let runningSum = 0;
  173.  
  174.            for (let item of items) {
  175.                runningSum += item.weight;
  176.                if (randomChoice <= runningSum) {
  177.                    return item.text;
  178.                }
  179.            }
  180.            return items[items.length - 1].text;
  181.        }
  182.  
  183.        // --- TEXT GENERATION (<*Tag> and <Tag>) ---
  184.         function generateTextBase(templateText, skipKey = "") {
  185.             let resultText = templateText;
  186.             const gridData = getGridData();
  187.  
  188.             for (let item of gridData) {
  189.                 const keyStr = item.key;
  190.                 const valStr = item.val;
  191.  
  192.                 // Skip for Variation Batch
  193.                 if (skipKey !== "" && keyStr.toLowerCase() === skipKey.toLowerCase()) continue;
  194.  
  195.                 // 1. Check if <*Key> exists (Fixation)
  196.                 const searchTagStar = `<*${keyStr}>`;
  197.                 if (resultText.includes(searchTagStar)) {
  198.                     // Roll once
  199.                     const replacement = getRandomValue(valStr);
  200.                    
  201.                     // Replace all <*Key>
  202.                     resultText = resultText.split(searchTagStar).join(replacement);
  203.                     // Replace all normal <Key> with the same value
  204.                     const searchTag = `<${keyStr}>`;
  205.                     resultText = resultText.split(searchTag).join(replacement);
  206.                 } else {
  207.                     // 2. Normal behavior: Roll new for each <Key>
  208.                     const searchTag = `<${keyStr}>`;
  209.                     let index = resultText.indexOf(searchTag);
  210.                     while (index !== -1) {
  211.                         const replacement = getRandomValue(valStr);
  212.                         resultText = resultText.substring(0, index) + replacement + resultText.substring(index + searchTag.length);
  213.                         index = resultText.indexOf(searchTag, index + replacement.length);
  214.                     }
  215.                 }
  216.             }
  217.             return resultText;
  218.         }
  219.  
  220.         function generateSingle() {
  221.             const template = document.getElementById('memoTemplate').value;
  222.             const final = generateTextBase(template);
  223.             document.getElementById('memoOutput').value = final;
  224.             return final;
  225.         }
  226.  
  227.         // --- BATCH GENERATION ---
  228.         function generateBatch() {
  229.             const amount = parseInt(document.getElementById('spinEdit').value);
  230.             const template = document.getElementById('memoTemplate').value;
  231.             let batchList = [];
  232.            
  233.             for (let i = 0; i < amount; i++) {
  234.                let text = generateTextBase(template);
  235.                text = text.replace(/\n/g, ' ').replace(/\r/g, ''); // Linebreaks to spaces
  236.                batchList.push(text);
  237.            }
  238.            
  239.            downloadFile('Randobatch.txt', batchList.join('\r\n'));
  240.            alert(`${amount} texts generated and downloaded as Randobatch.txt!`);
  241.        }
  242.  
  243.        // --- VARIATION BATCH ---
  244.        function variationBatch() {
  245.            const gridData = getGridData();
  246.            const rows = document.querySelectorAll('#gridBody tr');
  247.            
  248.            // Find the selected row (Radio Button)
  249.            let selectedKey = "";
  250.            let selectedValuesStr = "";
  251.            rows.forEach((tr, index) => {
  252.                 const radio = document.getElementById(`radio_${index}`);
  253.                 if (radio && radio.checked) {
  254.                    selectedKey = tr.querySelector('.cell-key').innerText.trim();
  255.                     selectedValuesStr = tr.querySelector('.cell-val').innerText.trim();
  256.                 }
  257.             });
  258.  
  259.             if (selectedKey === "") {
  260.                 alert("Please select a key first using the radio button on the left in the table.");
  261.                 return;
  262.             }
  263.  
  264.             const templateText = document.getElementById('memoTemplate').value;
  265.             const searchTag = `<${selectedKey}>`;
  266.             const searchTagStar = `<*${selectedKey}>`;
  267.  
  268.             if (!templateText.includes(searchTag) && !templateText.includes(searchTagStar)) {
  269.                alert(`The tag <${selectedKey}> or <*${selectedKey}> does not appear in your template!`);
  270.                 return;
  271.             }
  272.  
  273.             const valuesArray = selectedValuesStr.split('/');
  274.             if (valuesArray.length === 0 || valuesArray[0] === "") {
  275.                 alert("No values found for variation.");
  276.                 return;
  277.             }
  278.  
  279.             let baseText = "";
  280.             let userAccepted = false;
  281.  
  282.             // Roll loop (corresponds to Retry in MessageDlg)
  283.             while (!userAccepted) {
  284.                 baseText = generateTextBase(templateText, selectedKey);
  285.                 let msg = "The following text was generated as a fixed base:\n\n" +
  286.                           baseText + "\n\n" +
  287.                           "OK = Create Batch\nCancel = Reroll (or reload page to abort completely)";
  288.                
  289.                 if (confirm(msg)) {
  290.                     userAccepted = true;
  291.                 }
  292.             }
  293.  
  294.             let batchList = [];
  295.             let labelList = [];
  296.  
  297.             for (let rawVal of valuesArray) {
  298.                 // For "Blonde:3", trim the weight for the label name
  299.                 let pureVal = rawVal.trim();
  300.                 if (pureVal.includes(':')) {
  301.                     pureVal = pureVal.substring(0, pureVal.indexOf(':')).trim();
  302.                 }
  303.  
  304.                 // Replace both tag variants in BaseText
  305.                 let finalText = baseText.split(searchTag).join(pureVal);
  306.                 finalText = finalText.split(searchTagStar).join(pureVal);
  307.                 finalText = finalText.replace(/\n/g, ' ').replace(/\r/g, '');
  308.                
  309.                 batchList.push(finalText);
  310.                 labelList.push(pureVal);
  311.             }
  312.  
  313.             downloadFile('Variationsbatch.txt', batchList.join('\r\n'));
  314.             downloadFile('Variationslabels.txt', labelList.join('\r\n'));
  315.            
  316.             alert(`${valuesArray.length} variations generated.\nTwo files have been downloaded (Prompts & Labels).`);
  317.         }
  318.  
  319.         // --- DOWNLOAD HELPER ---
  320.         function downloadFile(filename, text) {
  321.             const element = document.createElement('a');
  322.             element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
  323.             element.setAttribute('download', filename);
  324.             element.style.display = 'none';
  325.             document.body.appendChild(element);
  326.             element.click();
  327.             document.body.removeChild(element);
  328.         }
  329.  
  330.         function saveOutput(filename, content) {
  331.             downloadFile(filename, content);
  332.         }
  333.  
  334.         // --- LOAD & SAVE CONFIGURATION ---
  335.        function saveConfig() {
  336.            const data = {
  337.                template: document.getElementById('memoTemplate').value,
  338.                grid: getGridData()
  339.            };
  340.             downloadFile('config.cfg', JSON.stringify(data, null, 2));
  341.         }
  342.  
  343.         function loadConfig(event) {
  344.             const file = event.target.files[0];
  345.             if (!file) return;
  346.  
  347.             const reader = new FileReader();
  348.             reader.onload = function(e) {
  349.                 try {
  350.                     const data = JSON.parse(e.target.result);
  351.                     document.getElementById('memoTemplate').value = data.template;
  352.                    
  353.                     // Clear table and refill
  354.                     document.getElementById('gridBody').innerHTML = '';
  355.                     data.grid.forEach(item => addRow(item.key, item.val));
  356.                    
  357.                     if (data.grid.length > 0) selectRow(0);
  358.                    
  359.                     document.getElementById('fileInput').value = ""; // Reset input
  360.                     alert("Configuration successfully loaded!");
  361.                 } catch (err) {
  362.                     alert("Error reading file. Is it a valid .cfg (JSON)?");
  363.                 }
  364.             };
  365.             reader.readAsText(file);
  366.         }
  367.  
  368.         // --- CONTEXT MENU (Right-click) ---
  369.         const memo = document.getElementById('memoTemplate');
  370.         const contextMenu = document.getElementById('contextMenu');
  371.  
  372.         memo.addEventListener('contextmenu', (e) => {
  373.             e.preventDefault();
  374.             const gridData = getGridData();
  375.             contextMenu.innerHTML = ''; // Clear
  376.  
  377.             if (gridData.length === 0) {
  378.                 const div = document.createElement('div');
  379.                 div.innerText = "(No keys found)";
  380.                 contextMenu.appendChild(div);
  381.             } else {
  382.                 gridData.forEach(item => {
  383.                     // Normal Key
  384.                     const divNorm = document.createElement('div');
  385.                     divNorm.innerText = `<${item.key}>`;
  386.                     divNorm.onclick = () => insertTextAtCursor(memo, `<${item.key}>`);
  387.                     contextMenu.appendChild(divNorm);
  388.                    
  389.                     // Fixed Key
  390.                     const divStar = document.createElement('div');
  391.                     divStar.innerText = `<*${item.key}> (Fixed)`;
  392.                     divStar.onclick = () => insertTextAtCursor(memo, `<*${item.key}>`);
  393.                     contextMenu.appendChild(divStar);
  394.                 });
  395.             }
  396.  
  397.             contextMenu.style.display = 'flex';
  398.             contextMenu.style.left = `${e.pageX}px`;
  399.             contextMenu.style.top = `${e.pageY}px`;
  400.         });
  401.  
  402.         // Hide Context Menu on click elsewhere
  403.         document.addEventListener('click', () => {
  404.             contextMenu.style.display = 'none';
  405.         });
  406.  
  407.         // Helper: Inserts text at cursor position in text field
  408.         function insertTextAtCursor(textarea, text) {
  409.             const startPos = textarea.selectionStart;
  410.             const endPos = textarea.selectionEnd;
  411.             textarea.value = textarea.value.substring(0, startPos) + text + textarea.value.substring(endPos, textarea.value.length);
  412.             textarea.selectionStart = startPos + text.length;
  413.             textarea.selectionEnd = startPos + text.length;
  414.             textarea.focus();
  415.         }
  416.     </script>
  417. </body>
  418. </html>
Add Comment
Please, Sign In to add comment