Copy Engine ships: Suite AI layer (own engine, RM facts + compliance in our system prompt, deterministic link injection), metered generate/meters APIs, /suite/copy tool page; L2 tile now live
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
// Copy Engine client. Entitlement + meter come from the server; the page just
|
||||
// collects a brief, streams nothing (single response), and shows the result.
|
||||
(function () {
|
||||
'use strict';
|
||||
var KINDS = [
|
||||
{ k: 'post', label: 'Social post' },
|
||||
{ k: 'dm', label: 'Direct message' },
|
||||
{ k: 'followup', label: 'Follow-up' },
|
||||
{ k: 'objection', label: 'Objection reply' },
|
||||
{ k: 'email', label: 'Email' },
|
||||
{ k: 'story', label: 'Personal story post' }
|
||||
];
|
||||
var PLACEHOLDERS = {
|
||||
post: "e.g. A post for people who've been burned by side hustles before — honest, no hype, mention that everything is verifiable on-chain",
|
||||
dm: "e.g. A message to an old coworker who's always complaining about money but is skeptical of crypto",
|
||||
followup: "e.g. Following up with someone who watched the video last week and said 'let me think about it'",
|
||||
objection: "e.g. They said: isn't this just a pyramid scheme?",
|
||||
email: "e.g. Email to my list introducing the team build — they know me from my marketing content, not crypto",
|
||||
story: "e.g. Why I stopped chasing complicated funnels and went back to something simple I could teach"
|
||||
};
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var kind = 'post', busy = false, lastBrief = '';
|
||||
|
||||
function renderKinds() {
|
||||
var host = $('cpKinds');
|
||||
host.innerHTML = '';
|
||||
KINDS.forEach(function (x) {
|
||||
var b = document.createElement('button');
|
||||
b.type = 'button';
|
||||
b.className = 'cp-kind' + (x.k === kind ? ' on' : '');
|
||||
b.textContent = x.label;
|
||||
b.addEventListener('click', function () {
|
||||
kind = x.k;
|
||||
$('cpBrief').placeholder = PLACEHOLDERS[kind] || '';
|
||||
renderKinds();
|
||||
});
|
||||
host.appendChild(b);
|
||||
});
|
||||
}
|
||||
|
||||
function showMeter(m) {
|
||||
if (!m || !m.limit) { $('cpMeter').textContent = ''; return; }
|
||||
$('cpMeter').innerHTML = '<b>' + m.remaining + '</b> of ' + m.limit + ' generations left this month';
|
||||
}
|
||||
|
||||
function gate(msg, gold) {
|
||||
var g = $('cpGate');
|
||||
g.style.display = 'block';
|
||||
g.innerHTML = msg;
|
||||
if (gold === false) g.style.borderColor = 'var(--teal)';
|
||||
$('cpCard').style.opacity = '.55';
|
||||
$('cpGo').disabled = true;
|
||||
$('cpBrief').disabled = true;
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
renderKinds();
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-meters');
|
||||
if (r.status === 401) {
|
||||
gate('You’re not signed in yet. <a href="/suite" style="color:var(--gold);font-weight:700">Open the Suite</a> and connect the wallet that holds your position — one free signature — then come back.');
|
||||
return;
|
||||
}
|
||||
if (r.status === 403) {
|
||||
gate('The Copy Engine isn’t open for this position yet. <a href="/suite" style="color:var(--gold);font-weight:700">See your Suite</a>.');
|
||||
return;
|
||||
}
|
||||
if (!r.ok) return;
|
||||
var d = await r.json();
|
||||
var m = d.meters && d.meters.copy;
|
||||
if (m && m.reason === 'locked') {
|
||||
gate('The Copy Engine unlocks at <b>Ascensus (level 2)</b>. You’re at level ' + d.level + ' — your next upgrade opens it, along with the Page Builder. <a href="/how-pay-works#exact-costs" style="color:var(--gold);font-weight:700">See what an upgrade costs →</a>');
|
||||
return;
|
||||
}
|
||||
showMeter(m);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (busy) return;
|
||||
var brief = $('cpBrief').value.trim();
|
||||
if (brief.length < 3) { $('cpBrief').focus(); return; }
|
||||
lastBrief = brief;
|
||||
busy = true;
|
||||
$('cpErr').style.display = 'none';
|
||||
$('cpGo').disabled = true;
|
||||
$('cpGo').innerHTML = '<span class="spin"></span>Writing… (up to a minute)';
|
||||
try {
|
||||
var r = await fetch('/api/public/suite-generate', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ kind: kind, brief: brief })
|
||||
});
|
||||
var d = await r.json();
|
||||
if (!r.ok) {
|
||||
$('cpErr').textContent = d.error || 'That didn’t go through — try again.';
|
||||
$('cpErr').style.display = 'block';
|
||||
if (d.meter) showMeter(d.meter);
|
||||
} else {
|
||||
$('cpText').textContent = d.text;
|
||||
$('cpOut').style.display = 'block';
|
||||
$('cpAgain').style.display = '';
|
||||
showMeter(d.meter);
|
||||
$('cpOut').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
}
|
||||
} catch (e) {
|
||||
$('cpErr').textContent = 'Connection hiccup — try again.';
|
||||
$('cpErr').style.display = 'block';
|
||||
}
|
||||
busy = false;
|
||||
$('cpGo').disabled = false;
|
||||
$('cpGo').textContent = '✍️ Write it';
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
boot();
|
||||
$('cpGo').addEventListener('click', run);
|
||||
$('cpAgain').addEventListener('click', run);
|
||||
$('cpCopy').addEventListener('click', function () {
|
||||
var t = $('cpText').textContent;
|
||||
navigator.clipboard.writeText(t).then(function () {
|
||||
$('cpCopied').style.display = '';
|
||||
setTimeout(function () { $('cpCopied').style.display = 'none'; }, 2500);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user