Siteβ―Builder
Editing:
index.php
writable 0666
<?php // CONFIG define('COOKIE_NAME','openai_key'); define('COOKIE_TTL', 30 * 24 * 3600); define('SECRET', __FILE__); function encrypt_val($v) { $method = 'aes-128-ctr'; $key = substr(hash('sha256', SECRET, true), 0, 16); $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method)); $ct = openssl_encrypt($v, $method, $key, 0, $iv); return base64_encode("$ct::$iv"); } function decrypt_val($c) { @list($ct, $iv) = explode('::', base64_decode($c), 2); $method = 'aes-128-ctr'; $key = substr(hash('sha256', SECRET, true), 0, 16); return openssl_decrypt($ct, $method, $key, 0, $iv); } // handle API key save/delete... $msg = ''; if ($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['save_key'])) { $raw = trim($_POST['api_key']); $opts = ['http'=>[ 'method'=>'GET', 'header'=>"Authorization: Bearer $raw\r\n", 'timeout'=>10 ]]; if (@file_get_contents('https://api.openai.com/v1/models', false, stream_context_create($opts))) { setcookie( COOKIE_NAME, encrypt_val($raw), time() + COOKIE_TTL, '/', '', isset($_SERVER['HTTPS']), true ); header("Location: " . $_SERVER['REQUEST_URI']); exit; } else { $msg = "β Invalid key."; } } if (isset($_GET['delete_key'])) { setcookie(COOKIE_NAME,'',time()-3600,'/','',isset($_SERVER['HTTPS']),true); header("Location: " . $_SERVER['PHP_SELF']); exit; } // params $allowed_models = ['gpt-3.5-turbo','gpt-4o-mini','gpt-4']; $allowed_types = ['B','C','Dtxt']; // removed Dnum $model = in_array($_GET['model'] ?? '', $allowed_models, true) ? $_GET['model'] : 'gpt-3.5-turbo'; $type = in_array($_GET['type'] ?? '', $allowed_types, true) ? $_GET['type'] : 'B'; $scope = $_GET['scope'] ?? 'general'; $word = trim($_GET['word'] ?? ''); $count = max(1, min(15, intval($_GET['count'] ?? 5))); // decrypt key $cookie = $_COOKIE[COOKIE_NAME] ?? ''; $key = $cookie ? decrypt_val($cookie) : ''; // fetch tokens for B/C/Dtxt $items = []; if ($key && $word && in_array($type, ['B','C','Dtxt'], true)) { $payload = [ 'model' => $model, 'messages' => [ ['role'=>'system','content'=>"You are a helpful assistant that replies ONLY with a comma-separated list of {$scope}, no extra text."], ['role'=>'user', 'content'=>"List up to {$count} {$scope} variations for '{$word}'"], ], 'max_tokens' => 60, 'temperature' => 0.3, 'stop' => ["\n"] ]; $ch = curl_init('https://api.openai.com/v1/chat/completions'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer '.$key,'Content-Type: application/json'], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_TIMEOUT => 15, ]); $resp = curl_exec($ch); curl_close($ch); $data = json_decode($resp, true); $raw = array_filter(array_map('trim', explode(',', $data['choices'][0]['message']['content'] ?? ''))); array_unshift($raw, $word); $items = array_slice(array_unique($raw), 0, $count); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Promptinator</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> :root { --blue: #4f8cff; --blue-d: #275dbe; --grey: #23253a; --grey-l: #f5f7fa; --white: #fff; --radius: .75rem; --gap: 1.25rem; --pad: 2rem; --fs-lg: 1.125rem; --fs-xl: 1.35rem; } * { box-sizing:border-box; } body { margin:0; display:flex; min-height:100vh; font-family:'Segoe UI',Roboto,Arial,sans-serif; background:var(--grey-l); color:var(--grey); } /* SIDEBAR */ .sidebar { flex: 0 0 300px; background: #181c23; color: #fff; display: flex; flex-direction: column; justify-content: flex-start; padding: var(--pad); gap: var(--gap); } .sidebar h1 { margin: 0; font-size: 1.75rem; color: var(--blue); } .sidebar form { display: flex; flex-direction: column; gap: .5rem; } .sidebar input { width: 100%; padding: .75em; border-radius: var(--radius); border: none; } .sidebar button { padding: .75em; background: var(--blue); color: #fff; border: none; border-radius: var(--radius); cursor: pointer; } #sidebar-copy-csv { /* NEW BUTTON */ margin-top:.5rem; display:none; /* hidden until items exist */ } .sidebar .muted { font-size: .9em; color: #aaa; } .sidebar-status { margin-top: 0; display: flex; align-items: center; gap: .5ch; font-size: var(--fs-lg); } .sidebar-status span { font-size: 1.2em; color: #72eb99; } .sidebar .row { display: flex; flex-direction: column; gap: .5rem; } .sidebar select { width: 100%; padding: .75em; border-radius: var(--radius); background: #22304a; border: none; color: #fff; font-size: var(--fs-lg); } .sidebar select option { background: #fff !important; color: #000 !important; } #sidebar-copy { margin-top: var(--gap); padding: .75em; background: var(--blue); color: #fff; border: none; border-radius: var(--radius); cursor: pointer; transition: background .2s; } #sidebar-copy:hover { background: var(--blue-d); } .logout { margin-top:var(--gap); text-align:center; color:#fff; text-decoration:none; font-weight:600; } /* MAIN AREA */ .main-area { flex:1; display:flex; align-items:center; justify-content:center; padding:var(--pad); } .main-panel { background:#fff; box-shadow:0 4px 24px rgba(20,22,37,.1); border-radius:var(--radius); width:100%; max-width:700px; padding:var(--pad); display:grid; gap:var(--gap); } /* WELCOME MESSAGE (no key) */ .welcome { line-height:1.5; } .welcome h2 { margin-top:0; font-size:1.5rem; } .welcome p { margin-bottom:1rem; } .welcome a { color:var(--blue); text-decoration:underline; } /* FORM (when logged in) */ form#cfg { display:grid; grid-template-columns:1fr 1fr; gap:var(--gap); } .seed-row { grid-column:1/-1; } form label { font-weight:600; font-size:var(--fs-lg); } form input, form select { width:100%; padding:.75em 1em; font-size:var(--fs-lg); border:1px solid #dde2ee; border-radius:var(--radius); background:#f7faff; transition:border-color .2s, background .2s; } form input:focus, form select:focus { outline:none; border-color:var(--blue); background:#fff; } #gen { grid-column:1/-1; justify-self:start; background:var(--blue); color:#fff; border:none; border-radius:var(--radius); padding:.75em 2em; font-size:var(--fs-lg); cursor:pointer; transition:background .2s; } #gen:hover { background:var(--blue-d); } #tokens-section { grid-column:1/-1; display:grid; gap:.5em; } #tokens { display:flex; flex-wrap:wrap; gap:.5em; } .token { position:relative; background:#eaf2ff; color:#3256a8; padding:.5em 1.5em .5em .8em; border-radius:1.5em; font-weight:600; } .token .remove { position:absolute; top:2px; right:4px; cursor:pointer; color:#d04a3f; font-weight:bold; } #default-picker { width:100%; padding:.75em 1em; font-size:var(--fs-lg); border:1px solid #dde2ee; border-radius:var(--radius); background:#fff; } pre#shortcode { grid-column:1/-1; background:var(--grey); color:#fff; padding:var(--pad); border-radius:var(--radius); font-family:'Fira Mono',monospace; white-space:pre-wrap; overflow-wrap:break-word; font-size:var(--fs-lg); } @media (max-width:768px) { body { flex-direction:column; } .main-area { order:1; width:100%; padding:1rem; } .sidebar { order:2; flex:none; width:100%; position:static!important; transform:none!important; padding:1rem; } form#cfg { grid-template-columns:1fr; } #gen { justify-self:stretch; text-align:center; } } </style> </head> <body> <div class="sidebar"> <h1>Promptinator</h1> <?php if (!$key): ?> <form method="post" autocomplete="off"> <input type="password" name="api_key" placeholder="sk-..." required> <button name="save_key">Save & Login</button> <a href="https://platform.openai.com/api-keys" target="_blank" style="display:block; margin-top:0.25rem; font-size:0.9rem; color:var(--blue);"> π Get your API key </a> </form> <div class="muted"><?= $msg ?: 'Your API key stays in your browser cookie.' ?></div> <?php else: ?> <div class="sidebar-status"><span>β</span> Logged in</div> <div class="row"> <form id="sidebar-model-form"> <label>Model</label> <select id="sidebar-model" name="model"> <option value="gpt-3.5-turbo" <?= $model==='gpt-3.5-turbo'?'selected':''?>>GPT-3.5 Turbo</option> <option value="gpt-4o-mini" <?= $model==='gpt-4o-mini'?'selected':''?>>GPT-4.1 Mini</option> <option value="gpt-4" <?= $model==='gpt-4'?'selected':''?>>GPT-4</option> </select> </form> <form id="sidebar-type-form"> <label>Type</label> <select id="sidebar-type" name="type"> <option value="B" <?= $type==='B' ?'selected':''?>>Radio</option> <option value="C" <?= $type==='C' ?'selected':''?>>Checkbox</option> <option value="Dtxt" <?= $type==='Dtxt' ?'selected':''?>>Dropdown Text</option> </select> </form> </div> <button id="sidebar-copy">Copy Shortcode</button> <button id="sidebar-copy-csv">Copy CSV</button> <a href="?delete_key=1" class="logout">Logout</a> <?php endif; ?> </div> <div class="main-area"> <div class="main-panel"> <?php if (!$key): ?> <div class="welcome"> <h2>Welcome to Promptinator</h2> <p> Promptinator is a tiny tool that turns simple βshortcodesβ into live form controls (text inputs, radios, checkboxes, dropdowns, URL pickers, etc.) right in your browser. </p> <p> <strong>How it works:</strong><br> You provide your own OpenAI API key (we never store it), and Promptinator uses it to generate choice lists on the fly. You can monitor your usage at <a href="https://platform.openai.com/account/usage" target="_blank" rel="noopener"> platform.openai.com/account/usage </a>. </p> <p> To get started, enter your API key in the box on the left and click <em>Save & Login</em>. </p> </div> <?php else: ?> <h2>Configure Promptinator</h2> <form id="cfg" method="get" autocomplete="off"> <input type="hidden" id="model" name="model" value="<?=htmlspecialchars($model)?>"> <input type="hidden" id="type" name="type" value="<?=htmlspecialchars($type)?>"> <div class="seed-row"> <label for="seed">Seed <span id="mic-btn" style="cursor:pointer;">π€</span></label> <input id="seed" name="word" value="<?=htmlspecialchars($word)?>" placeholder="seed" autocomplete="off"> </div> <div id="scope-wrapper"> <label for="scope">Relation</label> <select id="scope" name="scope"> <option value="general" <?= $scope==='general' ?'selected':''?>>General</option> <option value="broad" <?= $scope==='broad' ?'selected':''?>>Broad</option> <option value="narrow" <?= $scope==='narrow' ?'selected':''?>>Narrow</option> <option value="longtail" <?= $scope==='longtail' ?'selected':''?>>Long-tail</option> <option value="shorttail"<?= $scope==='shorttail'?'selected':''?>>Short-tail</option> </select> </div> <div> <label for="count">Count</label> <select id="count" name="count"> <?php for($i=1;$i<=15;$i++): ?> <option value="<?=$i?>" <?=$i===$count?'selected':''?>><?=$i?></option> <?php endfor; ?> </select> </div> <button id="gen">Generate</button> </form> <div id="tokens-section"> <h3>Tokens</h3> <div id="tokens"></div> <label for="default-picker">Default</label> <select id="default-picker"></select> </div> <pre id="shortcode"></pre> <?php endif; ?> </div> </div> <script> (() => { const seedInput = document.getElementById('seed'), mainTypeIn = document.getElementById('type'), sidebarType = document.getElementById('sidebar-type'), mainModelIn = document.getElementById('model'), sidebarModel = document.getElementById('sidebar-model'), countEl = document.getElementById('count'), tokensEl = document.getElementById('tokens'), defPicker = document.getElementById('default-picker'), scEl = document.getElementById('shortcode'), genBtn = document.getElementById('gen'), sidebarCopy = document.getElementById('sidebar-copy'), sidebarCopyCsv = document.getElementById('sidebar-copy-csv'); // NEW let items = <?= json_encode($items) ?>; function renderTokens() { tokensEl.innerHTML = ''; items.forEach((t,i) => { const span = document.createElement('span'); span.className = 'token'; span.textContent = t; const x = document.createElement('span'); x.className = 'remove'; x.textContent = 'Γ'; x.onclick = () => { items.splice(i,1); renderTokens(); renderDefaultPicker(); updateShortcode(); updateCsvVisibility(); }; span.appendChild(x); tokensEl.appendChild(span); }); } function renderDefaultPicker() { defPicker.innerHTML = ''; items.forEach(t => { const opt = document.createElement('option'); opt.value = t; opt.textContent = t; defPicker.appendChild(opt); }); defPicker.value = items[0] || ''; } function updateShortcode() { const t = mainTypeIn.value, ps = items.join('|'), df = items[0] || '', opt = seedInput.value.trim() || 'seed', prefix = (t==='Dtxt')?'D':t; let sc = `[${prefix}-${opt}-|${ps}|~${df}~]`; scEl.textContent = sc; } function updateCsvVisibility() { if (!sidebarCopyCsv) return; sidebarCopyCsv.style.display = items.length ? 'block' : 'none'; } // events seedInput.oninput = () => {updateShortcode(); updateCsvVisibility();}; sidebarType.onchange = () => { mainTypeIn.value = sidebarType.value; document.getElementById('cfg').submit(); }; sidebarModel.onchange = () => { mainModelIn.value = sidebarModel.value; document.getElementById('cfg').submit(); }; countEl.onchange = () => document.getElementById('cfg').submit(); genBtn.onclick = e => { e.preventDefault(); document.getElementById('cfg').submit(); }; defPicker.onchange = () => { const sel = defPicker.value; items = [sel, ...items.filter(x=>x!==sel)]; renderTokens(); renderDefaultPicker(); updateShortcode(); updateCsvVisibility(); }; sidebarCopy.onclick = () => { navigator.clipboard.writeText(scEl.textContent) .then(()=> sidebarCopy.textContent='Copied!') .catch(()=> sidebarCopy.textContent='Failed'); setTimeout(()=> sidebarCopy.textContent='Copy Shortcode', 1500); }; // NEW CSV copy handler if (sidebarCopyCsv) { sidebarCopyCsv.onclick = () => { const csv = items.join(', '); navigator.clipboard.writeText(csv) .then(()=> sidebarCopyCsv.textContent='Copied!') .catch(()=> sidebarCopyCsv.textContent='Failed'); setTimeout(()=> sidebarCopyCsv.textContent='Copy CSV', 1500); }; } // initial renderTokens(); renderDefaultPicker(); updateShortcode(); updateCsvVisibility(); })(); </script> <script> document.addEventListener('DOMContentLoaded', ()=> { const micBtn = document.getElementById('mic-btn'); const seedInput = document.getElementById('seed'); const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition || !micBtn || !seedInput) { console.warn('π€ SpeechRecognition not supported or elements missing.'); if (micBtn) micBtn.style.display = 'none'; return; } const recog = new SpeechRecognition(); recog.lang = 'en-US'; recog.interimResults = false; recog.maxAlternatives = 1; recog.onstart = () => console.log('π€ recognition started'); recog.onend = () => console.log('π€ recognition ended'); recog.onerror = e => console.error('π€ recognition error', e); recog.onresult = (e) => { const transcript = e.results[0][0].transcript; seedInput.value = transcript; seedInput.focus(); micBtn.textContent = 'π€'; }; micBtn.addEventListener('click', () => { micBtn.textContent = 'π€β¦'; try { recog.start(); } catch(err) { console.error('π€ start() error', err);} }); }); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel