diff --git a/src/categories/category-ai.service.ts b/src/categories/category-ai.service.ts index d3b1a69..67fa907 100644 --- a/src/categories/category-ai.service.ts +++ b/src/categories/category-ai.service.ts @@ -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}.`; diff --git a/src/categories/category-technical-form-ai.service.ts b/src/categories/category-technical-form-ai.service.ts index d495d96..1239a55 100644 --- a/src/categories/category-technical-form-ai.service.ts +++ b/src/categories/category-technical-form-ai.service.ts @@ -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.`; diff --git a/src/products/dto/product-ai.dto.ts b/src/products/dto/product-ai.dto.ts index 2656a74..f17e371 100644 --- a/src/products/dto/product-ai.dto.ts +++ b/src/products/dto/product-ai.dto.ts @@ -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) diff --git a/src/products/product-ai.service.ts b/src/products/product-ai.service.ts index feb7abd..470c594 100644 --- a/src/products/product-ai.service.ts +++ b/src/products/product-ai.service.ts @@ -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(); + } }