diff --git a/scripts/campaigns/templates/gift-email-fr-native.html b/scripts/campaigns/templates/gift-email-fr-native.html new file mode 100644 index 0000000..10ce4df --- /dev/null +++ b/scripts/campaigns/templates/gift-email-fr-native.html @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/scripts/campaigns/templates/gift-email-fr-native.json b/scripts/campaigns/templates/gift-email-fr-native.json new file mode 100644 index 0000000..01c74a9 --- /dev/null +++ b/scripts/campaigns/templates/gift-email-fr-native.json @@ -0,0 +1,665 @@ +{ + "counters": { + "u_row": 4, + "u_column": 4, + "u_content_text": 7, + "u_content_image": 1, + "u_content_button": 1, + "u_content_divider": 0, + "u_content_html": 6 + }, + "body": { + "id": "u_body", + "rows": [ + { + "id": "u_row_1", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_1", + "contents": [ + { + "id": "u_content_html_1", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "8px 36px 4px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_1", + "htmlClassNames": "u_content_html" + }, + "html": "{{#view_url}}
Affichage incorrect ? Voir dans le navigateur
{{/view_url}}" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_1", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#F5FAF7", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_1", + "htmlClassNames": "u_row" + } + } + }, + { + "id": "u_row_2", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_2", + "contents": [ + { + "id": "u_content_image_1", + "type": "image", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "28px 36px 22px", + "anchor": "", + "src": { + "url": "https://xqy3m.mjt.lu/img2/xqy3m/eed4d18c-8065-4c5f-b47c-58af63171cd0/content", + "width": 140, + "height": "auto", + "autoWidth": false, + "maxWidth": "140px" + }, + "textAlign": "left", + "altText": "TARGO", + "action": { + "name": "web", + "values": { + "href": "", + "target": "_blank" + } + }, + "_meta": { + "htmlID": "u_content_image_1", + "htmlClassNames": "u_content_image" + } + } + }, + { + "id": "u_content_text_1", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px 14px", + "anchor": "", + "fontWeight": 400, + "fontSize": "16px", + "color": "#374151", + "textAlign": "left", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_1", + "htmlClassNames": "u_content_text" + }, + "text": "Bonjour {{firstname}}," + } + }, + { + "id": "u_content_text_2", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px 14px", + "anchor": "", + "fontWeight": 400, + "fontSize": "16px", + "color": "#374151", + "textAlign": "justify", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_2", + "htmlClassNames": "u_content_text" + }, + "text": "Avec l'arrivée de l'été, voici un cadeau pour toi, disponible pour un temps limité.

On veut te remercier pour ta loyauté envers l'achat local.
Comme toi, on aime les connexions stables et les relations durables." + } + }, + { + "id": "u_content_text_3", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px 0px", + "anchor": "", + "fontWeight": 400, + "fontSize": "16px", + "color": "#374151", + "textAlign": "justify", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_3", + "htmlClassNames": "u_content_text" + }, + "text": "Grâce à la confiance de nos clients, on offre maintenant les forfaits à la plus haute vitesse dans le secteur, jusqu'à 3.5 Gbit/s.
Que tu souhaites plus de vitesse, battre une autre offre ou faire optimiser des équipements, n'hésite pas. On est juste à côté et on aime aider." + } + }, + { + "id": "u_content_html_2", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 8px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_2", + "htmlClassNames": "u_content_html" + }, + "html": "
✅ Option 1
" + } + }, + { + "id": "u_content_html_3", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 22px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_3", + "htmlClassNames": "u_content_html" + }, + "html": "
🎁 {{amount}} chez des centaines de marques

\"Tim\"Walmart\"\"Home\"IGA\"\"Homeet plus
⚡ Disponible instantanément sur Giftbit en cliquant sur ton montant
🤝 Condition : Maintenir l'abonnement {{commitment_months}} mois ou +
" + } + }, + { + "id": "u_content_button_1", + "type": "button", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px", + "anchor": "", + "href": { + "name": "web", + "values": { + "href": "{{gift_url}}", + "target": "_blank" + } + }, + "buttonColors": { + "color": "#ffffff", + "backgroundColor": "#00C853", + "hoverColor": "#ffffff", + "hoverBackgroundColor": "#005026" + }, + "size": { + "autoWidth": false, + "width": "100%" + }, + "fontSize": "32px", + "fontWeight": 700, + "textAlign": "center", + "lineHeight": "120%", + "padding": "30px 24px", + "border": {}, + "borderRadius": "12px", + "_meta": { + "htmlID": "u_content_button_1", + "htmlClassNames": "u_content_button" + }, + "text": "🎁  {{amount}}" + } + }, + { + "id": "u_content_html_4", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "0 36px 4px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_4", + "htmlClassNames": "u_content_html" + }, + "html": "{{#expires_at_date}}\n
\n \n \n
\n
\n ⏰ Cadeau valide jusqu'au {{expires_at_date}}\n
\n
\n
\n {{/expires_at_date}}" + } + }, + { + "id": "u_content_text_4", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 36px 22px", + "anchor": "", + "fontWeight": 400, + "fontSize": "14px", + "color": "#6b7280", + "textAlign": "left", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_4", + "htmlClassNames": "u_content_text" + }, + "text": "🪂 Annulation avant {{commitment_months}} mois : seulement à rembourser au prorata des mois restants." + } + }, + { + "id": "u_content_html_5", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 6px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_5", + "htmlClassNames": "u_content_html" + }, + "html": "
⏭️ Option 2
" + } + }, + { + "id": "u_content_text_5", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "6px 36px 22px", + "anchor": "", + "fontWeight": 400, + "fontSize": "15px", + "color": "#4b5563", + "textAlign": "left", + "lineHeight": "155%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_5", + "htmlClassNames": "u_content_text" + }, + "text": "Ne rien faire. Aucun changement à ton abonnement actuel." + } + }, + { + "id": "u_content_text_6", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 28px", + "anchor": "", + "fontWeight": 400, + "fontSize": "15px", + "color": "#1B2E24", + "textAlign": "left", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_6", + "htmlClassNames": "u_content_text" + }, + "text": "🤝 Merci de faire rouler l'économie de notre région avec nous !

L'équipe TARGO" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_2", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#ffffff", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_2", + "htmlClassNames": "u_row" + } + } + }, + { + "id": "u_row_3", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_3", + "contents": [ + { + "id": "u_content_text_7", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 8px", + "anchor": "", + "fontWeight": 400, + "fontSize": "12px", + "color": "#64748B", + "textAlign": "center", + "lineHeight": "155%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_7", + "htmlClassNames": "u_content_text" + }, + "text": "Tu reçois ce courriel parce que tu es client(e) TARGO à {{description}}.
Une question ? N'hésite pas à nous écrire à support@targo.ca ou nous appeler au 514-448-0773." + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_3", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#F5FAF7", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_3", + "htmlClassNames": "u_row" + } + } + }, + { + "id": "u_row_4", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_4", + "contents": [ + { + "id": "u_content_html_6", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "26px 36px 22px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_6", + "htmlClassNames": "u_content_html" + }, + "html": "
\"TARGO\"
www.targo.ca ·  1867 ch. de la rivière, Ste-Clotilde, QC
© {{year}} TARGO Communications · Tous droits réservés.
" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_4", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#1C1E26", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_4", + "htmlClassNames": "u_row" + } + } + } + ], + "values": { + "popupPosition": "center", + "popupWidth": "600px", + "popupHeight": "auto", + "borderRadius": "10px", + "contentAlign": "center", + "contentVerticalAlign": "center", + "contentWidth": "600px", + "fontFamily": { + "label": "Plus Jakarta Sans", + "value": "'Plus Jakarta Sans', sans-serif", + "url": "https://fonts.googleapis.com/css?family=Plus+Jakarta+Sans:400,500,600,700" + }, + "textColor": "#1B2E24", + "popupBackgroundColor": "#FFFFFF", + "backgroundColor": "#F5FAF7", + "preheaderText": "Comme toi, on aime les connexions stables et les relations durables.", + "linkStyle": { + "body": true, + "linkColor": "#00C853", + "linkHoverColor": "#005026", + "linkUnderline": true, + "linkHoverUnderline": true + }, + "_meta": { + "htmlID": "u_body", + "htmlClassNames": "u_body" + } + } + }, + "schemaVersion": 16 +} \ No newline at end of file diff --git a/services/targo-hub/lib/campaigns.js b/services/targo-hub/lib/campaigns.js index 3ecfbe6..4566355 100644 --- a/services/targo-hub/lib/campaigns.js +++ b/services/targo-hub/lib/campaigns.js @@ -1912,6 +1912,7 @@ async function handle (req, res, method, path) { description: '123 Rue de Test', gift_url: 'http://gtbt.co/PREVIEW', amount: '60 $', expiry: '31 décembre 2026', commitment_months: '3', expires_at_date: sampleExpAt, expires_in_days: '30', + year: new Date().getFullYear(), view_url: '', ...(body.vars || {}), } diff --git a/services/targo-hub/scripts/ai-convert-to-native.js b/services/targo-hub/scripts/ai-convert-to-native.js new file mode 100644 index 0000000..856f259 --- /dev/null +++ b/services/targo-hub/scripts/ai-convert-to-native.js @@ -0,0 +1,213 @@ +#!/usr/bin/env node +'use strict' +/** + * ai-convert-to-native.js — interpret an existing compiled .html template + * via Gemini Flash and produce a native-block templates-spec/.js file + * that can be built by build-native-template.js. + * + * The AI handles SEMANTIC interpretation only (which paragraph is a + * greeting? which div is the CTA? which span is a chip?) — the conversion + * to specific Unlayer JSON / HTML markup is then deterministic in + * build-native-template.js. This split prevents AI hallucinations in the + * final markup that gets sent to recipients. + * + * Usage: + * AI_API_KEY=... node ai-convert-to-native.js gift-email-fr + * + * Output: + * scripts/templates-spec/gift-email-fr-native.js (generated) + * templates/gift-email-fr-native.html + .json (built from the spec) + */ + +const fs = require('fs') +const path = require('path') +const { aiCall } = require('../lib/ai') + +// The AI returns an array of "block descriptors". Our deterministic builder +// then maps each one to the matching factory in build-native-template.js. +// Keeping the schema TIGHT means less surface for the model to invent things. +const SYSTEM_PROMPT = `You are an email-template interpreter. Given the inner-body HTML of a transactional email, identify the SEMANTIC BLOCKS and return ONLY a JSON object: { "preheader": "...", "ariaLabel": "...", "blocks": [ ... ] }. + +Each block is an object with: +- "type" — one of: "text", "image", "button", "html", "divider" +- "purpose" — short label like "greeting", "intro", "cta", "expiry-badge", "prorata", "signature", "contact-info", "header-logo", "footer-logo", "option-1-chip", "option-2-chip", "brand-logos-card", "view-in-browser-link", "footer-text", "footer-band" +- type-specific fields (see below) + +For type "text": +- "html" : the rich HTML content (preserve , ,
, , Mustache {{vars}} like {{firstname}}, etc.) +- "fontSize" : e.g. "16px", "14px", "13px" +- "fontWeight": 400 / 500 / 600 / 700 +- "color" : hex like "#374151" or "#1B2E24" +- "textAlign" : "left" / "center" / "right" / "justify" +- "padding" : CSS shorthand for container padding, e.g. "10px 36px 14px" +- "background": (optional) row background color hex; default "#ffffff" if part of the white card +- "lineHeight": (optional) e.g. "150%" or "140%" + +For type "image": +- "src" : full URL +- "alt" : alt text +- "width" : pixel width as number (e.g. 140) +- "padding" : container padding +- "textAlign": "left" / "center" / "right" +- "background": (optional) row background +- "href" : (optional) link URL + +For type "button": +- "text" : button label HTML (often with Mustache like "🎁 {{amount}}") +- "href" : link URL (often "{{gift_url}}") +- "bgColor" : background hex +- "color" : text color hex +- "padding" : container padding +- "buttonPadding": inner button padding (e.g. "30px 24px") +- "borderRadius": e.g. "12px" +- "fontSize": e.g. "32px" +- "background": (optional) row background + +For type "html": +- "html" : the raw HTML markup (keep this for chips, multi-image rows, anything too custom for native blocks) +- "padding" : container padding +- "background": (optional) row background + +For type "divider": +- "padding" : container padding +- "background": (optional) row background + +ROW STYLING — if multiple consecutive blocks share the same card (white background, side borders, etc.), they should each carry the SAME "background" value so the deterministic builder groups them into one row with that background. + +CRITICAL CONSTRAINTS: +- Preserve ALL Mustache placeholders verbatim: {{firstname}}, {{amount}}, {{gift_url}}, {{expires_at_date}}, {{commitment_months}}, {{description}}, {{year}}, {{view_url}}. +- Preserve all Mustache section blocks: {{#view_url}}...{{/view_url}} — these go inside an "html" block as-is. +- Preserve external image URLs verbatim (xqy3m.mjt.lu/...). +- The "preheader" is the hidden div at the top of the body — extract its inner text. +- The "ariaLabel" is the aria-label of the role="article" outer div. +- Output VALID JSON, no markdown fences, no commentary.` + +function buildUserPrompt (innerHtml) { + return `Inner body HTML to interpret: + +\`\`\`html +${innerHtml.slice(0, 60000)} +\`\`\` + +Return the JSON now.` +} + +function extractInnerBody (fullHtml) { + const m = fullHtml.match(/]*>([\s\S]*?)<\/body>/i) + return m ? m[1] : fullHtml +} + +// Map AI block descriptors → templates-spec JS source. The spec is a JS +// module so the operator can hand-tweak before re-running the builder. +function generateSpec (aiResult) { + const { preheader, ariaLabel, blocks } = aiResult + if (!Array.isArray(blocks)) throw new Error('AI did not return blocks[]') + + // Group consecutive blocks sharing the same background into one row. + const rows = [] + let current = null + for (const b of blocks) { + const bg = b.background || '' + if (!current || current.bg !== bg) { + if (current) rows.push(current) + current = { bg, blocks: [] } + } + current.blocks.push(b) + } + if (current) rows.push(current) + + const factoryFor = (b) => { + switch (b.type) { + case 'text': + return `textBlock({ html: ${JSON.stringify(b.html || '')}, padding: ${JSON.stringify(b.padding || '10px')}, fontSize: ${JSON.stringify(b.fontSize || '16px')}, fontWeight: ${JSON.stringify(b.fontWeight || 400)}, color: ${JSON.stringify(b.color || '#374151')}, textAlign: ${JSON.stringify(b.textAlign || 'left')}${b.lineHeight ? `, lineHeight: ${JSON.stringify(b.lineHeight)}` : ''} })` + case 'image': + return `imageBlock({ src: ${JSON.stringify(b.src || '')}, alt: ${JSON.stringify(b.alt || '')}, width: ${b.width || 140}, padding: ${JSON.stringify(b.padding || '10px')}, textAlign: ${JSON.stringify(b.textAlign || 'center')}${b.href ? `, href: ${JSON.stringify(b.href)}` : ''} })` + case 'button': + return `buttonBlock({ text: ${JSON.stringify(b.text || '')}, href: ${JSON.stringify(b.href || '')}, padding: ${JSON.stringify(b.padding || '10px 25px')}, buttonPadding: ${JSON.stringify(b.buttonPadding || '30px 24px')}, bgColor: ${JSON.stringify(b.bgColor || '#00C853')}, color: ${JSON.stringify(b.color || '#FFFFFF')}, borderRadius: ${JSON.stringify(b.borderRadius || '12px')}, fontSize: ${JSON.stringify(b.fontSize || '32px')} })` + case 'divider': + return `dividerBlock({ padding: ${JSON.stringify(b.padding || '10px')} })` + case 'html': + default: + return `htmlBlock({ html: ${JSON.stringify(b.html || '')}, padding: ${JSON.stringify(b.padding || '0')} })` + } + } + + const rowSrcs = rows.map(r => { + const blockSrcs = r.blocks.map(b => ` ${factoryFor(b)}, // ${b.purpose || b.type}`).join('\n') + const opts = r.bg ? `, { backgroundColor: ${JSON.stringify(r.bg)}, border: '1px solid #e5e7eb' }` : '' + return ` row([\n${blockSrcs}\n ]${opts}),` + }) + + return `'use strict' +/** + * AUTO-GENERATED by ai-convert-to-native.js — review before deploying. + * + * The AI identified ${blocks.length} semantic block(s) in ${rows.length} row(s). + * Each text/image/button block is individually editable in Unlayer. + * html blocks (chips, multi-image strips) require raw HTML edits. + */ + +module.exports = { + preheader: ${JSON.stringify(preheader || '')}, + ariaLabel: ${JSON.stringify(ariaLabel || '')}, + + rows: ({ textBlock, imageBlock, buttonBlock, htmlBlock, dividerBlock, row }) => [ + +${rowSrcs.join('\n\n')} + + ], +} +` +} + +// ── CLI ─────────────────────────────────────────────────────────────────── +async function main () { + const srcName = process.argv[2] + if (!srcName) { console.error('Usage: ai-convert-to-native.js '); process.exit(1) } + + const tplDir = path.resolve(__dirname, '..', 'templates') + const srcPath = path.join(tplDir, srcName + '.html') + if (!fs.existsSync(srcPath)) { + console.error(`✗ Source not found: ${srcPath}`); process.exit(1) + } + const innerHtml = extractInnerBody(fs.readFileSync(srcPath, 'utf8')) + console.log(`→ Source: ${srcName}.html (${innerHtml.length}b inner body)`) + console.log('→ Asking Gemini Flash to identify blocks...') + + const aiResult = await aiCall(SYSTEM_PROMPT, buildUserPrompt(innerHtml), { + jsonMode: true, maxTokens: 32768, temperature: 0.2, + }) + if (aiResult.error || aiResult.raw) { + console.error('✗ AI returned unparseable output:', aiResult); process.exit(1) + } + console.log(`→ AI identified ${aiResult.blocks?.length || 0} blocks`) + + const outName = srcName + '-native' + const specPath = path.resolve(__dirname, 'templates-spec', outName + '.js') + const specSrc = generateSpec(aiResult) + fs.writeFileSync(specPath, specSrc, 'utf8') + console.log(`✓ Spec written: scripts/templates-spec/${outName}.js (${specSrc.length}b)`) + + // Sanity check: every {{var}} present in the source must survive into the + // generated spec. The AI occasionally drops minor placeholders ({{year}} + // in a copyright line is the canonical miss) — warn loudly so the + // operator can patch the spec before building. + const srcVars = new Set([...innerHtml.matchAll(/\{\{\s*[#/]?\s*([a-z0-9_]+)\s*\}\}/gi)].map(m => m[1].toLowerCase())) + const specVars = new Set([...specSrc.matchAll(/\{\{\s*[#/]?\s*([a-z0-9_]+)\s*\}\}/gi)].map(m => m[1].toLowerCase())) + const missing = [...srcVars].filter(v => !specVars.has(v)) + if (missing.length) { + console.warn(`⚠ ${missing.length} Mustache variable(s) NOT preserved in spec — review before building:`) + for (const v of missing) console.warn(` - {{${v}}}`) + console.warn(' The AI sometimes drops minor placeholders. Edit the spec file to restore them.') + } else { + console.log(`✓ All ${srcVars.size} Mustache variable(s) preserved in spec`) + } + console.log() + console.log('Next step: review the spec, then build the files:') + console.log(` node scripts/build-native-template.js ${outName}`) + console.log() + console.log('If the layout is good, deploy by copying the generated') + console.log(`templates/${outName}.html and .json to prod.`) +} + +main().catch(e => { console.error('Fatal:', e); process.exit(1) }) diff --git a/services/targo-hub/scripts/templates-spec/gift-email-fr-native.js b/services/targo-hub/scripts/templates-spec/gift-email-fr-native.js new file mode 100644 index 0000000..61d65b8 --- /dev/null +++ b/services/targo-hub/scripts/templates-spec/gift-email-fr-native.js @@ -0,0 +1,44 @@ +'use strict' +/** + * AUTO-GENERATED by ai-convert-to-native.js — review before deploying. + * + * The AI identified 15 semantic block(s) in 4 row(s). + * Each text/image/button block is individually editable in Unlayer. + * html blocks (chips, multi-image strips) require raw HTML edits. + */ + +module.exports = { + preheader: "Comme toi, on aime les connexions stables et les relations durables.", + ariaLabel: "Une offre exclusive de TARGO", + + rows: ({ textBlock, imageBlock, buttonBlock, htmlBlock, dividerBlock, row }) => [ + + row([ + htmlBlock({ html: "{{#view_url}}
Affichage incorrect ? Voir dans le navigateur
{{/view_url}}", padding: "8px 36px 4px" }), // view-in-browser-link + ], { backgroundColor: "#F5FAF7", border: '1px solid #e5e7eb' }), + + row([ + imageBlock({ src: "https://xqy3m.mjt.lu/img2/xqy3m/eed4d18c-8065-4c5f-b47c-58af63171cd0/content", alt: "TARGO", width: 140, padding: "28px 36px 22px", textAlign: "left" }), // header-logo + textBlock({ html: "Bonjour {{firstname}},", padding: "10px 25px 14px", fontSize: "16px", fontWeight: 400, color: "#374151", textAlign: "left", lineHeight: "150%" }), // greeting + textBlock({ html: "Avec l'arrivée de l'été, voici un cadeau pour toi, disponible pour un temps limité.

On veut te remercier pour ta loyauté envers l'achat local.
Comme toi, on aime les connexions stables et les relations durables.", padding: "10px 25px 14px", fontSize: "16px", fontWeight: 400, color: "#374151", textAlign: "justify", lineHeight: "150%" }), // intro + textBlock({ html: "Grâce à la confiance de nos clients, on offre maintenant les forfaits à la plus haute vitesse dans le secteur, jusqu'à 3.5 Gbit/s.
Que tu souhaites plus de vitesse, battre une autre offre ou faire optimiser des équipements, n'hésite pas. On est juste à côté et on aime aider.", padding: "10px 25px 0px", fontSize: "16px", fontWeight: 400, color: "#374151", textAlign: "justify", lineHeight: "150%" }), // intro + htmlBlock({ html: "
✅ Option 1
", padding: "18px 36px 8px" }), // option-1-chip + htmlBlock({ html: "
🎁 {{amount}} chez des centaines de marques

\"Tim\"Walmart\"\"Home\"IGA\"\"Homeet plus
⚡ Disponible instantanément sur Giftbit en cliquant sur ton montant
🤝 Condition : Maintenir l'abonnement {{commitment_months}} mois ou +
", padding: "18px 36px 22px" }), // brand-logos-card + buttonBlock({ text: "🎁  {{amount}}", href: "{{gift_url}}", padding: "10px 25px", buttonPadding: "30px 24px", bgColor: "#00C853", color: "#ffffff", borderRadius: "12px", fontSize: "32px" }), // cta + htmlBlock({ html: "{{#expires_at_date}}\n
\n \n \n
\n
\n ⏰ Cadeau valide jusqu'au {{expires_at_date}}\n
\n
\n
\n {{/expires_at_date}}", padding: "0 36px 4px" }), // expiry-badge + textBlock({ html: "🪂 Annulation avant {{commitment_months}} mois : seulement à rembourser au prorata des mois restants.", padding: "10px 36px 22px", fontSize: "14px", fontWeight: 400, color: "#6b7280", textAlign: "left", lineHeight: "150%" }), // prorata + htmlBlock({ html: "
⏭️ Option 2
", padding: "18px 36px 6px" }), // option-2-chip + textBlock({ html: "Ne rien faire. Aucun changement à ton abonnement actuel.", padding: "6px 36px 22px", fontSize: "15px", fontWeight: 400, color: "#4b5563", textAlign: "left", lineHeight: "155%" }), // option-2-text + textBlock({ html: "🤝 Merci de faire rouler l'économie de notre région avec nous !

L'équipe TARGO", padding: "18px 36px 28px", fontSize: "15px", fontWeight: 400, color: "#1B2E24", textAlign: "left", lineHeight: "150%" }), // signature + ], { backgroundColor: "#ffffff", border: '1px solid #e5e7eb' }), + + row([ + textBlock({ html: "Tu reçois ce courriel parce que tu es client(e) TARGO à {{description}}.
Une question ? N'hésite pas à nous écrire à support@targo.ca ou nous appeler au 514-448-0773.", padding: "18px 36px 8px", fontSize: "12px", fontWeight: 400, color: "#64748B", textAlign: "center", lineHeight: "155%" }), // contact-info + ], { backgroundColor: "#F5FAF7", border: '1px solid #e5e7eb' }), + + row([ + htmlBlock({ html: "
\"TARGO\"
www.targo.ca ·  1867 ch. de la rivière, Ste-Clotilde, QC
© {{year}} TARGO Communications · Tous droits réservés.
", padding: "26px 36px 22px" }), // footer-band + ], { backgroundColor: "#1C1E26", border: '1px solid #e5e7eb' }), + + ], +} diff --git a/services/targo-hub/templates/gift-email-fr-native.html b/services/targo-hub/templates/gift-email-fr-native.html new file mode 100644 index 0000000..10ce4df --- /dev/null +++ b/services/targo-hub/templates/gift-email-fr-native.html @@ -0,0 +1,182 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/services/targo-hub/templates/gift-email-fr-native.json b/services/targo-hub/templates/gift-email-fr-native.json new file mode 100644 index 0000000..01c74a9 --- /dev/null +++ b/services/targo-hub/templates/gift-email-fr-native.json @@ -0,0 +1,665 @@ +{ + "counters": { + "u_row": 4, + "u_column": 4, + "u_content_text": 7, + "u_content_image": 1, + "u_content_button": 1, + "u_content_divider": 0, + "u_content_html": 6 + }, + "body": { + "id": "u_body", + "rows": [ + { + "id": "u_row_1", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_1", + "contents": [ + { + "id": "u_content_html_1", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "8px 36px 4px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_1", + "htmlClassNames": "u_content_html" + }, + "html": "{{#view_url}}
Affichage incorrect ? Voir dans le navigateur
{{/view_url}}" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_1", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#F5FAF7", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_1", + "htmlClassNames": "u_row" + } + } + }, + { + "id": "u_row_2", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_2", + "contents": [ + { + "id": "u_content_image_1", + "type": "image", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "28px 36px 22px", + "anchor": "", + "src": { + "url": "https://xqy3m.mjt.lu/img2/xqy3m/eed4d18c-8065-4c5f-b47c-58af63171cd0/content", + "width": 140, + "height": "auto", + "autoWidth": false, + "maxWidth": "140px" + }, + "textAlign": "left", + "altText": "TARGO", + "action": { + "name": "web", + "values": { + "href": "", + "target": "_blank" + } + }, + "_meta": { + "htmlID": "u_content_image_1", + "htmlClassNames": "u_content_image" + } + } + }, + { + "id": "u_content_text_1", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px 14px", + "anchor": "", + "fontWeight": 400, + "fontSize": "16px", + "color": "#374151", + "textAlign": "left", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_1", + "htmlClassNames": "u_content_text" + }, + "text": "Bonjour {{firstname}}," + } + }, + { + "id": "u_content_text_2", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px 14px", + "anchor": "", + "fontWeight": 400, + "fontSize": "16px", + "color": "#374151", + "textAlign": "justify", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_2", + "htmlClassNames": "u_content_text" + }, + "text": "Avec l'arrivée de l'été, voici un cadeau pour toi, disponible pour un temps limité.

On veut te remercier pour ta loyauté envers l'achat local.
Comme toi, on aime les connexions stables et les relations durables." + } + }, + { + "id": "u_content_text_3", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px 0px", + "anchor": "", + "fontWeight": 400, + "fontSize": "16px", + "color": "#374151", + "textAlign": "justify", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_3", + "htmlClassNames": "u_content_text" + }, + "text": "Grâce à la confiance de nos clients, on offre maintenant les forfaits à la plus haute vitesse dans le secteur, jusqu'à 3.5 Gbit/s.
Que tu souhaites plus de vitesse, battre une autre offre ou faire optimiser des équipements, n'hésite pas. On est juste à côté et on aime aider." + } + }, + { + "id": "u_content_html_2", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 8px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_2", + "htmlClassNames": "u_content_html" + }, + "html": "
✅ Option 1
" + } + }, + { + "id": "u_content_html_3", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 22px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_3", + "htmlClassNames": "u_content_html" + }, + "html": "
🎁 {{amount}} chez des centaines de marques

\"Tim\"Walmart\"\"Home\"IGA\"\"Homeet plus
⚡ Disponible instantanément sur Giftbit en cliquant sur ton montant
🤝 Condition : Maintenir l'abonnement {{commitment_months}} mois ou +
" + } + }, + { + "id": "u_content_button_1", + "type": "button", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 25px", + "anchor": "", + "href": { + "name": "web", + "values": { + "href": "{{gift_url}}", + "target": "_blank" + } + }, + "buttonColors": { + "color": "#ffffff", + "backgroundColor": "#00C853", + "hoverColor": "#ffffff", + "hoverBackgroundColor": "#005026" + }, + "size": { + "autoWidth": false, + "width": "100%" + }, + "fontSize": "32px", + "fontWeight": 700, + "textAlign": "center", + "lineHeight": "120%", + "padding": "30px 24px", + "border": {}, + "borderRadius": "12px", + "_meta": { + "htmlID": "u_content_button_1", + "htmlClassNames": "u_content_button" + }, + "text": "🎁  {{amount}}" + } + }, + { + "id": "u_content_html_4", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "0 36px 4px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_4", + "htmlClassNames": "u_content_html" + }, + "html": "{{#expires_at_date}}\n
\n \n \n
\n
\n ⏰ Cadeau valide jusqu'au {{expires_at_date}}\n
\n
\n
\n {{/expires_at_date}}" + } + }, + { + "id": "u_content_text_4", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "10px 36px 22px", + "anchor": "", + "fontWeight": 400, + "fontSize": "14px", + "color": "#6b7280", + "textAlign": "left", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_4", + "htmlClassNames": "u_content_text" + }, + "text": "🪂 Annulation avant {{commitment_months}} mois : seulement à rembourser au prorata des mois restants." + } + }, + { + "id": "u_content_html_5", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 6px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_5", + "htmlClassNames": "u_content_html" + }, + "html": "
⏭️ Option 2
" + } + }, + { + "id": "u_content_text_5", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "6px 36px 22px", + "anchor": "", + "fontWeight": 400, + "fontSize": "15px", + "color": "#4b5563", + "textAlign": "left", + "lineHeight": "155%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_5", + "htmlClassNames": "u_content_text" + }, + "text": "Ne rien faire. Aucun changement à ton abonnement actuel." + } + }, + { + "id": "u_content_text_6", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 28px", + "anchor": "", + "fontWeight": 400, + "fontSize": "15px", + "color": "#1B2E24", + "textAlign": "left", + "lineHeight": "150%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_6", + "htmlClassNames": "u_content_text" + }, + "text": "🤝 Merci de faire rouler l'économie de notre région avec nous !

L'équipe TARGO" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_2", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#ffffff", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_2", + "htmlClassNames": "u_row" + } + } + }, + { + "id": "u_row_3", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_3", + "contents": [ + { + "id": "u_content_text_7", + "type": "text", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "18px 36px 8px", + "anchor": "", + "fontWeight": 400, + "fontSize": "12px", + "color": "#64748B", + "textAlign": "center", + "lineHeight": "155%", + "linkStyle": { + "inherit": true + }, + "_meta": { + "htmlID": "u_content_text_7", + "htmlClassNames": "u_content_text" + }, + "text": "Tu reçois ce courriel parce que tu es client(e) TARGO à {{description}}.
Une question ? N'hésite pas à nous écrire à support@targo.ca ou nous appeler au 514-448-0773." + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_3", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#F5FAF7", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_3", + "htmlClassNames": "u_row" + } + } + }, + { + "id": "u_row_4", + "cells": [ + 1 + ], + "columns": [ + { + "id": "u_column_4", + "contents": [ + { + "id": "u_content_html_6", + "type": "html", + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "containerPadding": "26px 36px 22px", + "anchor": "", + "_meta": { + "htmlID": "u_content_html_6", + "htmlClassNames": "u_content_html" + }, + "html": "
\"TARGO\"
www.targo.ca ·  1867 ch. de la rivière, Ste-Clotilde, QC
© {{year}} TARGO Communications · Tous droits réservés.
" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "_meta": { + "htmlID": "u_column_4", + "htmlClassNames": "u_column" + }, + "padding": "0px", + "border": {}, + "borderRadius": "0px", + "backgroundColor": "" + } + } + ], + "values": { + "selectable": true, + "draggable": true, + "duplicatable": true, + "deletable": true, + "hideable": true, + "hideDesktop": false, + "displayCondition": null, + "columns": false, + "backgroundColor": "#1C1E26", + "columnsBackgroundColor": "", + "backgroundImage": { + "url": "", + "fullWidth": true, + "repeat": "no-repeat", + "size": "custom", + "position": "center" + }, + "padding": "0px", + "anchor": "", + "borderRadius": "", + "_meta": { + "htmlID": "u_row_4", + "htmlClassNames": "u_row" + } + } + } + ], + "values": { + "popupPosition": "center", + "popupWidth": "600px", + "popupHeight": "auto", + "borderRadius": "10px", + "contentAlign": "center", + "contentVerticalAlign": "center", + "contentWidth": "600px", + "fontFamily": { + "label": "Plus Jakarta Sans", + "value": "'Plus Jakarta Sans', sans-serif", + "url": "https://fonts.googleapis.com/css?family=Plus+Jakarta+Sans:400,500,600,700" + }, + "textColor": "#1B2E24", + "popupBackgroundColor": "#FFFFFF", + "backgroundColor": "#F5FAF7", + "preheaderText": "Comme toi, on aime les connexions stables et les relations durables.", + "linkStyle": { + "body": true, + "linkColor": "#00C853", + "linkHoverColor": "#005026", + "linkUnderline": true, + "linkHoverUnderline": true + }, + "_meta": { + "htmlID": "u_body", + "htmlClassNames": "u_body" + } + } + }, + "schemaVersion": 16 +} \ No newline at end of file