Implement S03 follow-up draft context loop

This commit is contained in:
2026-03-24 11:05:41 +01:00
parent b5b430947b
commit 0cacb4e51b
10 changed files with 645 additions and 26 deletions
@@ -19,7 +19,7 @@ import {
} from "@mui/material";
import { api, getApiErrorMessage } from "../api";
import { ApplicationPackageResponse, CandidateFit, FocusPlanResponse, InterviewPrepResponse, JobApplication, ReadinessResponse } from "../types";
import { ApplicationPackageResponse, CandidateFit, FocusPlanResponse, FollowUpDraft, InterviewPrepResponse, JobApplication, ReadinessResponse } from "../types";
import { useToast } from "../toast";
import { useDialogActions } from "../dialogs";
@@ -38,16 +38,8 @@ type AttachmentItem = {
useForAi: boolean;
};
type FollowUpDraft = {
subject: string;
body: string;
reason: string;
suggestedSendOn: string;
};
type GenerationMode = "default" | "concise" | "ats" | "achievement" | "interview";
type CoverLetterStyle = "balanced" | "concise" | "formal" | "bold";
type PackageDraftKind = "tailoredCv" | "coverLetter" | "applicationAnswer" | "recruiterMessage";
type PackageWorkspaceState = {
coverLetter: string;
@@ -612,9 +604,20 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
<Box>
{loadingDraft ? <Box sx={{ py: 4, display: "flex", justifyContent: "center" }}><CircularProgress size={28} /></Box> : followUpDraft ? (
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2 }}>
<Box><Typography variant="overline">{t("jobDetailsReason")}</Typography><Typography>{followUpDraft.reason}</Typography></Box>
<Box><Typography variant="overline">{t("jobDetailsSuggestedSendDate")}</Typography><Typography>{new Date(followUpDraft.suggestedSendOn).toLocaleDateString()}</Typography></Box>
<Box sx={{ p: 1.5, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.default" }}>
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, flexWrap: "wrap", mb: 1 }}>
<Typography variant="overline">Follow-up context</Typography>
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap" }}>
{followUpDraft.threadSubject ? <Chip size="small" variant="outlined" label={`Thread: ${followUpDraft.threadSubject}`} /> : null}
{followUpDraft.lastCorrespondenceAt ? <Chip size="small" variant="outlined" label={`Last activity: ${new Date(followUpDraft.lastCorrespondenceAt).toLocaleDateString()}`} /> : null}
</Box>
</Box>
<Typography sx={{ whiteSpace: "pre-wrap", mb: 1.5 }}>{followUpDraft.contextSummary}</Typography>
<Box sx={{ display: "grid", gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2 }}>
<Box><Typography variant="overline">Why now</Typography><Typography>{followUpDraft.reason}</Typography></Box>
<Box><Typography variant="overline">Last sender</Typography><Typography>{followUpDraft.lastCorrespondenceFrom ?? "No imported sender yet"}</Typography></Box>
</Box>
{followUpDraft.contextSignals?.length ? <Box sx={{ mt: 1.5 }}><ListCard title="Draft grounding" items={followUpDraft.contextSignals} /></Box> : null}
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', alignItems: 'center' }}>
<FormControl size="small" sx={{ minWidth: 240 }}>
@@ -629,9 +632,9 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
</FormControl>
<Button variant="outlined" onClick={() => setDraftReloadToken((value) => value + 1)}>{t("jobDetailsRegenerateDraft")}</Button>
</Box>
<TextField label={t("jobDetailsRecipient")} value={draftRecipient} onChange={(e) => setDraftRecipient(e.target.value)} helperText={t("jobDetailsRecipientHelp")} />
<TextField label={t("jobDetailsRecipient")} value={draftRecipient} onChange={(e) => setDraftRecipient(e.target.value)} helperText={`${t("jobDetailsRecipientHelp")} Manual send only — nothing is dispatched until you press send.`} />
<TextField label={t("jobDetailsSubject")} value={draftSubject} onChange={(e) => setDraftSubject(e.target.value)} />
<TextField label={t("jobDetailsDraft")} multiline minRows={8} value={draftBody} onChange={(e) => setDraftBody(e.target.value)} />
<TextField label={t("jobDetailsDraft")} multiline minRows={8} value={draftBody} onChange={(e) => setDraftBody(e.target.value)} helperText="You can edit this before sending. Sending stays manual and logs the sent note back to correspondence." />
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 1, flexWrap: "wrap" }}>
<Button variant="outlined" onClick={() => navigator.clipboard.writeText(`${draftSubject}\n\n${draftBody}`)}>{t("jobDetailsCopyDraft")}</Button>
<Button variant="contained" disabled={sendingDraft || !draftSubject.trim() || !draftBody.trim()} onClick={async () => {