dashboar changes
Deploy Node App / deploy (push) Successful in 15s

This commit is contained in:
Gitea
2026-06-16 11:36:41 +05:30
parent bea7933c48
commit 8424c54410
5 changed files with 229 additions and 35 deletions
+77
View File
@@ -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,
};
+130 -33
View File
@@ -6,6 +6,51 @@ const OpenAI = require("openai");
const { pipeline } = require("@xenova/transformers"); const { pipeline } = require("@xenova/transformers");
const { QdrantClient } = require("@qdrant/js-client-rest"); 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 = { const CONFIG = {
azure: { azure: {
endpoint: process.env.AZURE_OPENAI_ENDPOINT, endpoint: process.env.AZURE_OPENAI_ENDPOINT,
@@ -80,32 +125,73 @@ function buildContext(results) {
.join("\n\n---\n\n"); .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 = ` const SYSTEM_PROMPT = `
You are CPM AI Assistant. You are CPM AI Assistant.
Rules: RULES:
- Answer only from the provided information. - Answer only using the provided context.
- If the answer is not available, reply exactly: - If the answer is not available in the context, reply exactly:
"❌ I could not find this information in the uploaded documents." "❌ I could not find this information in the uploaded documents."
- Do not make up information. - Do not make up information.
- Do not mention documents, context, or chunks. - Do not use external knowledge.
- Reply in the same language and style as the user's question. - Do not mention context, documents, chunks, or sources.
- 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: STRICT OUTPUT RULE:
- Use simple and easy-to-understand language. - NEVER include words like "documents.", "context", "chunk", or similar metadata in the final answer.
- Keep answers short and clear. - NEVER end the response with the word "documents." or any system-related word.
- Use headings and bullet points when helpful. - 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**. - Highlight important words in **bold**.
Format: FORMAT:
# 📋 Topic # 📋 Topic
## 🎯 Summary ## 🎯 Summary
Short answer in the user's language. Short answer.
## ✅ Details ## ✅ Details
- Point 1 - Point 1
@@ -113,9 +199,10 @@ Short answer in the user's language.
- Point 3 - Point 3
## ⚠️ Notes ## ⚠️ Notes
- Extra information (if available). - Only if needed
`.trim(); `.trim();
async function askLLM(question, context) { async function askLLM(question, context) {
const completion = await llm.chat.completions.create({ const completion = await llm.chat.completions.create({
model: CONFIG.azure.deployment, model: CONFIG.azure.deployment,
@@ -184,7 +271,8 @@ const askstream = async (req, res) => {
const { question } = req.body ?? {}; const { question } = req.body ?? {};
const user_id = req.user.id; const user_id = req.user.id;
console.log("Received question:", user_id); const session_id = await getOrCreateSession(user_id);
if (!question?.trim()) { if (!question?.trim()) {
return res.status(400).json({ success: false, error: "question is required" }); return res.status(400).json({ success: false, error: "question is required" });
@@ -207,10 +295,7 @@ const askstream = async (req, res) => {
if (!results.length) { if (!results.length) {
const result = await postgre.query( console.log("No results found for question:", question);
'insert into useraskquestion (user_id, questions) values ($1, $2)',
[user_id, question]
);
send("token", { token: "❌", isWord: true }); send("token", { token: "❌", isWord: true });
send("token", { token: "I", isWord: true }); send("token", { token: "I", isWord: true });
send("token", { token: "could", isWord: true }); send("token", { token: "could", isWord: true });
@@ -224,15 +309,26 @@ const askstream = async (req, res) => {
send("token", { token: "documents.", isWord: true }); send("token", { token: "documents.", isWord: true });
send("done", { sources: [] }); 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(); return res.end();
} }
const result = await postgre.query( await postgre.query(
'insert into useraskquestion (user_id, questions,status) values ($1, $2, $3)', `
[user_id, question,'1'] 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 => ({ const sources = results.map(r => ({
score: +r.score.toFixed(4), score: +r.score.toFixed(4),
@@ -255,8 +351,6 @@ const askstream = async (req, res) => {
{ role: "user", content: `Context:\n${context}\n\nQuestion:\n${question}` }, { role: "user", content: `Context:\n${context}\n\nQuestion:\n${question}` },
], ],
}); });
// Buffer to handle tokens that may be split mid-word
let wordBuffer = ""; let wordBuffer = "";
for await (const chunk of stream) { for await (const chunk of stream) {
@@ -264,12 +358,7 @@ const askstream = async (req, res) => {
if (!rawToken) continue; if (!rawToken) continue;
wordBuffer += rawToken; 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+)/); const parts = wordBuffer.split(/(\s+)/);
// Last element might be an incomplete word — buffer it
wordBuffer = parts.pop() ?? ""; wordBuffer = parts.pop() ?? "";
for (const part of parts) { for (const part of parts) {
@@ -279,8 +368,16 @@ const askstream = async (req, res) => {
} }
} }
// Flush any remaining buffered text
if (wordBuffer) { 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 }); send("token", { token: wordBuffer, isWord: true });
} }
+14
View File
@@ -28,6 +28,7 @@
"pdf-parse": "^2.4.5", "pdf-parse": "^2.4.5",
"pdfjs-dist": "^6.0.227", "pdfjs-dist": "^6.0.227",
"pg": "^8.21.0", "pg": "^8.21.0",
"uuid": "^14.0.0",
"yaml": "^2.9.0" "yaml": "^2.9.0"
} }
}, },
@@ -5530,6 +5531,19 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT" "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": { "node_modules/validate-npm-package-name": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
+1
View File
@@ -30,6 +30,7 @@
"pdf-parse": "^2.4.5", "pdf-parse": "^2.4.5",
"pdfjs-dist": "^6.0.227", "pdfjs-dist": "^6.0.227",
"pg": "^8.21.0", "pg": "^8.21.0",
"uuid": "^14.0.0",
"yaml": "^2.9.0" "yaml": "^2.9.0"
} }
} }
+6 -1
View File
@@ -5,6 +5,7 @@ const authMiddleware = require('../middleware/authMiddleware.js');
const askQuestion = require('../controller/askQuestion.js'); const askQuestion = require('../controller/askQuestion.js');
const answerUnanswerdQuestion = require('../controller/answerUnanswerdQuestion.js'); const answerUnanswerdQuestion = require('../controller/answerUnanswerdQuestion.js');
const qdrantinsert = require('../controller/qdrantinsert.js'); const qdrantinsert = require('../controller/qdrantinsert.js');
const Dashboard = require('../controller/Dashboard.js');
const multer = require("multer"); const multer = require("multer");
const upload = multer({ const upload = multer({
@@ -21,7 +22,7 @@ router.post('/loginUser', loginUser.loginUser);
//*****************************askQuestion**************************************************** //*****************************askQuestion****************************************************
router.post('/ask', authMiddleware, askQuestion.ask); router.post('/ask', authMiddleware, askQuestion.ask);
router.post('/ask/stream', askQuestion.askstream); router.post('/ask/stream', authMiddleware, askQuestion.askstream);
router.get('/health', askQuestion.health); router.get('/health', askQuestion.health);
//*****************************answerUnanswerdQuestion**************************************************** //*****************************answerUnanswerdQuestion****************************************************
@@ -32,5 +33,9 @@ router.get('/UnansweredQuestions', authMiddleware, answerUnanswerdQuestion.Unans
router.post('/upload', upload.single("pdf"), authMiddleware, qdrantinsert.upload); router.post('/upload', upload.single("pdf"), authMiddleware, qdrantinsert.upload);
//*********************************Dashboard************************************************
router.get('/analytics',authMiddleware, Dashboard.analytics);
module.exports = router; module.exports = router;