ai-reports/index-SBII.html
DJP 0da5b0e73d Initial commit: AI Tool Usage Reporting Dashboard
Complete suite of HTML dashboards for AI tool usage and conversation reporting via webhook APIs. Includes main navigation dashboard, multiple conversation reports (Latam, SBII, Sidekick), and AI tools usage report with comprehensive analytics.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-15 10:41:07 -04:00

1191 lines
No EOL
47 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Conversation Reporting Tool</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: 'Montserrat', sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 20px;
font-size: 22px;
}
.controls {
display: flex;
gap: 20px;
margin-bottom: 15px;
align-items: center;
justify-content: center;
}
select {
padding: 10px;
border-radius: 4px;
border: 1px solid #ddd;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
}
button {
padding: 10px 20px;
background-color: #4285f4;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-size: 16px;
font-weight: 500;
}
button:hover {
background-color: #3367d6;
}
.loading {
text-align: center;
display: none;
}
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 2s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.results {
margin-top: 15px;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th, td {
padding: 6px 12px;
text-align: left;
border-bottom: 1px solid #ddd;
line-height: 1.3;
}
th {
background-color: #f8f9fa;
font-weight: 600;
}
tr:hover {
background-color: #f5f5f5;
}
.error {
color: red;
text-align: center;
margin-top: 20px;
font-weight: 500;
}
.export-btn {
margin-top: 15px;
display: flex;
justify-content: flex-end;
}
.export-btn button {
padding: 6px 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-size: 13px;
display: flex;
align-items: center;
gap: 5px;
}
.export-btn button:hover {
background-color: #45a049;
}
.export-btn svg {
width: 14px;
height: 14px;
}
.time-filter {
display: flex;
gap: 10px;
margin-bottom: 15px;
justify-content: center;
flex-wrap: wrap;
}
.time-filter button {
padding: 6px 12px;
background-color: #f0f0f0;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-size: 13px;
font-weight: 500;
}
.time-filter button.active {
background-color: #4285f4;
color: white;
border-color: #4285f4;
}
.time-filter button:hover:not(.active) {
background-color: #e0e0e0;
}
.view-toggle {
display: flex;
gap: 10px;
margin-bottom: 15px;
justify-content: center;
flex-wrap: wrap;
}
.view-toggle button {
padding: 6px 12px;
background-color: #f0f0f0;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
font-family: 'Montserrat', sans-serif;
font-size: 13px;
font-weight: 500;
}
.view-toggle button.active {
background-color: #4285f4;
color: white;
border-color: #4285f4;
}
.view-toggle button:hover:not(.active) {
background-color: #e0e0e0;
}
.chart-container {
margin-top: 30px;
margin-bottom: 30px;
height: 300px;
width: 100%;
}
.panel {
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
padding: 15px;
margin-bottom: 15px;
}
.panel-title {
margin-top: 0;
margin-bottom: 15px;
font-size: 16px;
color: #333;
text-align: center;
}
</style>
</head>
<body>
<div class="container">
<h1>Conversation Reporting Tool</h1>
<div class="controls">
<select id="optionSelect">
<option value="itau2-Conversations">SBII-Conversations</option>
</select>
<button id="sendButton">Send Request</button>
<div id="exportContainer" class="export-btn" style="display: none;">
<button id="exportCsvButton">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
Export CSV
</button>
</div>
</div>
<div class="view-toggle" id="viewToggle" style="display: none;">
<button data-view="conversations" class="active">Conversations</button>
<button data-view="users">Unique Users</button>
</div>
<div class="time-filter" id="timeFilter" style="display: none;">
<button data-days="1" class="active">Today</button>
<button data-days="7">Last 7 Days</button>
<button data-days="30">Last 30 Days</button>
<button data-days="90">Last 3 Months</button>
<button data-days="all">All Time</button>
</div>
<div id="loading" class="loading">
<div class="loader"></div>
<p>Loading data, please wait...</p>
</div>
<div id="error" class="error"></div>
<div id="usage-chart-container" class="chart-container panel" style="display: none;">
<h3 class="panel-title">Conversation Usage Over Time</h3>
<canvas id="usageChart"></canvas>
</div>
<div id="users-chart-container" class="chart-container panel" style="display: none;">
<h3 class="panel-title">Unique Users Over Time</h3>
<canvas id="usersChart"></canvas>
</div>
<div id="results" class="results">
<!-- Results will be dynamically inserted here -->
</div>
</div>
<script>
// Global variables
let currentData = [];
let assistantData = [];
let usageChart = null;
let usersChart = null;
let currentView = 'conversations'; // Default view
// Map to store assistant IDs to names
let assistantNames = {};
// CSV Export functionality
document.getElementById('exportCsvButton').addEventListener('click', () => {
exportToCSV(currentData);
});
function exportToCSV(data) {
// Define columns for the CSV
const columns = [
'User ID', 'Start Time', 'Brand Voice Setting', 'Title',
'End Time', 'Assistant ID', 'Assistant Name', 'Assistant Key', 'Conversation ID',
'Vision Images'
];
// Create CSV header row
let csvContent = columns.join(',') + '\n';
// Add data rows
data.forEach(item => {
if (!item.data || Object.keys(item.data).length === 0) return;
const d = item.data;
const assistantId = d.Assistant_ID || '';
const assistantName = assistantNames[assistantId] || '';
const row = [
d.User_ID ? `"${d.User_ID}"` : '',
d.StartTime ? `"${d.StartTime}"` : '',
d['Brand Voice Setting'] ? `"${d['Brand Voice Setting'].replace(/"/g, '""')}"` : '',
d.Title ? `"${d.Title.replace(/"/g, '""')}"` : '',
d.EndTime ? `"${d.EndTime}"` : '',
assistantId ? `"${assistantId}"` : '',
assistantName ? `"${assistantName.replace(/"/g, '""')}"` : '',
d.Assistant_Key ? `"${d.Assistant_Key}"` : '',
d.Conversation_ID ? `"${d.Conversation_ID}"` : '',
d.Vision_Images ? `"${d.Vision_Images}"` : ''
];
csvContent += row.join(',') + '\n';
});
// Create a download link
const encodedUri = 'data:text/csv;charset=utf-8,' + encodeURIComponent(csvContent);
const link = document.createElement('a');
link.setAttribute('href', encodedUri);
link.setAttribute('download', `sbii-conversations-${new Date().toISOString().split('T')[0]}.csv`);
document.body.appendChild(link);
// Trigger download and remove link
link.click();
document.body.removeChild(link);
}
// Function to fetch assistant data
async function fetchAssistantData() {
try {
const response = await fetch('https://hook.us1.make.celonis.com/qyrswn18r3rrs8rpvo4n62mny6rorhah', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
auth_code: "jh87dkjs9sdj392sw72sj0210h*^&b$#rfg"
})
});
if (!response.ok) {
throw new Error(`HTTP error fetching assistant data! Status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching assistant data:', error);
return [];
}
}
// Build assistant name mapping
function buildAssistantMapping(data) {
const mapping = {};
data.forEach(item => {
if (item.data && item.data['Assistant ID'] && item.data['Name']) {
mapping[item.data['Assistant ID']] = item.data['Name'];
}
});
return mapping;
}
// Initialize view toggle buttons
function initializeViewToggle() {
const viewToggleButtons = document.querySelectorAll('#viewToggle button');
viewToggleButtons.forEach(button => {
button.addEventListener('click', () => {
// Update active button
viewToggleButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Get the view type
const view = button.getAttribute('data-view');
currentView = view;
// Toggle chart and result displays based on view
updateViewDisplay();
// Get the current time filter
const activeTimeFilter = document.querySelector('#timeFilter button.active');
const days = activeTimeFilter ? activeTimeFilter.getAttribute('data-days') : '1';
// Update display based on selected view
if (view === 'conversations') {
createUsageChart(currentData, days === 'all' ? null : parseInt(days));
const pivotData = processPivotData(currentData);
displayPivotTable(pivotData);
} else if (view === 'users') {
createUsersChart(currentData, days === 'all' ? null : parseInt(days));
const uniqueUsersData = processUniqueUsersData(currentData);
displayUniqueUsersTable(uniqueUsersData);
}
});
});
}
// Toggle display based on current view
function updateViewDisplay() {
if (currentView === 'conversations') {
document.getElementById('usage-chart-container').style.display = 'block';
document.getElementById('users-chart-container').style.display = 'none';
} else if (currentView === 'users') {
document.getElementById('usage-chart-container').style.display = 'none';
document.getElementById('users-chart-container').style.display = 'block';
}
}
// Process data for unique users view
function processUniqueUsersData(data) {
// Group users by Assistant_ID
const assistantGroups = {};
data.forEach(item => {
const assistantId = item.data.Assistant_ID || 'Unknown';
const userId = item.data.User_ID || 'Unknown';
if (!assistantGroups[assistantId]) {
assistantGroups[assistantId] = {
uniqueUsers: new Set(),
userDetails: {}
};
}
assistantGroups[assistantId].uniqueUsers.add(userId);
if (!assistantGroups[assistantId].userDetails[userId]) {
assistantGroups[assistantId].userDetails[userId] = {
conversationCount: 0,
firstSeen: item.data.StartTime || null,
lastSeen: item.data.StartTime || null
};
} else {
// Update last seen if current date is newer
const currentStartTime = new Date(item.data.StartTime || 0);
const lastSeen = new Date(assistantGroups[assistantId].userDetails[userId].lastSeen || 0);
if (currentStartTime > lastSeen) {
assistantGroups[assistantId].userDetails[userId].lastSeen = item.data.StartTime;
}
// Update first seen if current date is older
const firstSeen = new Date(assistantGroups[assistantId].userDetails[userId].firstSeen || Infinity);
if (currentStartTime < firstSeen) {
assistantGroups[assistantId].userDetails[userId].firstSeen = item.data.StartTime;
}
}
assistantGroups[assistantId].userDetails[userId].conversationCount++;
});
return assistantGroups;
}
// Create unique users chart
function createUsersChart(data, days) {
// Show chart container
document.getElementById('users-chart-container').style.display = 'block';
// Filter data based on days
const filteredData = filterDataByDays(data, days);
// Process data for the chart
const { labels, datasets } = processUsersDataForChart(filteredData);
// Create or update chart
if (usersChart) {
usersChart.data.labels = labels;
usersChart.data.datasets = datasets;
usersChart.update();
} else {
const ctx = document.getElementById('usersChart').getContext('2d');
usersChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: false
},
legend: {
position: 'top',
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
ticks: {
precision: 0,
stepSize: 1
}
}
}
}
});
}
}
// Process data for users chart
function processUsersDataForChart(data) {
// Get date range
const groupedByDate = {};
const groupedByAssistant = {};
const assistantColors = {};
const colorPalette = [
'#4285F4', '#EA4335', '#FBBC05', '#34A853',
'#673AB7', '#FF9800', '#00BCD4', '#795548',
'#9C27B0', '#607D8B', '#3F51B5', '#CDDC39'
];
// Track unique users by date and assistant
data.forEach(item => {
if (!item.data || !item.data.StartTime || !item.data.User_ID) return;
const date = new Date(item.data.StartTime);
const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
const assistantId = item.data.Assistant_ID || 'Unknown';
const userId = item.data.User_ID;
// Initialize for this date
if (!groupedByDate[dateStr]) {
groupedByDate[dateStr] = {};
}
if (!groupedByDate[dateStr][assistantId]) {
groupedByDate[dateStr][assistantId] = new Set();
}
// Add user to set of unique users for this date and assistant
groupedByDate[dateStr][assistantId].add(userId);
// Track assistants
if (!groupedByAssistant[assistantId]) {
groupedByAssistant[assistantId] = new Set();
// Assign color
const colorIndex = Object.keys(groupedByAssistant).length - 1;
assistantColors[assistantId] = colorPalette[colorIndex % colorPalette.length];
}
groupedByAssistant[assistantId].add(userId);
});
// Sort dates
const sortedDates = Object.keys(groupedByDate).sort();
// Create datasets for each assistant
const datasets = [];
for (const assistantId in groupedByAssistant) {
// Get assistant name for label if available
const assistantName = assistantNames[assistantId] || assistantId;
const dataset = {
label: assistantName,
data: [],
borderColor: assistantColors[assistantId],
backgroundColor: assistantColors[assistantId] + '20', // Add transparency
tension: 0.3,
fill: true
};
// Add data for each date - count of unique users
for (const date of sortedDates) {
const userCount = groupedByDate[date][assistantId] ?
groupedByDate[date][assistantId].size : 0;
dataset.data.push(userCount);
}
datasets.push(dataset);
}
return {
labels: sortedDates,
datasets: datasets
};
}
// Display unique users table
function displayUniqueUsersTable(usersData) {
const resultsDiv = document.getElementById('results');
resultsDiv.innerHTML = ''; // Clear previous results
// Create the table
const table = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
// Create header row
const headerRow = document.createElement('tr');
const headers = ['Assistant', 'Unique Users', 'Most Active User', 'Conversations/User (Avg)'];
headers.forEach(headerText => {
const header = document.createElement('th');
header.textContent = headerText;
headerRow.appendChild(header);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Create table body - one row per assistant
let totalUniqueUsers = 0;
let allUsers = new Set();
for (const assistantId in usersData) {
const row = document.createElement('tr');
const assistantData = usersData[assistantId];
const uniqueUserCount = assistantData.uniqueUsers.size;
totalUniqueUsers += uniqueUserCount;
// Add all users to the global set for deduplication
assistantData.uniqueUsers.forEach(userId => allUsers.add(userId));
// Assistant name column
const assistantCell = document.createElement('td');
const assistantName = assistantNames[assistantId] || null;
if (assistantName) {
assistantCell.textContent = `${assistantName} (${assistantId})`;
assistantCell.title = assistantId;
} else {
assistantCell.textContent = assistantId;
}
assistantCell.style.fontWeight = 'bold';
row.appendChild(assistantCell);
// Unique user count column
const userCountCell = document.createElement('td');
userCountCell.textContent = uniqueUserCount;
row.appendChild(userCountCell);
// Find most active user
let mostActiveUser = { id: null, count: 0 };
for (const userId in assistantData.userDetails) {
const userDetails = assistantData.userDetails[userId];
if (userDetails.conversationCount > mostActiveUser.count) {
mostActiveUser = {
id: userId,
count: userDetails.conversationCount
};
}
}
// Most active user column
const mostActiveCell = document.createElement('td');
if (mostActiveUser.id) {
mostActiveCell.textContent = `${mostActiveUser.id} (${mostActiveUser.count} conversations)`;
} else {
mostActiveCell.textContent = 'N/A';
}
row.appendChild(mostActiveCell);
// Average conversations per user
const avgCell = document.createElement('td');
let totalConversations = 0;
for (const userId in assistantData.userDetails) {
totalConversations += assistantData.userDetails[userId].conversationCount;
}
const average = uniqueUserCount > 0 ? (totalConversations / uniqueUserCount).toFixed(1) : 0;
avgCell.textContent = average;
row.appendChild(avgCell);
tbody.appendChild(row);
}
// Add grand total row
const grandTotalRow = document.createElement('tr');
const grandTotalLabelCell = document.createElement('td');
grandTotalLabelCell.textContent = 'GRAND TOTAL';
grandTotalLabelCell.style.fontWeight = 'bold';
grandTotalLabelCell.style.fontSize = '16px';
grandTotalRow.appendChild(grandTotalLabelCell);
const grandTotalUsersCell = document.createElement('td');
grandTotalUsersCell.textContent = allUsers.size; // Deduplicated count across all assistants
grandTotalUsersCell.style.fontWeight = 'bold';
grandTotalUsersCell.style.fontSize = '16px';
grandTotalRow.appendChild(grandTotalUsersCell);
// Fill remaining cells for formatting
const emptyCell1 = document.createElement('td');
grandTotalRow.appendChild(emptyCell1);
const emptyCell2 = document.createElement('td');
grandTotalRow.appendChild(emptyCell2);
tbody.appendChild(grandTotalRow);
table.appendChild(tbody);
resultsDiv.appendChild(table);
// Add user details expansion section
const detailsContainer = document.createElement('div');
detailsContainer.className = 'user-details-container';
detailsContainer.style.marginTop = '30px';
const detailsTitle = document.createElement('h3');
detailsTitle.textContent = 'Detailed User List';
detailsTitle.className = 'panel-title';
detailsContainer.appendChild(detailsTitle);
// Create detailed users table
const detailsTable = document.createElement('table');
const detailsThead = document.createElement('thead');
const detailsTbody = document.createElement('tbody');
// Create header row for details
const detailsHeaderRow = document.createElement('tr');
const detailsHeaders = ['User ID', 'Assistant Used', 'Conversations', 'First Seen', 'Last Seen'];
detailsHeaders.forEach(headerText => {
const header = document.createElement('th');
header.textContent = headerText;
detailsHeaderRow.appendChild(header);
});
detailsThead.appendChild(detailsHeaderRow);
detailsTable.appendChild(detailsThead);
// Collect all user details across assistants
const allUserDetails = [];
for (const assistantId in usersData) {
const assistantData = usersData[assistantId];
const assistantName = assistantNames[assistantId] || assistantId;
for (const userId in assistantData.userDetails) {
const userDetail = assistantData.userDetails[userId];
allUserDetails.push({
userId: userId,
assistantId: assistantId,
assistantName: assistantName,
conversationCount: userDetail.conversationCount,
firstSeen: userDetail.firstSeen,
lastSeen: userDetail.lastSeen
});
}
}
// Sort by conversation count (descending)
allUserDetails.sort((a, b) => b.conversationCount - a.conversationCount);
// Add rows for each user
allUserDetails.forEach(detail => {
const row = document.createElement('tr');
// User ID
const userCell = document.createElement('td');
userCell.textContent = detail.userId;
userCell.style.fontWeight = 'bold';
row.appendChild(userCell);
// Assistant Used
const assistantCell = document.createElement('td');
assistantCell.textContent = detail.assistantName;
assistantCell.title = detail.assistantId;
row.appendChild(assistantCell);
// Conversation Count
const countCell = document.createElement('td');
countCell.textContent = detail.conversationCount;
row.appendChild(countCell);
// First Seen
const firstSeenCell = document.createElement('td');
if (detail.firstSeen) {
const firstDate = new Date(detail.firstSeen);
firstSeenCell.textContent = firstDate.toLocaleDateString();
} else {
firstSeenCell.textContent = 'N/A';
}
row.appendChild(firstSeenCell);
// Last Seen
const lastSeenCell = document.createElement('td');
if (detail.lastSeen) {
const lastDate = new Date(detail.lastSeen);
lastSeenCell.textContent = lastDate.toLocaleDateString();
} else {
lastSeenCell.textContent = 'N/A';
}
row.appendChild(lastSeenCell);
detailsTbody.appendChild(row);
});
detailsTable.appendChild(detailsTbody);
detailsContainer.appendChild(detailsTable);
resultsDiv.appendChild(detailsContainer);
}
document.getElementById('sendButton').addEventListener('click', async () => {
const selectedOption = document.getElementById('optionSelect').value;
const loadingDiv = document.getElementById('loading');
const errorDiv = document.getElementById('error');
const resultsDiv = document.getElementById('results');
// Clear previous results
errorDiv.textContent = '';
resultsDiv.innerHTML = '';
loadingDiv.style.display = 'block';
try {
// First, fetch assistant data in parallel with conversation data
const assistantDataPromise = fetchAssistantData();
// Fetch conversation data
const response = await fetch('https://hook.us1.make.celonis.com/sqombh44pan463cgfw99f12j9vmugtvg', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
option: selectedOption,
auth_code: "jh87dkjs9sdj392sw72sj0210h*^&b$#rfg"
})
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
// Wait for assistant data and build mapping
assistantData = await assistantDataPromise;
assistantNames = buildAssistantMapping(assistantData);
// Store the data globally for CSV export
currentData = data;
// Show export button, time filter, and view toggle
document.getElementById('exportContainer').style.display = 'block';
document.getElementById('timeFilter').style.display = 'flex';
document.getElementById('viewToggle').style.display = 'flex';
// Initialize view toggle
initializeViewToggle();
// Initialize the time filter
initializeTimeFilter(data);
// Set default view
currentView = 'conversations';
updateViewDisplay();
// Create default charts and tables
createUsageChart(data, 1); // Default to showing 1 day (today)
const pivotData = processPivotData(data);
displayPivotTable(pivotData);
} catch (error) {
errorDiv.textContent = `Error: ${error.message}`;
console.error('Error:', error);
document.getElementById('exportContainer').style.display = 'none';
document.getElementById('timeFilter').style.display = 'none';
document.getElementById('viewToggle').style.display = 'none';
document.getElementById('usage-chart-container').style.display = 'none';
document.getElementById('users-chart-container').style.display = 'none';
} finally {
loadingDiv.style.display = 'none';
}
});
function processPivotData(data) {
// Group conversations by Assistant_ID
const assistantGroups = {};
data.forEach(item => {
const assistantId = item.data.Assistant_ID || 'Unknown';
const userId = item.data.User_ID || 'Unknown';
if (!assistantGroups[assistantId]) {
assistantGroups[assistantId] = {
users: {},
totalCount: 0
};
}
if (!assistantGroups[assistantId].users[userId]) {
assistantGroups[assistantId].users[userId] = 0;
}
assistantGroups[assistantId].users[userId]++;
assistantGroups[assistantId].totalCount++;
});
return assistantGroups;
}
// Initialize time filter buttons
function initializeTimeFilter(data) {
const timeFilterButtons = document.querySelectorAll('#timeFilter button');
timeFilterButtons.forEach(button => {
button.addEventListener('click', () => {
// Update active button
timeFilterButtons.forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Get the day filter value
const days = button.getAttribute('data-days');
// Update charts and tables based on current view
if (currentView === 'conversations') {
createUsageChart(data, days === 'all' ? null : parseInt(days));
const pivotData = processPivotData(data);
displayPivotTable(pivotData);
} else if (currentView === 'users') {
createUsersChart(data, days === 'all' ? null : parseInt(days));
const uniqueUsersData = processUniqueUsersData(data);
displayUniqueUsersTable(uniqueUsersData);
}
});
});
}
// Create usage chart
function createUsageChart(data, days) {
// Show chart container
document.getElementById('usage-chart-container').style.display = 'block';
// Filter data based on days
const filteredData = filterDataByDays(data, days);
// Process data for the chart
const { labels, datasets } = processDataForChart(filteredData);
// Create or update chart
if (usageChart) {
usageChart.data.labels = labels;
usageChart.data.datasets = datasets;
usageChart.update();
} else {
const ctx = document.getElementById('usageChart').getContext('2d');
usageChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: datasets
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: false
},
legend: {
position: 'top',
}
},
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
ticks: {
precision: 0,
stepSize: 1
}
}
}
}
});
}
}
// Filter data by time range
function filterDataByDays(data, days) {
if (!days) return data; // Return all data if no day filter
const now = new Date();
const cutoffDate = new Date(now);
cutoffDate.setDate(cutoffDate.getDate() - days);
return data.filter(item => {
if (!item.data || !item.data.StartTime) return false;
const startTime = new Date(item.data.StartTime);
return startTime >= cutoffDate;
});
}
// Process data for chart
function processDataForChart(data) {
// Get date range
const groupedByDate = {};
const groupedByAssistant = {};
const assistantColors = {};
const colorPalette = [
'#4285F4', '#EA4335', '#FBBC05', '#34A853',
'#673AB7', '#FF9800', '#00BCD4', '#795548',
'#9C27B0', '#607D8B', '#3F51B5', '#CDDC39'
];
// Group data by date and assistant
data.forEach(item => {
if (!item.data || !item.data.StartTime) return;
const date = new Date(item.data.StartTime);
const dateStr = date.toISOString().split('T')[0]; // YYYY-MM-DD
const assistantId = item.data.Assistant_ID || 'Unknown';
// Track for this date
if (!groupedByDate[dateStr]) {
groupedByDate[dateStr] = {};
}
if (!groupedByDate[dateStr][assistantId]) {
groupedByDate[dateStr][assistantId] = 0;
}
groupedByDate[dateStr][assistantId]++;
// Track assistants
if (!groupedByAssistant[assistantId]) {
groupedByAssistant[assistantId] = 0;
// Assign color
const colorIndex = Object.keys(groupedByAssistant).length - 1;
assistantColors[assistantId] = colorPalette[colorIndex % colorPalette.length];
}
groupedByAssistant[assistantId]++;
});
// Sort dates
const sortedDates = Object.keys(groupedByDate).sort();
// Create datasets for each assistant
const datasets = [];
for (const assistantId in groupedByAssistant) {
// Get assistant name for label if available
const assistantName = assistantNames[assistantId] || assistantId;
const dataset = {
label: assistantName,
data: [],
borderColor: assistantColors[assistantId],
backgroundColor: assistantColors[assistantId] + '20', // Add transparency
tension: 0.3,
fill: true
};
// Add data for each date
for (const date of sortedDates) {
dataset.data.push(groupedByDate[date][assistantId] || 0);
}
datasets.push(dataset);
}
return {
labels: sortedDates,
datasets: datasets
};
}
function displayPivotTable(pivotData) {
const resultsDiv = document.getElementById('results');
// Create the table
const table = document.createElement('table');
const thead = document.createElement('thead');
const tbody = document.createElement('tbody');
// Create header row
const headerRow = document.createElement('tr');
const headers = ['Assistant', 'User ID', 'Conversation Count'];
headers.forEach(headerText => {
const header = document.createElement('th');
header.textContent = headerText;
headerRow.appendChild(header);
});
thead.appendChild(headerRow);
table.appendChild(thead);
// Create table body
for (const assistantId in pivotData) {
const assistantData = pivotData[assistantId];
let firstRow = true;
for (const userId in assistantData.users) {
const row = document.createElement('tr');
// Assistant column (ID and Name, only show in first row of group)
const assistantCell = document.createElement('td');
if (firstRow) {
// Get assistant name if available
const assistantName = assistantNames[assistantId] || null;
if (assistantName) {
// If we have a name, show "Name (ID)"
assistantCell.textContent = `${assistantName} (${assistantId})`;
// Add title attribute for hover tooltip with full ID
assistantCell.title = assistantId;
} else {
// If no name, just show ID
assistantCell.textContent = assistantId;
}
assistantCell.style.fontWeight = 'bold';
} else {
assistantCell.textContent = '';
}
row.appendChild(assistantCell);
// User ID column
const userCell = document.createElement('td');
userCell.textContent = userId;
row.appendChild(userCell);
// Count column
const countCell = document.createElement('td');
countCell.textContent = assistantData.users[userId];
row.appendChild(countCell);
tbody.appendChild(row);
firstRow = false;
}
// Add a total row for this assistant
const totalRow = document.createElement('tr');
const totalLabelCell = document.createElement('td');
totalLabelCell.textContent = '';
totalRow.appendChild(totalLabelCell);
const totalUserCell = document.createElement('td');
totalUserCell.textContent = 'Total';
totalUserCell.style.fontWeight = 'bold';
totalRow.appendChild(totalUserCell);
const totalCountCell = document.createElement('td');
totalCountCell.textContent = assistantData.totalCount;
totalCountCell.style.fontWeight = 'bold';
totalRow.appendChild(totalCountCell);
tbody.appendChild(totalRow);
// Add an empty row as a separator
const spacerRow = document.createElement('tr');
const spacerCell = document.createElement('td');
spacerCell.setAttribute('colspan', '3');
spacerCell.style.height = '4px';
spacerRow.appendChild(spacerCell);
tbody.appendChild(spacerRow);
}
// Add grand total row
const grandTotalRow = document.createElement('tr');
// Create grand total cells
const grandTotalLabelCell = document.createElement('td');
grandTotalLabelCell.textContent = 'GRAND TOTAL';
grandTotalLabelCell.style.fontWeight = 'bold';
grandTotalLabelCell.style.fontSize = '16px';
grandTotalRow.appendChild(grandTotalLabelCell);
const grandTotalBlankCell = document.createElement('td');
grandTotalRow.appendChild(grandTotalBlankCell);
// Calculate grand total
let grandTotal = 0;
for (const assistantId in pivotData) {
grandTotal += pivotData[assistantId].totalCount;
}
const grandTotalCountCell = document.createElement('td');
grandTotalCountCell.textContent = grandTotal;
grandTotalCountCell.style.fontWeight = 'bold';
grandTotalCountCell.style.fontSize = '16px';
grandTotalRow.appendChild(grandTotalCountCell);
tbody.appendChild(grandTotalRow);
table.appendChild(tbody);
resultsDiv.appendChild(table);
}
</script>
</body>
</html>