myavana-bot-test-core 2.1.4 โ 2.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +3 -0
- package/package.json +1 -1
- package/setup-cache-maintenance.sh +126 -0
- package/src/ai.js +1 -1
- package/src/cacheMaintenanceScript.js +256 -0
- package/src/cacheManager.js +262 -0
package/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// packages/core/index.js - Fixed exports with proper module loading
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const cacheManager = require('./src/cacheManager');
|
|
3
4
|
|
|
4
5
|
// Helper function to safely load modules
|
|
5
6
|
function safeRequire(modulePath, moduleName) {
|
|
@@ -28,6 +29,8 @@ const modules = {
|
|
|
28
29
|
// AI and session management
|
|
29
30
|
ai: safeRequire('./src/ai', 'ai'),
|
|
30
31
|
DatabaseSessionStore: safeRequire('./src/session', 'DatabaseSessionStore'),
|
|
32
|
+
cacheManager: safeRequire('./src/cacheManager', 'cacheManager'),
|
|
33
|
+
|
|
31
34
|
|
|
32
35
|
// User and conversation management
|
|
33
36
|
getUserDetails: safeRequire('./src/user', 'user')?.getUserDetails,
|
package/package.json
CHANGED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# setup-cache-maintenance.sh
|
|
3
|
+
# Sets up automated cache maintenance via cron
|
|
4
|
+
|
|
5
|
+
echo "๐ง Setting up Gemini Cache Maintenance"
|
|
6
|
+
echo "======================================"
|
|
7
|
+
|
|
8
|
+
# Get the directory where this script is located
|
|
9
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
10
|
+
MAINTENANCE_SCRIPT="$SCRIPT_DIR/cacheMaintenanceScript.js"
|
|
11
|
+
LOG_DIR="$SCRIPT_DIR/logs"
|
|
12
|
+
LOG_FILE="$LOG_DIR/cache-maintenance.log"
|
|
13
|
+
|
|
14
|
+
# Check if Node.js is installed
|
|
15
|
+
if ! command -v node &> /dev/null; then
|
|
16
|
+
echo "โ Error: Node.js is not installed"
|
|
17
|
+
echo "Please install Node.js first: https://nodejs.org/"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
echo "โ
Node.js found: $(node --version)"
|
|
22
|
+
|
|
23
|
+
# Check if maintenance script exists
|
|
24
|
+
if [ ! -f "$MAINTENANCE_SCRIPT" ]; then
|
|
25
|
+
echo "โ Error: Maintenance script not found at $MAINTENANCE_SCRIPT"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo "โ
Maintenance script found"
|
|
30
|
+
|
|
31
|
+
# Create logs directory if it doesn't exist
|
|
32
|
+
if [ ! -d "$LOG_DIR" ]; then
|
|
33
|
+
mkdir -p "$LOG_DIR"
|
|
34
|
+
echo "โ
Created logs directory: $LOG_DIR"
|
|
35
|
+
else
|
|
36
|
+
echo "โ
Logs directory exists: $LOG_DIR"
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Create log file if it doesn't exist
|
|
40
|
+
touch "$LOG_FILE"
|
|
41
|
+
echo "โ
Log file ready: $LOG_FILE"
|
|
42
|
+
|
|
43
|
+
# Test the maintenance script
|
|
44
|
+
echo ""
|
|
45
|
+
echo "๐งช Testing maintenance script..."
|
|
46
|
+
node "$MAINTENANCE_SCRIPT"
|
|
47
|
+
|
|
48
|
+
if [ $? -eq 0 ]; then
|
|
49
|
+
echo "โ
Maintenance script test successful"
|
|
50
|
+
else
|
|
51
|
+
echo "โ Maintenance script test failed"
|
|
52
|
+
echo "Please check the script for errors before setting up cron"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Generate cron job entry
|
|
57
|
+
CRON_ENTRY="0 2 * * * /usr/bin/node $MAINTENANCE_SCRIPT >> $LOG_FILE 2>&1"
|
|
58
|
+
|
|
59
|
+
echo ""
|
|
60
|
+
echo "๐ Cron Job Configuration"
|
|
61
|
+
echo "========================="
|
|
62
|
+
echo "Schedule: Daily at 2:00 AM"
|
|
63
|
+
echo "Script: $MAINTENANCE_SCRIPT"
|
|
64
|
+
echo "Log: $LOG_FILE"
|
|
65
|
+
echo ""
|
|
66
|
+
echo "Cron entry:"
|
|
67
|
+
echo "$CRON_ENTRY"
|
|
68
|
+
echo ""
|
|
69
|
+
|
|
70
|
+
# Ask user if they want to add to crontab
|
|
71
|
+
read -p "Do you want to add this to your crontab? (y/n) " -n 1 -r
|
|
72
|
+
echo
|
|
73
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
74
|
+
# Check if cron entry already exists
|
|
75
|
+
if crontab -l 2>/dev/null | grep -q "$MAINTENANCE_SCRIPT"; then
|
|
76
|
+
echo "โ ๏ธ Cron job already exists for this script"
|
|
77
|
+
read -p "Do you want to remove the old entry and add a new one? (y/n) " -n 1 -r
|
|
78
|
+
echo
|
|
79
|
+
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
|
80
|
+
# Remove old entries
|
|
81
|
+
crontab -l 2>/dev/null | grep -v "$MAINTENANCE_SCRIPT" | crontab -
|
|
82
|
+
echo "โ
Removed old cron entry"
|
|
83
|
+
else
|
|
84
|
+
echo "โ Setup cancelled"
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Add new cron entry
|
|
90
|
+
(crontab -l 2>/dev/null; echo "$CRON_ENTRY") | crontab -
|
|
91
|
+
|
|
92
|
+
if [ $? -eq 0 ]; then
|
|
93
|
+
echo "โ
Cron job added successfully!"
|
|
94
|
+
echo ""
|
|
95
|
+
echo "Current crontab:"
|
|
96
|
+
crontab -l | grep "$MAINTENANCE_SCRIPT"
|
|
97
|
+
else
|
|
98
|
+
echo "โ Failed to add cron job"
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
else
|
|
102
|
+
echo "โน๏ธ Cron job not added"
|
|
103
|
+
echo ""
|
|
104
|
+
echo "To add manually, run:"
|
|
105
|
+
echo "crontab -e"
|
|
106
|
+
echo ""
|
|
107
|
+
echo "Then add this line:"
|
|
108
|
+
echo "$CRON_ENTRY"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo ""
|
|
112
|
+
echo "๐ Setup Complete!"
|
|
113
|
+
echo ""
|
|
114
|
+
echo "๐ Next Steps:"
|
|
115
|
+
echo "1. Monitor logs: tail -f $LOG_FILE"
|
|
116
|
+
echo "2. Test manually: node $MAINTENANCE_SCRIPT"
|
|
117
|
+
echo "3. View cron jobs: crontab -l"
|
|
118
|
+
echo "4. Edit cron schedule: crontab -e"
|
|
119
|
+
echo ""
|
|
120
|
+
echo "โฐ Schedule Options:"
|
|
121
|
+
echo "Daily at 2 AM: 0 2 * * *"
|
|
122
|
+
echo "Every 12 hours: 0 */12 * * *"
|
|
123
|
+
echo "Every 6 hours: 0 */6 * * *"
|
|
124
|
+
echo "Hourly: 0 * * * *"
|
|
125
|
+
echo ""
|
|
126
|
+
echo "To change schedule: crontab -e"
|
package/src/ai.js
CHANGED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// cacheMaintenanceScript.js
|
|
2
|
+
// Run this script daily via cron to cleanup expired caches and monitor cache health
|
|
3
|
+
|
|
4
|
+
const cacheManager = require('./cacheManager');
|
|
5
|
+
const { pgClient, redisClient } = require('./database');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Comprehensive cache maintenance and monitoring script
|
|
9
|
+
*/
|
|
10
|
+
async function runMaintenance() {
|
|
11
|
+
console.log('๐งน ===== CACHE MAINTENANCE SCRIPT START =====');
|
|
12
|
+
console.log('๐
Timestamp:', new Date().toISOString());
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
|
|
16
|
+
if (!pgClient) {
|
|
17
|
+
throw new Error('Database connection not available');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log('โ
Database connection established');
|
|
21
|
+
|
|
22
|
+
// Step 1: Get cache statistics BEFORE cleanup
|
|
23
|
+
console.log('\n๐ ===== PRE-CLEANUP STATISTICS =====');
|
|
24
|
+
const preStats = await cacheManager.getCacheStats();
|
|
25
|
+
|
|
26
|
+
if (preStats) {
|
|
27
|
+
console.log('Cache Statistics:', {
|
|
28
|
+
totalCaches: preStats.total_caches,
|
|
29
|
+
activeCaches: preStats.active_caches,
|
|
30
|
+
expiredCaches: preStats.expired_caches,
|
|
31
|
+
avgAgeHours: parseFloat(preStats.avg_age_hours).toFixed(2),
|
|
32
|
+
hitRate: preStats.active_caches > 0
|
|
33
|
+
? ((preStats.active_caches / preStats.total_caches) * 100).toFixed(2) + '%'
|
|
34
|
+
: 'N/A'
|
|
35
|
+
});
|
|
36
|
+
} else {
|
|
37
|
+
console.log('โ ๏ธ Could not retrieve cache statistics');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Step 2: Cleanup expired caches
|
|
41
|
+
console.log('\n๐งน ===== CLEANING UP EXPIRED CACHES =====');
|
|
42
|
+
const cleanupStartTime = Date.now();
|
|
43
|
+
await cacheManager.cleanupExpiredCaches();
|
|
44
|
+
const cleanupTime = Date.now() - cleanupStartTime;
|
|
45
|
+
console.log(`โ
Cleanup completed in ${cleanupTime}ms`);
|
|
46
|
+
|
|
47
|
+
// Step 3: Get cache statistics AFTER cleanup
|
|
48
|
+
console.log('\n๐ ===== POST-CLEANUP STATISTICS =====');
|
|
49
|
+
const postStats = await cacheManager.getCacheStats();
|
|
50
|
+
|
|
51
|
+
if (postStats) {
|
|
52
|
+
console.log('Updated Cache Statistics:', {
|
|
53
|
+
totalCaches: postStats.total_caches,
|
|
54
|
+
activeCaches: postStats.active_caches,
|
|
55
|
+
expiredCaches: postStats.expired_caches,
|
|
56
|
+
avgAgeHours: parseFloat(postStats.avg_age_hours).toFixed(2),
|
|
57
|
+
hitRate: postStats.active_caches > 0
|
|
58
|
+
? ((postStats.active_caches / postStats.total_caches) * 100).toFixed(2) + '%'
|
|
59
|
+
: 'N/A'
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Calculate cleanup impact
|
|
63
|
+
if (preStats) {
|
|
64
|
+
const recordsRemoved = preStats.total_caches - postStats.total_caches;
|
|
65
|
+
console.log('\n๐ Cleanup Impact:', {
|
|
66
|
+
recordsRemoved: recordsRemoved,
|
|
67
|
+
percentageRemoved: preStats.total_caches > 0
|
|
68
|
+
? ((recordsRemoved / preStats.total_caches) * 100).toFixed(2) + '%'
|
|
69
|
+
: '0%'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Step 4: Get cache age distribution
|
|
75
|
+
console.log('\n๐ ===== CACHE AGE DISTRIBUTION =====');
|
|
76
|
+
const ageDistribution = await getCacheAgeDistribution();
|
|
77
|
+
if (ageDistribution) {
|
|
78
|
+
console.log('Age Distribution:', ageDistribution);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Step 5: Identify users with stale or missing caches
|
|
82
|
+
console.log('\n๐ ===== CACHE HEALTH CHECK =====');
|
|
83
|
+
const healthCheck = await performCacheHealthCheck();
|
|
84
|
+
if (healthCheck) {
|
|
85
|
+
console.log('Health Check Results:', healthCheck);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Step 6: Generate recommendations
|
|
89
|
+
console.log('\n๐ก ===== RECOMMENDATIONS =====');
|
|
90
|
+
generateRecommendations(preStats, postStats);
|
|
91
|
+
|
|
92
|
+
console.log('\nโ
===== CACHE MAINTENANCE COMPLETE =====');
|
|
93
|
+
console.log('๐
Completed at:', new Date().toISOString());
|
|
94
|
+
|
|
95
|
+
// Close database connection if needed
|
|
96
|
+
|
|
97
|
+
process.exit(0);
|
|
98
|
+
|
|
99
|
+
} catch (error) {
|
|
100
|
+
console.error('\nโ ===== MAINTENANCE SCRIPT FAILED =====');
|
|
101
|
+
console.error('Error:', {
|
|
102
|
+
message: error.message,
|
|
103
|
+
stack: error.stack,
|
|
104
|
+
timestamp: new Date().toISOString()
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get cache age distribution
|
|
113
|
+
*/
|
|
114
|
+
async function getCacheAgeDistribution() {
|
|
115
|
+
try {
|
|
116
|
+
const query = `
|
|
117
|
+
SELECT
|
|
118
|
+
CASE
|
|
119
|
+
WHEN TIMESTAMPDIFF(HOUR, created_at, NOW()) < 6 THEN '0-6 hours'
|
|
120
|
+
WHEN TIMESTAMPDIFF(HOUR, created_at, NOW()) < 12 THEN '6-12 hours'
|
|
121
|
+
WHEN TIMESTAMPDIFF(HOUR, created_at, NOW()) < 18 THEN '12-18 hours'
|
|
122
|
+
WHEN TIMESTAMPDIFF(HOUR, created_at, NOW()) < 24 THEN '18-24 hours'
|
|
123
|
+
ELSE '24+ hours (expired)'
|
|
124
|
+
END as age_range,
|
|
125
|
+
COUNT(*) as count
|
|
126
|
+
FROM user_gemini_caches
|
|
127
|
+
WHERE expires_at > NOW()
|
|
128
|
+
GROUP BY age_range
|
|
129
|
+
ORDER BY MIN(TIMESTAMPDIFF(HOUR, created_at, NOW()))
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
const result = await pgClient.query(query);
|
|
133
|
+
return result;
|
|
134
|
+
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error('Failed to get age distribution:', error.message);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Perform cache health check
|
|
143
|
+
*/
|
|
144
|
+
async function performCacheHealthCheck() {
|
|
145
|
+
try {
|
|
146
|
+
// Check for users with very old caches (approaching expiration)
|
|
147
|
+
const expiringQuery = `
|
|
148
|
+
SELECT user_id, cache_name,
|
|
149
|
+
TIMESTAMPDIFF(HOUR, NOW(), expires_at) as hours_until_expiration
|
|
150
|
+
FROM user_gemini_caches
|
|
151
|
+
WHERE expires_at > NOW()
|
|
152
|
+
AND TIMESTAMPDIFF(HOUR, NOW(), expires_at) < 2
|
|
153
|
+
LIMIT 10
|
|
154
|
+
`;
|
|
155
|
+
|
|
156
|
+
const expiringSoon = await pgClient.query(expiringQuery);
|
|
157
|
+
|
|
158
|
+
// Get total unique users who have used chat
|
|
159
|
+
const totalUsersQuery = `
|
|
160
|
+
SELECT COUNT(DISTINCT user_id) as total_users
|
|
161
|
+
FROM user_gemini_caches
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const totalUsersResult = await pgClient.query(totalUsersQuery);
|
|
165
|
+
const totalUsers = totalUsersResult[0].total_users;
|
|
166
|
+
|
|
167
|
+
// Get active cache count
|
|
168
|
+
const activeCacheQuery = `
|
|
169
|
+
SELECT COUNT(*) as active_count
|
|
170
|
+
FROM user_gemini_caches
|
|
171
|
+
WHERE expires_at > NOW()
|
|
172
|
+
`;
|
|
173
|
+
|
|
174
|
+
const activeCacheResult = await pgClient.query(activeCacheQuery);
|
|
175
|
+
const activeCaches = activeCacheResult[0].active_count;
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
totalUsers: totalUsers,
|
|
179
|
+
usersWithActiveCache: activeCaches,
|
|
180
|
+
cacheHitPotential: totalUsers > 0
|
|
181
|
+
? ((activeCaches / totalUsers) * 100).toFixed(2) + '%'
|
|
182
|
+
: 'N/A',
|
|
183
|
+
cachesExpiringSoon: expiringSoon.length,
|
|
184
|
+
expiringSoonDetails: expiringSoon.slice(0, 3) // Show first 3
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Health check failed:', error.message);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generate recommendations based on statistics
|
|
195
|
+
*/
|
|
196
|
+
function generateRecommendations(preStats, postStats) {
|
|
197
|
+
const recommendations = [];
|
|
198
|
+
|
|
199
|
+
if (!postStats) {
|
|
200
|
+
console.log('โ ๏ธ Cannot generate recommendations without statistics');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check cache hit rate
|
|
205
|
+
const hitRate = postStats.active_caches > 0
|
|
206
|
+
? (postStats.active_caches / postStats.total_caches) * 100
|
|
207
|
+
: 0;
|
|
208
|
+
|
|
209
|
+
if (hitRate < 50) {
|
|
210
|
+
recommendations.push('โ ๏ธ Low cache hit rate (<50%). Consider:');
|
|
211
|
+
recommendations.push(' - Investigating why caches are expiring quickly');
|
|
212
|
+
recommendations.push(' - Checking if TTL needs adjustment');
|
|
213
|
+
} else if (hitRate > 80) {
|
|
214
|
+
recommendations.push('โ
Excellent cache hit rate (>80%)!');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Check average age
|
|
218
|
+
const avgAge = parseFloat(postStats.avg_age_hours);
|
|
219
|
+
if (avgAge < 6) {
|
|
220
|
+
recommendations.push('โ ๏ธ Low average cache age (<6 hours). Consider:');
|
|
221
|
+
recommendations.push(' - Checking if caches are being invalidated prematurely');
|
|
222
|
+
recommendations.push(' - Verifying system time is correct');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check total cache count
|
|
226
|
+
if (postStats.total_caches < 10) {
|
|
227
|
+
recommendations.push('โน๏ธ Low total cache count. This is normal for new deployments.');
|
|
228
|
+
} else if (postStats.total_caches > 10000) {
|
|
229
|
+
recommendations.push('โ ๏ธ High cache count (>10k). Consider:');
|
|
230
|
+
recommendations.push(' - More frequent cleanup runs');
|
|
231
|
+
recommendations.push(' - Adding database indexes for performance');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Check cleanup effectiveness
|
|
235
|
+
if (preStats && postStats) {
|
|
236
|
+
const recordsRemoved = preStats.total_caches - postStats.total_caches;
|
|
237
|
+
if (recordsRemoved === 0 && preStats.expired_caches > 0) {
|
|
238
|
+
recommendations.push('โ ๏ธ Cleanup ran but removed no records. Check cleanup logic.');
|
|
239
|
+
} else if (recordsRemoved > 0) {
|
|
240
|
+
recommendations.push(`โ
Cleanup successfully removed ${recordsRemoved} expired records.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (recommendations.length === 0) {
|
|
245
|
+
console.log('โ
All systems normal. No recommendations.');
|
|
246
|
+
} else {
|
|
247
|
+
recommendations.forEach(rec => console.log(rec));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Run the maintenance script
|
|
252
|
+
if (require.main === module) {
|
|
253
|
+
runMaintenance();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = { runMaintenance };
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// cacheManager.js
|
|
2
|
+
|
|
3
|
+
const ai = require('./ai');
|
|
4
|
+
const { googleAI } = require('@genkit-ai/google-genai');
|
|
5
|
+
const { pgClient, redisClient } = require('./database');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cache Manager for Gemini API
|
|
9
|
+
* Handles creation, retrieval, and expiration of cached system instructions
|
|
10
|
+
*/
|
|
11
|
+
class CacheManager {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.CACHE_TTL_HOURS = 24;
|
|
14
|
+
this.CACHE_TTL_SECONDS = this.CACHE_TTL_HOURS * 60 * 60; // 24 hours in seconds
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get or create a cache for a user's system prompt
|
|
19
|
+
* @param {string} userId - User identifier
|
|
20
|
+
* @param {string} systemPrompt - The large system instruction to cache
|
|
21
|
+
* @returns {Promise<{cacheName: string, isNewCache: boolean}>}
|
|
22
|
+
*/
|
|
23
|
+
async getOrCreateCache(userId, systemPrompt) {
|
|
24
|
+
console.log('๐ ===== CACHE MANAGER: GET OR CREATE =====');
|
|
25
|
+
console.log('๐ Cache Request:', {
|
|
26
|
+
userId: userId,
|
|
27
|
+
systemPromptLength: systemPrompt.length,
|
|
28
|
+
estimatedTokens: Math.ceil(systemPrompt.length / 4),
|
|
29
|
+
timestamp: new Date().toISOString()
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Try to get existing cache for user
|
|
34
|
+
const existingCache = await this.getUserCache(userId);
|
|
35
|
+
|
|
36
|
+
if (existingCache && !this.isCacheExpired(existingCache)) {
|
|
37
|
+
console.log('โ
Using existing cache:', {
|
|
38
|
+
cacheName: existingCache.cache_name,
|
|
39
|
+
createdAt: existingCache.created_at,
|
|
40
|
+
expiresAt: existingCache.expires_at,
|
|
41
|
+
ageHours: this.getCacheAgeHours(existingCache),
|
|
42
|
+
remainingHours: this.getCacheRemainingHours(existingCache)
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
cacheName: existingCache.cache_name,
|
|
47
|
+
isNewCache: false
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create new cache
|
|
52
|
+
console.log('๐ Creating new cache for user:', userId);
|
|
53
|
+
const cacheStartTime = performance.now();
|
|
54
|
+
|
|
55
|
+
const cache = await ai.caches.create({
|
|
56
|
+
model: googleAI.model('gemini-3-flash-preview'),
|
|
57
|
+
config: {
|
|
58
|
+
systemInstruction: systemPrompt,
|
|
59
|
+
},
|
|
60
|
+
ttlSeconds: this.CACHE_TTL_SECONDS
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const cacheCreationTime = performance.now() - cacheStartTime;
|
|
64
|
+
console.log('โ
Cache created successfully:', {
|
|
65
|
+
cacheName: cache.name,
|
|
66
|
+
creationTime: cacheCreationTime.toFixed(2) + 'ms',
|
|
67
|
+
ttlHours: this.CACHE_TTL_HOURS,
|
|
68
|
+
expiresAt: new Date(Date.now() + this.CACHE_TTL_SECONDS * 1000).toISOString()
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Save cache info to database
|
|
72
|
+
await this.saveUserCache(userId, cache.name);
|
|
73
|
+
|
|
74
|
+
console.log('๐พ Cache info saved to database');
|
|
75
|
+
console.log('๐ ===== CACHE MANAGER: COMPLETE =====');
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
cacheName: cache.name,
|
|
79
|
+
isNewCache: true
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('โ Cache creation/retrieval failed:', {
|
|
84
|
+
error: error.message,
|
|
85
|
+
userId: userId,
|
|
86
|
+
timestamp: new Date().toISOString()
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Return null to indicate cache unavailable - caller should proceed without cache
|
|
90
|
+
return {
|
|
91
|
+
cacheName: null,
|
|
92
|
+
isNewCache: false,
|
|
93
|
+
error: error.message
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get user's cache info from database
|
|
100
|
+
* @param {string} userId
|
|
101
|
+
* @returns {Promise<object|null>}
|
|
102
|
+
*/
|
|
103
|
+
async getUserCache(userId) {
|
|
104
|
+
console.log('๐ Fetching cache info from database for user:', userId);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const query = `
|
|
108
|
+
SELECT cache_name, created_at, expires_at
|
|
109
|
+
FROM user_gemini_caches
|
|
110
|
+
WHERE user_id = ?
|
|
111
|
+
ORDER BY created_at DESC
|
|
112
|
+
LIMIT 1
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const result = await pgClient.query(query, [userId]);
|
|
116
|
+
|
|
117
|
+
if (result && result.length > 0) {
|
|
118
|
+
console.log('โ
Found existing cache record:', {
|
|
119
|
+
cacheName: result[0].cache_name,
|
|
120
|
+
createdAt: result[0].created_at,
|
|
121
|
+
expiresAt: result[0].expires_at
|
|
122
|
+
});
|
|
123
|
+
return result[0];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log('๐ญ No existing cache found for user');
|
|
127
|
+
return null;
|
|
128
|
+
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.error('โ Database query failed:', error.message);
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Save cache info to database
|
|
137
|
+
* @param {string} userId
|
|
138
|
+
* @param {string} cacheName
|
|
139
|
+
*/
|
|
140
|
+
async saveUserCache(userId, cacheName) {
|
|
141
|
+
console.log('๐พ Saving cache info to database:', {
|
|
142
|
+
userId: userId,
|
|
143
|
+
cacheName: cacheName
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const expiresAt = new Date(Date.now() + this.CACHE_TTL_SECONDS * 1000);
|
|
148
|
+
|
|
149
|
+
const query = `
|
|
150
|
+
INSERT INTO user_gemini_caches
|
|
151
|
+
(user_id, cache_name, created_at, expires_at)
|
|
152
|
+
VALUES (?, ?, NOW(), ?)
|
|
153
|
+
ON DUPLICATE KEY UPDATE
|
|
154
|
+
cache_name = VALUES(cache_name),
|
|
155
|
+
created_at = NOW(),
|
|
156
|
+
expires_at = VALUES(expires_at)
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
await pgClient.query(query, [userId, cacheName, expiresAt]);
|
|
160
|
+
|
|
161
|
+
console.log('โ
Cache info saved successfully');
|
|
162
|
+
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error('โ Failed to save cache info:', error.message);
|
|
165
|
+
// Don't throw - cache creation succeeded, DB save is secondary
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check if cache is expired
|
|
171
|
+
* @param {object} cacheRecord
|
|
172
|
+
* @returns {boolean}
|
|
173
|
+
*/
|
|
174
|
+
isCacheExpired(cacheRecord) {
|
|
175
|
+
const now = new Date();
|
|
176
|
+
const expiresAt = new Date(cacheRecord.expires_at);
|
|
177
|
+
|
|
178
|
+
const isExpired = now >= expiresAt;
|
|
179
|
+
|
|
180
|
+
if (isExpired) {
|
|
181
|
+
console.log('โฐ Cache expired:', {
|
|
182
|
+
expiresAt: expiresAt.toISOString(),
|
|
183
|
+
now: now.toISOString(),
|
|
184
|
+
expiredBy: ((now - expiresAt) / (1000 * 60)).toFixed(2) + ' minutes'
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return isExpired;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get cache age in hours
|
|
193
|
+
* @param {object} cacheRecord
|
|
194
|
+
* @returns {number}
|
|
195
|
+
*/
|
|
196
|
+
getCacheAgeHours(cacheRecord) {
|
|
197
|
+
const now = new Date();
|
|
198
|
+
const createdAt = new Date(cacheRecord.created_at);
|
|
199
|
+
const ageMs = now - createdAt;
|
|
200
|
+
return (ageMs / (1000 * 60 * 60)).toFixed(2);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get remaining cache lifetime in hours
|
|
205
|
+
* @param {object} cacheRecord
|
|
206
|
+
* @returns {number}
|
|
207
|
+
*/
|
|
208
|
+
getCacheRemainingHours(cacheRecord) {
|
|
209
|
+
const now = new Date();
|
|
210
|
+
const expiresAt = new Date(cacheRecord.expires_at);
|
|
211
|
+
const remainingMs = expiresAt - now;
|
|
212
|
+
return Math.max(0, (remainingMs / (1000 * 60 * 60)).toFixed(2));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Clean up expired caches from database (maintenance task)
|
|
217
|
+
*/
|
|
218
|
+
async cleanupExpiredCaches() {
|
|
219
|
+
console.log('๐งน Cleaning up expired caches...');
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const query = `
|
|
223
|
+
DELETE FROM user_gemini_caches
|
|
224
|
+
WHERE expires_at < NOW()
|
|
225
|
+
`;
|
|
226
|
+
|
|
227
|
+
const result = await pgClient.query(query);
|
|
228
|
+
|
|
229
|
+
console.log('โ
Cleanup complete:', {
|
|
230
|
+
deletedRecords: result.affectedRows || 0
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
} catch (error) {
|
|
234
|
+
console.error('โ Cleanup failed:', error.message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get cache statistics for monitoring
|
|
240
|
+
*/
|
|
241
|
+
async getCacheStats() {
|
|
242
|
+
try {
|
|
243
|
+
const query = `
|
|
244
|
+
SELECT
|
|
245
|
+
COUNT(*) as total_caches,
|
|
246
|
+
COUNT(CASE WHEN expires_at > NOW() THEN 1 END) as active_caches,
|
|
247
|
+
COUNT(CASE WHEN expires_at <= NOW() THEN 1 END) as expired_caches,
|
|
248
|
+
AVG(TIMESTAMPDIFF(HOUR, created_at, NOW())) as avg_age_hours
|
|
249
|
+
FROM user_gemini_caches
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
const result = await pgClient.query(query);
|
|
253
|
+
return result[0];
|
|
254
|
+
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('โ Failed to get cache stats:', error.message);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
module.exports = new CacheManager();
|