From be2ddda4d5659937ad9bb4a5e2cc79fe2c37c12e Mon Sep 17 00:00:00 2001 From: MrPiglr <31398225+MrPiglr@users.noreply.github.com> Date: Mon, 22 Dec 2025 02:26:12 +0000 Subject: [PATCH] modified: server/supabase.ts --- .env | 5 + client/src/components/Chatbot.tsx | 45 +++++++-- package-lock.json | 59 ++++++------ package.json | 1 + server/.env | 5 + server/index.ts | 5 + server/openai.ts | 153 +++++++++++++++++++++++++++--- server/routes.ts | 34 ++++++- server/storage.ts | 41 ++++++++ server/supabase.ts | 5 + 10 files changed, 306 insertions(+), 47 deletions(-) create mode 100644 .env create mode 100644 server/.env diff --git a/.env b/.env new file mode 100644 index 0000000..fd075aa --- /dev/null +++ b/.env @@ -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 \ No newline at end of file diff --git a/client/src/components/Chatbot.tsx b/client/src/components/Chatbot.tsx index de576d4..fb8c58c 100644 --- a/client/src/components/Chatbot.tsx +++ b/client/src/components/Chatbot.tsx @@ -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 */}
-
- +
+
-
AeThex Assistant
-
- Online +
AEGIS
+
+ Security Active
diff --git a/package-lock.json b/package-lock.json index 4fc438f..848f352 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" } diff --git a/package.json b/package.json index 1e15f2e..5578171 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/server/.env b/server/.env new file mode 100644 index 0000000..fd075aa --- /dev/null +++ b/server/.env @@ -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 \ No newline at end of file diff --git a/server/index.ts b/server/index.ts index ba3ef70..5418ead 100644 --- a/server/index.ts +++ b/server/index.ts @@ -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"; diff --git a/server/openai.ts b/server/openai.ts index 9a1cd14..d744066 100644 --- a/server/openai.ts +++ b/server/openai.ts @@ -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 { +// Live data fetching functions +async function fetchSystemMetrics(): Promise { + 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 { + 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 { + 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 { + 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 { 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") { diff --git a/server/routes.ts b/server/routes.ts index 0c579b9..ebe73c7 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -489,6 +489,18 @@ export async function registerRoutes( const chatRateLimits = new Map(); + // 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); diff --git a/server/storage.ts b/server/storage.ts index fdb8d8d..f3079f0 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -205,6 +205,47 @@ export class SupabaseStorage implements IStorage { return data; } + // Chat Messages (AI memory) + async getChatHistory(userId: string, limit: number = 50): Promise { + 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 { + 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 { + 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; diff --git a/server/supabase.ts b/server/supabase.ts index 54d423c..695795a 100644 --- a/server/supabase.ts +++ b/server/supabase.ts @@ -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;