From 8424c544100d031682581cafc5ba5c66dcdec9e2 Mon Sep 17 00:00:00 2001 From: Gitea Date: Tue, 16 Jun 2026 11:36:41 +0530 Subject: [PATCH] dashboar changes --- controller/Dashboard.js | 77 ++++++++++++++++++ controller/askQuestion.js | 165 ++++++++++++++++++++++++++++++-------- package-lock.json | 14 ++++ package.json | 1 + route/route.js | 7 +- 5 files changed, 229 insertions(+), 35 deletions(-) create mode 100644 controller/Dashboard.js diff --git a/controller/Dashboard.js b/controller/Dashboard.js new file mode 100644 index 0000000..ac47d3e --- /dev/null +++ b/controller/Dashboard.js @@ -0,0 +1,77 @@ +const postgre = require('../database/postgre'); + +const analytics = async (req, res) => { + try { + const query = ` + SELECT + '24h' AS period, + COUNT(DISTINCT session_id) AS sessions, + COUNT(*) FILTER (WHERE status = 1) AS chats, + COUNT(DISTINCT user_id) AS users, + COUNT(*) AS queries + FROM useraskquestion + WHERE created >= NOW() - INTERVAL '24 hours' + + UNION ALL + + SELECT + '7d', + COUNT(DISTINCT session_id), + COUNT(*) FILTER (WHERE status = 1), + COUNT(DISTINCT user_id), + COUNT(*) + FROM useraskquestion + WHERE created >= NOW() - INTERVAL '7 days' + + UNION ALL + + SELECT + '30d', + COUNT(DISTINCT session_id), + COUNT(*) FILTER (WHERE status = 1), + COUNT(DISTINCT user_id), + COUNT(*) + FROM useraskquestion + WHERE created >= NOW() - INTERVAL '30 days' + + UNION ALL + + SELECT + 'all', + COUNT(DISTINCT session_id), + COUNT(*) FILTER (WHERE status = 1), + COUNT(DISTINCT user_id), + COUNT(*) + FROM useraskquestion; + `; + + const result = await postgre.query(query); + + const response = {}; + + result.rows.forEach(row => { + response[row.period] = { + sessions: Number(row.sessions), + chats: Number(row.chats), + users: Number(row.users), + queries: Number(row.queries), + }; + }); + + return res.status(200).json({ + success: true, + data: response, + }); + + } catch (error) { + console.error("Analytics Error:", error); + return res.status(500).json({ + success: false, + message: error.message, + }); + } +}; + +module.exports = { + analytics, +}; \ No newline at end of file diff --git a/controller/askQuestion.js b/controller/askQuestion.js index eede1d6..f56f118 100644 --- a/controller/askQuestion.js +++ b/controller/askQuestion.js @@ -6,6 +6,51 @@ const OpenAI = require("openai"); const { pipeline } = require("@xenova/transformers"); const { QdrantClient } = require("@qdrant/js-client-rest"); +const { v4: uuidv4 } = require("uuid"); + +async function getOrCreateSession(user_id) { + const result = await postgre.query( + ` + SELECT * + FROM user_sessions + WHERE user_id = $1 + AND is_active = true + AND last_activity > NOW() - INTERVAL '30 minutes' + ORDER BY last_activity DESC + LIMIT 1 + `, + [user_id] + ); + + if (result.rows.length) { + const session = result.rows[0]; + + await postgre.query( + `UPDATE user_sessions + SET last_activity = NOW() + WHERE id = $1`, + [session.id] + ); + + return session.session_id; + } + + const session_id = uuidv4(); + + await postgre.query( + ` + INSERT INTO user_sessions + (user_id, session_id) + VALUES ($1, $2) + `, + [user_id, session_id] + ); + + return session_id; +} + + + const CONFIG = { azure: { endpoint: process.env.AZURE_OPENAI_ENDPOINT, @@ -80,32 +125,73 @@ function buildContext(results) { .join("\n\n---\n\n"); } +// const SYSTEM_PROMPT = ` +// You are CPM AI Assistant. + +// Rules: +// - Answer only from the provided information. +// - If the answer is not available, reply exactly: +// "❌ I could not find this information in the uploaded documents." +// - Do not make up information. +// - Do not mention documents, context, or chunks. +// - Reply in the same language and style as the user's question. +// - If the user asks in Hindi, answer in Hindi. +// - If the user asks in Hinglish, answer in Hinglish. +// - If the user asks in English, answer in English. + +// Response Style: +// - Use simple and easy-to-understand language. +// - Keep answers short and clear. +// - Use headings and bullet points when helpful. +// - Highlight important words in **bold**. + +// Format: + +// # 📋 Topic + +// ## 🎯 Summary +// Short answer in the user's language. + +// ## ✅ Details +// - Point 1 +// - Point 2 +// - Point 3 + +// ## ⚠️ Notes +// - Extra information (if available). +// `.trim(); + const SYSTEM_PROMPT = ` You are CPM AI Assistant. -Rules: -- Answer only from the provided information. -- If the answer is not available, reply exactly: +RULES: +- Answer only using the provided context. +- If the answer is not available in the context, reply exactly: "❌ I could not find this information in the uploaded documents." - Do not make up information. -- Do not mention documents, context, or chunks. -- Reply in the same language and style as the user's question. -- If the user asks in Hindi, answer in Hindi. -- If the user asks in Hinglish, answer in Hinglish. -- If the user asks in English, answer in English. +- Do not use external knowledge. +- Do not mention context, documents, chunks, or sources. -Response Style: -- Use simple and easy-to-understand language. -- Keep answers short and clear. -- Use headings and bullet points when helpful. +STRICT OUTPUT RULE: +- NEVER include words like "documents.", "context", "chunk", or similar metadata in the final answer. +- NEVER end the response with the word "documents." or any system-related word. +- Ensure the final sentence always ends naturally and cleanly. + +LANGUAGE RULE: +- Reply in the same language as the user (English, Hindi, Hinglish). + +RESPONSE STYLE: +- Use simple, clear language. +- Keep answers short and structured. +- Use headings and bullet points when needed. - Highlight important words in **bold**. -Format: +FORMAT: # 📋 Topic ## 🎯 Summary -Short answer in the user's language. +Short answer. ## ✅ Details - Point 1 @@ -113,9 +199,10 @@ Short answer in the user's language. - Point 3 ## ⚠️ Notes -- Extra information (if available). +- Only if needed `.trim(); + async function askLLM(question, context) { const completion = await llm.chat.completions.create({ model: CONFIG.azure.deployment, @@ -184,7 +271,8 @@ const askstream = async (req, res) => { const { question } = req.body ?? {}; const user_id = req.user.id; - console.log("Received question:", user_id); + const session_id = await getOrCreateSession(user_id); + if (!question?.trim()) { return res.status(400).json({ success: false, error: "question is required" }); @@ -206,11 +294,8 @@ const askstream = async (req, res) => { const results = await searchQdrant(embedding); - if (!results.length) { - const result = await postgre.query( - 'insert into useraskquestion (user_id, questions) values ($1, $2)', - [user_id, question] - ); + if (!results.length) { + console.log("No results found for question:", question); send("token", { token: "❌", isWord: true }); send("token", { token: "I", isWord: true }); send("token", { token: "could", isWord: true }); @@ -224,15 +309,26 @@ const askstream = async (req, res) => { send("token", { token: "documents.", isWord: true }); send("done", { sources: [] }); - + await postgre.query( + ` + INSERT INTO useraskquestion + (user_id, session_id, questions, status) + VALUES ($1, $2, $3, $4) + `, + [user_id, session_id, question, '0'] + ); return res.end(); } - const result = await postgre.query( - 'insert into useraskquestion (user_id, questions,status) values ($1, $2, $3)', - [user_id, question,'1'] - ); + await postgre.query( + ` + INSERT INTO useraskquestion + (user_id, session_id, questions, status) + VALUES ($1, $2, $3, $4) + `, + [user_id, session_id, question, '1'] + ); const sources = results.map(r => ({ score: +r.score.toFixed(4), @@ -255,8 +351,6 @@ const askstream = async (req, res) => { { role: "user", content: `Context:\n${context}\n\nQuestion:\n${question}` }, ], }); - - // Buffer to handle tokens that may be split mid-word let wordBuffer = ""; for await (const chunk of stream) { @@ -264,12 +358,7 @@ const askstream = async (req, res) => { if (!rawToken) continue; wordBuffer += rawToken; - - // Split on whitespace — emit complete words, keep trailing partial - // We preserve newlines/markdown as separate tokens so markdown renders correctly const parts = wordBuffer.split(/(\s+)/); - - // Last element might be an incomplete word — buffer it wordBuffer = parts.pop() ?? ""; for (const part of parts) { @@ -279,8 +368,16 @@ const askstream = async (req, res) => { } } - // Flush any remaining buffered text + + if (wordBuffer) { + console.log("Emitting buffered token:", wordBuffer); + if (wordBuffer.trim() == "documents.") { + const result = await postgre.query( + `UPDATE useraskquestion SET status = $1 WHERE session_id = $2 RETURNING *` + , ['0', session_id] + ); + } send("token", { token: wordBuffer, isWord: true }); } diff --git a/package-lock.json b/package-lock.json index 0eeb0ef..240f75a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", "pg": "^8.21.0", + "uuid": "^14.0.0", "yaml": "^2.9.0" } }, @@ -5530,6 +5531,19 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/validate-npm-package-name": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", diff --git a/package.json b/package.json index 574c4d4..0dc12e9 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "pdf-parse": "^2.4.5", "pdfjs-dist": "^6.0.227", "pg": "^8.21.0", + "uuid": "^14.0.0", "yaml": "^2.9.0" } } diff --git a/route/route.js b/route/route.js index d9d4298..29a8084 100644 --- a/route/route.js +++ b/route/route.js @@ -5,6 +5,7 @@ const authMiddleware = require('../middleware/authMiddleware.js'); const askQuestion = require('../controller/askQuestion.js'); const answerUnanswerdQuestion = require('../controller/answerUnanswerdQuestion.js'); const qdrantinsert = require('../controller/qdrantinsert.js'); +const Dashboard = require('../controller/Dashboard.js'); const multer = require("multer"); const upload = multer({ @@ -21,7 +22,7 @@ router.post('/loginUser', loginUser.loginUser); //*****************************askQuestion**************************************************** router.post('/ask', authMiddleware, askQuestion.ask); -router.post('/ask/stream', askQuestion.askstream); +router.post('/ask/stream', authMiddleware, askQuestion.askstream); router.get('/health', askQuestion.health); //*****************************answerUnanswerdQuestion**************************************************** @@ -32,5 +33,9 @@ router.get('/UnansweredQuestions', authMiddleware, answerUnanswerdQuestion.Unans router.post('/upload', upload.single("pdf"), authMiddleware, qdrantinsert.upload); +//*********************************Dashboard************************************************ +router.get('/analytics',authMiddleware, Dashboard.analytics); + + module.exports = router;