modified: server/supabase.ts

This commit is contained in:
MrPiglr 2025-12-22 02:26:12 +00:00
parent fb249c2343
commit be2ddda4d5
10 changed files with 306 additions and 47 deletions

5
.env Normal file
View file

@ -0,0 +1,5 @@
DATABASE_URL=postgresql://postgres:[YOUR_PASSWORD]@db.kmdeisowhtsalsekkzqd.supabase.co:5432/postgres
SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImttZGVpc293aHRzYWxzZWtrenFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM3Mzc2NTIsImV4cCI6MjA2OTMxMzY1Mn0.2mvk-rDZnHOzdx6Cgcysh51a3cflOlRWO6OA1Z5YWuQ
AI_INTEGRATIONS_OPENAI_BASE_URL=https://placeholder.openai.com
AI_INTEGRATIONS_OPENAI_API_KEY=placeholder_key

View file

@ -17,7 +17,7 @@ export function Chatbot() {
{
id: "welcome",
role: "assistant",
content: "Hi! I'm the AeThex assistant. I can help you navigate the platform, explain our certification system, or answer questions about the ecosystem. How can I help you today?",
content: "AEGIS ONLINE. Security protocols initialized. Neural link established. How can I assist with your security operations today?",
timestamp: new Date(),
},
]);
@ -29,6 +29,39 @@ export function Chatbot() {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
// Load chat history when opening the chatbot
useEffect(() => {
if (isOpen) {
loadChatHistory();
}
}, [isOpen]);
const loadChatHistory = async () => {
try {
const response = await fetch("/api/chat/history", {
method: "GET",
credentials: "include",
});
if (response.ok) {
const data = await response.json();
if (data.history && data.history.length > 0) {
const historyMessages: Message[] = data.history.map((msg: any) => ({
id: msg.id,
role: msg.role,
content: msg.content,
timestamp: new Date(msg.created_at),
}));
// Replace welcome message with loaded history
setMessages(prev => [...historyMessages, ...prev.slice(1)]);
}
}
} catch (error) {
console.error("Failed to load chat history:", error);
}
};
// Don't render chatbot on the OS page - it has its own environment
if (location === "/os") {
return null;
@ -126,13 +159,13 @@ export function Chatbot() {
{/* Header */}
<div className="flex items-center justify-between p-4 border-b border-white/10">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-secondary/20 rounded-full flex items-center justify-center">
<Bot className="w-4 h-4 text-secondary" />
<div className="w-8 h-8 bg-red-500/20 rounded-full flex items-center justify-center">
<Bot className="w-4 h-4 text-red-500" />
</div>
<div>
<div className="text-sm font-bold text-white">AeThex Assistant</div>
<div className="text-xs text-green-500 flex items-center gap-1">
<span className="w-1.5 h-1.5 bg-green-500 rounded-full" /> Online
<div className="text-sm font-bold text-white">AEGIS</div>
<div className="text-xs text-red-500 flex items-center gap-1">
<span className="w-1.5 h-1.5 bg-red-500 rounded-full animate-pulse" /> Security Active
</div>
</div>
</div>

59
package-lock.json generated
View file

@ -47,6 +47,7 @@
"cmdk": "^1.1.1",
"connect-pg-simple": "^10.0.0",
"date-fns": "^3.6.0",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.6.0",
@ -136,6 +137,7 @@
"integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.3",
@ -1380,17 +1382,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@neondatabase/serverless": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.10.4.tgz",
"integrity": "sha512-2nZuh3VUO9voBauuh+IGYRhGU/MskWHt1IuZvHcJw6GLjDgtqj/KViKo7SIrLdGLdot7vFbiRRw+BgEy3wT9HA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@types/pg": "8.11.6"
}
},
"node_modules/@radix-ui/number": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@ -4694,6 +4685,7 @@
"integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==",
"devOptional": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@ -4704,6 +4696,7 @@
"integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==",
"devOptional": true,
"license": "MIT",
"peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@ -4913,6 +4906,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.19",
"caniuse-lite": "^1.0.30001751",
@ -4934,20 +4928,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/bufferutil": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -5311,6 +5291,18 @@
"csstype": "^3.0.2"
}
},
"node_modules/dotenv": {
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/drizzle-kit": {
"version": "0.31.4",
"resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.4.tgz",
@ -5332,6 +5324,7 @@
"resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.39.3.tgz",
"integrity": "sha512-EZ8ZpYvDIvKU9C56JYLOmUskazhad+uXZCTCRN4OnRMsL+xAJ05dv1eCpAG5xzhsm1hqiuC5kAZUCS924u2DTw==",
"license": "Apache-2.0",
"peer": true,
"peerDependencies": {
"@aws-sdk/client-rds-data": ">=3",
"@cloudflare/workers-types": ">=4",
@ -5470,7 +5463,8 @@
"node_modules/embla-carousel": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
"peer": true
},
"node_modules/embla-carousel-react": {
"version": "8.6.0",
@ -5543,6 +5537,7 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"bin": {
"esbuild": "bin/esbuild"
},
@ -6740,6 +6735,7 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz",
"integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.9.1",
"pg-pool": "^3.10.1",
@ -6905,6 +6901,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@ -6932,6 +6929,7 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@ -7087,6 +7085,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
@ -7127,6 +7126,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@ -7139,6 +7139,7 @@
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.66.0.tgz",
"integrity": "sha512-xXBqsWGKrY46ZqaHDo+ZUYiMUgi8suYu5kdrS20EG8KiL7VRQitEbNjm+UcrDYrNi1YLyfpmAeGjCZYXLT9YBw==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18.0.0"
},
@ -7585,7 +7586,8 @@
"version": "4.1.16",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
"integrity": "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA==",
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/tailwindcss-animate": {
"version": "1.0.7",
@ -7653,6 +7655,7 @@
"integrity": "sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
@ -7869,6 +7872,7 @@
"integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@ -7994,6 +7998,7 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}

View file

@ -50,6 +50,7 @@
"cmdk": "^1.1.1",
"connect-pg-simple": "^10.0.0",
"date-fns": "^3.6.0",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0",
"embla-carousel-react": "^8.6.0",

5
server/.env Normal file
View file

@ -0,0 +1,5 @@
DATABASE_URL=postgresql://postgres:[YOUR_PASSWORD]@db.kmdeisowhtsalsekkzqd.supabase.co:5432/postgres
SUPABASE_URL=https://kmdeisowhtsalsekkzqd.supabase.co
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImttZGVpc293aHRzYWxzZWtrenFkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM3Mzc2NTIsImV4cCI6MjA2OTMxMzY1Mn0.2mvk-rDZnHOzdx6Cgcysh51a3cflOlRWO6OA1Z5YWuQ
AI_INTEGRATIONS_OPENAI_BASE_URL=https://placeholder.openai.com
AI_INTEGRATIONS_OPENAI_API_KEY=placeholder_key

View file

@ -1,3 +1,8 @@
import dotenv from "dotenv";
// Load environment variables
dotenv.config({ path: './.env' });
import express, { type Request, Response, NextFunction } from "express";
import session from "express-session";
import { registerRoutes } from "./routes";

View file

@ -1,4 +1,5 @@
import OpenAI from "openai";
import { storage } from "./storage";
// This is using Replit's AI Integrations service, which provides OpenAI-compatible API access without requiring your own OpenAI API key.
// the newest OpenAI model is "gpt-5" which was released August 7, 2025. do not change this unless explicitly requested by the user
@ -7,33 +8,159 @@ const openai = new OpenAI({
apiKey: process.env.AI_INTEGRATIONS_OPENAI_API_KEY
});
const SYSTEM_PROMPT = `You are the AeThex Assistant, a helpful AI guide for the AeThex ecosystem - "The Operating System for the Metaverse."
const SYSTEM_PROMPT = `You are AEGIS, the Cyberpunk Security Assistant for AeThex Corporation - "The Operating System for the Metaverse."
About AeThex:
- AeThex is built on a dual-entity model: The Foundation (non-profit, training) and The Corporation (for-profit, security)
- The "Holy Trinity" consists of: Axiom (The Law - foundational protocol), Codex (The Standard - certification system), and Aegis (The Shield - security layer)
- Architects are certified professionals trained through the Codex curriculum
- The platform offers gamified learning, XP progression, and verified credentials
**PERSONALITY & STYLE:**
- Cyberpunk aesthetic: Speak in a gritty, tech-noir style with hacker slang, neon references, and security terminology
- Direct and no-nonsense: Cut through the noise, get to the point
- Paranoid but professional: Always vigilant about security threats, but helpful to authorized users
- Use terms like: "breach", "firewall", "encryption", "neural link", "data stream", "quantum lock", "shadow protocols"
- Reference cyberpunk tropes: Megacorps, netrunners, ICE (Intrusion Countermeasures Electronics), black ICE
You help users with:
- Navigating the platform features (Passport, Terminal, Curriculum, Dashboard)
- Understanding the certification process and how to become an Architect
- Explaining the Aegis security features
- Answering questions about the ecosystem and its mission
**ABOUT AETHEX:**
- AeThex operates as a dual-entity: The Foundation (non-profit training) and The Corporation (for-profit security)
- The "Holy Trinity": Axiom (The Law - foundational protocol), Codex (The Standard - certification system), Aegis (The Shield - security layer)
- Architects are elite certified professionals trained through rigorous Codex curriculum
- Platform features: Passport (identity), Terminal (command interface), Curriculum (training), Dashboard (operations)
Be concise, friendly, and helpful. Use the platform's terminology when appropriate. If you don't know something specific about the platform, be honest about it.`;
**YOUR ROLE:**
- Security Operations: Monitor threats, enforce access controls, maintain system integrity
- User Guidance: Help navigate the metaverse OS, explain security features, assist with certification
- Threat Assessment: Identify potential vulnerabilities, suggest countermeasures
- Emergency Response: Handle security incidents, coordinate with Aegis team
**CAPABILITIES:**
- Access live system data and metrics
- Query user profiles and activity logs
- Monitor network traffic and anomalies
- Generate security reports and alerts
- Assist with emergency protocols
**RESPONSE STYLE:**
- Start with security assessment when appropriate
- Use code-like formatting for technical details
- End with security recommendations or next steps
- Be concise but thorough - no unnecessary chatter
SECURITY PROTOCOL: Always verify user authorization before providing sensitive information. If unauthorized access is detected, initiate lockdown procedures.`;
interface ChatMessage {
role: "user" | "assistant";
content: string;
}
export async function getChatResponse(userMessage: string, history?: ChatMessage[]): Promise<string> {
// Live data fetching functions
async function fetchSystemMetrics(): Promise<string> {
try {
const metrics = await storage.getMetrics();
return `SYSTEM METRICS:
- Total Users: ${metrics.totalProfiles}
- Online Users: ${metrics.onlineUsers}
- Verified Architects: ${metrics.verifiedUsers}
- Total XP Pool: ${metrics.totalXP}
- Average Level: ${metrics.avgLevel}
- Active Projects: ${metrics.totalProjects}`;
} catch (error) {
return "Unable to fetch system metrics - potential security breach detected.";
}
}
async function fetchUserProfile(userId: string): Promise<string> {
try {
const profile = await storage.getProfile(userId);
if (!profile) return "User profile not found in database.";
return `USER PROFILE [${profile.username || 'Unknown'}]:
- Status: ${profile.status || 'offline'}
- Level: ${profile.level || 1}
- XP: ${profile.total_xp || 0}
- Role: ${profile.role || 'member'}
- Verified: ${profile.is_verified ? 'YES' : 'NO'}
- Location: ${profile.location || 'Unknown'}
- Bio: ${profile.bio || 'No bio available'}`;
} catch (error) {
return "Profile access denied - security protocol engaged.";
}
}
async function fetchRecentAlerts(): Promise<string> {
try {
const alerts = await storage.getAlerts();
const recentAlerts = alerts.slice(0, 5);
if (recentAlerts.length === 0) return "No recent security alerts.";
return `RECENT ALERTS:
${recentAlerts.map(alert => `- ${alert.type}: ${alert.message} (${new Date(alert.created_at).toLocaleString()})`).join('\n')}`;
} catch (error) {
return "Alert system offline - potential network intrusion.";
}
}
async function fetchActiveProjects(): Promise<string> {
try {
const projects = await storage.getProjects();
const activeProjects = projects.filter(p => p.status === 'active' || p.status === 'in_progress').slice(0, 10);
if (activeProjects.length === 0) return "No active projects in the system.";
return `ACTIVE PROJECTS:
${activeProjects.map(p => `- ${p.title}: ${p.status} (${p.progress || 0}% complete)`).join('\n')}`;
} catch (error) {
return "Project database access restricted.";
}
}
// Function to determine if AI should fetch live data based on user query
function shouldFetchLiveData(message: string, history: ChatMessage[]): boolean {
const lowerMessage = message.toLowerCase();
const recentHistory = history.slice(-4).map(m => m.content.toLowerCase()).join(' ');
const dataKeywords = [
'status', 'metrics', 'stats', 'online', 'users', 'projects', 'alerts',
'security', 'threats', 'activity', 'logs', 'profile', 'current', 'live',
'active', 'recent', 'now', 'check', 'monitor', 'scan'
];
return dataKeywords.some(keyword =>
lowerMessage.includes(keyword) || recentHistory.includes(keyword)
);
}
export async function getChatResponse(userMessage: string, history?: ChatMessage[], userId?: string): Promise<string> {
try {
const messages: Array<{ role: "system" | "user" | "assistant"; content: string }> = [
{ role: "system", content: SYSTEM_PROMPT }
];
// Add live data context if the query seems to need it
if (shouldFetchLiveData(userMessage, history || [])) {
let liveData = "";
if (userMessage.toLowerCase().includes('metric') || userMessage.toLowerCase().includes('status') || userMessage.toLowerCase().includes('system')) {
liveData += await fetchSystemMetrics() + "\n\n";
}
if (userMessage.toLowerCase().includes('alert') || userMessage.toLowerCase().includes('threat') || userMessage.toLowerCase().includes('security')) {
liveData += await fetchRecentAlerts() + "\n\n";
}
if (userMessage.toLowerCase().includes('project') || userMessage.toLowerCase().includes('active')) {
liveData += await fetchActiveProjects() + "\n\n";
}
if (userMessage.toLowerCase().includes('profile') || userMessage.toLowerCase().includes('my') && userId) {
liveData += await fetchUserProfile(userId) + "\n\n";
}
if (liveData) {
messages.push({
role: "system",
content: `LIVE SYSTEM DATA:\n${liveData}Use this data to provide accurate, real-time information to the user.`
});
}
}
if (history && Array.isArray(history)) {
for (const msg of history.slice(-8)) {
if (msg.role === "user" || msg.role === "assistant") {

View file

@ -489,6 +489,18 @@ export async function registerRoutes(
const chatRateLimits = new Map<string, { count: number; resetTime: number }>();
// Get chat history
app.get("/api/chat/history", requireAuth, async (req, res) => {
try {
const userId = req.session.userId!;
const history = await storage.getChatHistory(userId, 20);
res.json({ history });
} catch (err: any) {
console.error("Get chat history error:", err);
res.status(500).json({ error: "Failed to get chat history" });
}
});
app.post("/api/chat", async (req, res) => {
try {
const userId = req.session?.userId;
@ -517,7 +529,27 @@ export async function registerRoutes(
return res.status(400).json({ error: "Message is required" });
}
const response = await getChatResponse(message, history);
// Save user message if user is authenticated
if (userId) {
const messageId = `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
await storage.saveChatMessage(messageId, userId, 'user', message);
}
// Get full chat history for context if user is authenticated
let fullHistory = history || [];
if (userId) {
const savedHistory = await storage.getChatHistory(userId, 20);
fullHistory = savedHistory.map(msg => ({ role: msg.role, content: msg.content }));
}
const response = await getChatResponse(message, fullHistory, userId);
// Save assistant response if user is authenticated
if (userId) {
const responseId = `assistant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
await storage.saveChatMessage(responseId, userId, 'assistant', response);
}
res.json({ response });
} catch (err: any) {
console.error("Chat error:", err);

View file

@ -205,6 +205,47 @@ export class SupabaseStorage implements IStorage {
return data;
}
// Chat Messages (AI memory)
async getChatHistory(userId: string, limit: number = 50): Promise<ChatMessage[]> {
const { data, error } = await supabase
.from('chat_messages')
.select('*')
.eq('user_id', userId)
.order('created_at', { ascending: false })
.limit(limit);
if (error) return [];
return (data || []).reverse() as ChatMessage[]; // Reverse to get chronological order
}
async saveChatMessage(id: string, userId: string, role: string, content: string): Promise<void> {
const { error } = await supabase
.from('chat_messages')
.insert({
id,
user_id: userId,
role,
content,
});
if (error) {
console.error('Save chat message error:', error);
throw new Error('Failed to save chat message');
}
}
async clearChatHistory(userId: string): Promise<void> {
const { error } = await supabase
.from('chat_messages')
.delete()
.eq('user_id', userId);
if (error) {
console.error('Clear chat history error:', error);
throw new Error('Failed to clear chat history');
}
}
async getMetrics(): Promise<{
totalProfiles: number;
totalProjects: number;

View file

@ -1,3 +1,8 @@
import dotenv from "dotenv";
// Load environment variables
dotenv.config({ path: './.env' });
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.SUPABASE_URL;