Site Builder
Editing:
tools-promptinator.php
writable 0666
<?php /********************************************************************** * Promptinator — Business AI Tool (AJAX + toolbar, Aug 2025) **********************************************************************/ require_once $_SERVER['DOCUMENT_ROOT'] . '/openai/init.php'; ai_handle_key_post(); // save / delete key /* ---------- 1. AJAX end‑point ------------------------------------ */ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_GET['ajax'] ?? '') === '1') { header('Content-Type: application/json; charset=utf-8'); if (!ai_has_key()) { echo json_encode(['error'=>'No API key']); exit; } $word = trim($_POST['word'] ?? ''); $type = in_array($_POST['type'] ?? '', ['B','C','Dtxt']) ? $_POST['type'] : 'B'; $scope = $_POST['scope'] ?? 'general'; $count = max(1, min(15, intval($_POST['count'] ?? 5))); if ($word === '') { echo json_encode(['error'=>'Seed word required']); exit; } $prompt = "You are a helpful assistant that replies ONLY with a " . "comma‑separated list of {$scope} variations, no extra words.\n" . "List up to {$count} {$scope} variations for '{$word}'."; $raw = array_filter(array_map('trim', explode(',', ai_chat($prompt)) )); array_unshift($raw, $word); $items = array_slice(array_unique($raw), 0, $count); echo json_encode(['items' => $items]); exit; } /* ---------- first load: empty tokens (page never reloads) -------- */ $items = []; ?> <!doctype html> <html lang="en"><head> <meta charset="utf-8"> <title>Promptinator – AI Toolbox</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> :root{--bg:#f1f4fb;--brand:#004cff;--brand-d:#0841c4;--card:#fff;--shadow:0 6px 30px rgba(0,0,0,.08); --dark:#121720;--green:#5af287;--red:#e24d4b;--radius:26px; font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif} body{margin:0;background:var(--bg);color:#111;min-height:100vh;display:flex;flex-direction:column} .breadcrumb{background:#eee;padding:.6rem 1rem;font-weight:600} .breadcrumb a{color:var(--brand);text-decoration:none} main{width:100%;max-width:900px;margin:2.2rem auto;padding:0 1rem;flex:1} .tool-card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:2rem;display:grid;gap:1.6rem} /* ---------- form -------------------------------------------------- */ #cfg{display:grid;gap:1.4rem;grid-template-columns:1fr 1fr} #cfg .full{grid-column:1/-1} label{font-weight:600;font-size:.95rem} select,input[type=text]{width:100%;padding:.8rem 1rem;border:1px solid #d0d6e7;border-radius:10px;font-size:1rem} select:focus,input:focus{outline:none;border-color:var(--brand)} #gen{justify-self:start;padding:.75rem 2rem;background:var(--brand);color:#fff;border:none; border-radius:10px;font-size:1rem;cursor:pointer} .seed-wrap{position:relative} .seed-wrap input{padding-right:3rem;font-size:1.05rem;height:58px} .micBtn{position:absolute;right:.8rem;top:50%;transform:translateY(-50%); background:none;border:none;font-size:1.4rem;cursor:pointer} /* ---------- spinner ------------------------------------------------ */ #spin{display:none;gap:.6ch;align-items:center;margin-top:.8rem} #spin.show{display:flex} @keyframes rot{to{transform:rotate(360deg)}} #spin svg{width:20px;height:20px;animation:rot 1s linear infinite} /* ---------- tokens / shortcode ------------------------------------ */ #tokens{display:flex;flex-wrap:wrap;gap:.5rem} .token{position:relative;background:#e8efff;color:#003c9e;padding:.5rem 1.4rem .5rem .9rem; border-radius:20px;font-weight:600;font-size:.95rem} .token .remove{position:absolute;right:4px;top:-2px;cursor:pointer;color:var(--red)} #shortcode{background:#0d111a;color:#fff;border-radius:10px;padding:1.3rem; font-family:ui-monospace,monospace;font-size:.95rem;white-space:pre-wrap} .action-row{display:flex;gap:1rem} .action-row button{padding:.7rem 1.4rem;background:var(--brand);color:#fff;border:none; border-radius:9px;cursor:pointer;flex:1} .action-row button:hover{background:var(--brand-d)} .action-row .csv{display:none} /* ---------- responsive ------------------------------------------- */ @media(max-width:640px){ #cfg{grid-template-columns:1fr} #gen{justify-self:stretch;text-align:center} } </style> </head><body> <nav class="breadcrumb"> <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Promptinator <a href="/<?= htmlspecialchars($_SESSION['slug'] ?? '') ?>/" style="float:right">View Site</a> </nav> <?php ai_render_key_bar(); ?> <?php require_once $_SERVER['DOCUMENT_ROOT'].'/ai-tools/share.php'; ?> <main> <?php if (!ai_has_key()): ?> <div class="tool-card" style="text-align:center"> <h2>Get started</h2> <p>Save your OpenAI API key in the black bar above to unlock Promptinator.</p> </div> <?php else: ?> <div class="tool-card"> <h2>Generate options</h2> <!-- generation form (AJAX) --> <form id="cfg" autocomplete="off"> <!-- dummy textarea so toolbar helper injects model / temp / max_tokens --> <textarea name="prompt" style="display:none" tabindex="-1"></textarea> <input type="hidden" id="typeHidden" name="type" value="B"> <div class="seed-wrap full"> <label for="seed">Seed word</label> <input id="seed" name="word" placeholder="e.g. shoes"> <button type="button" id="mic" class="micBtn">🎤</button> </div> <div> <label>Relation</label> <select id="scope" name="scope"> <option>general</option><option>broad</option><option>narrow</option> <option>longtail</option><option>shorttail</option> </select> </div> <div> <label>Count</label> <select id="count" name="count"> <?php for ($i=1;$i<=15;$i++) echo "<option>$i</option>"; ?> </select> </div> <div> <label>Control type</label> <select id="ctype"> <option value="B">Radio</option> <option value="C">Checkbox</option> <option value="Dtxt">Dropdown Text</option> </select> </div> <button id="gen" class="full">Generate</button> <div id="spin"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/></svg> <span id="sec">0</span> s</div> </form> <!-- results --> <div id="tokens-area" style="display:none"> <h3>Tokens</h3> <div id="tokens"></div> <label for="def">Default value</label> <select id="def"></select> <pre id="shortcode"></pre> <div class="action-row"> <button class="copy" onclick="copyTxt('shortcode')">Copy Shortcode</button> <button class="copy csv" id="csvBtn" onclick="copyCsv()">Copy CSV</button> </div> </div> </div> <?php endif; ?> </main> <script> (()=>{ /* ---------- STATE ----------------------------------------------- */ let items=[]; /* ---------- DOM helpers ----------------------------------------- */ const $=q=>document.querySelector(q), tokensBox=$('#tokens'),defSel=$('#def'),scEl=$('#shortcode'), csvBtn=$('#csvBtn'),spin=$('#spin'),sec=$('#sec'); /* ---------- rendering helpers ----------------------------------- */ function renderTokens(){ tokensBox.innerHTML=''; items.forEach((t,i)=>{ const s=document.createElement('span'); s.className='token'; s.textContent=t; const x=document.createElement('span'); x.className='remove'; x.textContent='×'; x.onclick=()=>{items.splice(i,1);updateAll();}; s.appendChild(x); tokensBox.appendChild(s); }); } function renderDefault(){ defSel.innerHTML=''; items.forEach(t=>{ const o=document.createElement('option'); o.value=t;o.textContent=t;defSel.appendChild(o); }); defSel.value=items[0]||''; } function updateShortcode(){ const t = $('#typeHidden').value; const ps = items.join('|'); const df = items[0]||''; const opt = ($('#seed').value.trim()||'seed').replace(/\s+/g,'-'); const prefix = (t==='Dtxt')?'D':t; scEl.textContent=`[${prefix}-${opt}-|${ps}|~${df}~]`; } function updateAll(){ renderTokens();renderDefault();updateShortcode(); $('#tokens-area').style.display = items.length?'block':'none'; csvBtn.style.display = items.length?'block':'none'; } /* ---------- AJAX submit ----------------------------------------- */ const frm=$('#cfg'); frm.onsubmit=e=>{ e.preventDefault(); /* spinner */ $('#gen').disabled=true; let t=0; sec.textContent='0'; spin.classList.add('show'); const tick=setInterval(()=>sec.textContent=++t,1000); const fd=new FormData(frm); // model/temp/max_tokens already injected by helper fetch('?ajax=1',{method:'POST',body:fd}) .then(r=>r.json()) .then(j=>{ clearInterval(tick); spin.classList.remove('show'); $('#gen').disabled=false; if(j.error){alert(j.error);return;} items=j.items||[]; updateAll(); }) .catch(err=>{ clearInterval(tick); spin.classList.remove('show'); $('#gen').disabled=false; alert(err); }); }; /* ---------- control type switch --------------------------------- */ $('#ctype').onchange=e=>{ $('#typeHidden').value=e.target.value; updateShortcode(); }; /* ---------- default picker reorder ------------------------------ */ defSel.onchange=()=>{ const v=defSel.value,idx=items.indexOf(v); if(idx>0){items.splice(idx,1);items.unshift(v);} updateAll(); }; /* ---------- copy helpers ---------------------------------------- */ window.copyTxt=id=>{ navigator.clipboard.writeText($('#'+id).textContent).then(()=>{ const btn=event.target,old=btn.textContent;btn.textContent='✔ Copied'; setTimeout(()=>btn.textContent=old,1200); }); }; window.copyCsv=()=>{ navigator.clipboard.writeText(items.join(', ')).then(()=>{ const old=csvBtn.textContent;csvBtn.textContent='✔ Copied'; setTimeout(()=>csvBtn.textContent=old,1200); }); }; /* ---------- microphone inside input ----------------------------- */ const mic=$('#mic'); if(mic){ const SR=window.SpeechRecognition||window.webkitSpeechRecognition; if(!SR) mic.style.display='none'; else{ const rec=new SR();rec.lang='en-US'; mic.onclick=()=>{mic.textContent='🎤…';try{rec.start();}catch{}}; rec.onresult=e=>{ $('#seed').value=e.results[0][0].transcript; mic.textContent='🎤'; updateShortcode(); }; } } })(); </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel