diff --git a/app/dashboard/overview/page.tsx b/app/dashboard/overview/page.tsx
index 57beb61..10e1790 100644
--- a/app/dashboard/overview/page.tsx
+++ b/app/dashboard/overview/page.tsx
@@ -17,6 +17,7 @@ import GeographicMap from "../../../components/GeographicMap";
import ResponseTimeDistribution from "../../../components/ResponseTimeDistribution";
import WelcomeBanner from "../../../components/WelcomeBanner";
import DateRangePicker from "../../../components/DateRangePicker";
+import TopQuestionsChart from "../../../components/TopQuestionsChart";
// Safely wrapped component with useSession
function DashboardContent() {
@@ -268,7 +269,7 @@ function DashboardContent() {
/>
)}
-
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ trend={{
+ value: metrics.resolvedChatsPercentage ?? 0,
+ isPositive: (metrics.resolvedChatsPercentage ?? 0) >= 80, // 80%+ resolution rate is good
+ }}
+ />
@@ -429,6 +494,9 @@ function DashboardContent() {
+ {/* Top Questions Chart */}
+
+
Response Time Distribution
diff --git a/components/TopQuestionsChart.tsx b/components/TopQuestionsChart.tsx
new file mode 100644
index 0000000..716a17e
--- /dev/null
+++ b/components/TopQuestionsChart.tsx
@@ -0,0 +1,74 @@
+'use client';
+
+import React from 'react';
+import { TopQuestion } from '../lib/types';
+
+interface TopQuestionsChartProps {
+ data: TopQuestion[];
+ title?: string;
+}
+
+export default function TopQuestionsChart({ data, title = "Top 5 Asked Questions" }: TopQuestionsChartProps) {
+ if (!data || data.length === 0) {
+ return (
+
+
{title}
+
+ No questions data available
+
+
+ );
+ }
+
+ // Find the maximum count to calculate relative bar widths
+ const maxCount = Math.max(...data.map(q => q.count));
+
+ return (
+
+
{title}
+
+
+ {data.map((question, index) => {
+ const percentage = maxCount > 0 ? (question.count / maxCount) * 100 : 0;
+
+ return (
+
+ {/* Question text */}
+
+
+ {question.question}
+
+
+ {question.count}
+
+
+
+ {/* Progress bar */}
+
+
+ {/* Rank indicator */}
+
+ {index + 1}
+
+
+ );
+ })}
+
+
+ {/* Summary */}
+
+
+ Total questions analyzed
+
+ {data.reduce((sum, q) => sum + q.count, 0)}
+
+
+
+
+ );
+}
diff --git a/lib/metrics.ts b/lib/metrics.ts
index 4e2b69e..1d03f73 100644
--- a/lib/metrics.ts
+++ b/lib/metrics.ts
@@ -7,6 +7,7 @@ import {
CountryMetrics, // Added CountryMetrics
MetricsResult,
WordCloudWord, // Added WordCloudWord
+ TopQuestion, // Added TopQuestion
} from "./types";
interface CompanyConfig {
@@ -348,8 +349,24 @@ export function sessionMetrics(
let totalTokensEur = 0;
const wordCounts: { [key: string]: number } = {};
let alerts = 0;
+
+ // New metrics variables
+ const hourlySessionCounts: { [hour: string]: number } = {};
+ let resolvedChatsCount = 0;
+ const questionCounts: { [question: string]: number } = {};
for (const session of sessions) {
+ // Track hourly usage for peak time calculation
+ if (session.startTime) {
+ const hour = new Date(session.startTime).getHours();
+ const hourKey = `${hour.toString().padStart(2, '0')}:00`;
+ hourlySessionCounts[hourKey] = (hourlySessionCounts[hourKey] || 0) + 1;
+ }
+
+ // Count resolved chats (sessions that have ended and are not escalated)
+ if (session.endTime && !session.escalated) {
+ resolvedChatsCount++;
+ }
// Unique Users: Prefer non-empty ipAddress, fallback to non-empty sessionId
let identifierAdded = false;
if (session.ipAddress && session.ipAddress.trim() !== "") {
@@ -487,6 +504,51 @@ export function sessionMetrics(
byCountry[session.country] = (byCountry[session.country] || 0) + 1;
}
+ // Extract questions from session
+ const extractQuestions = () => {
+ // 1. Extract from questions JSON field
+ if (session.questions) {
+ try {
+ const questionsArray = JSON.parse(session.questions);
+ if (Array.isArray(questionsArray)) {
+ questionsArray.forEach((question: string) => {
+ if (question && question.trim().length > 0) {
+ const cleanQuestion = question.trim();
+ questionCounts[cleanQuestion] = (questionCounts[cleanQuestion] || 0) + 1;
+ }
+ });
+ }
+ } catch (error) {
+ console.warn(`[metrics] Failed to parse questions JSON for session ${session.id}: ${error}`);
+ }
+ }
+
+ // 2. Extract questions from user messages (if available)
+ if (session.messages) {
+ session.messages
+ .filter(msg => msg.role === 'User')
+ .forEach(msg => {
+ const content = msg.content.trim();
+ // Simple heuristic: if message ends with ? or contains question words, treat as question
+ if (content.endsWith('?') ||
+ /\b(what|when|where|why|how|who|which|can|could|would|will|is|are|do|does|did)\b/i.test(content)) {
+ questionCounts[content] = (questionCounts[content] || 0) + 1;
+ }
+ });
+ }
+
+ // 3. Extract questions from initial message as fallback
+ if (session.initialMsg) {
+ const content = session.initialMsg.trim();
+ if (content.endsWith('?') ||
+ /\b(what|when|where|why|how|who|which|can|could|would|will|is|are|do|does|did)\b/i.test(content)) {
+ questionCounts[content] = (questionCounts[content] || 0) + 1;
+ }
+ }
+ };
+
+ extractQuestions();
+
// Word Cloud Data (from initial message and transcript content)
const processTextForWordCloud = (text: string | undefined | null) => {
if (!text) return;
@@ -506,7 +568,8 @@ export function sessionMetrics(
}
};
processTextForWordCloud(session.initialMsg);
- processTextForWordCloud(session.transcriptContent);
+ // Note: transcriptContent is not available in ChatSession type
+ // Could be added later if transcript parsing is implemented
}
const uniqueUsers = uniqueUserIds.size;
@@ -547,6 +610,30 @@ export function sessionMetrics(
mockPreviousPeriodData.avgResponseTime
);
+ // Calculate new metrics
+
+ // 1. Average Daily Costs (euros)
+ const avgDailyCosts = numDaysWithSessions > 0 ? totalTokensEur / numDaysWithSessions : 0;
+
+ // 2. Peak Usage Time
+ let peakUsageTime = "N/A";
+ if (Object.keys(hourlySessionCounts).length > 0) {
+ const peakHour = Object.entries(hourlySessionCounts)
+ .sort(([, a], [, b]) => b - a)[0][0];
+ const peakHourNum = parseInt(peakHour.split(':')[0]);
+ const endHour = (peakHourNum + 1) % 24;
+ peakUsageTime = `${peakHour}-${endHour.toString().padStart(2, '0')}:00`;
+ }
+
+ // 3. Resolved Chats Percentage
+ const resolvedChatsPercentage = totalSessions > 0 ? (resolvedChatsCount / totalSessions) * 100 : 0;
+
+ // 4. Top 5 Asked Questions
+ const topQuestions: TopQuestion[] = Object.entries(questionCounts)
+ .sort(([, a], [, b]) => b - a)
+ .slice(0, 5) // Top 5 questions
+ .map(([question, count]) => ({ question, count }));
+
// console.log("Debug metrics calculation:", {
// totalSessionDuration,
// validSessionsForDuration,
@@ -585,5 +672,11 @@ export function sessionMetrics(
lastUpdated: Date.now(),
totalSessionDuration,
validSessionsForDuration,
+
+ // New metrics
+ avgDailyCosts,
+ peakUsageTime,
+ resolvedChatsPercentage,
+ topQuestions,
};
}
diff --git a/lib/types.ts b/lib/types.ts
index 2f04ff0..1800191 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -119,6 +119,11 @@ export interface WordCloudWord {
value: number;
}
+export interface TopQuestion {
+ question: string;
+ count: number;
+}
+
export interface MetricsResult {
totalSessions: number;
avgSessionsPerDay: number;
@@ -152,6 +157,12 @@ export interface MetricsResult {
usersTrend?: number; // e.g., percentage change in uniqueUsers
avgSessionTimeTrend?: number; // e.g., percentage change in avgSessionLength
avgResponseTimeTrend?: number; // e.g., percentage change in avgResponseTime
+
+ // New metrics for enhanced dashboard
+ avgDailyCosts?: number; // Average daily costs in euros
+ peakUsageTime?: string; // Peak usage time (e.g., "14:00-15:00")
+ resolvedChatsPercentage?: number; // Percentage of resolved chats
+ topQuestions?: TopQuestion[]; // Top 5 most asked questions
// Debug properties
totalSessionDuration?: number;
diff --git a/pages/api/dashboard/metrics.ts b/pages/api/dashboard/metrics.ts
index 487f0a8..e4a27a5 100644
--- a/pages/api/dashboard/metrics.ts
+++ b/pages/api/dashboard/metrics.ts
@@ -48,6 +48,9 @@ export default async function handler(
const prismaSessions = await prisma.session.findMany({
where: whereClause,
+ include: {
+ messages: true, // Include messages for question extraction
+ },
});
// Convert Prisma sessions to ChatSession[] type for sessionMetrics
@@ -74,6 +77,9 @@ export default async function handler(
forwardedHr: ps.forwardedHr || false,
initialMsg: ps.initialMsg || undefined,
fullTranscriptUrl: ps.fullTranscriptUrl || undefined,
+ questions: ps.questions || undefined, // Include questions field
+ summary: ps.summary || undefined, // Include summary field
+ messages: ps.messages || [], // Include messages for question extraction
// userId is missing in Prisma Session model, assuming it's not strictly needed for metrics or can be null
userId: undefined, // Or some other default/mapping if available
}));