Added limits to aI Suggestions

This commit is contained in:
Josh 2025-05-28 15:08:32 +00:00
parent c959367f38
commit 6298eedaba

View File

@ -298,6 +298,10 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
const [recommendations, setRecommendations] = useState([]); // parsed array const [recommendations, setRecommendations] = useState([]); // parsed array
const [selectedIds, setSelectedIds] = useState([]); // which rec IDs are checked const [selectedIds, setSelectedIds] = useState([]); // which rec IDs are checked
const [lastClickTime, setLastClickTime] = useState(null);
const RATE_LIMIT_SECONDS = 15; // adjust as needed
const [buttonDisabled, setButtonDisabled] = useState(false);
const { const {
projectionData: initProjData = [], projectionData: initProjData = [],
loanPayoffMonth: initLoanMonth = null loanPayoffMonth: initLoanMonth = null
@ -329,6 +333,14 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
const userArea = userProfile?.area || 'U.S.'; const userArea = userProfile?.area || 'U.S.';
const userState = getFullStateName(userProfile?.state || '') || 'United States'; const userState = getFullStateName(userProfile?.state || '') || 'United States';
useEffect(() => {
let timer;
if (buttonDisabled) {
timer = setTimeout(() => setButtonDisabled(false), RATE_LIMIT_SECONDS * 1000);
}
return () => clearTimeout(timer);
}, [buttonDisabled]);
useEffect(() => { useEffect(() => {
const storedRecs = localStorage.getItem('aiRecommendations'); const storedRecs = localStorage.getItem('aiRecommendations');
if (storedRecs) { if (storedRecs) {
@ -677,6 +689,19 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
}; };
}); });
const [clickCount, setClickCount] = useState(() => {
const storedCount = localStorage.getItem('aiClickCount');
const storedDate = localStorage.getItem('aiClickDate');
const today = new Date().toISOString().slice(0, 10);
if (storedDate !== today) {
localStorage.setItem('aiClickDate', today);
localStorage.setItem('aiClickCount', '0');
return 0;
}
return parseInt(storedCount || '0', 10);
});
const DAILY_CLICK_LIMIT = 10; // example limit per day
const hasStudentLoan = projectionData.some((p) => p.loanBalance > 0); const hasStudentLoan = projectionData.some((p) => p.loanBalance > 0);
const annotationConfig = {}; const annotationConfig = {};
if (loanPayoffMonth && hasStudentLoan) { if (loanPayoffMonth && hasStudentLoan) {
@ -746,16 +771,20 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
// -- AI Handler -- // -- AI Handler --
async function handleAiClick() { async function handleAiClick() {
if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) {
alert('You have reached the daily limit for suggestions.');
return;
}
if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) {
alert('You have reached your daily limit of AI-generated recommendations. Please check back tomorrow.');
return;
}
setAiLoading(true); setAiLoading(true);
setSelectedIds([]); setSelectedIds([]);
// gather all previously used titles from: const oldRecTitles = recommendations.map(r => r.title.trim()).filter(Boolean);
// A) existing recommendations const acceptedTitles = scenarioMilestones.map(m => (m.title || '').trim()).filter(Boolean);
// 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]; const allToAvoid = [...oldRecTitles, ...acceptedTitles];
try { try {
@ -767,12 +796,12 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
previouslyUsedTitles: allToAvoid previouslyUsedTitles: allToAvoid
}; };
// We'll rely on the server to integrate "previouslyUsedTitles" into the prompt
const res = await authFetch('/api/premium/ai/next-steps', { const res = await authFetch('/api/premium/ai/next-steps', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload) body: JSON.stringify(payload)
}); });
if (!res.ok) throw new Error('AI request failed'); if (!res.ok) throw new Error('AI request failed');
const data = await res.json(); const data = await res.json();
@ -781,12 +810,22 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
setRecommendations(arr); setRecommendations(arr);
localStorage.setItem('aiRecommendations', JSON.stringify(arr)); localStorage.setItem('aiRecommendations', JSON.stringify(arr));
// Update click count
setClickCount(prev => {
const newCount = prev + 1;
localStorage.setItem('aiClickCount', newCount);
return newCount;
});
} catch (err) { } catch (err) {
console.error('Error fetching AI next steps =>', err); console.error('Error fetching AI next steps =>', err);
} finally { } finally {
setAiLoading(false); setAiLoading(false);
} }
} }
function handleToggle(recId) { function handleToggle(recId) {
setSelectedIds((prev) => { setSelectedIds((prev) => {
@ -844,13 +883,11 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?'; const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?';
return ( return (
<div className="milestone-tracker max-w-screen-lg mx-auto px-4 py-6 space-y-6"> <div className="milestone-tracker max-w-screen-lg mx-auto px-4 py-6 space-y-4">
<h2 className="text-2xl font-bold mb-4">Where Am I Now?</h2> <h2 className="text-2xl font-bold mb-4">Where Am I Now?</h2>
{/* 1) Career */} {/* 1) Career */}
<div className="bg-white p-4 rounded shadow mb-4"> <div className="bg-white p-4 rounded shadow mb-4 flex flex-col justify-center items-center min-h-[80px]">
<div className="mt-4">
<p> <p>
<strong>Current Career:</strong>{' '} <strong>Current Career:</strong>{' '}
{scenarioRow?.career_name || '(Select a career)'} {scenarioRow?.career_name || '(Select a career)'}
@ -862,7 +899,6 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
</p> </p>
)} )}
</div> </div>
</div>
{/* 2) Salary Benchmarks */} {/* 2) Salary Benchmarks */}
<div className="flex flex-col md:flex-row gap-4"> <div className="flex flex-col md:flex-row gap-4">
@ -874,22 +910,23 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
<p> <p>
10th percentile:{' '} 10th percentile:{' '}
{salaryData.regional.regional_PCT10 {salaryData.regional.regional_PCT10
? `$${salaryData.regional.regional_PCT10.toLocaleString()}` ? `$${parseFloat(salaryData.regional.regional_PCT10).toLocaleString()}`
: 'N/A'} : 'N/A'}
</p> </p>
<p> <p>
Median:{' '} Median:{' '}
{salaryData.regional.regional_MEDIAN {salaryData.regional.regional_MEDIAN
? `$${salaryData.regional.regional_MEDIAN.toLocaleString()}` ? `$${parseFloat(salaryData.regional.regional_MEDIAN).toLocaleString()}`
: 'N/A'} : 'N/A'}
</p> </p>
<p> <p>
90th percentile:{' '} 90th percentile:{' '}
{salaryData.regional.regional_PCT90 {salaryData.regional.regional_PCT90
? `$${salaryData.regional.regional_PCT90.toLocaleString()}` ? `$${parseFloat(salaryData.regional.regional_PCT90).toLocaleString()}`
: 'N/A'} : 'N/A'}
</p> </p>
<SalaryGauge <SalaryGauge
userSalary={userSalary} userSalary={userSalary}
percentileRow={salaryData.regional} percentileRow={salaryData.regional}
@ -904,19 +941,19 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
<p> <p>
10th percentile:{' '} 10th percentile:{' '}
{salaryData.national.national_PCT10 {salaryData.national.national_PCT10
? `$${salaryData.national.national_PCT10.toLocaleString()}` ? `$${parseFloat(salaryData.national.national_PCT10).toLocaleString()}`
: 'N/A'} : 'N/A'}
</p> </p>
<p> <p>
Median:{' '} Median:{' '}
{salaryData.national.national_MEDIAN {salaryData.national.national_MEDIAN
? `$${salaryData.national.national_MEDIAN.toLocaleString()}` ? `$${parseFloat(salaryData.national.national_MEDIAN).toLocaleString()}`
: 'N/A'} : 'N/A'}
</p> </p>
<p> <p>
90th percentile:{' '} 90th percentile:{' '}
{salaryData.national.national_PCT90 {salaryData.national.national_PCT90
? `$${salaryData.national.national_PCT90.toLocaleString()}` ? `$${parseFloat(salaryData.national.national_PCT90).toLocaleString()}`
: 'N/A'} : 'N/A'}
</p> </p>
@ -1022,9 +1059,10 @@ export default function MilestoneTracker({ selectedCareer: initialCareer }) {
{/* 7) AI Next Steps */} {/* 7) AI Next Steps */}
<div className="bg-white p-4 rounded shadow mt-4"> <div className="bg-white p-4 rounded shadow mt-4">
<Button onClick={handleAiClick}> <Button onClick={handleAiClick} disabled={aiLoading || clickCount >= DAILY_CLICK_LIMIT}>
{buttonLabel} {buttonLabel}
</Button> </Button>
{aiLoading && <p>Generating your next steps</p>} {aiLoading && <p>Generating your next steps</p>}
{/* If we have structured recs, show checkboxes */} {/* If we have structured recs, show checkboxes */}