Skip technical form fill on AI product create.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
a1e73d1c9e
commit
f8b865b0aa
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user