Add attachment-aware AI drafting and CV section tools
This commit is contained in:
@@ -36,6 +36,7 @@ type FollowUpDraft = {
|
||||
};
|
||||
|
||||
type GenerationMode = "default" | "concise" | "ats" | "achievement" | "interview";
|
||||
type CoverLetterStyle = "balanced" | "concise" | "formal" | "bold";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
@@ -97,6 +98,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
const [generatingPackage, setGeneratingPackage] = useState(false);
|
||||
const [applicationPackage, setApplicationPackage] = useState<ApplicationPackageResponse | null>(null);
|
||||
const [generationMode, setGenerationMode] = useState<GenerationMode>("default");
|
||||
const [coverLetterStyle, setCoverLetterStyle] = useState<CoverLetterStyle>("balanced");
|
||||
const [tailoredCvText, setTailoredCvText] = useState("");
|
||||
const [draftRecipient, setDraftRecipient] = useState("");
|
||||
const [followUpMode, setFollowUpMode] = useState(initialFollowUpMode || "post-apply");
|
||||
@@ -279,6 +281,15 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
<MenuItem value="interview">{t("jobDetailsGenerationInterview")}</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl size="small" sx={{ minWidth: 190 }}>
|
||||
<InputLabel>{t("jobDetailsCoverLetterStyle")}</InputLabel>
|
||||
<Select value={coverLetterStyle} label={t("jobDetailsCoverLetterStyle")} onChange={(e) => setCoverLetterStyle(e.target.value as CoverLetterStyle)}>
|
||||
<MenuItem value="balanced">{t("jobDetailsCoverLetterStyleBalanced")}</MenuItem>
|
||||
<MenuItem value="concise">{t("jobDetailsCoverLetterStyleConcise")}</MenuItem>
|
||||
<MenuItem value="formal">{t("jobDetailsCoverLetterStyleFormal")}</MenuItem>
|
||||
<MenuItem value="bold">{t("jobDetailsCoverLetterStyleBold")}</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button size="small" variant="outlined" onClick={async () => {
|
||||
try {
|
||||
const me = await api.get<{ profileCvText?: string | null }>("/auth/me");
|
||||
@@ -292,7 +303,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
if (!jobId) return;
|
||||
setGeneratingPackage(true);
|
||||
try {
|
||||
const res = await api.post<ApplicationPackageResponse>(`/jobapplications/${jobId}/generate-application-package`, null, { params: { mode: generationMode } });
|
||||
const res = await api.post<ApplicationPackageResponse>(`/jobapplications/${jobId}/generate-application-package`, null, { params: { mode: generationMode, coverLetterStyle } });
|
||||
setApplicationPackage(res.data);
|
||||
setTailoredCvText(res.data.tailoredCvText ?? "");
|
||||
toast(t("jobDetailsPackageGenerated"), "success");
|
||||
@@ -369,6 +380,7 @@ export default function JobDetailsDialog({ open, jobId, onClose, initialTab = 0,
|
||||
}
|
||||
}} saving={savingApplicationDrafts} />
|
||||
<ListCard title={t("jobDetailsKeyPoints")} items={applicationPackage.keyPoints} />
|
||||
<ListCard title={t("jobDetailsAttachmentSignals")} items={applicationPackage.attachmentSignals.length > 0 ? applicationPackage.attachmentSignals : [t("jobDetailsNoAttachmentSignals")]} subtitle={applicationPackage.attachmentFilesUsed.length > 0 ? applicationPackage.attachmentFilesUsed.join(", ") : undefined} />
|
||||
</Box>
|
||||
) : null}
|
||||
</Box>
|
||||
@@ -525,13 +537,16 @@ function TwoColumnSection({ leftTitle, leftItems, rightTitle, rightItems }: { le
|
||||
);
|
||||
}
|
||||
|
||||
function ListCard({ title, items }: { title: string; items: string[] }) {
|
||||
function ListCard({ title, items, subtitle }: { title: string; items: string[]; subtitle?: string }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
return (
|
||||
<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">{title}</Typography>
|
||||
<Box>
|
||||
<Typography variant="overline">{title}</Typography>
|
||||
{subtitle ? <Typography variant="caption" sx={{ display: "block", color: "text.secondary" }}>{subtitle}</Typography> : null}
|
||||
</Box>
|
||||
<Button size="small" variant="outlined" onClick={() => copyLines(items)}>{t("jobDetailsCopy")}</Button>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 0.75 }}>
|
||||
|
||||
Reference in New Issue
Block a user