import React, { useEffect, useRef, useState } from "react"; import AutoFixHighIcon from "@mui/icons-material/AutoFixHigh"; import { Alert, Box, Button, MenuItem, Paper, Stack, TextField, Typography } from "@mui/material"; import { getApiErrorMessage } from "../../api"; import { useAccountPlan } from "../../accountPlan"; import { AI_ACTIONS, CvAiCallbacks, CvAiProposal, normalizeCvLanguage } from "../../cvBuilder"; import { useToast } from "../../toast"; import ProFeatureNotice from "../ProFeatureNotice"; import { useI18n } from "../../i18n/I18nProvider"; type Props = CvAiCallbacks & { sectionName: string; text: string; context?: string; documentLanguage?: string | null; targetId: string; }; const SECTION_ACTIONS = new Set(["improve", "professional", "shorten", "grammar", "impact", "bullets"]); export default function AiSectionAssistant({ sectionName, text, context, documentLanguage, targetId, onGenerate, onAccept }: Props) { const { t } = useI18n(); const { toast } = useToast(); const { canUseAi } = useAccountPlan(); const [open, setOpen] = useState(false); const [action, setAction] = useState("improve"); const [suggestion, setSuggestion] = useState(""); const [proposal, setProposal] = useState(null); const [busy, setBusy] = useState(false); const [applying, setApplying] = useState(false); const requestId = useRef(0); const normalizedLanguage = normalizeCvLanguage(documentLanguage); const [targetLanguage, setTargetLanguage] = useState(normalizedLanguage); useEffect(() => setTargetLanguage(normalizedLanguage), [normalizedLanguage]); useEffect(() => { requestId.current += 1; setProposal(null); setSuggestion(""); setBusy(false); setApplying(false); return () => { requestId.current += 1; }; }, [text, targetId, normalizedLanguage, canUseAi]); const discard = () => { requestId.current += 1; setProposal(null); setSuggestion(""); setBusy(false); setApplying(false); }; const run = async () => { if (!text.trim() || !canUseAi || busy) return; const request = ++requestId.current; setBusy(true); try { const response = await onGenerate({ action, text, targetId, language: targetLanguage, context: [`Section: ${sectionName}. Preserve facts and do not invent achievements.`, context].filter(Boolean).join("\n"), }); if (request !== requestId.current) return; setProposal(response); setSuggestion(response.result); } catch (error) { if (request === requestId.current) toast(getApiErrorMessage(error, t("cvAiFailed")), "error"); } finally { if (request === requestId.current) { setBusy(false); setApplying(false); } } }; const accept = async () => { if (!proposal || busy) return; const request = ++requestId.current; setBusy(true); setApplying(true); try { await onAccept(proposal, suggestion); if (request !== requestId.current) return; discard(); setOpen(false); toast(t("cvAiApplied", { section: sectionName }), "success"); } catch (error) { if (request === requestId.current) toast(getApiErrorMessage(error, t("cvAiFailed")), "error"); } finally { if (request === requestId.current) { setBusy(false); setApplying(false); } } }; if (!open) { return ( ); } if (!canUseAi) { return ( {t("cvAiProBody")} ); } return ( setAction(event.target.value)} sx={{ minWidth: 190 }}> {AI_ACTIONS.filter((item) => SECTION_ACTIONS.has(item.key)).map((item) => {actionLabel(item.key, item.label, t)})} setTargetLanguage(event.target.value)} sx={{ minWidth: 150 }}> {t("languageEnglish")} {t("languageNorwegianBokmal")} {suggestion && <> {t("cvAiCurrent")} {proposal?.original ?? text} setSuggestion(event.target.value)} /> {t("cvAiCheckFacts")} } ); } type Translate = ReturnType["t"]; function actionLabel(key: string, fallback: string, t: Translate) { const labels: Record = { improve: t("cvAiImprove"), professional: t("cvAiProfessional"), shorten: t("cvAiShorten"), grammar: t("cvAiGrammar"), impact: t("cvAiImpact"), bullets: t("cvAiBullets"), }; return labels[key] ?? fallback; }