import React, { useEffect, useState } from 'react'; import type { PageContentType, PageTemplate, } from '../../service/visibility/visibility.interface'; import { generateArticle } from '../../service/visibility/visibility.service'; import { contentTypeLabel, contentTypeTone, languageLabel, PAGE_CONTENT_TYPES, toneToBadgeClass, } from './helpers'; import LanguageMultiSelect from './LanguageMultiSelect'; import Modal from './Modal'; /** Most additional languages a merchant may add on top of the primary one. */ const MAX_ADDITIONAL_LANGUAGES = 2; /** Matches the backend ``instructions`` cap on the generate request. */ const MAX_INSTRUCTIONS_CHARS = 4000; /** Props for {@link RunPageTemplateModal}. */ interface RunPageTemplateModalProps { open: boolean; onClose: () => void; clientId: string; token: string; brandId: string; /** Brand language; the article's primary language unless extras are added. */ language: string; /** * The brief to run. When set, the modal pre-fills from it (page type + * directive + target prompts) and the type is locked to the brief. When * ``null`` the merchant picks the page type for a one-off run. */ template: PageTemplate | null; /** Fired after an article is dispatched so the parent can route / refresh. */ onStarted: () => void; } /** * "Run template" modal. Pre-fills the topic, page type and directive from a * saved page-content brief, lets the merchant edit the directive (and topic) * for this run, optionally add languages, then dispatches article generation. * The edited directive overrides the brief's saved one for this run only. * * @param {RunPageTemplateModalProps} props - Modal props. * @returns {JSX.Element} The modal. */ const RunPageTemplateModal = ({ open, onClose, clientId, token, brandId, language, template, onStarted, }: RunPageTemplateModalProps): JSX.Element | null => { const primary = (language || 'en').toLowerCase(); const [topic, setTopic] = useState(''); const [contentType, setContentType] = useState('blog'); const [instructions, setInstructions] = useState(''); const [additional, setAdditional] = useState([]); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); // Re-seed every time the modal opens so a previous run never leaks into the // next. The template's saved directive pre-fills the (editable) textarea. // Profile runs pre-fill the topic from the template name: the directive // already defines the whole output, so the merchant shouldn't have to // invent a topic for "X Bio" or "Press Boilerplate". useEffect(() => { if (!open) return; setTopic(template?.content_type === 'profile' ? template.name : ''); setContentType(template?.content_type ?? 'blog'); setInstructions(template?.default_instructions ?? ''); setAdditional([]); setError(null); setBusy(false); }, [open, template]); const handleGenerate = async (): Promise => { if (topic.trim().length < 5) { setError('Give the page a clear topic (at least a few words).'); return; } setBusy(true); setError(null); const languages = additional.length ? [primary, ...additional] : undefined; try { await generateArticle(clientId, token, { brand_id: brandId, topic: topic.trim(), content_type: contentType, // Empty for a preset run - the backend then generates from content_type // + instructions without a saved template. template_id: template?.template_id || undefined, instructions: instructions.trim() || undefined, target_prompts: template?.default_target_prompts, keywords: template?.default_keywords, languages, triggered_by: 'manual', }); onClose(); onStarted(); } catch (err) { setError( err instanceof Error ? err.message : 'Could not start generation. Try again.' ); } finally { setBusy(false); } }; return ( } >

Set the topic and tweak the directive for this run. Your edits here override the saved template for this generation only.

{error && (
{error}
)}
Page type {template ? (
{contentTypeLabel(contentType)}
) : (
{PAGE_CONTENT_TYPES.map(type => ( ))}
)}