feat(cv): redesign document editing with revision-safe letters and writing tools

This commit is contained in:
cesnimda
2026-10-25 22:44:14 +01:00
parent 01e10996c5
commit 2bfaf6c14b
57 changed files with 2859 additions and 118 deletions
@@ -18,7 +18,7 @@ type Props = CvAiCallbacks & {
targetId: string;
};
const SECTION_ACTIONS = new Set(["improve", "professional", "shorten", "grammar", "impact", "bullets"]);
const SECTION_ACTIONS = new Set(["improve", "suggest", "professional", "shorten", "grammar", "impact", "bullets"]);
export default function AiSectionAssistant({ sectionName, text, context, documentLanguage, targetId, onGenerate, onAccept }: Props) {
const { t } = useI18n();
@@ -42,7 +42,7 @@ export default function AiSectionAssistant({ sectionName, text, context, documen
setBusy(false);
setApplying(false);
return () => { requestId.current += 1; };
}, [text, targetId, normalizedLanguage, canUseAi]);
}, [text, targetId, normalizedLanguage, targetLanguage, canUseAi]);
const discard = () => {
requestId.current += 1;
@@ -52,13 +52,13 @@ export default function AiSectionAssistant({ sectionName, text, context, documen
setApplying(false);
};
const run = async () => {
const run = async (requestedAction = action) => {
if (!text.trim() || !canUseAi || busy) return;
const request = ++requestId.current;
setBusy(true);
try {
const response = await onGenerate({
action,
action: requestedAction,
text,
targetId,
language: targetLanguage,
@@ -94,9 +94,17 @@ export default function AiSectionAssistant({ sectionName, text, context, documen
if (!open) {
return (
<Stack direction="row" gap={0.5} flexWrap="wrap" aria-label={t("cvAiSuggestions")}>
{(["improve", "suggest", "grammar", "shorten"] as const).map(key => (
<Button key={key} size="small" disabled={!canUseAi || (key !== "suggest" && !text.trim())}
onClick={() => { setAction(key); setOpen(true); void run(key); }}>
{key === "suggest" ? t("cvAiSuggestContent") : key === "grammar" ? t("cvAiGrammarCheck") : key === "shorten" ? t("cvAiShorter") : t("cvAiImprove")}
</Button>
))}
<Button size="small" variant="outlined" startIcon={<AutoFixHighIcon />} disabled={!text.trim()} onClick={() => setOpen(true)}>
{t("cvAiSuggestions")}
</Button>
</Stack>
);
}
@@ -114,6 +122,7 @@ export default function AiSectionAssistant({ sectionName, text, context, documen
return (
<Paper variant="outlined" sx={{ p: 1.25, borderColor: "primary.main", bgcolor: "action.hover" }}>
<Stack spacing={1}>
{!text.trim() && <Alert severity="info">{t("cvAiNeedSource")}</Alert>}
<Stack direction={{ xs: "column", sm: "row" }} spacing={1} alignItems={{ sm: "center" }}>
<TextField select size="small" disabled={busy} label={t("cvAiAction")} value={action} onChange={(event) => setAction(event.target.value)} sx={{ minWidth: 190 }}>
{AI_ACTIONS.filter((item) => SECTION_ACTIONS.has(item.key)).map((item) => <MenuItem key={item.key} value={item.key}>{actionLabel(item.key, item.label, t)}</MenuItem>)}
@@ -146,6 +155,7 @@ type Translate = ReturnType<typeof useI18n>["t"];
function actionLabel(key: string, fallback: string, t: Translate) {
const labels: Record<string, string> = {
suggest: t("cvAiSuggestContent"),
improve: t("cvAiImprove"), professional: t("cvAiProfessional"), shorten: t("cvAiShorten"),
grammar: t("cvAiGrammar"), impact: t("cvAiImpact"), bullets: t("cvAiBullets"),
};