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>
1191 lines
No EOL
47 KiB
HTML
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> |