Treat AI create input as a research brief and localize FA prompts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-19 08:20:34 +03:30
co-authored by Cursor
parent d37b920c01
commit 57800634ca
4 changed files with 25 additions and 15 deletions
+1 -1
View File
@@ -105,7 +105,7 @@ Rules:
- Use 2-6 top-level categories when appropriate.
- Add subcategories only where they help shoppers browse (max depth ${MAX_DEPTH}).
- Every node needs nameEn and nameFa (Farsi in Persian script).
- Descriptions are optional, max 120 characters, English only.
- Descriptions are optional, max 120 characters. Match the user prompt language (Persian if the prompt is Persian, otherwise English).
- Do not duplicate category names at the same level.
- Total nodes must not exceed ${MAX_TOTAL}.`;
@@ -92,7 +92,7 @@ Return ONLY valid JSON with this shape:
{
"fields": [
{
"label": "Field label in English",
"label": "Field label",
"type": "text | textarea | select | multi_select",
"isRequired": true,
"options": ["Option A", "Option B"]
@@ -108,6 +108,7 @@ Rules:
- textarea fields are for longer specs (features, care instructions).
- Mark important buyer-facing specs as required.
- Labels must be unique and human-readable.
- Write labels and option text in the same language as the user hint. If the hint is Persian (Farsi), use Persian labels and options; otherwise use English.
- Do not include price, stock, SKU, or warranty period fields.
- Prefer metric units common in Iran when relevant.`;
+3 -1
View File
@@ -1,4 +1,4 @@
import { IsEnum, IsOptional, IsString, MinLength, ValidateIf } from 'class-validator';
import { IsEnum, IsOptional, IsString, MaxLength, MinLength, ValidateIf } from 'class-validator';
export enum ProductAiLanguage {
en = 'en',
@@ -16,8 +16,10 @@ export class CreateProductByAiDto {
@MinLength(1)
categoryId!: string;
/** Product brief for AI: model name, optional URL, and notes. */
@IsString()
@MinLength(2)
@MaxLength(4000)
name!: string;
@IsEnum(ProductAiLanguage)
+19 -12
View File
@@ -285,18 +285,21 @@ Return ONLY valid JSON with this shape:
}
Rules:
- Be factual and realistic for the named product in the given category.
- The user input is a research brief in ${inputLanguage}. It may include a model/product name, a product page URL, and extra notes — not a final storefront title.
- Infer the real product from the brief (and any URL/model clues). Do not copy URLs, markdown, or raw notes into title/nameFa.
- Write clean storefront titles: "title" in English, "nameFa" in Persian. Keep brand/model accurate.
- Be factual and realistic for the identified product in the given category.
- If technical form fields are provided, fill technicalValues using exact field keys.
- For select fields use one allowed option value exactly.
- For multi_select fields use an array of allowed option values.
- For text/textarea fields use plain strings.
- The user input name is in ${inputLanguage}; keep that spelling in the matching field and translate/generate the other language field.
- Prefer the input language for summary/description tone, but always return both title and nameFa.
- descriptionHtml should be 2-4 paragraphs with key specs.
- tags: 3-8 relevant lowercase tags.`;
const userPrompt = JSON.stringify({
category: input.categoryName,
inputName: input.productName,
researchBrief: input.productName,
inputLanguage: input.language,
technicalFields: technicalSchema,
});
@@ -327,15 +330,9 @@ Rules:
fields: TechnicalFormField[];
},
): AiProductDraft {
const title =
input.language === ProductAiLanguage.en
? input.productName
: String(draft.title ?? input.productName).trim();
const nameFa =
input.language === ProductAiLanguage.fa
? input.productName
: String(draft.nameFa ?? '').trim();
const fallbackName = this.shortProductLabel(input.productName);
const title = String(draft.title ?? '').trim() || fallbackName;
const nameFa = String(draft.nameFa ?? '').trim() || fallbackName;
const summary = String(draft.summary ?? '').trim();
const descriptionHtml = normalizeAiDescriptionHtml(
@@ -395,4 +392,14 @@ Rules:
technicalValues,
};
}
/** First non-URL line of the research brief, truncated for fallback titles. */
private shortProductLabel(brief: string): string {
const line =
brief
.split(/\r?\n/)
.map((part) => part.trim())
.find((part) => part && !/^https?:\/\//i.test(part)) ?? brief.trim();
return line.slice(0, 120).trim();
}
}