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>
This commit is contained in:
DJP 2025-09-15 10:41:07 -04:00
commit 0da5b0e73d
23 changed files with 8620 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

BIN
Archive.zip Normal file

Binary file not shown.

147
README.md Normal file
View file

@ -0,0 +1,147 @@
# AI Tool Usage Reporting Dashboard
A comprehensive suite of HTML dashboards for reporting on AI tool usage and conversation data via webhook APIs.
Total cost: $6.36
Total duration (API): 17m 17.1s
Total duration (wall): 3h 40m 1.3s
## Overview
This repository contains several connected reporting tools:
1. **Main Navigation Dashboard** (`main-index.html`): Central hub for accessing all reports
2. **Conversation Reports**:
- **Latam Report** (`index-Latam.html`): Latin America conversation metrics
- **SBII Report** (`index-SBII.html`): SBII platform conversation metrics
- **Sidekick Report** (`index-sidekick.html`): Sidekick assistant conversation metrics
3. **AI Tool Usage Report** (`SANDBOX-ai-tools-report.html`): Monitors usage of AI tools like Text-to-Voice and Text-to-Image services
## Main Navigation Dashboard
A clean, card-based interface for navigating between all available reports. Each report is represented by a color-coded card with a description of its purpose and a direct link to open it.
## Conversation Reports
### Features
- **Simple Interface**: Clean, compact design using Montserrat font
- **Data Selection**: Dropdown menu to select different conversation datasets
- **Assistant Name Resolution**:
- Automatically looks up friendly names for assistant IDs
- Displays both name and ID in reports for easy identification
- Handles multiple assistant databases for Latam report
- **Pivot Table View**:
- Groups data by Assistant
- Shows conversation count per user
- Includes subtotals per Assistant
- Displays grand total of all conversations
- **Usage Graph**:
- Interactive line chart showing usage over time
- Filter data by time period (Today, 7 days, 30 days, 3 months, All time)
- Color-coded lines for different assistants
- Assistants labeled by name instead of ID for readability
- **CSV Export**:
- Export all raw data in CSV format
- Includes both assistant IDs and names in separate columns
- Timestamp included in filename for version tracking
### How to Use Conversation Reports
1. Open `main-index.html` in a web browser to access the navigation dashboard
2. Select the desired report from the available options
3. In each report, select the desired dataset from the dropdown (if applicable)
4. Click "Send Request" to fetch the data
5. View the usage graph showing trends over time, and use time filters to focus on specific periods
6. Explore the detailed pivot table showing conversation metrics
7. Use the "Export CSV" button to download the raw data as a CSV file
## AI Tool Usage Report
### Features
- **Multi-view Dashboard**: Comprehensive analytics with multiple views
- **Data Filtering**: Filter by date range, user, model and sub-tool
- **Summary Cards**: Quick overview of usage metrics
- **Multiple Pivot Tables**:
- By User: See which users are using which tools
- By Model: Track usage across different AI models
- By Sub-Tool: Analyze which sub-tools are most popular
- **Detailed View**: See all usage entries with full details
- **CSV Export**: Download full raw data as a CSV file
### How to Use AI Tools Report
1. Open the AI Tools Report from the main dashboard
2. Select report type from dropdown (All Tools, Text-to-Voice, Text-to-Image)
3. Click "Send Request" to fetch the data
4. Use the filters to narrow down results by date, user, model or sub-tool
5. Navigate between tabs to see different views of the data
## Technical Details
- **Pure HTML/CSS/JavaScript**: Uses native web technologies
- **Chart.js Integration**: For interactive data visualization
- **Multiple API Endpoints**:
- Each report connects to appropriate data webhooks
- Assistant name lookup uses separate assistant database webhooks
- **Parallel API Calls**: Makes simultaneous requests to improve loading performance
- **Authentication**: Includes secure auth_code token with each request
- **Error Handling**: Gracefully handles API errors and displays user-friendly messages
- **Responsive Design**: Works on desktop and tablet devices
## Data Structures
### Conversation Data
```json
[
{
"data": {
"User_ID": "example@domain.com",
"StartTime": "2024-10-21T21:29:47.470Z",
"Brand Voice Setting": "standard",
"Title": "Example Title",
"EndTime": "2024-10-21T21:29:57.203Z",
"Assistant_ID": "asst_identifier",
"Assistant_Key": "key_value",
"Conversation_ID": "conversation_id",
"Vision_Images": ""
}
}
]
```
### Assistant Data
```json
[
{
"data": {
"Name": "Friendly Assistant Name",
"Assistant ID": "asst_identifier",
"Instructions": "Assistant instructions...",
"Model": "claude-3-sonnet-20240229"
}
}
]
```
### AI Tool Usage Data
```json
[
{
"data": {
"Date": "2024-05-15T23:00:00.000Z",
"TOOL": "TEXT2VOICE",
"USER": "user@example.com",
"MODEL": "eleven_multilingual_v2",
"PROMPT": "Example prompt text",
"SUB_TOOL": "Elevenlabs",
"SETTINGS": "Voice settings",
"BOX_FILE_ID": "123456"
}
}
]
```

1110
SANDBOX-ai-tools-report.html Normal file

File diff suppressed because it is too large Load diff

BIN
Versions/.DS_Store vendored Normal file

Binary file not shown.

BIN
Versions/Archive.zip Normal file

Binary file not shown.

147
Versions/V0.1/README.md Normal file
View file

@ -0,0 +1,147 @@
# AI Tool Usage Reporting Dashboard
A comprehensive suite of HTML dashboards for reporting on AI tool usage and conversation data via webhook APIs.
Total cost: $6.36
Total duration (API): 17m 17.1s
Total duration (wall): 3h 40m 1.3s
## Overview
This repository contains several connected reporting tools:
1. **Main Navigation Dashboard** (`main-index.html`): Central hub for accessing all reports
2. **Conversation Reports**:
- **Latam Report** (`index-Latam.html`): Latin America conversation metrics
- **SBII Report** (`index-SBII.html`): SBII platform conversation metrics
- **Sidekick Report** (`index-sidekick.html`): Sidekick assistant conversation metrics
3. **AI Tool Usage Report** (`SANDBOX-ai-tools-report.html`): Monitors usage of AI tools like Text-to-Voice and Text-to-Image services
## Main Navigation Dashboard
A clean, card-based interface for navigating between all available reports. Each report is represented by a color-coded card with a description of its purpose and a direct link to open it.
## Conversation Reports
### Features
- **Simple Interface**: Clean, compact design using Montserrat font
- **Data Selection**: Dropdown menu to select different conversation datasets
- **Assistant Name Resolution**:
- Automatically looks up friendly names for assistant IDs
- Displays both name and ID in reports for easy identification
- Handles multiple assistant databases for Latam report
- **Pivot Table View**:
- Groups data by Assistant
- Shows conversation count per user
- Includes subtotals per Assistant
- Displays grand total of all conversations
- **Usage Graph**:
- Interactive line chart showing usage over time
- Filter data by time period (Today, 7 days, 30 days, 3 months, All time)
- Color-coded lines for different assistants
- Assistants labeled by name instead of ID for readability
- **CSV Export**:
- Export all raw data in CSV format
- Includes both assistant IDs and names in separate columns
- Timestamp included in filename for version tracking
### How to Use Conversation Reports
1. Open `main-index.html` in a web browser to access the navigation dashboard
2. Select the desired report from the available options
3. In each report, select the desired dataset from the dropdown (if applicable)
4. Click "Send Request" to fetch the data
5. View the usage graph showing trends over time, and use time filters to focus on specific periods
6. Explore the detailed pivot table showing conversation metrics
7. Use the "Export CSV" button to download the raw data as a CSV file
## AI Tool Usage Report
### Features
- **Multi-view Dashboard**: Comprehensive analytics with multiple views
- **Data Filtering**: Filter by date range, user, model and sub-tool
- **Summary Cards**: Quick overview of usage metrics
- **Multiple Pivot Tables**:
- By User: See which users are using which tools
- By Model: Track usage across different AI models
- By Sub-Tool: Analyze which sub-tools are most popular
- **Detailed View**: See all usage entries with full details
- **CSV Export**: Download full raw data as a CSV file
### How to Use AI Tools Report
1. Open the AI Tools Report from the main dashboard
2. Select report type from dropdown (All Tools, Text-to-Voice, Text-to-Image)
3. Click "Send Request" to fetch the data
4. Use the filters to narrow down results by date, user, model or sub-tool
5. Navigate between tabs to see different views of the data
## Technical Details
- **Pure HTML/CSS/JavaScript**: Uses native web technologies
- **Chart.js Integration**: For interactive data visualization
- **Multiple API Endpoints**:
- Each report connects to appropriate data webhooks
- Assistant name lookup uses separate assistant database webhooks
- **Parallel API Calls**: Makes simultaneous requests to improve loading performance
- **Authentication**: Includes secure auth_code token with each request
- **Error Handling**: Gracefully handles API errors and displays user-friendly messages
- **Responsive Design**: Works on desktop and tablet devices
## Data Structures
### Conversation Data
```json
[
{
"data": {
"User_ID": "example@domain.com",
"StartTime": "2024-10-21T21:29:47.470Z",
"Brand Voice Setting": "standard",
"Title": "Example Title",
"EndTime": "2024-10-21T21:29:57.203Z",
"Assistant_ID": "asst_identifier",
"Assistant_Key": "key_value",
"Conversation_ID": "conversation_id",
"Vision_Images": ""
}
}
]
```
### Assistant Data
```json
[
{
"data": {
"Name": "Friendly Assistant Name",
"Assistant ID": "asst_identifier",
"Instructions": "Assistant instructions...",
"Model": "claude-3-sonnet-20240229"
}
}
]
```
### AI Tool Usage Data
```json
[
{
"data": {
"Date": "2024-05-15T23:00:00.000Z",
"TOOL": "TEXT2VOICE",
"USER": "user@example.com",
"MODEL": "eleven_multilingual_v2",
"PROMPT": "Example prompt text",
"SUB_TOOL": "Elevenlabs",
"SETTINGS": "Voice settings",
"BOX_FILE_ID": "123456"
}
}
]
```

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,703 @@
<!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="itau2-Conversations">itau2-Conversations</option>
<option value="Oliver Latam-Conversations">Oliver Latam-Conversations</option>
<option value="Ancar-Conversations">Ancar-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', `latam-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(option) {
try {
const response = await fetch('https://hook.us1.make.celonis.com/g2199hlyi5bn0ggve51xeda5d51o3tpi', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
option: option, // Pass the selected option to get the right assistants DB
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
// Pass the selected option to get the correct assistant database
const assistantDataPromise = fetchAssistantData(selectedOption);
// Fetch conversation data
const response = await fetch('https://hook.us1.make.celonis.com/6arvs3ebpel494664x7xl4if9az91oh2', {
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>

Binary file not shown.

View file

@ -0,0 +1,699 @@
<!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="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="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', `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;
}
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 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>

Binary file not shown.

View file

@ -0,0 +1,309 @@
<!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">
<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;
}
</style>
</head>
<body>
<div class="container">
<h1>Conversation Reporting Tool</h1>
<div class="controls">
<select id="optionSelect">
<option value="itau2-Conversations">itau2-Conversations</option>
<option value="Oliver Latam-Conversations">Oliver Latam-Conversations</option>
<option value="Ancar-Conversations">Ancar-Conversations</option>
</select>
<button id="sendButton">Send Request</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="results" class="results">
<!-- Results will be dynamically inserted here -->
</div>
</div>
<script>
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 {
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();
// Process the data for pivot table
const pivotData = processPivotData(data);
// Generate and display the table
displayPivotTable(pivotData);
} catch (error) {
errorDiv.textContent = `Error: ${error.message}`;
console.error('Error:', error);
} 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;
}
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 ID', '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 ID column (only show in first row of group)
const assistantCell = document.createElement('td');
assistantCell.textContent = firstRow ? assistantId : '';
if (firstRow) {
assistantCell.style.fontWeight = 'bold';
}
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>

View file

@ -0,0 +1,699 @@
<!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>

151
Versions/V0.1/index.html Normal file
View file

@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Reporting Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Montserrat', sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1000px;
margin: 0 auto;
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
font-size: 28px;
}
.reports-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-top: 30px;
}
.report-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.report-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
.card-header {
background-color: #4285f4;
color: white;
padding: 15px;
font-weight: 600;
font-size: 18px;
}
.card-content {
padding: 15px;
color: #555;
font-size: 14px;
}
.card-footer {
padding: 15px;
text-align: center;
border-top: 1px solid #eee;
}
.btn {
display: inline-block;
text-decoration: none;
color: white;
background-color: #4285f4;
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.btn:hover {
background-color: #3367d6;
}
.description {
text-align: center;
max-width: 700px;
margin: 0 auto 30px auto;
color: #666;
line-height: 1.6;
}
.ai-tools { background-color: #4285f4; }
.latam { background-color: #ea4335; }
.sbii { background-color: #34a853; }
.sidekick { background-color: #fbbc05; }
.conversations { background-color: #673ab7; }
</style>
</head>
<body>
<div class="container">
<h1>AI Reporting Dashboard</h1>
<div class="description">
Welcome to the AI Reporting Dashboard. Select one of the reporting tools below to access detailed analytics and usage data for various AI services and platforms.
</div>
<div class="reports-grid">
<div class="report-card">
<div class="card-header ai-tools">SANDBOX AI Tools</div>
<div class="card-content">
Track usage of AI tools like Text-to-Voice and Text-to-Image services with comprehensive analytics by user, model, and sub-tool.
</div>
<div class="card-footer">
<a href="SANDBOX-ai-tools-report.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header latam">Latam Report</div>
<div class="card-content">
View analytics and data specifically for Latin American operations and AI tool usage across the region.
</div>
<div class="card-footer">
<a href="index-Latam.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header sbii">SBII Report</div>
<div class="card-content">
Access data and metrics for the SBII platform, including usage patterns and performance analytics.
</div>
<div class="card-footer">
<a href="index-SBII.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header sidekick">Sidekick Report</div>
<div class="card-content">
View data for Sidekick assistant usage, including conversation metrics and user engagement analysis.
</div>
<div class="card-footer">
<a href="index-sidekick.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header conversations">LLM Report</div>
<div class="card-content">
Detailed analytics on conversations LLM platforms, with user metrics and interaction data.
</div>
<div class="card-footer">
<a href="llm_report.html" class="btn">Open Report</a>
</div>
</div>
</div>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1195
index-Latam.html Normal file

File diff suppressed because it is too large Load diff

1191
index-SBII.html Normal file

File diff suppressed because it is too large Load diff

309
index-convo.html Normal file
View file

@ -0,0 +1,309 @@
<!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">
<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;
}
</style>
</head>
<body>
<div class="container">
<h1>Conversation Reporting Tool</h1>
<div class="controls">
<select id="optionSelect">
<option value="itau2-Conversations">itau2-Conversations</option>
<option value="Oliver Latam-Conversations">Oliver Latam-Conversations</option>
<option value="Ancar-Conversations">Ancar-Conversations</option>
</select>
<button id="sendButton">Send Request</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="results" class="results">
<!-- Results will be dynamically inserted here -->
</div>
</div>
<script>
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 {
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();
// Process the data for pivot table
const pivotData = processPivotData(data);
// Generate and display the table
displayPivotTable(pivotData);
} catch (error) {
errorDiv.textContent = `Error: ${error.message}`;
console.error('Error:', error);
} 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;
}
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 ID', '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 ID column (only show in first row of group)
const assistantCell = document.createElement('td');
assistantCell.textContent = firstRow ? assistantId : '';
if (firstRow) {
assistantCell.style.fontWeight = 'bold';
}
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>

699
index-sidekick.html Normal file
View file

@ -0,0 +1,699 @@
<!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>

151
index.html Normal file
View file

@ -0,0 +1,151 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Reporting Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Montserrat', sans-serif;
margin: 0;
padding: 20px;
background-color: #f5f5f5;
}
.container {
max-width: 1000px;
margin: 0 auto;
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
font-size: 28px;
}
.reports-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-top: 30px;
}
.report-card {
background-color: #fff;
border-radius: 8px;
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.report-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
}
.card-header {
background-color: #4285f4;
color: white;
padding: 15px;
font-weight: 600;
font-size: 18px;
}
.card-content {
padding: 15px;
color: #555;
font-size: 14px;
}
.card-footer {
padding: 15px;
text-align: center;
border-top: 1px solid #eee;
}
.btn {
display: inline-block;
text-decoration: none;
color: white;
background-color: #4285f4;
padding: 8px 16px;
border-radius: 4px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.btn:hover {
background-color: #3367d6;
}
.description {
text-align: center;
max-width: 700px;
margin: 0 auto 30px auto;
color: #666;
line-height: 1.6;
}
.ai-tools { background-color: #4285f4; }
.latam { background-color: #ea4335; }
.sbii { background-color: #34a853; }
.sidekick { background-color: #fbbc05; }
.conversations { background-color: #673ab7; }
</style>
</head>
<body>
<div class="container">
<h1>AI Reporting Dashboard</h1>
<div class="description">
Welcome to the AI Reporting Dashboard. Select one of the reporting tools below to access detailed analytics and usage data for various AI services and platforms.
</div>
<div class="reports-grid">
<div class="report-card">
<div class="card-header ai-tools">SANDBOX AI Tools</div>
<div class="card-content">
Track usage of AI tools like Text-to-Voice and Text-to-Image services with comprehensive analytics by user, model, and sub-tool.
</div>
<div class="card-footer">
<a href="SANDBOX-ai-tools-report.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header latam">Latam Report</div>
<div class="card-content">
View analytics and data specifically for Latin American operations and AI tool usage across the region.
</div>
<div class="card-footer">
<a href="index-Latam.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header sbii">SBII Report</div>
<div class="card-content">
Access data and metrics for the SBII platform, including usage patterns and performance analytics.
</div>
<div class="card-footer">
<a href="index-SBII.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header sidekick">Sidekick Report</div>
<div class="card-content">
View data for Sidekick assistant usage, including conversation metrics and user engagement analysis.
</div>
<div class="card-footer">
<a href="index-sidekick.html" class="btn">Open Report</a>
</div>
</div>
<div class="report-card">
<div class="card-header conversations">LLM Report</div>
<div class="card-content">
Detailed analytics on conversations LLM platforms, with user metrics and interaction data.
</div>
<div class="card-footer">
<a href="https://sb-llm-reporting:8890/" class="btn">Open Report</a>
</div>
</div>
</div>
</div>
</body>
</html>