Normalize AI product HTML for the rich text description editor.

Sanitize model output to an allowlisted fragment and convert plain/markdown fallbacks so description text stays inside the advanced editor.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-10 20:44:47 +03:30
co-authored by Cursor
parent 23ba6344f6
commit 2c4d02bd0e
2 changed files with 162 additions and 4 deletions
+9 -4
View File
@@ -14,6 +14,7 @@ import {
requestAiJsonCompletion,
resolveAiProvider,
} from '../common/ai-provider.util';
import { normalizeAiDescriptionHtml } from '../common/ai-html.util';
type TechnicalFormField = {
key: string;
@@ -150,13 +151,15 @@ Follow the user's instructions for language and tone.
Keep the summary to 1–3 sentences. No HTML. No markdown.
Prefer a technical, informative style: what the product is and why its traits matter.
Never write sales CTAs, discounts, "buy now", or vague praise like "excellent choice" / "amazing experience".`
: `You write e-commerce product descriptions as HTML for Iranian stores.
: `You write e-commerce product descriptions as HTML for an advanced rich-text editor in Iranian stores.
Return ONLY valid JSON: { "html": "..." }
The "html" value must be a raw HTML fragment (real tags, not escaped entities, not markdown, not code fences).
Allowed tags only: p, ul, ol, li, strong, em, br.
Do not wrap in html/body/div. Do not include scripts, styles, classes, or attributes.
Follow the user's instructions for language and tone.
Prefer a technical, informative structure: what it is, key traits/composition, typical use.
Never write sales CTAs, discounts, or vague marketing slogans.
Aim for 2–4 short paragraphs and optionally a bullet list. No scripts, styles, or markdown.`;
Aim for 2–4 short paragraphs and optionally a bullet list.`;
const userPrompt = JSON.stringify({
instructions: dto.prompt.trim(),
@@ -190,7 +193,7 @@ Aim for 2–4 short paragraphs and optionally a bullet list. No scripts, styles,
return { field: ProductAiFillField.summary, text };
}
const html = String(parsed.html ?? '').trim();
const html = normalizeAiDescriptionHtml(String(parsed.html ?? ''));
if (!html) {
throw new BadRequestException('AI returned an empty description');
}
@@ -284,7 +287,9 @@ Rules:
: String(draft.nameFa ?? '').trim();
const summary = String(draft.summary ?? '').trim();
const descriptionHtml = String(draft.descriptionHtml ?? '').trim();
const descriptionHtml = normalizeAiDescriptionHtml(
String(draft.descriptionHtml ?? ''),
);
const tags = Array.isArray(draft.tags)
? draft.tags.map((tag) => String(tag).trim()).filter(Boolean).slice(0, 12)
: [];