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:
commit
0da5b0e73d
23 changed files with 8620 additions and 0 deletions
BIN
.DS_Store
vendored
Normal file
BIN
.DS_Store
vendored
Normal file
Binary file not shown.
BIN
Archive.zip
Normal file
BIN
Archive.zip
Normal file
Binary file not shown.
147
README.md
Normal file
147
README.md
Normal 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
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
BIN
Versions/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
Versions/Archive.zip
Normal file
BIN
Versions/Archive.zip
Normal file
Binary file not shown.
147
Versions/V0.1/README.md
Normal file
147
Versions/V0.1/README.md
Normal 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
Versions/V0.1/SANDBOX-ai-tools-report.html
Normal file
1110
Versions/V0.1/SANDBOX-ai-tools-report.html
Normal file
File diff suppressed because it is too large
Load diff
703
Versions/V0.1/index-Latam.html
Normal file
703
Versions/V0.1/index-Latam.html
Normal 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>
|
||||
BIN
Versions/V0.1/index-Latam.html.zip
Normal file
BIN
Versions/V0.1/index-Latam.html.zip
Normal file
Binary file not shown.
699
Versions/V0.1/index-SBII.html
Normal file
699
Versions/V0.1/index-SBII.html
Normal 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>
|
||||
BIN
Versions/V0.1/index-SBII.html.zip
Normal file
BIN
Versions/V0.1/index-SBII.html.zip
Normal file
Binary file not shown.
309
Versions/V0.1/index-convo.html
Normal file
309
Versions/V0.1/index-convo.html
Normal 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
Versions/V0.1/index-sidekick.html
Normal file
699
Versions/V0.1/index-sidekick.html
Normal 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
151
Versions/V0.1/index.html
Normal 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>
|
||||
BIN
Versions/index-Latam.html.zip
Normal file
BIN
Versions/index-Latam.html.zip
Normal file
Binary file not shown.
BIN
Versions/index-SBII.html.zip
Normal file
BIN
Versions/index-SBII.html.zip
Normal file
Binary file not shown.
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
1195
index-Latam.html
Normal file
1195
index-Latam.html
Normal file
File diff suppressed because it is too large
Load diff
1191
index-SBII.html
Normal file
1191
index-SBII.html
Normal file
File diff suppressed because it is too large
Load diff
309
index-convo.html
Normal file
309
index-convo.html
Normal 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
699
index-sidekick.html
Normal 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
151
index.html
Normal 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>
|
||||
Loading…
Add table
Reference in a new issue