ai-reports/index-sidekick.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

699 lines
No EOL
26 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;
}
.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="sidekick_conversations">sidekick_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="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="results" class="results">
<!-- Results will be dynamically inserted here -->
</div>
</div>
<script>
// Global variables
let currentData = [];
let assistantData = [];
let usageChart = null;
// 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', `sidekick-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/y1o8pqxv40a4qhk55rbsdklpyjyt4nvd', {
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;
}
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/ewnkda13g1b65s1h2on2clo8ql7vdkl0', {
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 and time filter
document.getElementById('exportContainer').style.display = 'block';
document.getElementById('timeFilter').style.display = 'flex';
// Process the data for pivot table
const pivotData = processPivotData(data);
// Initialize the usage chart
initializeTimeFilter(data);
createUsageChart(data, 1); // Default to showing 1 day (today)
// Generate and display the table
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('usage-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 chart
createUsageChart(data, days === 'all' ? null : parseInt(days));
});
});
}
// 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>