Site Builder
Editing:
tools-writing-article-point1s.php
writable 0666
<?php /********************************************************************** * Promptinator — Article‑with‑Points Generator (Aug 2025, a11y polish) **********************************************************************/ require_once $_SERVER['DOCUMENT_ROOT'] . '/openai/init.php'; ai_handle_key_post(); /* ---------- 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; } $topic = trim($_POST['topic']??''); $points = array_filter(array_map('trim', $_POST['points']??[])); $kw = trim($_POST['keywords']??''); $url = trim($_POST['linkurl']??''); $sec = max(1,intval($_POST['sections']??3)); $par = max(1,intval($_POST['paras']??3)); $lang = $_POST['lang'] ?? 'English'; $style = $_POST['style'] ?? 'Creative'; $tone = $_POST['tone'] ?? 'Neutral'; if ($topic===''){ echo json_encode(['error'=>'Topic required']); exit; } $prompt = "Write a $lang article in a $tone tone and $style style about \"$topic\". " ."Structure it into $sec sections, each starting with an <h2> and containing $par paragraph" .($par>1?'s':'').". Use additional <h3> sub‑headings if it improves readability."; if ($kw) $prompt.=" Include the following keywords naturally throughout the article: $kw."; if ($kw && $url) $prompt.=" Hyperlink each keyword exactly once to $url using <a> tags."; if ($points) $prompt.=" The following main points must each be covered (preferably as section headings): " .implode('; ',$points).'.'; $prompt.=" After the body, append <p class=\"excerpt\">Excerpt: …</p>. Return ONLY the complete HTML."; $html = ai_chat($prompt,['max_tokens'=>max(800,$sec*$par*200)]); echo json_encode(['html'=>$html]); exit; } /* ---------- static lists ----------------------------------------- */ $langs = ['English','Spanish','German','French','Italian','Portuguese','Dutch']; $styles = ['Creative','Informative','Narrative','Persuasive','Analytical','Journalistic']; $tones = ['Neutral','Cheerful','Humorous','Assertive','Inspirational','Professional','Emotional']; ?> <!doctype html> <html lang="en"><head> <meta charset="utf-8"> <title>Article Generator • Promptinator</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> /* ===== CSS reset / variables ===================================== */ :root{ --bg:#f1f4fb;--card:#fff;--brand:#004cff;--brand-d:#0d5bfd; --shadow:0 6px 30px rgba(0,0,0,.08);--radius:26px; --dark:#121720;--green:#5af287;--red:#e24d4b } *,*::before,*::after{box-sizing:border-box} body{margin:0;min-height:100vh;display:flex;flex-direction:column;background:var(--bg); font:400 16px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;color:#111} /* ===== breadcrumb + toolbar ===================================== */ .breadcrumb{background:#eee;padding:.6rem 1rem;font-weight:600} .breadcrumb a{color:var(--brand);text-decoration:none} /* ===== card ====================================================== */ main{max-width:1000px;width:100%;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 controls ============================================ */ label{font-weight:600;font-size:.95rem;display:block} input,select,textarea{ width:100%;height:56px;padding:0 1rem;border:1px solid #d0d6e7;border-radius:10px;font-size:1rem } textarea{padding:1rem;min-height:150px;resize:vertical;font-family:ui-monospace,monospace;height:auto} input:focus,select:focus,textarea:focus{outline:none;border-color:var(--brand)} button.btn{background:var(--brand);color:#fff;border:none;border-radius:10px;padding:.9rem 1.8rem; font-size:1rem;font-weight:600;cursor:pointer;min-height:56px} button.btn:hover{background:var(--brand-d)} button.copyBtn{margin-top:.8rem} select{appearance:none;background:#fff url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' fill='%23444' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 6l4 4 4-4z'/%3E%3C/svg%3E") no-repeat right .8rem center;background-size:12px} /* ===== grid layout ============================================== */ #cfg{display:grid;gap:1.4rem} @media(min-width:768px){ #cfg{grid-template-columns:1fr 1fr} #cfg .full{grid-column:1 / -1} } /* ===== points rows ============================================== */ .points-row{position:relative} .points-row input{padding-right:5.2rem} .micBtn,.delBtn{ position:absolute;top:50%;transform:translateY(-50%);width:32px;height:32px;border:none; background:none;font-size:1.35rem;line-height:1;cursor:pointer } .micBtn{right:48px} .delBtn{right:12px;color:var(--red)} .micBtn:focus,.delBtn:focus{outline:2px solid var(--brand);outline-offset:2px} /* ===== spinner =================================================== */ #spin{display:none;gap:.6ch;align-items:center;margin-top:.8rem} #spin.show{display:flex} #spin svg{width:20px;height:20px;animation:rot 1s linear infinite}@keyframes rot{to{transform:rotate(360deg)}} /* ===== preview =================================================== */ .preview{border:1px solid #d0d4e0;padding:1rem;border-radius:var(--radius);background:#fafbff} /* ===== responsive tweaks ======================================== */ @media(max-width:640px){ .micBtn,.delBtn{right:40px} .delBtn{right:8px} } </style> </head><body> <nav class="breadcrumb"> <a href="/members/dashboard.php">Dashboard</a> » <a href="/ai-tools/tools.php">AI Toolbox</a> » Article Generator <a href="/<?= htmlspecialchars($_SESSION['slug']??'') ?>/" style="float:right">View Site</a> </nav> <?php ai_render_key_bar(); ?> <main> <?php if(!ai_has_key()): ?> <div class="tool-card" style="text-align:center"> <h2>Get started</h2> <p>Save your OpenAI key in the black bar above to unlock the generator.</p> </div> <?php else: ?> <div class="tool-card"> <h2>Create an Article</h2> <form id="cfg" autocomplete="off"> <!-- dummy field so toolbar helper injects hidden model/temp/max_tokens --> <textarea name="prompt" style="display:none" tabindex="-1"></textarea> <div class="seed-wrap full"> <label for="topic">Topic</label> <input id="topic" name="topic" placeholder="e.g. Benefits of Li‑Fi over Wi‑Fi"> <button type="button" class="micBtn" aria-label="Dictate topic">🎤</button> </div> <div class="full"> <label>Main Points</label> <div id="pointsWrap" style="display:flex;flex-direction:column;gap:.8rem"></div> <button type="button" id="addPoint" class="btn" style="margin-top:.8rem;width:160px">Add Point</button> </div> <label for="keywords">Keywords (comma‑separated) <input id="keywords" name="keywords" placeholder="keyword 1, keyword 2"> </label> <label for="linkurl">Link URL (optional) <input id="linkurl" name="linkurl" type="url" placeholder="https://example.com"> </label> <label for="sections"># Sections <select id="sections" name="sections"><?php for($i=1;$i<=8;$i++) echo "<option>$i</option>"; ?></select> </label> <label for="paras"># Paragraphs / section <select id="paras" name="paras"><?php for($i=1;$i<=6;$i++) echo "<option>$i</option>"; ?></select> </label> <label for="lang">Language <select id="lang" name="lang"><?php foreach($langs as $l) echo "<option>$l</option>"; ?></select> </label> <label for="style">Style <select id="style" name="style"><?php foreach($styles as $s) echo "<option>$s</option>"; ?></select> </label> <label for="tone">Tone <select id="tone" name="tone"><?php foreach($tones as $t) echo "<option>$t</option>"; ?></select> </label> <label class="full" for="promptPreview">Prompt Preview <textarea id="promptPreview" readonly></textarea> </label> <button type="button" class="btn copyBtn" id="copyPrompt">Copy Prompt</button> <button id="generate" class="btn full">Generate Article</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> <div id="out" style="display:none"> <h3>Raw HTML</h3> <textarea id="articleRaw" readonly></textarea> <button type="button" class="btn copyBtn" id="copyRaw">Copy HTML</button> <h3 style="margin-top:1.4rem">Rendered Preview</h3> <div id="articlePreview" class="preview"></div> <button type="button" class="btn copyBtn" id="copyRendered">Copy Rendered</button> </div> </div> <?php endif; ?> </main> <script> (()=>{ /* ===== helper =============================================== */ const $ = q => document.querySelector(q); /* ===== main‑points rows ===================================== */ const wrap = $('#pointsWrap'); function addPoint(val=''){ const row = document.createElement('div'); row.className = 'points-row'; const inp = document.createElement('input'); inp.type = 'text'; inp.name = 'points[]'; inp.value = val; inp.placeholder='Main point…'; const mic = document.createElement('button'); mic.type='button'; mic.className='micBtn'; mic.innerHTML='🎤'; mic.setAttribute('aria-label','Dictate'); const del = document.createElement('button'); del.type='button'; del.className='delBtn'; del.innerHTML='❌'; del.setAttribute('aria-label','Delete point'); mic.onclick=()=>startRec(inp); del.onclick=()=>{ row.remove(); buildPrompt(); }; row.append(inp,mic,del); wrap.appendChild(row); } $('#addPoint').onclick = ()=>addPoint(''); addPoint(''); // start with one empty row /* ===== microphone (re‑usable) ================================ */ const SR = window.SpeechRecognition || window.webkitSpeechRecognition; function startRec(target){ if(!SR){ alert('Speech Recognition not supported'); return; } const rec=new SR(); rec.lang='en-US'; rec.onresult=e=>{ target.value=e.results[0][0].transcript; buildPrompt(); }; try{rec.start();}catch{} } $('.micBtn').onclick = e=>startRec($('#topic')); /* ===== live prompt preview ================================== */ function buildPrompt(){ const topic=$('#topic').value.trim(); if(!topic){ $('#promptPreview').value=''; return;} const kw=$('#keywords').value.trim(), url=$('#linkurl').value.trim(); const sec=$('#sections').value, par=$('#paras').value; const lang=$('#lang').value, style=$('#style').value, tone=$('#tone').value; const pts=[...document.querySelectorAll('input[name="points[]"]')] .map(i=>i.value.trim()).filter(Boolean); let out= `Write a ${lang} article in a ${tone} tone and ${style} style about "${topic}". ` +`Structure it into ${sec} sections, each starting with an <h2> and containing ${par} paragraph` +(par>1?'s':'')+'. Use additional <h3> sub‑headings if it improves readability.'; if(kw) out+=` Include the following keywords naturally throughout the article: ${kw}.`; if(kw&&url) out+=` Hyperlink each keyword exactly once to ${url} using <a> tags.`; if(pts.length) out+=` The following main points must each be covered (preferably as section headings): ${pts.join('; ')}.`; out+=' After the body, append <p class="excerpt">Excerpt: …</p>. Return ONLY the HTML.'; $('#promptPreview').value=out; } document.addEventListener('input',e=>{ if(['topic','keywords','linkurl','sections','paras','lang','style','tone'].includes(e.target.id) || e.target.name==='points[]') buildPrompt(); }); buildPrompt(); /* ===== copy helpers ========================================= */ function mkCopy(btnID,srcSel,isHtml=false){ const btn=$(btnID); if(!btn) return; btn.onclick=()=>{ const txt=isHtml?$(srcSel).innerHTML:$(srcSel).value; navigator.clipboard.writeText(txt) .then(()=>{const t=btn.textContent;btn.textContent='✔ Copied';setTimeout(()=>btn.textContent=t,1200);}); }; } mkCopy('#copyPrompt','#promptPreview'); mkCopy('#copyRaw','#articleRaw'); mkCopy('#copyRendered','#articlePreview',true); /* ===== AJAX submit ========================================== */ const frm=$('#cfg'); if(frm){ const spin=$('#spin'),sec=$('#sec'),gen=$('#generate'); frm.onsubmit=e=>{ e.preventDefault(); 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 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;} $('#out').style.display='block'; $('#articleRaw').value=j.html; $('#articlePreview').innerHTML=j.html; $('#articlePreview').scrollIntoView({behavior:'smooth'}); }) .catch(err=>{ clearInterval(tick); spin.classList.remove('show'); gen.disabled=false; alert(err); }); }; } })(); </script> </body></html>
Save changes
Create folder
writable 0777
Create
Cancel