import { useState } from 'react'; import apiFetch from '@wordpress/api-fetch'; import { CheckIcon, Loader2Icon, MessageCircleQuestionIcon, } from 'lucide-react'; import { Button } from '../../components/ui/button'; import { Callout } from '../../components/ui/callout'; import { Checkbox } from '../../components/ui/checkbox'; import type { AiGate } from '../hooks/use-ai-gate'; import { balanceNote, repeatQuestion } from '../lib/ai-runs'; import { addFaqToHtml, type FaqPair } from '../lib/faq-merge'; import { errorOf, FixErrorCallout, NoAiAccess, useRepeatGuard, type FixError, } from './fix'; interface FaqAnswer { post_id: number; run_id: string; credits: number; faq: FaqPair[]; } /** * Questions and answers written from this page's own text, added to the * content as the FAQ block: it prints the questions on the page and the * FAQPage schema with them: marked-up questions must be visible on the page. */ export const FaqGenerator = ({ postId, credits, pricingUrl, content, page, ai, onAdd, }: { postId: number; credits: number; pricingUrl: string; /** The content as the editor holds it; the FAQ is appended to it. */ content: string; page: { title: string; keyword: string }; ai: AiGate; onAdd: (content: string) => void; }): JSX.Element => { const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [faq, setFaq] = useState(null); const [added, setAdded] = useState(false); /** The questions left out before adding, by their place in the list. */ const [dropped, setDropped] = useState>(new Set()); const kept = (faq ?? []).filter((_pair, index) => !dropped.has(index)); const again = repeatQuestion('faq', ai.ranToday, credits, ai.kind); const repeat = useRepeatGuard(again); const generate = async (): Promise => { setBusy(true); setError(null); try { const answer = await apiFetch({ path: '/recomaze/v1/seo/post-fix/faq', method: 'POST', data: { post_id: postId, title: page.title, content, keyword: page.keyword, }, }); setFaq(answer.faq); setDropped(new Set()); setAdded(false); ai.onRan('faq'); } catch (failure) { setError(errorOf(failure)); } finally { setBusy(false); } }; const add = (): void => { if (kept.length === 0) { return; } onAdd(addFaqToHtml(content, kept)); setAdded(true); }; return (
{error && } {faq !== null && faq.length === 0 && ( The text does not answer enough distinct questions for a FAQ yet. )} {faq !== null && faq.length > 0 && (
    {faq.map((pair, index) => (
  • setDropped(current => { const next = new Set(current); if (checked) { next.delete(index); } else { next.add(index); } return next; }) } /> {pair.question} {pair.answer}
  • ))}
)} {!ai.allowed && } {repeat.asking && ( {again} )}
{faq !== null && faq.length > 0 && ( )}

{credits} credits a run.{balanceNote(ai.balance, credits)} The questions are answered from this page only; untick the ones to leave out. A page with a FAQ already gets them in it, the ones it asks left out, and the FAQ block prints the FAQPage schema with them.

); };