mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 11:32:13 +01:00
Enhances dashboard metrics and session handling with word cloud data and country metrics
This commit is contained in:
@ -11,7 +11,7 @@ import {
|
|||||||
} from "../../components/Charts";
|
} from "../../components/Charts";
|
||||||
import DashboardSettings from "./settings";
|
import DashboardSettings from "./settings";
|
||||||
import UserManagement from "./users";
|
import UserManagement from "./users";
|
||||||
import { Company, MetricsResult } from "../../lib/types";
|
import { Company, MetricsResult, WordCloudWord } from "../../lib/types"; // Added WordCloudWord
|
||||||
import MetricCard from "../../components/MetricCard";
|
import MetricCard from "../../components/MetricCard";
|
||||||
import DonutChart from "../../components/DonutChart";
|
import DonutChart from "../../components/DonutChart";
|
||||||
import WordCloud from "../../components/WordCloud";
|
import WordCloud from "../../components/WordCloud";
|
||||||
@ -136,14 +136,10 @@ function DashboardContent() {
|
|||||||
return <div className="text-center py-10">Loading dashboard...</div>;
|
return <div className="text-center py-10">Loading dashboard...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to prepare word cloud data from categories
|
// Function to prepare word cloud data from metrics.wordCloudData
|
||||||
const getWordCloudData = () => {
|
const getWordCloudData = (): WordCloudWord[] => {
|
||||||
if (!metrics || !metrics.categories) return [];
|
if (!metrics || !metrics.wordCloudData) return [];
|
||||||
return Object.entries(metrics.categories)
|
return metrics.wordCloudData;
|
||||||
.map(([text, value]) => ({ text, value }))
|
|
||||||
.filter((item) => item.text.trim() !== "")
|
|
||||||
.sort((a, b) => b.value - a.value)
|
|
||||||
.slice(0, 30);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to prepare country data for the map - using simulated/dummy data
|
// Function to prepare country data for the map - using simulated/dummy data
|
||||||
@ -380,7 +376,7 @@ function DashboardContent() {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div className="bg-white p-6 rounded-xl shadow overflow-hidden">
|
<div className="bg-white p-6 rounded-xl shadow overflow-hidden">
|
||||||
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
<h3 className="font-bold text-lg text-gray-800 mb-4">
|
||||||
Categories Word Cloud
|
Transcript Word Cloud
|
||||||
</h3>
|
</h3>
|
||||||
<WordCloud words={getWordCloudData()} width={500} height={300} />
|
<WordCloud words={getWordCloudData()} width={500} height={300} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
327
lib/metrics.ts
327
lib/metrics.ts
@ -4,13 +4,308 @@ import {
|
|||||||
DayMetrics,
|
DayMetrics,
|
||||||
CategoryMetrics,
|
CategoryMetrics,
|
||||||
LanguageMetrics,
|
LanguageMetrics,
|
||||||
|
CountryMetrics, // Added CountryMetrics
|
||||||
MetricsResult,
|
MetricsResult,
|
||||||
|
WordCloudWord, // Added WordCloudWord
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
interface CompanyConfig {
|
interface CompanyConfig {
|
||||||
sentimentAlert?: number;
|
sentimentAlert?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List of common stop words - this can be expanded
|
||||||
|
const stopWords = new Set([
|
||||||
|
"assistant",
|
||||||
|
"user",
|
||||||
|
// Web
|
||||||
|
"com",
|
||||||
|
"www",
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"www2",
|
||||||
|
"href",
|
||||||
|
"html",
|
||||||
|
"php",
|
||||||
|
"js",
|
||||||
|
"css",
|
||||||
|
"xml",
|
||||||
|
"json",
|
||||||
|
"txt",
|
||||||
|
"jpg",
|
||||||
|
"jpeg",
|
||||||
|
"png",
|
||||||
|
"gif",
|
||||||
|
"bmp",
|
||||||
|
"svg",
|
||||||
|
"org",
|
||||||
|
"net",
|
||||||
|
"co",
|
||||||
|
"io",
|
||||||
|
// English stop words
|
||||||
|
"a",
|
||||||
|
"an",
|
||||||
|
"the",
|
||||||
|
"is",
|
||||||
|
"are",
|
||||||
|
"was",
|
||||||
|
"were",
|
||||||
|
"be",
|
||||||
|
"been",
|
||||||
|
"being",
|
||||||
|
"have",
|
||||||
|
"has",
|
||||||
|
"had",
|
||||||
|
"do",
|
||||||
|
"does",
|
||||||
|
"did",
|
||||||
|
"will",
|
||||||
|
"would",
|
||||||
|
"should",
|
||||||
|
"can",
|
||||||
|
"could",
|
||||||
|
"may",
|
||||||
|
"might",
|
||||||
|
"must",
|
||||||
|
"am",
|
||||||
|
"i",
|
||||||
|
"you",
|
||||||
|
"he",
|
||||||
|
"she",
|
||||||
|
"it",
|
||||||
|
"we",
|
||||||
|
"they",
|
||||||
|
"me",
|
||||||
|
"him",
|
||||||
|
"her",
|
||||||
|
"us",
|
||||||
|
"them",
|
||||||
|
"my",
|
||||||
|
"your",
|
||||||
|
"his",
|
||||||
|
"its",
|
||||||
|
"our",
|
||||||
|
"their",
|
||||||
|
"mine",
|
||||||
|
"yours",
|
||||||
|
"hers",
|
||||||
|
"ours",
|
||||||
|
"theirs",
|
||||||
|
"to",
|
||||||
|
"of",
|
||||||
|
"in",
|
||||||
|
"on",
|
||||||
|
"at",
|
||||||
|
"by",
|
||||||
|
"for",
|
||||||
|
"with",
|
||||||
|
"about",
|
||||||
|
"against",
|
||||||
|
"between",
|
||||||
|
"into",
|
||||||
|
"through",
|
||||||
|
"during",
|
||||||
|
"before",
|
||||||
|
"after",
|
||||||
|
"above",
|
||||||
|
"below",
|
||||||
|
"from",
|
||||||
|
"up",
|
||||||
|
"down",
|
||||||
|
"out",
|
||||||
|
"off",
|
||||||
|
"over",
|
||||||
|
"under",
|
||||||
|
"again",
|
||||||
|
"further",
|
||||||
|
"then",
|
||||||
|
"once",
|
||||||
|
"here",
|
||||||
|
"there",
|
||||||
|
"when",
|
||||||
|
"where",
|
||||||
|
"why",
|
||||||
|
"how",
|
||||||
|
"all",
|
||||||
|
"any",
|
||||||
|
"both",
|
||||||
|
"each",
|
||||||
|
"few",
|
||||||
|
"more",
|
||||||
|
"most",
|
||||||
|
"other",
|
||||||
|
"some",
|
||||||
|
"such",
|
||||||
|
"no",
|
||||||
|
"nor",
|
||||||
|
"not",
|
||||||
|
"only",
|
||||||
|
"own",
|
||||||
|
"same",
|
||||||
|
"so",
|
||||||
|
"than",
|
||||||
|
"too",
|
||||||
|
"very",
|
||||||
|
"s",
|
||||||
|
"t",
|
||||||
|
"just",
|
||||||
|
"don",
|
||||||
|
"shouldve",
|
||||||
|
"now",
|
||||||
|
"d",
|
||||||
|
"ll",
|
||||||
|
"m",
|
||||||
|
"o",
|
||||||
|
"re",
|
||||||
|
"ve",
|
||||||
|
"y",
|
||||||
|
"ain",
|
||||||
|
"aren",
|
||||||
|
"couldn",
|
||||||
|
"didn",
|
||||||
|
"doesn",
|
||||||
|
"hadn",
|
||||||
|
"hasn",
|
||||||
|
"haven",
|
||||||
|
"isn",
|
||||||
|
"ma",
|
||||||
|
"mightn",
|
||||||
|
"mustn",
|
||||||
|
"needn",
|
||||||
|
"shan",
|
||||||
|
"shouldn",
|
||||||
|
"wasn",
|
||||||
|
"weren",
|
||||||
|
"won",
|
||||||
|
"wouldn",
|
||||||
|
"hi",
|
||||||
|
"hello",
|
||||||
|
"thanks",
|
||||||
|
"thank",
|
||||||
|
"please",
|
||||||
|
"ok",
|
||||||
|
"okay",
|
||||||
|
"yes",
|
||||||
|
"yeah",
|
||||||
|
"bye",
|
||||||
|
"goodbye",
|
||||||
|
// French stop words
|
||||||
|
"la",
|
||||||
|
"le",
|
||||||
|
"les",
|
||||||
|
"un",
|
||||||
|
"une",
|
||||||
|
"des",
|
||||||
|
"et",
|
||||||
|
"ou",
|
||||||
|
"mais",
|
||||||
|
"donc",
|
||||||
|
// Dutch stop words
|
||||||
|
"dit",
|
||||||
|
"ben",
|
||||||
|
"de",
|
||||||
|
"het",
|
||||||
|
"ik",
|
||||||
|
"jij",
|
||||||
|
"hij",
|
||||||
|
"zij",
|
||||||
|
"wij",
|
||||||
|
"jullie",
|
||||||
|
"deze",
|
||||||
|
"dit",
|
||||||
|
"dat",
|
||||||
|
"die",
|
||||||
|
"een",
|
||||||
|
"en",
|
||||||
|
"of",
|
||||||
|
"maar",
|
||||||
|
"want",
|
||||||
|
"omdat",
|
||||||
|
"dus",
|
||||||
|
"als",
|
||||||
|
"ook",
|
||||||
|
"dan",
|
||||||
|
"nu",
|
||||||
|
"nog",
|
||||||
|
"al",
|
||||||
|
"naar",
|
||||||
|
"voor",
|
||||||
|
"van",
|
||||||
|
"door",
|
||||||
|
"met",
|
||||||
|
"bij",
|
||||||
|
"tot",
|
||||||
|
"om",
|
||||||
|
"over",
|
||||||
|
"tussen",
|
||||||
|
"onder",
|
||||||
|
"boven",
|
||||||
|
"tegen",
|
||||||
|
"aan",
|
||||||
|
"uit",
|
||||||
|
"sinds",
|
||||||
|
"tijdens",
|
||||||
|
"binnen",
|
||||||
|
"buiten",
|
||||||
|
"zonder",
|
||||||
|
"volgens",
|
||||||
|
"dankzij",
|
||||||
|
"ondanks",
|
||||||
|
"behalve",
|
||||||
|
"mits",
|
||||||
|
"tenzij",
|
||||||
|
"hoewel",
|
||||||
|
"alhoewel",
|
||||||
|
"toch",
|
||||||
|
"anders",
|
||||||
|
"echter",
|
||||||
|
"wel",
|
||||||
|
"niet",
|
||||||
|
"geen",
|
||||||
|
"iets",
|
||||||
|
"niets",
|
||||||
|
"veel",
|
||||||
|
"weinig",
|
||||||
|
"meer",
|
||||||
|
"meest",
|
||||||
|
"elk",
|
||||||
|
"ieder",
|
||||||
|
"sommige",
|
||||||
|
"hoe",
|
||||||
|
"wat",
|
||||||
|
"waar",
|
||||||
|
"wie",
|
||||||
|
"wanneer",
|
||||||
|
"waarom",
|
||||||
|
"welke",
|
||||||
|
"wordt",
|
||||||
|
"worden",
|
||||||
|
"werd",
|
||||||
|
"werden",
|
||||||
|
"geworden",
|
||||||
|
"zijn",
|
||||||
|
"ben",
|
||||||
|
"bent",
|
||||||
|
"was",
|
||||||
|
"waren",
|
||||||
|
"geweest",
|
||||||
|
"hebben",
|
||||||
|
"heb",
|
||||||
|
"hebt",
|
||||||
|
"heeft",
|
||||||
|
"had",
|
||||||
|
"hadden",
|
||||||
|
"gehad",
|
||||||
|
"kunnen",
|
||||||
|
"kan",
|
||||||
|
"kunt",
|
||||||
|
"kon",
|
||||||
|
"konden",
|
||||||
|
"zullen",
|
||||||
|
"zal",
|
||||||
|
"zult",
|
||||||
|
// Add more domain-specific stop words if necessary
|
||||||
|
]);
|
||||||
|
|
||||||
export function sessionMetrics(
|
export function sessionMetrics(
|
||||||
sessions: ChatSession[],
|
sessions: ChatSession[],
|
||||||
companyConfig: CompanyConfig = {}
|
companyConfig: CompanyConfig = {}
|
||||||
@ -19,6 +314,7 @@ export function sessionMetrics(
|
|||||||
const byDay: DayMetrics = {};
|
const byDay: DayMetrics = {};
|
||||||
const byCategory: CategoryMetrics = {};
|
const byCategory: CategoryMetrics = {};
|
||||||
const byLanguage: LanguageMetrics = {};
|
const byLanguage: LanguageMetrics = {};
|
||||||
|
const byCountry: CountryMetrics = {}; // Added for country data
|
||||||
const tokensByDay: DayMetrics = {};
|
const tokensByDay: DayMetrics = {};
|
||||||
const tokensCostByDay: DayMetrics = {};
|
const tokensCostByDay: DayMetrics = {};
|
||||||
|
|
||||||
@ -40,12 +336,15 @@ export function sessionMetrics(
|
|||||||
let totalDuration = 0;
|
let totalDuration = 0;
|
||||||
let durationCount = 0;
|
let durationCount = 0;
|
||||||
|
|
||||||
|
const wordCounts: { [key: string]: number } = {}; // For WordCloud
|
||||||
|
|
||||||
sessions.forEach((s) => {
|
sessions.forEach((s) => {
|
||||||
const day = s.startTime.toISOString().slice(0, 10);
|
const day = s.startTime.toISOString().slice(0, 10);
|
||||||
byDay[day] = (byDay[day] || 0) + 1;
|
byDay[day] = (byDay[day] || 0) + 1;
|
||||||
|
|
||||||
if (s.category) byCategory[s.category] = (byCategory[s.category] || 0) + 1;
|
if (s.category) byCategory[s.category] = (byCategory[s.category] || 0) + 1;
|
||||||
if (s.language) byLanguage[s.language] = (byLanguage[s.language] || 0) + 1;
|
if (s.language) byLanguage[s.language] = (byLanguage[s.language] || 0) + 1;
|
||||||
|
if (s.country) byCountry[s.country] = (byCountry[s.country] || 0) + 1; // Populate byCountry
|
||||||
|
|
||||||
// Process token usage by day
|
// Process token usage by day
|
||||||
if (s.tokens) {
|
if (s.tokens) {
|
||||||
@ -88,6 +387,24 @@ export function sessionMetrics(
|
|||||||
|
|
||||||
totalTokens += s.tokens || 0;
|
totalTokens += s.tokens || 0;
|
||||||
totalTokensEur += s.tokensEur || 0;
|
totalTokensEur += s.tokensEur || 0;
|
||||||
|
|
||||||
|
// Process transcript for WordCloud
|
||||||
|
if (s.transcriptContent) {
|
||||||
|
const words = s.transcriptContent.toLowerCase().match(/\b\w+\b/g); // Split into words, lowercase
|
||||||
|
if (words) {
|
||||||
|
words.forEach((word) => {
|
||||||
|
const cleanedWord = word.replace(/[^a-z0-9]/gi, ""); // Remove punctuation
|
||||||
|
if (
|
||||||
|
cleanedWord &&
|
||||||
|
!stopWords.has(cleanedWord) &&
|
||||||
|
cleanedWord.length > 2
|
||||||
|
) {
|
||||||
|
// Check if not a stop word and length > 2
|
||||||
|
wordCounts[cleanedWord] = (wordCounts[cleanedWord] || 0) + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now add sentiment alert logic:
|
// Now add sentiment alert logic:
|
||||||
@ -107,13 +424,20 @@ export function sessionMetrics(
|
|||||||
const avgSessionLength =
|
const avgSessionLength =
|
||||||
durationCount > 0 ? totalDuration / durationCount : null;
|
durationCount > 0 ? totalDuration / durationCount : null;
|
||||||
|
|
||||||
|
// Prepare wordCloudData
|
||||||
|
const wordCloudData: WordCloudWord[] = Object.entries(wordCounts)
|
||||||
|
.map(([text, value]) => ({ text, value }))
|
||||||
|
.sort((a, b) => b.value - a.value)
|
||||||
|
.slice(0, 500); // Take top 500 words
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalSessions: total,
|
totalSessions: total,
|
||||||
avgSessionsPerDay,
|
avgSessionsPerDay,
|
||||||
avgSessionLength,
|
avgSessionLength,
|
||||||
days: byDay,
|
days: byDay,
|
||||||
languages: byLanguage,
|
languages: byLanguage,
|
||||||
categories: byCategory,
|
categories: byCategory, // This will be empty if we are not using categories for word cloud
|
||||||
|
countries: byCountry, // Added countries to the result
|
||||||
belowThresholdCount: belowThreshold,
|
belowThresholdCount: belowThreshold,
|
||||||
// Additional metrics not in the interface - using type assertion
|
// Additional metrics not in the interface - using type assertion
|
||||||
escalatedCount: escalated,
|
escalatedCount: escalated,
|
||||||
@ -131,5 +455,6 @@ export function sessionMetrics(
|
|||||||
sentimentNegativeCount: sentimentNegative,
|
sentimentNegativeCount: sentimentNegative,
|
||||||
tokensByDay,
|
tokensByDay,
|
||||||
tokensCostByDay,
|
tokensCostByDay,
|
||||||
|
wordCloudData, // Added word cloud data
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
11
lib/types.ts
11
lib/types.ts
@ -74,6 +74,15 @@ export interface LanguageMetrics {
|
|||||||
[language: string]: number;
|
[language: string]: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CountryMetrics {
|
||||||
|
[country: string]: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WordCloudWord {
|
||||||
|
text: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MetricsResult {
|
export interface MetricsResult {
|
||||||
totalSessions: number;
|
totalSessions: number;
|
||||||
avgSessionsPerDay: number;
|
avgSessionsPerDay: number;
|
||||||
@ -81,6 +90,7 @@ export interface MetricsResult {
|
|||||||
days: DayMetrics;
|
days: DayMetrics;
|
||||||
languages: LanguageMetrics;
|
languages: LanguageMetrics;
|
||||||
categories: CategoryMetrics;
|
categories: CategoryMetrics;
|
||||||
|
countries: CountryMetrics; // Added for geographic distribution
|
||||||
belowThresholdCount: number;
|
belowThresholdCount: number;
|
||||||
// Additional properties for dashboard
|
// Additional properties for dashboard
|
||||||
escalatedCount?: number;
|
escalatedCount?: number;
|
||||||
@ -98,6 +108,7 @@ export interface MetricsResult {
|
|||||||
sentimentNegativeCount?: number;
|
sentimentNegativeCount?: number;
|
||||||
tokensByDay?: DayMetrics;
|
tokensByDay?: DayMetrics;
|
||||||
tokensCostByDay?: DayMetrics;
|
tokensCostByDay?: DayMetrics;
|
||||||
|
wordCloudData?: WordCloudWord[]; // Added for transcript-based word cloud
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiResponse<T> {
|
export interface ApiResponse<T> {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { getServerSession } from "next-auth";
|
|||||||
import { prisma } from "../../../lib/prisma";
|
import { prisma } from "../../../lib/prisma";
|
||||||
import { sessionMetrics } from "../../../lib/metrics";
|
import { sessionMetrics } from "../../../lib/metrics";
|
||||||
import { authOptions } from "../auth/[...nextauth]";
|
import { authOptions } from "../auth/[...nextauth]";
|
||||||
|
import { ChatSession } from "../../../lib/types"; // Import ChatSession
|
||||||
|
|
||||||
interface SessionUser {
|
interface SessionUser {
|
||||||
email: string;
|
email: string;
|
||||||
@ -32,13 +33,47 @@ export default async function handler(
|
|||||||
|
|
||||||
if (!user) return res.status(401).json({ error: "No user" });
|
if (!user) return res.status(401).json({ error: "No user" });
|
||||||
|
|
||||||
const sessions = await prisma.session.findMany({
|
const prismaSessions = await prisma.session.findMany({
|
||||||
where: { companyId: user.companyId },
|
where: { companyId: user.companyId },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Convert Prisma sessions to ChatSession[] type for sessionMetrics
|
||||||
|
const chatSessions: ChatSession[] = prismaSessions.map((ps) => ({
|
||||||
|
id: ps.id, // Map Prisma's id to ChatSession.id
|
||||||
|
sessionId: ps.id, // Map Prisma's id to ChatSession.sessionId
|
||||||
|
companyId: ps.companyId,
|
||||||
|
startTime: new Date(ps.startTime), // Ensure startTime is a Date object
|
||||||
|
endTime: ps.endTime ? new Date(ps.endTime) : null, // Ensure endTime is a Date object or null
|
||||||
|
transcriptContent: ps.transcriptContent || "", // Ensure transcriptContent is a string
|
||||||
|
createdAt: new Date(ps.createdAt), // Map Prisma's createdAt
|
||||||
|
updatedAt: new Date(ps.createdAt), // Use createdAt for updatedAt as Session model doesn't have updatedAt
|
||||||
|
category: ps.category || undefined,
|
||||||
|
language: ps.language || undefined,
|
||||||
|
country: ps.country || undefined,
|
||||||
|
ipAddress: ps.ipAddress || undefined,
|
||||||
|
sentiment: ps.sentiment === null ? undefined : ps.sentiment,
|
||||||
|
messagesSent: ps.messagesSent === null ? undefined : ps.messagesSent, // Handle null messagesSent
|
||||||
|
avgResponseTime:
|
||||||
|
ps.avgResponseTime === null ? undefined : ps.avgResponseTime,
|
||||||
|
tokens: ps.tokens === null ? undefined : ps.tokens,
|
||||||
|
tokensEur: ps.tokensEur === null ? undefined : ps.tokensEur,
|
||||||
|
escalated: ps.escalated || false,
|
||||||
|
forwardedHr: ps.forwardedHr || false,
|
||||||
|
initialMsg: ps.initialMsg || undefined,
|
||||||
|
fullTranscriptUrl: ps.fullTranscriptUrl || undefined,
|
||||||
|
// 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
|
||||||
|
}));
|
||||||
|
|
||||||
// Pass company config to metrics
|
// Pass company config to metrics
|
||||||
// @ts-expect-error - Type conversion is needed between prisma session and ChatSession
|
const companyConfigForMetrics = {
|
||||||
const metrics = sessionMetrics(sessions, user.company);
|
sentimentAlert:
|
||||||
|
user.company.sentimentAlert === null
|
||||||
|
? undefined
|
||||||
|
: user.company.sentimentAlert,
|
||||||
|
};
|
||||||
|
|
||||||
|
const metrics = sessionMetrics(chatSessions, companyConfigForMetrics);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
metrics,
|
metrics,
|
||||||
|
|||||||
@ -27,32 +27,38 @@ export default async function handler(
|
|||||||
|
|
||||||
// Map Prisma session object to ChatSession type
|
// Map Prisma session object to ChatSession type
|
||||||
const session: ChatSession = {
|
const session: ChatSession = {
|
||||||
|
// Spread prismaSession to include all its properties
|
||||||
...prismaSession,
|
...prismaSession,
|
||||||
sessionId: prismaSession.id, // Assuming ChatSession's sessionId is Prisma's id
|
// Override properties that need conversion or specific mapping
|
||||||
|
id: prismaSession.id, // ChatSession.id from Prisma.Session.id
|
||||||
|
sessionId: prismaSession.id, // ChatSession.sessionId from Prisma.Session.id
|
||||||
startTime: new Date(prismaSession.startTime),
|
startTime: new Date(prismaSession.startTime),
|
||||||
endTime: prismaSession.endTime ? new Date(prismaSession.endTime) : null,
|
endTime: prismaSession.endTime ? new Date(prismaSession.endTime) : null,
|
||||||
createdAt: new Date(prismaSession.createdAt),
|
createdAt: new Date(prismaSession.createdAt),
|
||||||
updatedAt: new Date(prismaSession.updatedAt),
|
// Prisma.Session does not have an `updatedAt` field. We'll use `createdAt` as a fallback.
|
||||||
userId: prismaSession.userId === undefined ? null : prismaSession.userId,
|
// Or, if your business logic implies an update timestamp elsewhere, use that.
|
||||||
category: prismaSession.category === undefined ? null : prismaSession.category,
|
updatedAt: new Date(prismaSession.createdAt), // Fallback to createdAt
|
||||||
language: prismaSession.language === undefined ? null : prismaSession.language,
|
// Prisma.Session does not have a `userId` field.
|
||||||
country: prismaSession.country === undefined ? null : prismaSession.country,
|
userId: null, // Explicitly set to null or map if available from another source
|
||||||
ipAddress: prismaSession.ipAddress === undefined ? null : prismaSession.ipAddress,
|
// Ensure nullable fields from Prisma are correctly mapped to ChatSession's optional or nullable fields
|
||||||
sentiment: prismaSession.sentiment === undefined ? null : prismaSession.sentiment,
|
category: prismaSession.category ?? null,
|
||||||
messagesSent: prismaSession.messagesSent === undefined ? undefined : prismaSession.messagesSent,
|
language: prismaSession.language ?? null,
|
||||||
avgResponseTime: prismaSession.avgResponseTime === undefined ? null : prismaSession.avgResponseTime,
|
country: prismaSession.country ?? null,
|
||||||
escalated: prismaSession.escalated === undefined ? undefined : prismaSession.escalated,
|
ipAddress: prismaSession.ipAddress ?? null,
|
||||||
forwardedHr: prismaSession.forwardedHr === undefined ? undefined : prismaSession.forwardedHr,
|
sentiment: prismaSession.sentiment ?? null,
|
||||||
tokens: prismaSession.tokens === undefined ? undefined : prismaSession.tokens,
|
messagesSent: prismaSession.messagesSent ?? undefined, // Use undefined if ChatSession expects number | undefined
|
||||||
tokensEur: prismaSession.tokensEur === undefined ? undefined : prismaSession.tokensEur,
|
avgResponseTime: prismaSession.avgResponseTime ?? null,
|
||||||
initialMsg: prismaSession.initialMsg === undefined ? null : prismaSession.initialMsg,
|
escalated: prismaSession.escalated ?? undefined,
|
||||||
fullTranscriptUrl: prismaSession.fullTranscriptUrl === undefined ? null : prismaSession.fullTranscriptUrl,
|
forwardedHr: prismaSession.forwardedHr ?? undefined,
|
||||||
transcriptContent: prismaSession.transcriptContent === undefined ? null : prismaSession.transcriptContent,
|
tokens: prismaSession.tokens ?? undefined,
|
||||||
|
tokensEur: prismaSession.tokensEur ?? undefined,
|
||||||
|
initialMsg: prismaSession.initialMsg ?? undefined,
|
||||||
|
fullTranscriptUrl: prismaSession.fullTranscriptUrl ?? null,
|
||||||
|
transcriptContent: prismaSession.transcriptContent ?? null,
|
||||||
};
|
};
|
||||||
|
|
||||||
return res.status(200).json({ session });
|
return res.status(200).json({ session });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch session ${id}:`, error);
|
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error instanceof Error ? error.message : "An unknown error occurred";
|
error instanceof Error ? error.message : "An unknown error occurred";
|
||||||
return res
|
return res
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "../auth/[...nextauth]";
|
||||||
import { prisma } from "../../../lib/prisma";
|
import { prisma } from "../../../lib/prisma";
|
||||||
import { ChatSession } from "../../../lib/types";
|
import { ChatSession } from "../../../lib/types";
|
||||||
|
|
||||||
@ -10,64 +12,70 @@ export default async function handler(
|
|||||||
return res.status(405).json({ error: "Method not allowed" });
|
return res.status(405).json({ error: "Method not allowed" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authSession = await getServerSession(req, res, authOptions);
|
||||||
|
|
||||||
|
if (!authSession || !authSession.user?.companyId) {
|
||||||
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const companyId = authSession.user.companyId;
|
||||||
const { searchTerm } = req.query;
|
const { searchTerm } = req.query;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let prismaSessions;
|
const whereClause: any = { companyId };
|
||||||
|
|
||||||
if (searchTerm && typeof searchTerm === "string" && searchTerm.trim() !== "") {
|
if (
|
||||||
|
searchTerm &&
|
||||||
|
typeof searchTerm === "string" &&
|
||||||
|
searchTerm.trim() !== ""
|
||||||
|
) {
|
||||||
const searchConditions = [
|
const searchConditions = [
|
||||||
{ id: { contains: searchTerm, mode: "insensitive" } },
|
{ id: { contains: searchTerm, mode: "insensitive" } },
|
||||||
{ category: { contains: searchTerm, mode: "insensitive" } },
|
{ category: { contains: searchTerm, mode: "insensitive" } },
|
||||||
{ initialMsg: { contains: searchTerm, mode: "insensitive" } },
|
{ initialMsg: { contains: searchTerm, mode: "insensitive" } },
|
||||||
{ transcriptContent: { contains: searchTerm, mode: "insensitive" } },
|
{ transcriptContent: { contains: searchTerm, mode: "insensitive" } },
|
||||||
];
|
];
|
||||||
|
whereClause.OR = searchConditions;
|
||||||
prismaSessions = await prisma.session.findMany({
|
|
||||||
where: {
|
|
||||||
OR: searchConditions,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
startTime: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
prismaSessions = await prisma.session.findMany({
|
|
||||||
orderBy: {
|
|
||||||
startTime: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessions: ChatSession[] = prismaSessions.map(ps => ({
|
const prismaSessions = await prisma.session.findMany({
|
||||||
...ps,
|
where: whereClause,
|
||||||
|
orderBy: {
|
||||||
|
startTime: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessions: ChatSession[] = prismaSessions.map((ps) => ({
|
||||||
|
id: ps.id,
|
||||||
sessionId: ps.id,
|
sessionId: ps.id,
|
||||||
|
companyId: ps.companyId,
|
||||||
startTime: new Date(ps.startTime),
|
startTime: new Date(ps.startTime),
|
||||||
endTime: ps.endTime ? new Date(ps.endTime) : null,
|
endTime: ps.endTime ? new Date(ps.endTime) : null,
|
||||||
createdAt: new Date(ps.createdAt),
|
createdAt: new Date(ps.createdAt),
|
||||||
updatedAt: new Date(ps.updatedAt),
|
updatedAt: new Date(ps.createdAt),
|
||||||
userId: ps.userId === undefined ? null : ps.userId,
|
userId: null,
|
||||||
category: ps.category === undefined ? null : ps.category,
|
category: ps.category ?? null,
|
||||||
language: ps.language === undefined ? null : ps.language,
|
language: ps.language ?? null,
|
||||||
country: ps.country === undefined ? null : ps.country,
|
country: ps.country ?? null,
|
||||||
ipAddress: ps.ipAddress === undefined ? null : ps.ipAddress,
|
ipAddress: ps.ipAddress ?? null,
|
||||||
sentiment: ps.sentiment === undefined ? null : ps.sentiment,
|
sentiment: ps.sentiment ?? null,
|
||||||
messagesSent: ps.messagesSent === undefined ? undefined : ps.messagesSent,
|
messagesSent: ps.messagesSent ?? undefined,
|
||||||
avgResponseTime: ps.avgResponseTime === undefined ? null : ps.avgResponseTime,
|
avgResponseTime: ps.avgResponseTime ?? null,
|
||||||
escalated: ps.escalated === undefined ? undefined : ps.escalated,
|
escalated: ps.escalated ?? undefined,
|
||||||
forwardedHr: ps.forwardedHr === undefined ? undefined : ps.forwardedHr,
|
forwardedHr: ps.forwardedHr ?? undefined,
|
||||||
tokens: ps.tokens === undefined ? undefined : ps.tokens,
|
tokens: ps.tokens ?? undefined,
|
||||||
tokensEur: ps.tokensEur === undefined ? undefined : ps.tokensEur,
|
tokensEur: ps.tokensEur ?? undefined,
|
||||||
initialMsg: ps.initialMsg === undefined ? null : ps.initialMsg,
|
initialMsg: ps.initialMsg ?? undefined,
|
||||||
fullTranscriptUrl: ps.fullTranscriptUrl === undefined ? null : ps.fullTranscriptUrl,
|
fullTranscriptUrl: ps.fullTranscriptUrl ?? null,
|
||||||
transcriptContent: ps.transcriptContent === undefined ? null : ps.transcriptContent,
|
transcriptContent: ps.transcriptContent ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return res.status(200).json({ sessions });
|
return res.status(200).json({ sessions });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch sessions:", error);
|
|
||||||
const errorMessage =
|
const errorMessage =
|
||||||
error instanceof Error ? error.message : "An unknown error occurred";
|
error instanceof Error ? error.message : "An unknown error occurred";
|
||||||
return res.status(500).json({ error: "Failed to fetch sessions", details: errorMessage });
|
return res
|
||||||
|
.status(500)
|
||||||
|
.json({ error: "Failed to fetch sessions", details: errorMessage });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user