Skip technical form fill on AI product create.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-19 15:43:18 +03:30
co-authored by Cursor
parent a1e73d1c9e
commit f8b865b0aa
+4 -57
View File
@@ -5,7 +5,6 @@ import {
import { ConfigService } from '@nestjs/config';
import { ContentStatus, TechnicalFieldType } from '@prisma/client';
import { AuthUser } from '../auth/auth.types';
import { CategoryTechnicalFormService } from '../categories/category-technical-form.service';
import { PrismaService } from '../prisma/prisma.service';
import {
CreateProductByAiDto,
@@ -37,7 +36,6 @@ type AiProductDraft = {
summary: string;
descriptionHtml: string;
tags: string[];
technicalValues: Record<string, string | string[]>;
};
@Injectable()
@@ -47,7 +45,6 @@ export class ProductAiService {
private readonly prisma: PrismaService,
private readonly products: ProductsService,
private readonly technicalInfo: ProductTechnicalInfoService,
private readonly technicalFormService: CategoryTechnicalFormService,
) {}
async createFromAi(
@@ -70,16 +67,10 @@ export class ProductAiService {
throw new BadRequestException('Product category not found');
}
const form = await this.technicalFormService.getFormForCategory(
businessId,
categoryId,
);
const draft = await this.generateDraft({
categoryName: category.name,
productName: dto.name.trim(),
language: dto.language,
fields: form?.fields ?? [],
});
const created = await this.products.create(
@@ -96,26 +87,8 @@ export class ProductAiService {
actor,
);
if (form && Object.keys(draft.technicalValues).length > 0) {
const values = Object.entries(draft.technicalValues)
.filter(([, value]) => {
if (Array.isArray(value)) return value.length > 0;
return value !== '';
})
.map(([fieldKey, value]) => ({ fieldKey, value }));
if (values.length > 0) {
await this.technicalInfo.replaceForProduct(
businessIdRaw,
created.product.id,
{ values },
actor,
);
}
}
return {
message: 'Product created with AI. Add images and variations next.',
message: 'Product created with AI. Add images, variations, and technical info next.',
product: created.product,
};
}
@@ -445,23 +418,11 @@ Rules:
categoryName: string;
productName: string;
language: ProductAiLanguage;
fields: TechnicalFormField[];
}): Promise<AiProductDraft> {
const provider = resolveAiProvider(this.config);
const referencePages = await loadReferencePagesFromText(input.productName);
const usableReferences = referencePages.filter((page) => page.text);
const technicalSchema = input.fields.map((field) => ({
key: field.key,
label: field.label,
type: field.type,
required: field.isRequired,
options:
field.type === 'select' || field.type === 'multi_select'
? field.options.map((option) => option.value)
: undefined,
}));
const inputLanguage =
input.language === ProductAiLanguage.fa ? 'Persian (Farsi)' : 'English';
@@ -472,24 +433,18 @@ Return ONLY valid JSON with this shape:
"nameFa": "Persian product name",
"summary": "short listing summary",
"descriptionHtml": "HTML description using <p> and <ul><li> only",
"tags": ["tag1", "tag2"],
"technicalValues": { "fieldKey": "value" }
"tags": ["tag1", "tag2"]
}
Rules:
- 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.
- When referencePages are provided, treat them as the source of truth for specs, capacity, power, dimensions, and description facts.
- When referencePages are provided, treat them as the source of truth for capacity, power, dimensions, and description facts.
- Prefer the main product specs near the product title; ignore conflicting related-product / upsell blocks.
- Do NOT invent specs from memory when a reference page was provided.
- 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 multi_value fields use an array of free-text strings.
- If a multi_value field is labeled «سایر ویژگی‌ها» (or similar “other features”), put any leftover real product attributes there that do not belong in the other fields — one short string per item; do not repeat values already used elsewhere.
- For text/textarea fields use plain strings.
- Do NOT return technical form fields — product content only (title, names, summary, description, tags).
- 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.`;
@@ -498,7 +453,6 @@ Rules:
category: input.categoryName,
researchBrief: input.productName,
inputLanguage: input.language,
technicalFields: technicalSchema,
referencePages: usableReferences.map((page) => ({
url: page.url,
text: page.text,
@@ -531,7 +485,6 @@ Rules:
input: {
productName: string;
language: ProductAiLanguage;
fields: TechnicalFormField[];
},
): AiProductDraft {
const fallbackName = this.shortProductLabel(input.productName);
@@ -554,11 +507,6 @@ Rules:
throw new BadRequestException('AI draft is missing a Persian product name');
}
const technicalValues = this.normalizeTechnicalValues(
(draft.technicalValues ?? {}) as Record<string, unknown>,
input.fields,
);
return {
title,
nameFa,
@@ -567,7 +515,6 @@ Rules:
descriptionHtml ||
`<p>${summary || `${title} product description.`}</p>`,
tags,
technicalValues,
};
}