mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-01-16 07:52:10 +01:00
Improves dashboard with new metrics and charts
Enhances the dashboard with new key performance indicators (KPIs) and visualizations. Introduces a new stat card component for displaying metrics with trends and icons. Adds sentiment analysis, language distribution, and token usage charts to provide a more comprehensive overview of session data. These additions provide deeper insights into user interactions and platform performance.
This commit is contained in:
@ -49,35 +49,37 @@ interface SessionData {
|
||||
* @returns A numeric score representing the sentiment
|
||||
*/
|
||||
function mapSentimentToScore(sentimentStr?: string): number | null {
|
||||
if (!sentimentStr) return null;
|
||||
if (!sentimentStr) return null;
|
||||
|
||||
// Convert to lowercase for case-insensitive matching
|
||||
const sentiment = sentimentStr.toLowerCase();
|
||||
// Convert to lowercase for case-insensitive matching
|
||||
const sentiment = sentimentStr.toLowerCase();
|
||||
|
||||
// Map sentiment strings to numeric values on a scale from -1 to 2
|
||||
const sentimentMap: Record<string, number> = {
|
||||
'happy': 1.0,
|
||||
'excited': 1.5,
|
||||
'positive': 0.8,
|
||||
'neutral': 0.0,
|
||||
'playful': 0.7,
|
||||
'negative': -0.8,
|
||||
'angry': -1.0,
|
||||
'sad': -0.7,
|
||||
'frustrated': -0.9,
|
||||
'positief': 0.8, // Dutch
|
||||
'neutraal': 0.0, // Dutch
|
||||
'negatief': -0.8, // Dutch
|
||||
'positivo': 0.8, // Spanish/Italian
|
||||
'neutro': 0.0, // Spanish/Italian
|
||||
'negativo': -0.8, // Spanish/Italian
|
||||
'yes': 0.5, // For any "yes" sentiment
|
||||
'no': -0.5, // For any "no" sentiment
|
||||
};
|
||||
// Map sentiment strings to numeric values on a scale from -1 to 2
|
||||
const sentimentMap: Record<string, number> = {
|
||||
happy: 1.0,
|
||||
excited: 1.5,
|
||||
positive: 0.8,
|
||||
neutral: 0.0,
|
||||
playful: 0.7,
|
||||
negative: -0.8,
|
||||
angry: -1.0,
|
||||
sad: -0.7,
|
||||
frustrated: -0.9,
|
||||
positief: 0.8, // Dutch
|
||||
neutraal: 0.0, // Dutch
|
||||
negatief: -0.8, // Dutch
|
||||
positivo: 0.8, // Spanish/Italian
|
||||
neutro: 0.0, // Spanish/Italian
|
||||
negativo: -0.8, // Spanish/Italian
|
||||
yes: 0.5, // For any "yes" sentiment
|
||||
no: -0.5, // For any "no" sentiment
|
||||
};
|
||||
|
||||
return sentimentMap[sentiment] !== undefined
|
||||
? sentimentMap[sentiment]
|
||||
: isNaN(parseFloat(sentiment)) ? null : parseFloat(sentiment);
|
||||
return sentimentMap[sentiment] !== undefined
|
||||
? sentimentMap[sentiment]
|
||||
: isNaN(parseFloat(sentiment))
|
||||
? null
|
||||
: parseFloat(sentiment);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,13 +88,22 @@ function mapSentimentToScore(sentimentStr?: string): number | null {
|
||||
* @returns True if the string indicates a positive/true value
|
||||
*/
|
||||
function isTruthyValue(value?: string): boolean {
|
||||
if (!value) return false;
|
||||
if (!value) return false;
|
||||
|
||||
const truthyValues = [
|
||||
'1', 'true', 'yes', 'y', 'ja', 'si', 'oui', 'да', 'да', 'はい'
|
||||
];
|
||||
const truthyValues = [
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"y",
|
||||
"ja",
|
||||
"si",
|
||||
"oui",
|
||||
"да",
|
||||
"да",
|
||||
"はい",
|
||||
];
|
||||
|
||||
return truthyValues.includes(value.toLowerCase());
|
||||
return truthyValues.includes(value.toLowerCase());
|
||||
}
|
||||
|
||||
export async function fetchAndParseCsv(
|
||||
@ -155,9 +166,9 @@ export async function fetchAndParseCsv(
|
||||
country: r.country,
|
||||
language: r.language,
|
||||
messagesSent: Number(r.messages_sent) || 0,
|
||||
sentiment: mapSentimentToScore(r.sentiment),
|
||||
escalated: isTruthyValue(r.escalated),
|
||||
forwardedHr: isTruthyValue(r.forwarded_hr),
|
||||
sentiment: mapSentimentToScore(r.sentiment),
|
||||
escalated: isTruthyValue(r.escalated),
|
||||
forwardedHr: isTruthyValue(r.forwarded_hr),
|
||||
fullTranscriptUrl: r.full_transcript_url,
|
||||
avgResponseTime: r.avg_response_time
|
||||
? parseFloat(r.avg_response_time)
|
||||
|
||||
@ -19,6 +19,9 @@ export function sessionMetrics(
|
||||
const byDay: DayMetrics = {};
|
||||
const byCategory: CategoryMetrics = {};
|
||||
const byLanguage: LanguageMetrics = {};
|
||||
const tokensByDay: DayMetrics = {};
|
||||
const tokensCostByDay: DayMetrics = {};
|
||||
|
||||
let escalated = 0,
|
||||
forwarded = 0;
|
||||
let totalSentiment = 0,
|
||||
@ -28,6 +31,11 @@ export function sessionMetrics(
|
||||
let totalTokens = 0,
|
||||
totalTokensEur = 0;
|
||||
|
||||
// For sentiment distribution
|
||||
let sentimentPositive = 0,
|
||||
sentimentNegative = 0,
|
||||
sentimentNeutral = 0;
|
||||
|
||||
// Calculate total session duration in minutes
|
||||
let totalDuration = 0;
|
||||
let durationCount = 0;
|
||||
@ -39,6 +47,16 @@ export function sessionMetrics(
|
||||
if (s.category) byCategory[s.category] = (byCategory[s.category] || 0) + 1;
|
||||
if (s.language) byLanguage[s.language] = (byLanguage[s.language] || 0) + 1;
|
||||
|
||||
// Process token usage by day
|
||||
if (s.tokens) {
|
||||
tokensByDay[day] = (tokensByDay[day] || 0) + s.tokens;
|
||||
}
|
||||
|
||||
// Process token cost by day
|
||||
if (s.tokensEur) {
|
||||
tokensCostByDay[day] = (tokensCostByDay[day] || 0) + s.tokensEur;
|
||||
}
|
||||
|
||||
if (s.endTime) {
|
||||
const duration =
|
||||
(s.endTime.getTime() - s.startTime.getTime()) / (1000 * 60); // minutes
|
||||
@ -52,6 +70,15 @@ export function sessionMetrics(
|
||||
if (s.sentiment != null) {
|
||||
totalSentiment += s.sentiment;
|
||||
sentimentCount++;
|
||||
|
||||
// Classify sentiment
|
||||
if (s.sentiment > 0.3) {
|
||||
sentimentPositive++;
|
||||
} else if (s.sentiment < -0.3) {
|
||||
sentimentNegative++;
|
||||
} else {
|
||||
sentimentNeutral++;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.avgResponseTime != null) {
|
||||
@ -91,11 +118,18 @@ export function sessionMetrics(
|
||||
// Additional metrics not in the interface - using type assertion
|
||||
escalatedCount: escalated,
|
||||
forwardedCount: forwarded,
|
||||
avgSentiment: sentimentCount ? totalSentiment / sentimentCount : null,
|
||||
avgResponseTime: responseCount ? totalResponse / responseCount : null,
|
||||
avgSentiment: sentimentCount ? totalSentiment / sentimentCount : undefined,
|
||||
avgResponseTime: responseCount ? totalResponse / responseCount : undefined,
|
||||
totalTokens,
|
||||
totalTokensEur,
|
||||
sentimentThreshold: threshold,
|
||||
lastUpdated: Date.now(), // Add current timestamp
|
||||
} as MetricsResult;
|
||||
|
||||
// New metrics for enhanced dashboard
|
||||
sentimentPositiveCount: sentimentPositive,
|
||||
sentimentNeutralCount: sentimentNeutral,
|
||||
sentimentNegativeCount: sentimentNegative,
|
||||
tokensByDay,
|
||||
tokensCostByDay,
|
||||
};
|
||||
}
|
||||
|
||||
@ -86,6 +86,13 @@ export interface MetricsResult {
|
||||
totalTokensEur?: number;
|
||||
sentimentThreshold?: number | null;
|
||||
lastUpdated?: number; // Timestamp for when metrics were last updated
|
||||
|
||||
// New metrics for enhanced dashboard
|
||||
sentimentPositiveCount?: number;
|
||||
sentimentNeutralCount?: number;
|
||||
sentimentNegativeCount?: number;
|
||||
tokensByDay?: DayMetrics;
|
||||
tokensCostByDay?: DayMetrics;
|
||||
}
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
|
||||
Reference in New Issue
Block a user