Site Builder
Editing:
tools-writing-blogsok.php
writable 0666
<?php /***************************************************************** * Promptinator – Article Generator v2.3 (toolbox skin, 2025) * ---------------------------------------------------------------- * Only SECTION ③ (“page”) has changed – all back‑end logic * in sections 0‑2 is verbatim from your previous file. *****************************************************************/ declare(strict_types=1); /* ---------- 0. shared cookie helpers (unchanged) --------------- */ define('COOKIE', 'openai_key'); define('TTL', 30*24*3600); // 30 days define('PEPPER', 'openai-shared-v1'); // <<< SAME in every tool! $host = $_SERVER['HTTP_HOST'] ?? ''; $root = (!filter_var($host, FILTER_VALIDATE_IP) && preg_match('/([a-z0-9-]+\.[a-z]{2,})$/i', $host,$m)) ? '.'.$m[1] : ''; function seal(string $p):string{ $m='aes-128-ctr'; $k=substr(hash('sha256',PEPPER,true),0,16); $iv=random_bytes(openssl_cipher_iv_length($m)); return base64_encode(openssl_encrypt($p,$m,$k,0,$iv)."::{$iv}"); } function peel(string $c):string{ [$ct,$iv]=explode('::',base64_decode($c),2)+['','']; $m='aes-128-ctr'; $k=substr(hash('sha256',PEPPER,true),0,16); return openssl_decrypt($ct,$m,$k,0,$iv)?:''; } /* ---------- 1. save / delete key (unchanged) ------------------- */ 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'=>6 ]]); if(@file_get_contents('https://api.openai.com/v1/models',false,$ctx)){ setcookie(COOKIE,seal($raw),time()+TTL,'/',$root,isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['REQUEST_URI']);exit; } http_response_code(400);echo'Bad key';exit; } if(isset($_GET['logout'])){ setcookie(COOKIE,'',time()-3600,'/',$root,isset($_SERVER['HTTPS']),true); header('Location: '.$_SERVER['PHP_SELF']);exit; } /* ---------- 2. AJAX end‑point (unchanged) ---------------------- */ if($_SERVER['REQUEST_METHOD']==='POST' && ($_GET['ajax']??'')==='1'){ header('Content-Type: application/json;charset=utf-8'); $prompt = trim($_POST['prompt']??''); $model = trim($_POST['model'] ?? 'gpt-3.5-turbo'); $key = isset($_COOKIE[COOKIE]) ? peel($_COOKIE[COOKIE]) : ''; if(!$key){echo json_encode(['error'=>'No API key']);exit;} if($prompt===''){echo json_encode(['error'=>'Prompt empty']);exit;} $payload=[ 'model'=>$model, 'messages'=>[['role'=>'user','content'=>$prompt]], 'max_tokens'=>1400,'temperature'=>0.7 ]; $ch=curl_init('https://api.openai.com/v1/chat/completions'); curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>1,CURLOPT_POST=>1, CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$key,'Content-Type: application/json'], CURLOPT_POSTFIELDS=>json_encode($payload),CURLOPT_TIMEOUT=>60]); $raw=curl_exec($ch);curl_close($ch); $html=$raw?json_decode($raw,true)['choices'][0]['message']['content']:''; echo $html?json_encode(['html'=>$html]):json_encode(['error'=>'Empty response']); exit; } /* ---------- 3. page (NEW THEME + MARK‑UP) --------------------- */ $logged = isset($_COOKIE[COOKIE]) && peel($_COOKIE[COOKIE]); $models=['gpt-3.5-turbo','gpt-4o-mini','gpt-4']; ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>Article Generator • Promptinator</title> <!-- --------- shared colour system -------------------------- --> <style> :root{ --bg:#f1f4fb; --card:#fff; --dark:#121720; --brand:#004cff; --brand-d:#0841c4; --green:#5af287; --red:#e24d4b; --radius:26px; --shadow:0 6px 30px rgba(0,0,0,.08); font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; font-size:16px; /* accessibility: 16 px base */ } /* ---- global layout ---------------------------------------- */ body{margin:0;min-height:100vh;background:var(--bg);display:flex;flex-direction:column;color:#111} .breadcrumb{background:#ececec;font-weight:600;padding:.7rem 1.2rem} .breadcrumb a{color:var(--brand);text-decoration:none} main{width:100%;max-width:960px;margin:2.5rem auto;padding:0 1.2rem;flex:1} .tool-card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:2rem} /* ---- top (API‑key) bar ------------------------------------ */ #bar{background:var(--dark);color:#fff;padding:.9rem 1.2rem;border-radius:var(--radius); display:flex;flex-wrap:wrap;gap:1rem;align-items:center} #bar select,#bar input{border:none;border-radius:8px;padding:.55rem .85rem;font-size:1rem} #bar select{background:#1e2535;color:#fff} .badge{display:flex;align-items:center;gap:.4ch;font-weight:600;font-size:.95rem} .badge i{width:.55rem;height:.55rem;border-radius:50%;background:var(--green)} .badge button{background:none;border:none;color:var(--red);font-size:1.1rem;cursor:pointer} /* ---- shared form controls --------------------------------- */ label{font-weight:600;font-size:.95rem;display:flex;flex-direction:column;gap:.45rem} input,select,textarea{width:100%;padding:.7rem 1rem;border:1px solid #ccd4e8;border-radius:10px;font-size:1rem; background:#fff;box-sizing:border-box} input:focus,select:focus,textarea:focus{outline:2px solid var(--brand);border-color:var(--brand)} textarea{min-height:120px;resize:vertical;font-family:ui-monospace,monospace} /* ---- responsive grid for generator ------------------------- */ .form-grid{display:grid;gap:1.4rem;margin-top:1.8rem} @media(min-width:700px){.form-grid{grid-template-columns:repeat(auto-fit,minmax(260px,1fr))}} @media(max-width:699px){.form-grid{grid-template-columns:1fr}} /* ---- buttons ---------------------------------------------- */ .btn{background:var(--brand);color:#fff;border:none;border-radius:10px;padding:.75rem 1.6rem;font-size:1rem; cursor:pointer;font-weight:600;transition:background .15s} .btn:hover{background:var(--brand-d)} .copyBtn{margin-top:.7rem} /* ---- 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} #spin[aria-live]{position:relative} /* ---- preview containers ----------------------------------- */ .preview{background:#fff;border:1px solid #dfe3ef;border-radius:12px;padding:1.2rem;max-height:65vh;overflow:auto} /* ---- mic icon --------------------------------------------- */ .micBtn{position:absolute;right:.7rem;top:50%;transform:translateY(-50%);background:none;border:none; font-size:1.2rem;cursor:pointer} /* ---- mobile tweaks ---------------------------------------- */ @media(max-width:520px){ #bar input{flex:1 1 100%} } </style> </head> <body> <!-- breadcrumbs identical to other tools ------------------------ --> <nav class="breadcrumb"> <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Article Generator <a href="/<?= $_SESSION['slug']??''?>/" style="float:right">View Site</a> </nav> <main> <!-- API key bar -------------------------------------------- --> <div id="bar" aria-label="OpenAI key status"> <?php if(!$logged): ?> <form method="post" style="display:flex;flex-wrap:wrap;gap:.7rem" autocomplete="off"> <input type="password" name="api_key" placeholder="sk-…" aria-label="OpenAI secret key" required> <button class="btn" name="save_key">Save Key</button> </form> <?php else: ?> <span class="badge" role="status"><i></i> API key saved <button title="Delete key" onclick="location='?logout=1'" aria-label="Delete saved key">×</button> </span> <select id="modelSel" aria-label="Model selector"> <?php foreach($models as $m):?><option <?=$m==='gpt-3.5-turbo'?'selected':''?>><?=$m?></option><?php endforeach;?> </select> <?php endif;?> </div> <?php if(!$logged): ?> <div class="tool-card" style="text-align:center;margin-top:2rem"> <h2>Get started</h2> <p>Paste your OpenAI API key into the bar above to unlock the Article Generator.</p> </div> <?php else: ?> <!-- generator card ----------------------------------------- --> <div class="tool-card"> <h2 style="margin-top:0">Generate an article</h2> <form id="gen" class="form-grid" autocomplete="off"> <!-- hidden fields sent to AJAX --> <input type="hidden" name="prompt" id="promptField"> <input type="hidden" name="model" id="modelHidden" value="gpt-3.5-turbo"> <!-- TOPIC ------------------------------------------------> <label>Topic <div style="position:relative"> <input id="topic" name="topic" placeholder="e.g. Sustainable boating" required> <button type="button" id="mic" class="micBtn" aria-label="Dictate topic">🎤</button> </div> </label> <!-- keyword / link URL ---------------------------------> <label>Keywords (comma separated) <input id="keywords" name="keywords" placeholder="eco-friendly, electric drive"> </label> <label>Link URL <input id="linkurl" type="url" name="linkurl" placeholder="https://example.com"> </label> <!-- numeric selectors -----------------------------------> <label># Sections <select id="sections" name="sections"><?php for($i=1;$i<=8;$i++):?><option><?=$i?></option><?php endfor;?></select> </label> <label># Paragraphs per section <select id="paras" name="paras"><?php for($i=1;$i<=6;$i++):?><option><?=$i?></option><?php endfor;?></select> </label> <!-- language / style / tone -----------------------------> <label>Language <select id="lang" name="lang"><?php foreach(['English','Spanish','German','French','Italian','Portuguese','Dutch'] as $l):?><option><?=$l?></option><?php endforeach;?></select> </label> <label>Style <select id="style" name="style"><?php foreach(['Creative','Informative','Narrative','Persuasive','Analytical','Journalistic'] as $s):?><option><?=$s?></option><?php endforeach;?></select> </label> <label>Tone <select id="tone" name="tone"><?php foreach(['Neutral','Cheerful','Humorous','Assertive','Inspirational','Professional','Emotional'] as $t):?><option><?=$t?></option><?php endforeach;?></select> </label> <!-- prompt preview + copy --------------------------------> <label class="full" style="grid-column:1/-1">Prompt preview <textarea id="promptBox" readonly aria-describedby="ppHelp"></textarea> </label> <button type="button" id="copyP" class="btn copyBtn" style="grid-column:1/-1" hidden>Copy Prompt</button> <!-- action & spinner ------------------------------------> <button class="btn" id="go" style="grid-column:1/-1">Generate Article</button> <div id="spin" role="status" aria-live="polite"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" fill="none"/></svg><span id="s">0</span> s</div> </form> <!-- results -------------------------------------------- --> <section id="out" style="display:none;margin-top:2.2rem"> <h3>HTML source</h3> <textarea id="raw" readonly style="min-height:180px"></textarea> <button type="button" id="copyRaw" class="btn copyBtn">Copy HTML</button> <h3 style="margin-top:2rem">Rendered preview</h3> <div id="rend" class="preview"></div> <button type="button" id="copyR" class="btn copyBtn">Copy Rendered HTML</button> </section> </div> <?php endif;?> </main> <!-- -------------- scripts --------------------------------------- --> <script> (()=>{ const $=id=>document.getElementById(id); /* ----- live prompt builder ----------------------------------- */ function makePrompt(){ const t=$('topic').value.trim(); if(!t){ $('promptBox').value=''; $('copyP').hidden=true; return; } const kw=$('keywords').value.trim(), ln=$('linkurl').value.trim(), sec=$('sections').value, par=$('paras').value; const p=`Write a ${$('lang').value} ${$('style').value} article in a ${$('tone').value} tone about «${t}». ` +`Use ${sec} section${sec>1?'s':''} (<h2>) and ${par} paragraph${par>1?'s':''} per section. ` +(kw?`Include these keywords: ${kw}. `:'') +(kw && ln?`Hyperlink every keyword to ${ln}. ` : '') +'Append a one‑sentence excerpt wrapped in <p class="excerpt"> … </p>. Return ONLY HTML.'; $('promptBox').value=p; $('promptField').value=p; $('copyP').hidden=false; } ['topic','keywords','linkurl','sections','paras','lang','style','tone'] .forEach(id=>$(id)?.addEventListener('input',makePrompt)); makePrompt(); $('copyP').onclick=()=>navigator.clipboard.writeText($('promptBox').value).then(()=>flash($('copyP'))); /* ----- mic / speech‑to‑text ---------------------------------- */ if($('mic') && (window.SpeechRecognition||window.webkitSpeechRecognition)){ const R = new (window.SpeechRecognition||window.webkitSpeechRecognition)(); R.lang='en-US'; R.interimResults=false; $('mic').onclick=()=>{try{R.start();}catch{}}; R.onresult=e=>{$('topic').value=e.results[0][0].transcript;makePrompt();}; }else if($('mic')){$('mic').style.display='none';} /* ----- model selector sync ----------------------------------- */ if($('modelSel')) $('modelSel').onchange=e=>$('modelHidden').value=e.target.value; /* ----- generator AJAX --------------------------------------- */ if($('gen')){ $('gen').onsubmit=e=>{ e.preventDefault(); makePrompt(); // seal latest edits $('go').disabled=true; let sec=0; $('s').textContent=0; $('spin').classList.add('show'); const tick=setInterval(()=>$('s').textContent=++sec,1000); fetch('?ajax=1',{method:'POST',body:new FormData($('gen'))}) .then(r=>r.json()).then(j=>{ clearInterval(tick); $('spin').classList.remove('show'); $('go').disabled=false; if(j.error){alert(j.error);return;} $('raw').value=j.html; $('rend').innerHTML=j.html; $('out').style.display=''; $('copyRaw').onclick=()=>navigator.clipboard.writeText($('raw').value).then(()=>flash($('copyRaw'))); $('copyR').onclick =()=>navigator.clipboard.writeText($('rend').innerHTML).then(()=>flash($('copyR'))); $('rend').scrollIntoView({behavior:'smooth'}); }) .catch(err=>{ clearInterval(tick); $('spin').classList.remove('show'); $('go').disabled=false; alert(err); }); }; } /* ----- helper ----------------------------------------------- */ function flash(btn){ const t=btn.textContent; btn.textContent='✔ Copied'; setTimeout(()=>btn.textContent=t,1200); } })(); </script> </body> </html>
Save changes
Create folder
writable 0777
Create
Cancel