diff --git a/backend/server3.js b/backend/server3.js index afe1172..6fc2311 100644 --- a/backend/server3.js +++ b/backend/server3.js @@ -323,7 +323,8 @@ app.post('/api/premium/ai/next-steps', authenticatePremiumUser, async (req, res) userProfile = {}, scenarioRow = {}, financialProfile = {}, - collegeProfile = {} + collegeProfile = {}, + previouslyUsedTitles = [] } = req.body; // 2) Build a summary for ChatGPT @@ -335,6 +336,13 @@ app.post('/api/premium/ai/next-steps', authenticatePremiumUser, async (req, res) collegeProfile }); + let avoidSection = ''; + if (previouslyUsedTitles.length > 0) { + avoidSection = `\nDO NOT repeat the following milestone titles:\n${previouslyUsedTitles + .map((t) => `- ${t}`) + .join('\n')}\n`; + } + // 3) Dynamically compute "today's" date and future cutoffs const now = new Date(); const isoToday = now.toISOString().slice(0, 10); // e.g. "2025-06-01" @@ -371,12 +379,14 @@ Respond ONLY in the requested JSON format.` Here is the user's current situation: ${summaryText} -Please provide exactly 3 short-term (within 6 months) and 2 long-term (1–3 years) milestones. +Please provide exactly 3 short-term (within 6 months) and 2 long-term (1–3 years) milestones. Avoid any previously suggested milestones. Each milestone must have: - "title" (up to 5 words) - "date" in YYYY-MM-DD format (>= ${isoToday}) - "description" (1-2 sentences) + ${avoidSection} + Return ONLY a JSON array, no extra text: [ diff --git a/src/components/MilestoneTracker.js b/src/components/MilestoneTracker.js index 2532c23..daf1d8a 100644 --- a/src/components/MilestoneTracker.js +++ b/src/components/MilestoneTracker.js @@ -329,6 +329,33 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { const userArea = userProfile?.area || 'U.S.'; const userState = getFullStateName(userProfile?.state || '') || 'United States'; + useEffect(() => { + const storedRecs = localStorage.getItem('aiRecommendations'); + if (storedRecs) { + try { + const arr = JSON.parse(storedRecs); + arr.forEach((m) => { + if (!m.id) { + m.id = crypto.randomUUID(); + } + }); + setRecommendations(arr); + } catch (err) { + console.error('Error parsing stored AI recs =>', err); + } + } + }, []); + + useEffect(() => { + if (recommendations.length > 0) { + localStorage.setItem('aiRecommendations', JSON.stringify(recommendations)); + } else { + // if it's empty, we can remove from localStorage if you want + localStorage.removeItem('aiRecommendations'); + } + }, [recommendations]); + + // 2) load local JSON => masterCareerRatings useEffect(() => { fetch('/careers_with_ratings.json') @@ -718,13 +745,29 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { const yearsInCareer = getYearsInCareer(scenarioRow?.start_date); // -- AI Handler -- - async function handleAiClick() { + async function handleAiClick() { setAiLoading(true); - setRecommendations([]); setSelectedIds([]); + // gather all previously used titles from: + // A) existing recommendations + // B) accepted milestones in scenarioMilestones + // We'll pass them in the same request to /api/premium/ai/next-steps + // so the server can incorporate them in the prompt + const oldRecTitles = recommendations.map((r) => r.title.trim()).filter(Boolean); + const acceptedTitles = scenarioMilestones.map((m) => (m.title || '').trim()).filter(Boolean); + const allToAvoid = [...oldRecTitles, ...acceptedTitles]; + try { - const payload = { userProfile, scenarioRow, financialProfile, collegeProfile }; + const payload = { + userProfile, + scenarioRow, + financialProfile, + collegeProfile, + previouslyUsedTitles: allToAvoid + }; + + // We'll rely on the server to integrate "previouslyUsedTitles" into the prompt const res = await authFetch('/api/premium/ai/next-steps', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -734,19 +777,17 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { const data = await res.json(); const rawText = data.recommendations || ''; - - // Parse JSON const arr = parseAiJson(rawText); - setRecommendations(arr); + setRecommendations(arr); + localStorage.setItem('aiRecommendations', JSON.stringify(arr)); } catch (err) { - console.error('Error fetching AI recommendations:', err); + console.error('Error fetching AI next steps =>', err); } finally { setAiLoading(false); } } - // Check/uncheck a recommendation function handleToggle(recId) { setSelectedIds((prev) => { if (prev.includes(recId)) { @@ -759,17 +800,17 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { async function handleCreateSelectedMilestones() { if (!careerProfileId) return; - const confirm = window.confirm('Convert selected AI suggestions into milestones?'); + const confirm = window.confirm('Create the selected AI suggestions as milestones?'); if (!confirm) return; - // filter out those that are checked const selectedRecs = recommendations.filter((r) => selectedIds.includes(r.id)); if (!selectedRecs.length) return; - const newMils = selectedRecs.map((rec) => ({ + // Use the AI-suggested date: + const payload = selectedRecs.map((rec) => ({ title: rec.title, description: rec.description || '', - date: new Date().toISOString().slice(0, 10), // for demonstration + date: rec.date, // <-- use AI's date, not today's date career_profile_id: careerProfileId })); @@ -777,20 +818,19 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { const r = await authFetch('/api/premium/milestone', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ milestones: newMils }) + body: JSON.stringify({ milestones: payload }) }); if (!r.ok) throw new Error('Failed to create new milestones'); - // re-run the projection to reflect newly inserted milestones + // re-run projection to see them in the chart await buildProjection(); - alert('Milestones successfully created! Check your timeline or projection.'); - - // optionally clear them + // optionally clear + alert('Milestones created successfully!'); setSelectedIds([]); } catch (err) { - console.error('Error saving new milestones:', err); - alert('Error saving AI milestones.'); + console.error('Error creating milestones =>', err); + alert('Error saving new AI milestones.'); } } @@ -801,6 +841,8 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { if (!simulationYearsInput.trim()) setSimulationYearsInput('20'); } + const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?'; + return (

Where Am I Now?

@@ -981,7 +1023,7 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) { {/* 7) AI Next Steps */}
{aiLoading &&

Generating your next steps…

}