import { useState } from 'react'; import apiFetch from '@wordpress/api-fetch'; import { ExternalLinkIcon, LinkIcon, Loader2Icon } from 'lucide-react'; import { Button } from '../../components/ui/button'; import { Callout } from '../../components/ui/callout'; import type { AiGate } from '../hooks/use-ai-gate'; import { balanceNote, repeatQuestion } from '../lib/ai-runs'; import { linkAnchor } from '../lib/anchor'; import { errorOf, FixErrorCallout, NoAiAccess, useRepeatGuard, type FixError, } from './fix'; /** One page of the site this one should link to, and the words to link. */ export interface LinkSuggestion { url: string; title: string; anchor: string; sentence: string; reason: string; } interface LinkSuggestionsAnswer { post_id: number; run_id: string; credits: number; links: LinkSuggestion[]; } /** * Internal links for the post being written: the agent reads the text and * the site's other published pages and says which to link to and with what * words; Insert wraps those words where they already stand in the content. * The analysis' internal-link test passes on the first one. */ export const LinkSuggestions = ({ postId, credits, pricingUrl, content, page, ai, onInsert, }: { postId: number; credits: number; pricingUrl: string; /** The content as the editor holds it, where the link is inserted. */ content: string; page: { title: string; keyword: string }; ai: AiGate; onInsert: (content: string) => void; }): JSX.Element => { const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [links, setLinks] = useState(null); const [inserted, setInserted] = useState([]); const [missing, setMissing] = useState([]); const again = repeatQuestion('links', ai.ranToday, credits, ai.kind); const repeat = useRepeatGuard(again); const suggest = async (): Promise => { setBusy(true); setError(null); setMissing([]); try { const answer = await apiFetch({ path: '/recomaze/v1/seo/post-fix/links', method: 'POST', data: { post_id: postId, title: page.title, content, keyword: page.keyword, }, }); setLinks(answer.links); setInserted([]); ai.onRan('links'); } catch (failure) { setError(errorOf(failure)); } finally { setBusy(false); } }; const insert = (link: LinkSuggestion): void => { const next = linkAnchor(content, link.anchor, link.url); if (next === null) { setMissing(state => [...state, link.url]); return; } onInsert(next); setInserted(state => [...state, link.url]); }; return (
{error && } {links !== null && links.length === 0 && ( Nothing to link yet: the other pages of the site do not answer what this one is about. )} {links !== null && links.map(link => { const done = inserted.includes(link.url); const notFound = missing.includes(link.url); return (

Links the words{' '} “{link.anchor}” {link.sentence !== '' && <> in “{link.sentence}”}

{notFound && (

Those words are no longer in the content; edit the post and ask again.

)}
); })} {!ai.allowed && } {repeat.asking && ( {again} )}

{credits} credits a run.{balanceNote(ai.balance, credits)} Recomaze reads this post and the site’s other published pages, then links words that already stand in the text.

); };