-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.ts
More file actions
120 lines (99 loc) · 3.89 KB
/
server.ts
File metadata and controls
120 lines (99 loc) · 3.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import express from 'express';
import path from 'path';
import { createServer as createViteServer } from 'vite';
import { GoogleGenAI } from '@google/genai';
import dotenv from 'dotenv';
dotenv.config();
const ai = new GoogleGenAI({
apiKey: process.env.GEMINI_API_KEY,
httpOptions: {
headers: {
'User-Agent': 'aistudio-build',
}
}
});
// 1-hour cache to prevent quota exhaustion
const tipsCache = new Map<string, { tips: string[], expiry: number }>();
async function startServer() {
const app = express();
const PORT = 3000;
app.use(express.json());
// API Route for Gemini Financial Tips
app.post('/api/tips', async (req, res) => {
console.log('[API_TIPS]: Received request');
try {
const { budgetSpent, totalBalance, budgetLimits, userId } = req.body;
// Use userId for caching if available, otherwise fallback to balance bucket
const cacheKey = userId || `bal_${Math.floor((totalBalance || 0) / 1000)}`;
// Check cache (valid for 1 hour)
const cached = tipsCache.get(cacheKey);
if (cached && cached.expiry > Date.now()) {
console.log('[API_TIPS]: Serving from 1-hour cache');
return res.json({ tips: cached.tips });
}
const apiKey = process.env.GEMINI_API_KEY;
if (!apiKey) {
console.error('[API_TIPS]: Missing GEMINI_API_KEY');
return res.status(500).json({ error: 'GEMINI_API_KEY is not configured' });
}
console.log('[API_TIPS]: Payload received', { totalBalance, userId });
const prompt = `
You are an AI Financial Advisor for an app called Pocket Pay (UPI Simulator).
Based on the following budget data, give 3 short, actionable financial tips.
Account Balance: ₹${totalBalance || 0}
Budget Spent: ${JSON.stringify(budgetSpent || {})}
Budget Limits: ${JSON.stringify(budgetLimits || {})}
Keep each tip under 12 words. Focus on overspending or smart savings.
Return as a raw JSON array of strings. Do not include markdown blocks.
`;
console.log('[API_TIPS]: Calling Gemini...');
let tips: string[] = ["Track your small daily spends.", "Save at least 20% of your income.", "Plan your major expenses."];
try {
const response = await ai.models.generateContent({
model: 'gemini-2.0-flash',
contents: prompt,
});
const responseText = response.text || '';
console.log('[API_TIPS]: Gemini responded');
const jsonMatch = responseText.match(/\[.*\]/s);
if (jsonMatch) {
tips = JSON.parse(jsonMatch[0]);
// Cache the successful response for 1 hour
tipsCache.set(cacheKey, { tips, expiry: Date.now() + 60 * 60 * 1000 });
}
} catch (geminiError: any) {
// Handle 429 quota errors silently to avoid cluttering logs
const isQuota = geminiError?.message?.includes('429') || geminiError?.status === 429;
if (!isQuota) {
console.error('[API_TIPS]: Gemini call failed', geminiError);
} else {
console.warn('[API_TIPS]: Quota hit, using fallbacks');
}
}
res.json({ tips });
} catch (error) {
console.error('[API_TIPS_CRITICAL]:', error);
res.status(500).json({
error: 'Failed to generate tips',
details: error instanceof Error ? error.message : String(error)
});
}
});
if (process.env.NODE_ENV !== 'production') {
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'spa',
});
app.use(vite.middlewares);
} else {
const distPath = path.join(process.cwd(), 'dist');
app.use(express.static(distPath));
app.get('*', (req, res) => {
res.sendFile(path.join(distPath, 'index.html'));
});
}
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server running at http://localhost:${PORT}`);
});
}
startServer();