Page Builder ships: labelled-section AI output parsed deterministically, escaped renderer, one page per position at public /p/<id> with angle video + QR, rebuild keeps the same URL; L2 tile live

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-08-28 07:15:11 -05:00
parent 2a8b311a83
commit 6a7867588d
6 changed files with 389 additions and 8 deletions
+22 -6
View File
@@ -68,10 +68,14 @@ function buildMessages(kind, brief, member) {
// Engine call. Long timeout: the agent narrates internally before answering.
function generate(kind, brief, member) {
return call(buildMessages(kind, brief, member)).then(function (t) { return cleanup(t, member); });
}
function call(messages) {
return new Promise(function (resolve, reject) {
const c = creds();
if (!c) return reject(new Error('The Copy Engine is not configured yet.'));
const body = JSON.stringify({ model: c.model || 'hermes-agent', messages: buildMessages(kind, brief, member) });
if (!c) return reject(new Error('The engine is not configured yet.'));
const body = JSON.stringify({ model: c.model || 'hermes-agent', messages: messages });
const u = new URL(c.url.replace(/\/$/, '') + '/chat/completions');
const lib = u.protocol === 'https:' ? require('https') : require('http');
const req = lib.request({
@@ -84,14 +88,13 @@ function generate(kind, brief, member) {
if (res.statusCode !== 200) return reject(new Error('Engine returned ' + res.statusCode));
try {
const j = JSON.parse(data);
let text = (j.choices && j.choices[0] && j.choices[0].message && j.choices[0].message.content || '').trim();
text = cleanup(text, member);
const text = (j.choices && j.choices[0] && j.choices[0].message && j.choices[0].message.content || '').trim();
if (!text) return reject(new Error('The engine came back empty — try again.'));
resolve(text);
} catch (e) { reject(new Error('Could not read the engine response.')); }
});
});
req.on('error', function (e) { reject(new Error('Could not reach the Copy Engine: ' + e.message)); });
req.on('error', function (e) { reject(new Error('Could not reach the engine: ' + e.message)); });
req.setTimeout(240000, function () { req.destroy(new Error('The engine took too long — try again.')); });
req.write(body); req.end();
});
@@ -114,4 +117,17 @@ function cleanup(text, member) {
return t.trim();
}
module.exports = { init, configured, generate, KINDS };
// Raw generation for tools that supply their own instruction (Page Builder).
// Still carries the facts + compliance + voice; only FORMAT is the caller's.
function generateRaw(instruction) {
return call([
{ role: 'system', content:
'You write for a member of the RM Circle team.\n\nWHAT IT IS: ' + FACTS +
'\n\nVOICE: ' + VOICE +
'\n\nCOMPLIANCE (absolute): ' + COMPLIANCE +
'\n\nFollow the requested output format EXACTLY. No preamble, no explanation, no markdown fences.' },
{ role: 'user', content: instruction }
]);
}
module.exports = { init, configured, generate, generateRaw, KINDS };