Site Builder
Editing:
tools-promptinator1.php
writable 0666
<?php /************************************************************************** * Promptinator (AI keyword / option generator) v1.2 – 2025 * ---------------------------------------------------------------------- * Location …/ai-tools/business-tools/promptinator/promptinator.php * • Requires member login (uses /members/lib/auth.php) * • Stores OpenAI key *locally* in an encrypted HttpOnly cookie. * • All UI, voice‑dictation & copy helpers retained from your prototype **************************************************************************/ /* ---------- boot & auth -------------------------------------------- */ require_once $_SERVER['DOCUMENT_ROOT'].'/members/lib/auth.php'; require_login(); $me = current_user(); /* ---------- config --------------------------------------------------- */ define('COOKIE_NAME', 'bdo_ai_key'); // one key for all AI tools define('COOKIE_TTL', 30 * 24 * 3600); // 30 days define('SECRET', __FILE__); // per‑file secret for AES function enc($v){ // AES‑128‑CTR small helper $m='aes-128-ctr'; $k=substr(hash('sha256',SECRET,true),0,16); $iv=random_bytes(openssl_cipher_iv_length($m)); return base64_encode(openssl_encrypt($v,$m,$k,0,$iv).'::'.$iv); } function dec($c){ [$ct,$iv]=explode('::',base64_decode($c),2)+[null,null]; return $ct?openssl_decrypt($ct,'aes-128-ctr', substr(hash('sha256',SECRET,true),0,16),0,$iv):''; } /* ---------- save / delete key -------------------------------------- */ $msg=''; if($_SERVER['REQUEST_METHOD']==='POST' && isset($_POST['save_key'])){ $raw=trim($_POST['api_key']??''); $ctx=stream_context_create(['http'=>[ 'method'=>'GET', 'header'=>"Authorization: Bearer $raw\r\n", 'timeout'=>8 ]]); if(@file_get_contents('https://api.openai.com/v1/models',false,$ctx)){ setcookie(COOKIE_NAME,enc($raw),time()+COOKIE_TTL,'/','', isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['REQUEST_URI']); exit; }else $msg='❌ Invalid key.'; } if(isset($_GET['logout'])){ setcookie(COOKIE_NAME,'',time()-3600,'/','',isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['PHP_SELF']); exit; } /* ---------- UI params & defaults ------------------------------------ */ $models = ['gpt-3.5-turbo','gpt-4o-mini','gpt-4']; $types = ['B','C','Dtxt']; // (radio, checkbox, dropdown‑text) $model = in_array($_GET['model']??'',$models,true) ? $_GET['model'] : $models[0]; $type = in_array($_GET['type'] ??'',$types ,true) ? $_GET['type'] : 'B'; $scope = $_GET['scope'] ?? 'general'; $word = trim($_GET['word'] ?? ''); $count = max(1,min(15,(int)($_GET['count']??5))); $key = isset($_COOKIE[COOKIE_NAME]) ? dec($_COOKIE[COOKIE_NAME]) : ''; /* ---------- fetch tokens from OpenAI (if applicable) ----------------- */ $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 options."], ['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]); $res=json_decode(curl_exec($ch),true); curl_close($ch); $raw=array_filter(array_map('trim',explode(',',$res['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 – AI Toolbox</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="preconnect" href="https://fonts.gstatic.com"> <style> /* == global palette (same as tools.php) =============================== */ :root{ --bg:#f4f6ff; --fg:#15254f; --gold:#ffb63b; --blue:#0066ff; --blue-d:#0a4fe3; --card:#ffffff; --card-sh:0 6px 26px rgba(0,0,0,.06); --radius:26px; } /* == reset & layout =================================================== */ *{box-sizing:border-box;font:inherit;margin:0} html,body{height:100%}body{background:var(--bg);color:var(--fg);font-family:system-ui,Arial,sans-serif;display:flex;flex-direction:column} a{color:var(--blue);text-decoration:none} .top{background:#eee;padding:.85rem 1.4rem;font-weight:900;display:flex;justify-content:space-between;align-items:center} .top a[href$="dashboard.php"]{margin:0 .3rem} .top .right{margin-left:auto} .main{flex:1;display:flex;flex-wrap:wrap;gap:2rem;max-width:1100px;margin:2.5rem auto;padding:0 1rem} .sidebar{flex:0 0 300px;background:#1c2331;color:#fff;border-radius:var(--radius);padding:1.8rem 1.5rem;align-self:flex-start;box-shadow:var(--card-sh)} .content{flex:1 1 400px;background:var(--card);border-radius:var(--radius);box-shadow:var(--card-sh);padding:2rem;min-width:0} /* == sidebar ========================================================== */ .sidebar h1{margin:0 0 1.2rem;font-size:1.5rem;color:var(--gold)} .sidebar form{display:grid;gap:.8rem} .sidebar input{padding:.55rem .8rem;border-radius:10px;border:none;font-size:1rem} .sidebar button{padding:.55rem .8rem;border:none;border-radius:10px;background:var(--blue);color:#fff;font-weight:700;cursor:pointer} .sidebar .muted{font-size:.85rem;color:#ccd} .status{display:flex;align-items:center;gap:.5rem;font-weight:600;margin-bottom:1rem} .status span{font-size:1.2rem;color:#72eb99} /* dropdowns & buttons */ .sb-select{width:100%;padding:.55rem .8rem;border-radius:10px;background:#22304a;color:#fff;border:none;font-size:1rem} .sb-select option{color:#000} .sb-copy{margin-top:1rem;width:100%;padding:.6rem;border:none;border-radius:10px;background:var(--blue);color:#fff;font-weight:700;cursor:pointer;transition:.2s} .sb-copy:hover{background:var(--blue-d)} .logout{display:block;margin-top:1rem;text-align:center;font-weight:600} /* == content ========================================================== */ .content h2{margin-top:0;font-size:1.5rem} #cfg{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1.2rem;margin-top:1.2rem} #cfg label{font-weight:600;margin-bottom:.35rem} #cfg input,#cfg select{width:100%;padding:.55rem .8rem;border:1px solid #cdd4e8;border-radius:10px;font-size:1rem;background:#f8faff} #cfg input:focus,#cfg select:focus{outline:none;border-color:var(--blue);background:#fff} #gen{grid-column:1/-1;justify-self:start;padding:.65rem 2rem;border:none;border-radius:12px;background:var(--blue);color:#fff;font-weight:700;cursor:pointer} #tokens{display:flex;flex-wrap:wrap;gap:.5rem} .token{position:relative;background:#eaf2ff;color:#3256a8;padding:.4rem 1.45rem .4rem .85rem;border-radius:1.4rem;font-weight:600} .token .remove{position:absolute;top:2px;right:6px;cursor:pointer;font-weight:bold;color:#c84337} #shortcode{margin-top:1.4rem;background:#0e1a35;color:#fff;border-radius:12px;padding:1.4rem;font-family:menlo,monospace;font-size:1rem;white-space:pre-wrap} /* == responsive tweaks ================================================ */ @media(max-width:768px){.sidebar{flex:1 1 100%}} </style> </head> <body> <!-- ───── breadcrumb header ───── --> <div class="top"> <div> <a href="/" style="color:#0050c8">BestDealOn</a> » <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Promptinator </div> <a class="right" href="/<?=$me['site_slug']??''?>/" target="_blank">View Site</a> </div> <div class="main"> <!-- ───── left: key / settings ───── --> <aside 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> </form> <div class="muted"> <?= $msg ?: 'Your OpenAI key is stored only in an encrypted, HttpOnly cookie on this device.' ?><br> <a style="color:#fff;text-decoration:underline" href="https://platform.openai.com/api-keys" target="_blank">Get an API key ↗</a> </div> <?php else: /* logged‑in sidebar */ ?> <div class="status"><span>●</span>API key saved</div> <label>Model</label> <select id="sidebar-model" class="sb-select"> <?php foreach($models as $m): ?> <option value="<?=$m?>" <?=$m===$model?'selected':''?>><?=$m?></option> <?php endforeach; ?> </select> <label style="margin-top:.9rem">Control type</label> <select id="sidebar-type" class="sb-select"> <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> <button id="sidebar-copy" class="sb-copy">Copy Shortcode</button> <button id="sidebar-copy-csv" class="sb-copy" style="display:none">Copy CSV</button> <a href="?logout=1" class="logout">Log out</a> <?php endif; ?> </aside> <!-- ───── right: main panel ───── --> <section class="content"> <?php if(!$key): ?> <div class="welcome"> <h2>Welcome 👋</h2> <p>Promptinator turns a single “seed” word into formatted short‑codes or CSV lists ready for your campaigns. Paste your <strong>OpenAI API key</strong> on the left to start.</p> </div> <?php else: ?> <h2>Generate options</h2> <form id="cfg" method="get" autocomplete="off"> <input type="hidden" name="model" id="model" value="<?=e($model='')?>"> <!-- js keeps in sync --> <input type="hidden" name="type" id="type" value="<?=e($type)?>"> <div style="grid-column:1/-1"> <label for="seed">Seed word <span id="mic-btn" style="cursor:pointer">🎤</span></label> <input id="seed" name="word" value="<?=e($word)?>" placeholder="e.g. shoes" autocorrect="off"> </div> <div> <label for="scope">Relation</label> <select id="scope" name="scope"> <?php foreach(['general','broad','narrow','longtail','shorttail'] as $s): ?> <option value="<?=$s?>" <?=$s===$scope?'selected':''?>><?=$s?></option> <?php endforeach; ?> </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 style="margin-top:1.7rem"> <h3 style="margin:0 0 .6rem">Tokens</h3> <div id="tokens"></div> <label style="display:block;margin-top:1.1rem" for="default-picker">Default value</label> <select id="default-picker" style="width:100%;padding:.55rem .8rem;border:1px solid #cdd4e8;border-radius:10px;background:#fff"></select> </div> <pre id="shortcode" aria-label="shortcode output"></pre> <?php endif; ?> </section> </div> <!-- ---------- vanilla JS helpers (unchanged core logic) ------------ --> <script> const qs = q => document.querySelector(q); const modelIn = qs('#model'), typeIn = qs('#type'), tokens = <?= json_encode($items) ?>; (function initUI(){ const sidebarModel = qs('#sidebar-model'), sidebarType = qs('#sidebar-type'), seedInput = qs('#seed'), countEl = qs('#count'), scopeEl = qs('#scope'), tokenWrap = qs('#tokens'), defPicker = qs('#default-picker'), sc = qs('#shortcode'), copyBtn = qs('#sidebar-copy'), copyCsvBtn = qs('#sidebar-copy-csv'), genBtn = qs('#gen'); /* ----- helpers ---------------------------------------------------- */ function renderTokens(){ tokenWrap.innerHTML=''; tokens.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=()=>{tokens.splice(i,1);renderTokens();renderPicker();updateSC();updateCsv();} span.appendChild(x); tokenWrap.appendChild(span); }); } function renderPicker(){ defPicker.innerHTML=''; tokens.forEach(t=>{ const o=document.createElement('option');o.value=t;o.textContent=t;defPicker.appendChild(o); }); defPicker.value=tokens[0]||''; } function updateSC(){ const pref=(typeIn.value==='Dtxt')?'D':typeIn.value, ps=tokens.join('|')||'...', df=tokens[0]||'', seed=seedInput.value.trim()||'seed'; sc.textContent=`[${pref}-${seed}-|${ps}|~${df}~]`; } function updateCsv(){ copyCsvBtn.style.display=tokens.length?'block':'none'; } /* ----- sidebar controls ------------------------------------------ */ if(sidebarModel){ sidebarModel.onchange=()=>{modelIn.value=sidebarModel.value;document.getElementById('cfg').submit();}; sidebarType.onchange =()=>{typeIn.value =sidebarType.value ;document.getElementById('cfg').submit();}; copyBtn.onclick =()=>{navigator.clipboard.writeText(sc.textContent).then(()=>{copyBtn.textContent='Copied!';setTimeout(()=>copyBtn.textContent='Copy Shortcode',1500);});}; if(copyCsvBtn) copyCsvBtn.onclick=()=>{navigator.clipboard.writeText(tokens.join(', ')).then(()=>{copyCsvBtn.textContent='Copied!';setTimeout(()=>copyCsvBtn.textContent='Copy CSV',1500);});}; } /* ----- main form events ------------------------------------------ */ if(seedInput){ seedInput.oninput =updateSC; genBtn.onclick =e=>{e.preventDefault();document.getElementById('cfg').submit();}; countEl.onchange =()=>document.getElementById('cfg').submit(); } defPicker?.addEventListener('change',()=>{ const sel=defPicker.value; tokens.splice(tokens.indexOf(sel),1); tokens.unshift(sel); renderTokens();renderPicker();updateSC();updateCsv(); }); renderTokens();renderPicker();updateSC();updateCsv(); })(); /* ---------- voice dictation (same as proto) -------------------- */ document.addEventListener('DOMContentLoaded',()=>{ const btn=document.getElementById('mic-btn'),seed=document.getElementById('seed'); if(!btn||!seed)return; const SR=window.SpeechRecognition||window.webkitSpeechRecognition; if(!SR){btn.style.display='none';return;} const recog=new SR();recog.lang='en-US';recog.interimResults=false; recog.onresult=e=>{seed.value=e.results[0][0].transcript;seed.focus();btn.textContent='🎤';}; recog.onerror =()=>{btn.textContent='🎤';}; btn.onclick =()=>{btn.textContent='🎤…';try{recog.start();}catch{}}; }); </script> </body></html> <?php /* ---------- small helper so htmlspecialchars() is readable ---------- */ function e($s){return htmlspecialchars($s,ENT_QUOTES,'UTF-8');} ?>
Save changes
Create folder
writable 0777
Create
Cancel