import React, { useEffect, useState } from 'react'; import { LoaderCircleIcon } from 'lucide-react'; import { getContentCheckJob, getLatestContentCheck, listContentCheckProducts, } from '../../../service/catalog-quality/catalog-quality.service'; import type { ContentCheckSeverity, IContentCheckJob, IContentCheckProductRow, } from '../../../service/catalog-quality/catalog-quality.interface'; import { useCopilot } from '../../../context/copilot.context'; import { TURN_CREDITS } from '../../../service/copilot/pricing'; import { Button } from '../../ui/button'; import { cn } from '../cn'; /** How often the panel re-reads a scan that is still running. */ const SCAN_POLL_INTERVAL_MS = 5_000; /** Job states that mean the worker is still going. */ const RUNNING_STATES: ReadonlySet = new Set(['pending', 'processing']); /** How many rows the panel shows; the page has the rest. */ const PRODUCT_LIMIT = 15; /** The severity vocabulary the Catalog Quality page uses. */ const SEVERITY_STYLE: Record< ContentCheckSeverity, { label: string; chip: string } > = { critical: { label: 'Critical', chip: 'bg-red-500/15 text-red-600' }, warning: { label: 'Warning', chip: 'bg-amber-400/20 text-amber-700' }, ok: { label: 'OK', chip: 'bg-emerald-500/15 text-emerald-600' }, }; /** * One audited product: what is wrong with its copy, in the words of the * audit itself. * * @param props - Component props. * @param props.product - The audited product. * @return The row. */ function ProductRow({ product, }: { product: IContentCheckProductRow; }): React.ReactElement { const style = SEVERITY_STYLE[product.severity] ?? SEVERITY_STYLE.warning; const issues: string[] = [ ...(product.title_issues ?? []), ...(product.description_issues ?? []), ]; return (
  • {style.label} {product.product_name || product.sku}
    {issues.length > 0 && ( )}
  • ); } /** * The catalog audit beside the conversation: the score, what it is made of, * and the products dragging it down. Nothing is generated from here - the * rewrites cost credits and live on the Catalog Quality page - so the panel * reads, and the conversation acts. * * @param props - Component props. * @param props.onAsk - Put a question to the copilot. * @return The panel. */ export function CatalogPanel({ onAsk, }: { onAsk: (prompt: string) => void; }): React.ReactElement { const copilot = useCopilot(); const [job, setJob] = useState(null); const [products, setProducts] = useState([]); const [isLoading, setIsLoading] = useState(true); useEffect(() => { let stale = false; let timer = 0; const read = async (jobId?: string): Promise => { const latest: IContentCheckJob | null = jobId ? await getContentCheckJob(jobId) : await getLatestContentCheck(); if (stale) return; setJob(latest); setIsLoading(false); if (!latest) return; if (RUNNING_STATES.has(latest.state)) { timer = window.setTimeout( () => void read(latest.job_id), SCAN_POLL_INTERVAL_MS ); return; } const list = await listContentCheckProducts(latest.job_id, { limit: PRODUCT_LIMIT, }); if (stale) return; setProducts(list.products ?? []); }; void read(); return () => { stale = true; window.clearTimeout(timer); }; }, []); if (isLoading) { return (
    Reading your catalog...
    ); } if (!job) { return (

    Your catalog has not been audited yet. The audit reads every product title and description and says which ones an AI engine cannot use.

    ); } const isRunning: boolean = RUNNING_STATES.has(job.state); return (

    Catalog score

    {Math.round(job.overall_score)} /100

    Critical
    {job.critical_count}
    Warnings
    {job.warning_count}
    Clean
    {job.ok_count}
    {isRunning ? (
    Auditing {job.processed_products} of {job.total_products} products...
    ) : ( <>
      {products.map((product: IContentCheckProductRow) => ( ))}
    {products.length > 0 && (
    )} )}
    ); }