1- // server/index.js
21import express from 'express' ;
32import cors from 'cors' ;
43import dotenv from 'dotenv' ;
5- import fetch from 'node-fetch' ; // For backend API call
4+ import fetch from 'node-fetch' ;
65import { fileURLToPath } from 'url' ;
76import { dirname , join } from 'path' ;
7+ import { isAbusiveQuery } from '../src/components/chatbot/assistantEngine/moderation.js' ;
88
99// Get the directory path of the current module
1010const __filename = fileURLToPath ( import . meta. url ) ;
@@ -14,56 +14,198 @@ const __dirname = dirname(__filename);
1414dotenv . config ( { path : join ( __dirname , '../.env' ) } ) ;
1515
1616const app = express ( ) ;
17- app . use ( cors ( ) ) ;
17+ const CORS_ALLOWED_ORIGINS = ( process . env . CORS_ALLOWED_ORIGINS || '' )
18+ . split ( ',' )
19+ . map ( origin => origin . trim ( ) )
20+ . filter ( Boolean ) ;
21+ const IS_DEVELOPMENT = process . env . NODE_ENV === 'development' ;
22+
23+ app . use (
24+ cors ( {
25+ origin : ( origin , callback ) => {
26+ if ( ! origin ) return callback ( null , true ) ;
27+ if ( CORS_ALLOWED_ORIGINS . includes ( origin ) ) return callback ( null , true ) ;
28+ if ( IS_DEVELOPMENT && CORS_ALLOWED_ORIGINS . length === 0 ) {
29+ return callback ( null , true ) ;
30+ }
31+ return callback ( null , false ) ;
32+ } ,
33+ } )
34+ ) ;
1835app . use ( express . json ( ) ) ;
1936
20- app . post ( '/api/gemini' , async ( req , res ) => {
21- console . log ( '📥 Received:' , req . body ) ;
37+ const ABUSE_THRESHOLD = Number ( process . env . CHAT_ABUSE_THRESHOLD || 3 ) ;
38+ const ABUSE_WINDOW_MS = Number (
39+ process . env . CHAT_ABUSE_WINDOW_MS || 10 * 60 * 1000
40+ ) ;
41+ const ABUSE_BLOCK_MS = Number (
42+ process . env . CHAT_ABUSE_BLOCK_MS || 24 * 60 * 60 * 1000
43+ ) ;
44+ const ABUSE_TRACKER_MAX_SIZE = Number (
45+ process . env . CHAT_ABUSE_TRACKER_MAX_SIZE || 5000
46+ ) ;
47+ const abuseTracker = new Map ( ) ;
48+
49+ const getClientKey = req => {
50+ const forwardedFor = req . headers [ 'x-forwarded-for' ] ;
51+ const ip = (
52+ Array . isArray ( forwardedFor )
53+ ? forwardedFor [ 0 ]
54+ : forwardedFor || req . ip || 'unknown-ip'
55+ )
56+ . toString ( )
57+ . split ( ',' ) [ 0 ]
58+ . trim ( ) ;
59+ const userAgent = ( req . headers [ 'user-agent' ] || 'unknown-agent' ) . toString ( ) ;
60+ return `${ ip } ::${ userAgent . slice ( 0 , 120 ) } ` ;
61+ } ;
62+
63+ const latestUserMessage = messages => {
64+ for ( let i = messages . length - 1 ; i >= 0 ; i -= 1 ) {
65+ const message = messages [ i ] ;
66+ const role = message ?. role ;
67+ if ( role === 'user' ) {
68+ const content = Array . isArray ( message ?. parts )
69+ ? message . parts . map ( part => part ?. text || '' ) . join ( '\n' )
70+ : message ?. content || '' ;
71+ return String ( content ) ;
72+ }
73+ }
74+ return '' ;
75+ } ;
76+
77+ const pruneAbuseTrackerIfNeeded = now => {
78+ if ( abuseTracker . size <= ABUSE_TRACKER_MAX_SIZE ) return ;
79+ for ( const [ key , record ] of abuseTracker . entries ( ) ) {
80+ if (
81+ record . blockedUntil <= now &&
82+ record . lastAbuseAt > 0 &&
83+ now - record . lastAbuseAt > ABUSE_WINDOW_MS
84+ ) {
85+ abuseTracker . delete ( key ) ;
86+ }
87+ if ( abuseTracker . size <= ABUSE_TRACKER_MAX_SIZE ) return ;
88+ }
89+ } ;
90+
91+ app . post ( '/api/chatbot' , async ( req , res ) => {
2292 const messages = req . body . messages ;
93+ if ( process . env . NODE_ENV === 'development' ) {
94+ console . log ( 'Received chat request metadata:' , {
95+ hasMessagesArray : Array . isArray ( messages ) ,
96+ messageCount : Array . isArray ( messages ) ? messages . length : 0 ,
97+ } ) ;
98+ }
2399
24- // ✅ Check if messages is a valid array
25100 if ( ! Array . isArray ( messages ) ) {
26101 return res
27102 . status ( 400 )
28103 . json ( { error : 'Expected prompt to be an array of messages' } ) ;
29104 }
30105
106+ const clientKey = getClientKey ( req ) ;
107+ const now = Date . now ( ) ;
108+ const record = abuseTracker . get ( clientKey ) || {
109+ abuseCount : 0 ,
110+ lastAbuseAt : 0 ,
111+ blockedUntil : 0 ,
112+ } ;
113+
114+ if ( record . blockedUntil > now ) {
115+ return res . status ( 403 ) . json ( {
116+ error :
117+ 'Chat access temporarily restricted due to repeated policy violations' ,
118+ policy : 'abuse_block' ,
119+ retryAfterMs : record . blockedUntil - now ,
120+ strikes : record . abuseCount ,
121+ forceLocalOnly : true ,
122+ } ) ;
123+ }
124+
125+ const latestQuery = latestUserMessage ( messages ) ;
126+ if ( isAbusiveQuery ( latestQuery ) ) {
127+ if ( record . lastAbuseAt > 0 && now - record . lastAbuseAt > ABUSE_WINDOW_MS ) {
128+ record . abuseCount = 0 ;
129+ }
130+ record . abuseCount += 1 ;
131+ record . lastAbuseAt = now ;
132+ if ( record . abuseCount >= ABUSE_THRESHOLD ) {
133+ record . blockedUntil = now + ABUSE_BLOCK_MS ;
134+ }
135+ abuseTracker . set ( clientKey , record ) ;
136+ pruneAbuseTrackerIfNeeded ( now ) ;
137+
138+ if ( record . blockedUntil > now ) {
139+ return res . status ( 403 ) . json ( {
140+ error :
141+ 'Chat access temporarily restricted due to repeated policy violations' ,
142+ policy : 'abuse_block' ,
143+ retryAfterMs : record . blockedUntil - now ,
144+ strikes : record . abuseCount ,
145+ forceLocalOnly : true ,
146+ } ) ;
147+ }
148+ } else if (
149+ record . lastAbuseAt > 0 &&
150+ now - record . lastAbuseAt > ABUSE_WINDOW_MS &&
151+ record . blockedUntil <= now
152+ ) {
153+ abuseTracker . delete ( clientKey ) ;
154+ }
155+
31156 try {
32- // Optional: Debug log to verify request body
33- console . log (
34- '🟢 Gemini Request Body:' ,
35- JSON . stringify ( { contents : messages } , null , 2 )
36- ) ;
37-
38- const result = await fetch (
39- `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${ process . env . NEXT_PUBLIC_GEMINI_API_KEY } ` ,
40- {
41- method : 'POST' ,
42- headers : { 'Content-Type' : 'application/json' } ,
43- body : JSON . stringify ( {
44- contents : messages , // ✅ Must match Gemini format exactly
45- } ) ,
46- }
47- ) ;
157+ const apiKey = process . env . NVIDIA_API_KEY ;
158+ const baseUrl =
159+ process . env . NVIDIA_BASE_URL || 'https://integrate.api.nvidia.com/v1' ;
160+ const model = process . env . NVIDIA_MODEL || 'moonshotai/kimi-k2-instruct' ;
161+
162+ if ( ! apiKey ) {
163+ return res
164+ . status ( 500 )
165+ . json ( { error : 'NVIDIA_API_KEY is not configured' } ) ;
166+ }
167+
168+ const result = await fetch ( `${ baseUrl } /chat/completions` , {
169+ method : 'POST' ,
170+ headers : {
171+ 'Content-Type' : 'application/json' ,
172+ Authorization : `Bearer ${ apiKey } ` ,
173+ } ,
174+ body : JSON . stringify ( {
175+ model,
176+ messages : messages . map ( message => ( {
177+ role :
178+ message ?. role === 'model'
179+ ? 'assistant'
180+ : [ 'user' , 'assistant' , 'system' ] . includes ( message ?. role )
181+ ? message . role
182+ : 'user' ,
183+ content : Array . isArray ( message ?. parts )
184+ ? message . parts . map ( part => part ?. text || '' ) . join ( '\n' )
185+ : message ?. content || '' ,
186+ } ) ) ,
187+ temperature : 0.6 ,
188+ top_p : 0.9 ,
189+ max_tokens : 1024 ,
190+ } ) ,
191+ } ) ;
48192
49- // Check if Gemini API returned a valid response
50193 if ( ! result . ok ) {
51194 const errorText = await result . text ( ) ;
52- console . error ( '❌ Gemini API error:' , errorText ) ;
195+ console . error ( 'Chat API error:' , errorText ) ;
53196 return res . status ( result . status ) . json ( { error : errorText } ) ;
54197 }
55198
56199 const data = await result . json ( ) ;
57- const text =
58- data ?. candidates ?. [ 0 ] ?. content ?. parts ?. [ 0 ] ?. text || 'No response' ;
200+ const text = data ?. choices ?. [ 0 ] ?. message ?. content || 'No response' ;
59201 res . status ( 200 ) . json ( { text } ) ;
60202 } catch ( error ) {
61- console . error ( '❌ Server Error :' , error ) ;
203+ console . error ( 'Server error :' , error ) ;
62204 res . status ( 500 ) . json ( { error : error . message } ) ;
63205 }
64206} ) ;
65207
66208const PORT = process . env . PORT || 3001 ;
67209app . listen ( PORT , ( ) => {
68- console . log ( `✅ Gemini proxy server running on port ${ PORT } ` ) ;
210+ console . log ( `Chatbot proxy server running on port ${ PORT } ` ) ;
69211} ) ;
0 commit comments