- Convert agent management from cards to searchable table view with search functionality - Add proper delete functionality for agents with confirmation dialogs - Fix chat input positioning to stay pinned at bottom with independent scrolling - Improve chat history layout to prevent delete buttons from being pushed off screen - Add padding to home page to prevent cards from hitting edges - Clean up database by removing CREATOR-BOT- prefix from 28 agent names - Enhance responsive layouts across admin dashboard and chat interface - Fix usage trends analytics API with proper assistant relationship filtering 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
307 lines
No EOL
8.5 KiB
JavaScript
307 lines
No EOL
8.5 KiB
JavaScript
const express = require('express');
|
|
const { Conversation, Message, User, Assistant } = require('../models');
|
|
const { Op } = require('sequelize');
|
|
|
|
const router = express.Router();
|
|
|
|
// Get usage statistics
|
|
router.get('/usage', async (req, res, next) => {
|
|
try {
|
|
const { startDate, endDate, userId, agentKey } = req.query;
|
|
|
|
// Build where clause for filtering
|
|
const whereClause = {};
|
|
|
|
if (startDate || endDate) {
|
|
whereClause.createdAt = {};
|
|
if (startDate) {
|
|
whereClause.createdAt[Op.gte] = new Date(startDate);
|
|
}
|
|
if (endDate) {
|
|
whereClause.createdAt[Op.lte] = new Date(endDate);
|
|
}
|
|
}
|
|
|
|
if (userId) {
|
|
whereClause.userId = userId;
|
|
}
|
|
|
|
if (agentKey) {
|
|
// Look in metadata for assistantKey
|
|
whereClause['metadata.assistantKey'] = agentKey;
|
|
}
|
|
|
|
// Get conversations with message counts
|
|
const conversations = await Conversation.findAll({
|
|
where: whereClause,
|
|
include: [
|
|
{
|
|
model: Message,
|
|
as: 'messages',
|
|
attributes: []
|
|
},
|
|
{
|
|
model: Assistant,
|
|
as: 'assistant',
|
|
attributes: ['key', 'name']
|
|
}
|
|
],
|
|
attributes: {
|
|
include: [
|
|
[Conversation.sequelize.fn('COUNT', Conversation.sequelize.col('messages.id')), 'messageCount']
|
|
]
|
|
},
|
|
group: ['Conversation.id', 'assistant.id'],
|
|
order: [['createdAt', 'DESC']]
|
|
});
|
|
|
|
// Calculate duration for each conversation (rough estimate based on message timestamps)
|
|
const conversationData = await Promise.all(
|
|
conversations.map(async (conv) => {
|
|
const messages = await Message.findAll({
|
|
where: { conversationId: conv.id },
|
|
order: [['createdAt', 'ASC']],
|
|
limit: 2 // First and last message to calculate duration
|
|
});
|
|
|
|
let duration = '--';
|
|
if (messages.length >= 2) {
|
|
const start = new Date(messages[0].createdAt);
|
|
const end = new Date(messages[messages.length - 1].createdAt);
|
|
const diffMinutes = Math.round((end - start) / 60000);
|
|
duration = `${diffMinutes} min`;
|
|
}
|
|
|
|
return {
|
|
id: conv.id,
|
|
date: conv.createdAt,
|
|
userName: conv.userId || 'Anonymous',
|
|
agentName: conv.assistant?.name || 'Unknown Agent',
|
|
messageCount: parseInt(conv.dataValues.messageCount) || 0,
|
|
duration: duration,
|
|
status: conv.status || 'active'
|
|
};
|
|
})
|
|
);
|
|
|
|
res.json({
|
|
conversations: conversationData,
|
|
total: conversations.length
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Analytics error:', error);
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// Get usage statistics summary
|
|
router.get('/stats', async (req, res, next) => {
|
|
try {
|
|
const { startDate, endDate } = req.query;
|
|
|
|
// Build date filter
|
|
const dateFilter = {};
|
|
if (startDate || endDate) {
|
|
dateFilter.createdAt = {};
|
|
if (startDate) {
|
|
dateFilter.createdAt[Op.gte] = new Date(startDate);
|
|
}
|
|
if (endDate) {
|
|
dateFilter.createdAt[Op.lte] = new Date(endDate);
|
|
}
|
|
}
|
|
|
|
// Get total conversations
|
|
const totalConversations = await Conversation.count({
|
|
where: dateFilter
|
|
});
|
|
|
|
// Get total messages
|
|
const totalMessages = await Message.count({
|
|
where: dateFilter
|
|
});
|
|
|
|
// Get unique users (approximation since we don't have proper user tracking yet)
|
|
const uniqueUserIds = await Conversation.findAll({
|
|
where: dateFilter,
|
|
attributes: [[Conversation.sequelize.fn('DISTINCT', Conversation.sequelize.col('userId')), 'userId']],
|
|
raw: true
|
|
});
|
|
|
|
const activeUsers = uniqueUserIds.filter(u => u.userId).length;
|
|
|
|
// Get most used agent
|
|
const agentUsage = await Conversation.findAll({
|
|
where: dateFilter,
|
|
include: [
|
|
{
|
|
model: Assistant,
|
|
as: 'assistant',
|
|
attributes: ['name']
|
|
}
|
|
],
|
|
attributes: [
|
|
'assistantId',
|
|
[Conversation.sequelize.fn('COUNT', Conversation.sequelize.col('Conversation.id')), 'usage_count']
|
|
],
|
|
group: ['assistantId', 'assistant.id'],
|
|
order: [[Conversation.sequelize.fn('COUNT', Conversation.sequelize.col('Conversation.id')), 'DESC']],
|
|
limit: 1,
|
|
raw: true
|
|
});
|
|
|
|
const mostUsedAgent = agentUsage.length > 0
|
|
? agentUsage[0]['assistant.name'] || 'Unknown'
|
|
: 'None';
|
|
|
|
res.json({
|
|
totalConversations,
|
|
totalMessages,
|
|
activeUsers,
|
|
mostUsedAgent
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Stats error:', error);
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// Get usage trends over time
|
|
router.get('/trends', async (req, res, next) => {
|
|
try {
|
|
const { period = 'daily', days = 30 } = req.query;
|
|
|
|
// Calculate date range
|
|
const endDate = new Date();
|
|
const startDate = new Date();
|
|
startDate.setDate(startDate.getDate() - parseInt(days));
|
|
|
|
// Get conversations grouped by date
|
|
const query = `
|
|
SELECT
|
|
DATE("createdAt") as date,
|
|
COUNT(*) as conversations,
|
|
COUNT(DISTINCT "userId") as unique_users,
|
|
(
|
|
SELECT COUNT(*)
|
|
FROM messages
|
|
WHERE DATE(messages."createdAt") = DATE(conversations."createdAt")
|
|
) as messages
|
|
FROM conversations
|
|
WHERE "createdAt" >= $1 AND "createdAt" <= $2
|
|
GROUP BY DATE("createdAt")
|
|
ORDER BY DATE("createdAt")
|
|
`;
|
|
|
|
const trends = await Conversation.sequelize.query(query, {
|
|
bind: [startDate, endDate],
|
|
type: Conversation.sequelize.QueryTypes.SELECT
|
|
});
|
|
|
|
// Fill in missing dates with zero values
|
|
const dateMap = new Map();
|
|
trends.forEach(trend => {
|
|
dateMap.set(trend.date, {
|
|
date: trend.date,
|
|
conversations: parseInt(trend.conversations),
|
|
unique_users: parseInt(trend.unique_users),
|
|
messages: parseInt(trend.messages) || 0
|
|
});
|
|
});
|
|
|
|
// Generate complete date range
|
|
const result = [];
|
|
const current = new Date(startDate);
|
|
while (current <= endDate) {
|
|
const dateStr = current.toISOString().split('T')[0];
|
|
result.push(dateMap.get(dateStr) || {
|
|
date: dateStr,
|
|
conversations: 0,
|
|
unique_users: 0,
|
|
messages: 0
|
|
});
|
|
current.setDate(current.getDate() + 1);
|
|
}
|
|
|
|
res.json({
|
|
trends: result,
|
|
period,
|
|
days: parseInt(days)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Trends error:', error);
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// Get agent usage trends
|
|
router.get('/agent-trends', async (req, res, next) => {
|
|
try {
|
|
const { days = 30 } = req.query;
|
|
|
|
// Calculate date range
|
|
const endDate = new Date();
|
|
const startDate = new Date();
|
|
startDate.setDate(startDate.getDate() - parseInt(days));
|
|
|
|
// Get agent usage over time
|
|
const agentTrends = await Conversation.findAll({
|
|
where: {
|
|
createdAt: {
|
|
[Op.gte]: startDate,
|
|
[Op.lte]: endDate
|
|
},
|
|
assistantId: {
|
|
[Op.not]: null
|
|
}
|
|
},
|
|
include: [{
|
|
model: Assistant,
|
|
as: 'assistant',
|
|
attributes: ['name', 'key']
|
|
}],
|
|
attributes: [
|
|
[Conversation.sequelize.fn('DATE', Conversation.sequelize.col('Conversation.createdAt')), 'date'],
|
|
[Conversation.sequelize.fn('COUNT', Conversation.sequelize.col('Conversation.id')), 'usage_count']
|
|
],
|
|
group: [
|
|
Conversation.sequelize.fn('DATE', Conversation.sequelize.col('Conversation.createdAt')),
|
|
'assistant.id',
|
|
'assistant.name',
|
|
'assistant.key'
|
|
],
|
|
order: [
|
|
[Conversation.sequelize.fn('DATE', Conversation.sequelize.col('Conversation.createdAt')), 'ASC'],
|
|
[Conversation.sequelize.fn('COUNT', Conversation.sequelize.col('Conversation.id')), 'DESC']
|
|
],
|
|
raw: true
|
|
});
|
|
|
|
// Group by agent
|
|
const agentData = {};
|
|
agentTrends.forEach(trend => {
|
|
const agentName = trend['assistant.name'];
|
|
if (!agentData[agentName]) {
|
|
agentData[agentName] = [];
|
|
}
|
|
agentData[agentName].push({
|
|
date: trend.date,
|
|
usage: parseInt(trend.usage_count)
|
|
});
|
|
});
|
|
|
|
res.json({
|
|
agent_trends: agentData,
|
|
days: parseInt(days)
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Agent trends error:', error);
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
module.exports = router; |