import { BadRequestException, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { AuthUser } from '../auth/auth.types'; import { normalizeAiDescriptionHtml } from '../common/ai-html.util'; import { requestAiJsonCompletion, resolveAiProvider, } from '../common/ai-provider.util'; import { BusinessProfileAiFillField, FillBusinessProfileFieldByAiDto, } from './dto/business-profile-ai.dto'; @Injectable() export class BusinessProfileAiService { constructor(private readonly config: ConfigService) {} async fillField( _businessIdRaw: string, dto: FillBusinessProfileFieldByAiDto, _actor: AuthUser, ) { const prompt = dto.prompt?.trim(); if (!prompt || prompt.length < 10) { throw new BadRequestException('Prompt is required'); } const nameEn = dto.nameEn?.trim() || ''; const nameFa = dto.nameFa?.trim() || ''; if (!nameEn && !nameFa) { throw new BadRequestException( 'Enter a business name before using AI fill', ); } const provider = resolveAiProvider(this.config); const isAbout = dto.field === BusinessProfileAiFillField.about; const systemPrompt = isAbout ? `You write short business abstracts for an Iranian B2B marketplace listing. Return ONLY valid JSON: { "text": "..." } Follow the user's instructions for language and tone. Keep the abstract to 1–4 sentences. No HTML. No markdown. Prefer a clear, professional, informative style about what the business does. Never write sales CTAs, discounts, or vague praise.` : `You write business profile descriptions as HTML for an advanced rich-text editor on an Iranian marketplace. 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 professional, informative structure about the company, offerings, and strengths. Never write sales CTAs, discounts, or vague marketing slogans. Aim for 2–5 short paragraphs and optionally a bullet list.`; const userPrompt = JSON.stringify({ instructions: prompt, business: { nameEn: nameEn || null, nameFa: nameFa || null, categories: dto.categories?.trim() || null, }, }); let content: string; try { content = await requestAiJsonCompletion( provider, systemPrompt, userPrompt, 0.5, ); } catch (err) { const message = err instanceof Error ? err.message : 'AI request failed'; throw new BadRequestException(message); } let parsed: { text?: unknown; html?: unknown }; try { parsed = JSON.parse(content) as { text?: unknown; html?: unknown }; } catch { throw new BadRequestException('AI returned invalid JSON'); } if (isAbout) { const text = String(parsed.text ?? '').trim(); if (!text) { throw new BadRequestException('AI returned an empty abstract'); } return { field: BusinessProfileAiFillField.about, text }; } const html = normalizeAiDescriptionHtml(String(parsed.html ?? '')); if (!html) { throw new BadRequestException('AI returned an empty description'); } return { field: BusinessProfileAiFillField.vision, html }; } }