Spaces:
Running
Running
Add 3 files
Browse files- README.md +7 -5
- index.html +946 -19
- prompts.txt +1 -0
README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: conv
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
index.html
CHANGED
|
@@ -1,19 +1,946 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>Conversation Analysis Dashboard</title>
|
| 7 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
| 9 |
+
<style>
|
| 10 |
+
@keyframes fadeIn {
|
| 11 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 12 |
+
to { opacity: 1; transform: translateY(0); }
|
| 13 |
+
}
|
| 14 |
+
.fade-in {
|
| 15 |
+
animation: fadeIn 0.3s ease-out forwards;
|
| 16 |
+
}
|
| 17 |
+
.sidebar {
|
| 18 |
+
transition: transform 0.3s ease-in-out;
|
| 19 |
+
}
|
| 20 |
+
.sidebar-open {
|
| 21 |
+
transform: translateX(0);
|
| 22 |
+
}
|
| 23 |
+
@media (max-width: 768px) {
|
| 24 |
+
.sidebar {
|
| 25 |
+
transform: translateX(-100%);
|
| 26 |
+
position: fixed;
|
| 27 |
+
top: 0;
|
| 28 |
+
left: 0;
|
| 29 |
+
z-index: 40;
|
| 30 |
+
height: 100vh;
|
| 31 |
+
}
|
| 32 |
+
.sidebar-open {
|
| 33 |
+
transform: translateX(0);
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
.conversation-card:hover {
|
| 37 |
+
transform: translateY(-2px);
|
| 38 |
+
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
| 39 |
+
}
|
| 40 |
+
.progress-bar {
|
| 41 |
+
transition: width 0.5s ease;
|
| 42 |
+
}
|
| 43 |
+
.modal-overlay {
|
| 44 |
+
background-color: rgba(0, 0, 0, 0.5);
|
| 45 |
+
}
|
| 46 |
+
.modal-content {
|
| 47 |
+
max-height: 90vh;
|
| 48 |
+
}
|
| 49 |
+
.line-clamp-2 {
|
| 50 |
+
display: -webkit-box;
|
| 51 |
+
-webkit-line-clamp: 2;
|
| 52 |
+
-webkit-box-orient: vertical;
|
| 53 |
+
overflow: hidden;
|
| 54 |
+
}
|
| 55 |
+
</style>
|
| 56 |
+
</head>
|
| 57 |
+
<body class="bg-gray-50 font-sans antialiased">
|
| 58 |
+
<!-- Mobile menu button -->
|
| 59 |
+
<div class="md:hidden fixed top-4 left-4 z-50">
|
| 60 |
+
<button id="mobileMenuBtn" class="p-2 rounded-md bg-white shadow-md text-gray-700">
|
| 61 |
+
<i class="fas fa-bars"></i>
|
| 62 |
+
</button>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<!-- Sidebar -->
|
| 66 |
+
<div id="sidebar" class="sidebar w-64 bg-white border-r border-gray-200 fixed h-full overflow-y-auto">
|
| 67 |
+
<div class="p-4 border-b border-gray-200">
|
| 68 |
+
<div class="flex items-center space-x-2">
|
| 69 |
+
<div class="bg-purple-600 text-white p-2 rounded-lg">
|
| 70 |
+
<i class="fas fa-comments text-xl"></i>
|
| 71 |
+
</div>
|
| 72 |
+
<h1 class="text-xl font-bold text-gray-800">ConvoAnalyzer</h1>
|
| 73 |
+
</div>
|
| 74 |
+
<p class="text-xs text-gray-500 mt-1">Conversation Analysis Dashboard</p>
|
| 75 |
+
</div>
|
| 76 |
+
|
| 77 |
+
<div class="p-4 space-y-6">
|
| 78 |
+
<div>
|
| 79 |
+
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Configuration</h3>
|
| 80 |
+
<div class="space-y-4">
|
| 81 |
+
<div>
|
| 82 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Worker Count</label>
|
| 83 |
+
<div class="flex items-center space-x-2">
|
| 84 |
+
<input id="workerCount" type="range" min="1" max="8" value="4" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
| 85 |
+
<span id="workerCountDisplay" class="text-sm font-medium text-gray-700 w-8 text-center">4</span>
|
| 86 |
+
</div>
|
| 87 |
+
</div>
|
| 88 |
+
|
| 89 |
+
<div>
|
| 90 |
+
<label class="block text-sm font-medium text-gray-700 mb-1">Analysis Model</label>
|
| 91 |
+
<select id="modelSelect" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-purple-500 focus:border-purple-500 sm:text-sm rounded-md">
|
| 92 |
+
<option value="default">Default Model</option>
|
| 93 |
+
<option value="advanced">Advanced Model</option>
|
| 94 |
+
<option value="custom">Custom Model</option>
|
| 95 |
+
</select>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
<div>
|
| 101 |
+
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Actions</h3>
|
| 102 |
+
<div class="space-y-2">
|
| 103 |
+
<button id="uploadBtn" class="w-full flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
|
| 104 |
+
<i class="fas fa-upload mr-2"></i> Upload Conversations
|
| 105 |
+
</button>
|
| 106 |
+
<button id="startAnalysisBtn" class="w-full flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-purple-700 bg-white hover:bg-purple-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500">
|
| 107 |
+
<i class="fas fa-play mr-2"></i> Start Analysis
|
| 108 |
+
</button>
|
| 109 |
+
<div class="flex space-x-2">
|
| 110 |
+
<button id="pauseBtn" disabled class="flex-1 flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-gray-700 bg-gray-200 opacity-50 cursor-not-allowed">
|
| 111 |
+
<i class="fas fa-pause mr-2"></i> Pause
|
| 112 |
+
</button>
|
| 113 |
+
<button id="stopBtn" disabled class="flex-1 flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-red-600 opacity-50 cursor-not-allowed hover:bg-red-700">
|
| 114 |
+
<i class="fas fa-stop mr-2"></i> Stop
|
| 115 |
+
</button>
|
| 116 |
+
</div>
|
| 117 |
+
<button id="downloadBtn" class="w-full flex items-center justify-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
| 118 |
+
<i class="fas fa-download mr-2"></i> Download Results
|
| 119 |
+
</button>
|
| 120 |
+
</div>
|
| 121 |
+
</div>
|
| 122 |
+
|
| 123 |
+
<div>
|
| 124 |
+
<h3 class="text-xs font-semibold text-gray-500 uppercase tracking-wider mb-2">Agent Status</h3>
|
| 125 |
+
<div class="space-y-2">
|
| 126 |
+
<div class="flex items-center justify-between p-2 bg-gray-50 rounded-md">
|
| 127 |
+
<div class="flex items-center space-x-2">
|
| 128 |
+
<i class="fas fa-smile text-blue-500"></i>
|
| 129 |
+
<span class="text-sm font-medium text-gray-700">Sentiment</span>
|
| 130 |
+
</div>
|
| 131 |
+
<span id="sentimentStatus" class="text-sm font-medium text-gray-600">Idle</span>
|
| 132 |
+
</div>
|
| 133 |
+
<div class="flex items-center justify-between p-2 bg-gray-50 rounded-md">
|
| 134 |
+
<div class="flex items-center space-x-2">
|
| 135 |
+
<i class="fas fa-tags text-blue-500"></i>
|
| 136 |
+
<span class="text-sm font-medium text-gray-700">Topic</span>
|
| 137 |
+
</div>
|
| 138 |
+
<span id="topicStatus" class="text-sm font-medium text-gray-600">Idle</span>
|
| 139 |
+
</div>
|
| 140 |
+
<div class="flex items-center justify-between p-2 bg-gray-50 rounded-md">
|
| 141 |
+
<div class="flex items-center space-x-2">
|
| 142 |
+
<i class="fas fa-tasks text-blue-500"></i>
|
| 143 |
+
<span class="text-sm font-medium text-gray-700">Progress</span>
|
| 144 |
+
</div>
|
| 145 |
+
<span id="progressStatus" class="text-sm font-medium text-gray-600">Idle</span>
|
| 146 |
+
</div>
|
| 147 |
+
<div class="flex items-center justify-between p-2 bg-gray-50 rounded-md">
|
| 148 |
+
<div class="flex items-center space-x-2">
|
| 149 |
+
<i class="fas fa-file-alt text-blue-500"></i>
|
| 150 |
+
<span class="text-sm font-medium text-gray-700">Summary</span>
|
| 151 |
+
</div>
|
| 152 |
+
<span id="summaryStatus" class="text-sm font-medium text-gray-600">Idle</span>
|
| 153 |
+
</div>
|
| 154 |
+
</div>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
<!-- Main content -->
|
| 160 |
+
<div class="md:ml-64">
|
| 161 |
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
| 162 |
+
<!-- Header -->
|
| 163 |
+
<div class="pt-6 pb-4">
|
| 164 |
+
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
|
| 165 |
+
<h2 class="text-2xl font-bold leading-tight text-gray-900">Conversation Analysis Dashboard</h2>
|
| 166 |
+
<div class="mt-2 md:mt-0 flex items-center space-x-2">
|
| 167 |
+
<span class="text-sm text-gray-500">Last run:</span>
|
| 168 |
+
<span id="lastRunTime" class="text-sm font-medium text-gray-700">Never</span>
|
| 169 |
+
</div>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
|
| 173 |
+
<!-- Stats -->
|
| 174 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
| 175 |
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
| 176 |
+
<div class="px-4 py-5 sm:p-6">
|
| 177 |
+
<div class="flex items-center">
|
| 178 |
+
<div class="flex-shrink-0 bg-green-500 rounded-md p-3">
|
| 179 |
+
<i class="fas fa-check-circle text-white"></i>
|
| 180 |
+
</div>
|
| 181 |
+
<div class="ml-5 w-0 flex-1">
|
| 182 |
+
<dl>
|
| 183 |
+
<dt class="text-sm font-medium text-gray-500 truncate">Completed</dt>
|
| 184 |
+
<dd class="flex items-baseline">
|
| 185 |
+
<div class="text-2xl font-semibold text-gray-900">
|
| 186 |
+
<span id="completedCount">0</span>
|
| 187 |
+
</div>
|
| 188 |
+
</dd>
|
| 189 |
+
</dl>
|
| 190 |
+
</div>
|
| 191 |
+
</div>
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
| 195 |
+
<div class="px-4 py-5 sm:p-6">
|
| 196 |
+
<div class="flex items-center">
|
| 197 |
+
<div class="flex-shrink-0 bg-red-500 rounded-md p-3">
|
| 198 |
+
<i class="fas fa-exclamation-circle text-white"></i>
|
| 199 |
+
</div>
|
| 200 |
+
<div class="ml-5 w-0 flex-1">
|
| 201 |
+
<dl>
|
| 202 |
+
<dt class="text-sm font-medium text-gray-500 truncate">Errors</dt>
|
| 203 |
+
<dd class="flex items-baseline">
|
| 204 |
+
<div class="text-2xl font-semibold text-gray-900">
|
| 205 |
+
<span id="errorCount">0</span>
|
| 206 |
+
</div>
|
| 207 |
+
</dd>
|
| 208 |
+
</dl>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
</div>
|
| 212 |
+
</div>
|
| 213 |
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
| 214 |
+
<div class="px-4 py-5 sm:p-6">
|
| 215 |
+
<div class="flex items-center">
|
| 216 |
+
<div class="flex-shrink-0 bg-blue-500 rounded-md p-3">
|
| 217 |
+
<i class="fas fa-spinner text-white"></i>
|
| 218 |
+
</div>
|
| 219 |
+
<div class="ml-5 w-0 flex-1">
|
| 220 |
+
<dl>
|
| 221 |
+
<dt class="text-sm font-medium text-gray-500 truncate">Processing</dt>
|
| 222 |
+
<dd class="flex items-baseline">
|
| 223 |
+
<div class="text-2xl font-semibold text-gray-900">
|
| 224 |
+
<span id="processingCount">0</span>
|
| 225 |
+
</div>
|
| 226 |
+
</dd>
|
| 227 |
+
</dl>
|
| 228 |
+
</div>
|
| 229 |
+
</div>
|
| 230 |
+
</div>
|
| 231 |
+
</div>
|
| 232 |
+
<div class="bg-white overflow-hidden shadow rounded-lg">
|
| 233 |
+
<div class="px-4 py-5 sm:p-6">
|
| 234 |
+
<div class="flex items-center">
|
| 235 |
+
<div class="flex-shrink-0 bg-purple-500 rounded-md p-3">
|
| 236 |
+
<i class="fas fa-chart-line text-white"></i>
|
| 237 |
+
</div>
|
| 238 |
+
<div class="ml-5 w-0 flex-1">
|
| 239 |
+
<dl>
|
| 240 |
+
<dt class="text-sm font-medium text-gray-500 truncate">Progress</dt>
|
| 241 |
+
<dd class="flex items-baseline">
|
| 242 |
+
<div class="text-2xl font-semibold text-gray-900">
|
| 243 |
+
<span id="progressPercent">0</span>%
|
| 244 |
+
</div>
|
| 245 |
+
</dd>
|
| 246 |
+
</dl>
|
| 247 |
+
</div>
|
| 248 |
+
</div>
|
| 249 |
+
</div>
|
| 250 |
+
</div>
|
| 251 |
+
</div>
|
| 252 |
+
|
| 253 |
+
<!-- Progress bar -->
|
| 254 |
+
<div class="mb-6">
|
| 255 |
+
<div class="flex justify-between mb-1">
|
| 256 |
+
<span class="text-sm font-medium text-gray-700">Analysis Progress</span>
|
| 257 |
+
<span class="text-sm font-medium text-gray-700"><span id="progressPercent2">0</span>%</span>
|
| 258 |
+
</div>
|
| 259 |
+
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
| 260 |
+
<div id="progressBar" class="progress-bar bg-purple-600 h-2.5 rounded-full" style="width: 0%"></div>
|
| 261 |
+
</div>
|
| 262 |
+
</div>
|
| 263 |
+
|
| 264 |
+
<!-- Agent Counts -->
|
| 265 |
+
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
| 266 |
+
<div class="bg-white p-4 rounded-lg shadow">
|
| 267 |
+
<div class="flex items-center justify-between">
|
| 268 |
+
<div class="flex items-center space-x-2">
|
| 269 |
+
<div class="p-2 rounded-md bg-blue-100 text-blue-600">
|
| 270 |
+
<i class="fas fa-smile"></i>
|
| 271 |
+
</div>
|
| 272 |
+
<span class="text-sm font-medium text-gray-700">Sentiment</span>
|
| 273 |
+
</div>
|
| 274 |
+
<span id="sentimentCount" class="text-lg font-bold text-gray-900">0</span>
|
| 275 |
+
</div>
|
| 276 |
+
</div>
|
| 277 |
+
<div class="bg-white p-4 rounded-lg shadow">
|
| 278 |
+
<div class="flex items-center justify-between">
|
| 279 |
+
<div class="flex items-center space-x-2">
|
| 280 |
+
<div class="p-2 rounded-md bg-green-100 text-green-600">
|
| 281 |
+
<i class="fas fa-tags"></i>
|
| 282 |
+
</div>
|
| 283 |
+
<span class="text-sm font-medium text-gray-700">Topics</span>
|
| 284 |
+
</div>
|
| 285 |
+
<span id="topicCount" class="text-lg font-bold text-gray-900">0</span>
|
| 286 |
+
</div>
|
| 287 |
+
</div>
|
| 288 |
+
<div class="bg-white p-4 rounded-lg shadow">
|
| 289 |
+
<div class="flex items-center justify-between">
|
| 290 |
+
<div class="flex items-center space-x-2">
|
| 291 |
+
<div class="p-2 rounded-md bg-purple-100 text-purple-600">
|
| 292 |
+
<i class="fas fa-tasks"></i>
|
| 293 |
+
</div>
|
| 294 |
+
<span class="text-sm font-medium text-gray-700">Progress</span>
|
| 295 |
+
</div>
|
| 296 |
+
<span id="progressCount" class="text-lg font-bold text-gray-900">0</span>
|
| 297 |
+
</div>
|
| 298 |
+
</div>
|
| 299 |
+
<div class="bg-white p-4 rounded-lg shadow">
|
| 300 |
+
<div class="flex items-center justify-between">
|
| 301 |
+
<div class="flex items-center space-x-2">
|
| 302 |
+
<div class="p-2 rounded-md bg-yellow-100 text-yellow-600">
|
| 303 |
+
<i class="fas fa-file-alt"></i>
|
| 304 |
+
</div>
|
| 305 |
+
<span class="text-sm font-medium text-gray-700">Summaries</span>
|
| 306 |
+
</div>
|
| 307 |
+
<span id="summaryCount" class="text-lg font-bold text-gray-900">0</span>
|
| 308 |
+
</div>
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
|
| 312 |
+
<!-- Visualizations -->
|
| 313 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
| 314 |
+
<div class="bg-white p-4 rounded-lg shadow">
|
| 315 |
+
<div id="sentimentChart">
|
| 316 |
+
<h4 class="font-medium text-gray-900 mb-3">Sentiment Distribution</h4>
|
| 317 |
+
<div class="text-center py-10 text-gray-400">
|
| 318 |
+
<i class="fas fa-chart-pie text-2xl mb-2"></i>
|
| 319 |
+
<p class="text-sm">No sentiment data available yet</p>
|
| 320 |
+
</div>
|
| 321 |
+
</div>
|
| 322 |
+
</div>
|
| 323 |
+
<div class="bg-white p-4 rounded-lg shadow">
|
| 324 |
+
<div id="topicChart">
|
| 325 |
+
<h4 class="font-medium text-gray-900 mb-3">Top Topics</h4>
|
| 326 |
+
<div class="text-center py-10 text-gray-400">
|
| 327 |
+
<i class="fas fa-chart-bar text-2xl mb-2"></i>
|
| 328 |
+
<p class="text-sm">No topic data available yet</p>
|
| 329 |
+
</div>
|
| 330 |
+
</div>
|
| 331 |
+
</div>
|
| 332 |
+
</div>
|
| 333 |
+
|
| 334 |
+
<!-- Results section -->
|
| 335 |
+
<div class="bg-white shadow rounded-lg overflow-hidden mb-6">
|
| 336 |
+
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
| 337 |
+
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
|
| 338 |
+
<h3 class="text-lg font-medium leading-6 text-gray-900">Conversation Results</h3>
|
| 339 |
+
<p class="mt-1 text-sm text-gray-500 md:mt-0">
|
| 340 |
+
Showing <span id="showingCount" class="font-medium">0</span> of <span id="totalCount" class="font-medium">0</span> conversations
|
| 341 |
+
</p>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
+
<div id="conversationResults" class="divide-y divide-gray-200">
|
| 345 |
+
<div class="text-center py-10 text-gray-400">
|
| 346 |
+
<i class="fas fa-comments text-4xl mb-2"></i>
|
| 347 |
+
<p>No analysis results yet. Start the analysis to see results here.</p>
|
| 348 |
+
</div>
|
| 349 |
+
</div>
|
| 350 |
+
</div>
|
| 351 |
+
</div>
|
| 352 |
+
</div>
|
| 353 |
+
|
| 354 |
+
<!-- Upload Modal -->
|
| 355 |
+
<div id="uploadModal" class="fixed z-50 inset-0 overflow-y-auto hidden">
|
| 356 |
+
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
| 357 |
+
<div class="fixed inset-0 transition-opacity modal-overlay" aria-hidden="true">
|
| 358 |
+
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
| 359 |
+
</div>
|
| 360 |
+
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
| 361 |
+
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
|
| 362 |
+
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
| 363 |
+
<div class="sm:flex sm:items-start">
|
| 364 |
+
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-purple-100 sm:mx-0 sm:h-10 sm:w-10">
|
| 365 |
+
<i class="fas fa-file-upload text-purple-600"></i>
|
| 366 |
+
</div>
|
| 367 |
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
| 368 |
+
<h3 class="text-lg leading-6 font-medium text-gray-900">Upload Conversations</h3>
|
| 369 |
+
<div class="mt-4">
|
| 370 |
+
<div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
|
| 371 |
+
<div class="space-y-1 text-center">
|
| 372 |
+
<div class="flex text-sm text-gray-600">
|
| 373 |
+
<label for="fileUpload" class="relative cursor-pointer bg-white rounded-md font-medium text-purple-600 hover:text-purple-500 focus-within:outline-none">
|
| 374 |
+
<span>Upload a file</span>
|
| 375 |
+
<input id="fileUpload" name="fileUpload" type="file" class="sr-only">
|
| 376 |
+
</label>
|
| 377 |
+
<p class="pl-1">or drag and drop</p>
|
| 378 |
+
</div>
|
| 379 |
+
<p class="text-xs text-gray-500">CSV, JSON, or TXT files up to 10MB</p>
|
| 380 |
+
</div>
|
| 381 |
+
</div>
|
| 382 |
+
<div class="mt-4">
|
| 383 |
+
<p class="text-sm text-gray-500">Selected file: <span id="fileNameDisplay" class="font-medium">None</span></p>
|
| 384 |
+
</div>
|
| 385 |
+
<div class="mt-4">
|
| 386 |
+
<div class="w-full bg-gray-200 rounded-full h-2.5">
|
| 387 |
+
<div id="uploadProgress" class="bg-purple-600 h-2.5 rounded-full" style="width: 0%"></div>
|
| 388 |
+
</div>
|
| 389 |
+
</div>
|
| 390 |
+
</div>
|
| 391 |
+
</div>
|
| 392 |
+
</div>
|
| 393 |
+
</div>
|
| 394 |
+
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
| 395 |
+
<button id="confirmUpload" type="button" disabled class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-purple-600 text-base font-medium text-white hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:ml-3 sm:w-auto sm:text-sm">
|
| 396 |
+
Upload and Extract
|
| 397 |
+
</button>
|
| 398 |
+
<button id="closeUploadModal" type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
|
| 399 |
+
Cancel
|
| 400 |
+
</button>
|
| 401 |
+
</div>
|
| 402 |
+
</div>
|
| 403 |
+
</div>
|
| 404 |
+
</div>
|
| 405 |
+
|
| 406 |
+
<!-- Analysis Details Modal -->
|
| 407 |
+
<div id="analysisModal" class="fixed z-50 inset-0 overflow-y-auto hidden">
|
| 408 |
+
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
|
| 409 |
+
<div class="fixed inset-0 transition-opacity modal-overlay" aria-hidden="true">
|
| 410 |
+
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
|
| 411 |
+
</div>
|
| 412 |
+
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
| 413 |
+
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-2xl sm:w-full">
|
| 414 |
+
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
| 415 |
+
<div class="sm:flex sm:items-start">
|
| 416 |
+
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-blue-100 sm:mx-0 sm:h-10 sm:w-10">
|
| 417 |
+
<i class="fas fa-chart-bar text-blue-600"></i>
|
| 418 |
+
</div>
|
| 419 |
+
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
| 420 |
+
<h3 class="text-lg leading-6 font-medium text-gray-900">Conversation Analysis Details</h3>
|
| 421 |
+
<div class="mt-4">
|
| 422 |
+
<div class="bg-gray-50 p-4 rounded-lg mb-4">
|
| 423 |
+
<h4 class="font-medium text-gray-900 mb-2">Summary</h4>
|
| 424 |
+
<p id="modalSummary" class="text-sm text-gray-700">Loading summary...</p>
|
| 425 |
+
</div>
|
| 426 |
+
|
| 427 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
| 428 |
+
<div>
|
| 429 |
+
<h4 class="font-medium text-gray-900 mb-2">Success Indicators</h4>
|
| 430 |
+
<div id="successIndicators" class="space-y-2">
|
| 431 |
+
<div class="bg-green-50 border border-green-100 p-3 rounded-lg">
|
| 432 |
+
<p class="text-sm text-green-800">Positive engagement throughout the conversation</p>
|
| 433 |
+
</div>
|
| 434 |
+
<div class="bg-green-50 border border-green-100 p-3 rounded-lg">
|
| 435 |
+
<p class="text-sm text-green-800">Clear communication of key points</p>
|
| 436 |
+
</div>
|
| 437 |
+
</div>
|
| 438 |
+
</div>
|
| 439 |
+
<div>
|
| 440 |
+
<h4 class="font-medium text-gray-900 mb-2">Struggle Indicators</h4>
|
| 441 |
+
<div id="struggleIndicators" class="space-y-2">
|
| 442 |
+
<div class="bg-red-50 border border-red-100 p-3 rounded-lg">
|
| 443 |
+
<p class="text-sm text-red-800">Some confusion around technical terms</p>
|
| 444 |
+
</div>
|
| 445 |
+
</div>
|
| 446 |
+
</div>
|
| 447 |
+
</div>
|
| 448 |
+
|
| 449 |
+
<div class="mb-4">
|
| 450 |
+
<h4 class="font-medium text-gray-900 mb-2">Main Topics</h4>
|
| 451 |
+
<div id="mainTopics" class="flex flex-wrap gap-1">
|
| 452 |
+
<span class="inline-block bg-white px-2 py-1 rounded-md text-xs mr-1 mb-1">Product Features</span>
|
| 453 |
+
<span class="inline-block bg-white px-2 py-1 rounded-md text-xs mr-1 mb-1">Pricing</span>
|
| 454 |
+
<span class="inline-block bg-white px-2 py-1 rounded-md text-xs mr-1 mb-1">Implementation</span>
|
| 455 |
+
</div>
|
| 456 |
+
</div>
|
| 457 |
+
|
| 458 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
| 459 |
+
<div class="bg-gray-50 p-3 rounded-lg">
|
| 460 |
+
<h4 class="font-medium text-gray-900 mb-2">Sentiment Trend</h4>
|
| 461 |
+
<div class="flex items-center">
|
| 462 |
+
<i class="fas fa-smile text-green-600 text-sm mr-2"></i>
|
| 463 |
+
<span id="sentimentTrend" class="text-sm font-medium text-green-600">Mostly Positive</span>
|
| 464 |
+
</div>
|
| 465 |
+
</div>
|
| 466 |
+
<div class="bg-gray-50 p-3 rounded-lg">
|
| 467 |
+
<h4 class="font-medium text-gray-900 mb-2">Key Learning</h4>
|
| 468 |
+
<p id="keyLearning" class="text-sm text-gray-700">The customer was particularly interested in integration capabilities with their existing systems.</p>
|
| 469 |
+
</div>
|
| 470 |
+
</div>
|
| 471 |
+
</div>
|
| 472 |
+
</div>
|
| 473 |
+
</div>
|
| 474 |
+
</div>
|
| 475 |
+
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
| 476 |
+
<button id="closeModal" type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm">
|
| 477 |
+
Close
|
| 478 |
+
</button>
|
| 479 |
+
</div>
|
| 480 |
+
</div>
|
| 481 |
+
</div>
|
| 482 |
+
</div>
|
| 483 |
+
|
| 484 |
+
<script>
|
| 485 |
+
// DOM Elements
|
| 486 |
+
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
|
| 487 |
+
const sidebar = document.getElementById('sidebar');
|
| 488 |
+
const workerCountInput = document.getElementById('workerCount');
|
| 489 |
+
const workerCountDisplay = document.getElementById('workerCountDisplay');
|
| 490 |
+
const uploadBtn = document.getElementById('uploadBtn');
|
| 491 |
+
const uploadModal = document.getElementById('uploadModal');
|
| 492 |
+
const closeUploadModalBtn = document.getElementById('closeUploadModal');
|
| 493 |
+
const fileUploadInput = document.getElementById('fileUpload');
|
| 494 |
+
const fileNameDisplay = document.getElementById('fileNameDisplay');
|
| 495 |
+
const confirmUploadBtn = document.getElementById('confirmUpload');
|
| 496 |
+
const uploadProgress = document.getElementById('uploadProgress');
|
| 497 |
+
const startAnalysisBtn = document.getElementById('startAnalysisBtn');
|
| 498 |
+
const lastRunTimeSpan = document.getElementById('lastRunTime');
|
| 499 |
+
const completedCountSpan = document.getElementById('completedCount');
|
| 500 |
+
const errorCountSpan = document.getElementById('errorCount');
|
| 501 |
+
const processingCountSpan = document.getElementById('processingCount');
|
| 502 |
+
const progressPercentSpan = document.getElementById('progressPercent');
|
| 503 |
+
const progressPercentSpan2 = document.getElementById('progressPercent2');
|
| 504 |
+
const progressBarDiv = document.getElementById('progressBar');
|
| 505 |
+
const sentimentCountSpan = document.getElementById('sentimentCount');
|
| 506 |
+
const topicCountSpan = document.getElementById('topicCount');
|
| 507 |
+
const progressCountSpan = document.getElementById('progressCount');
|
| 508 |
+
const summaryCountSpan = document.getElementById('summaryCount');
|
| 509 |
+
const sentimentStatusSpan = document.getElementById('sentimentStatus');
|
| 510 |
+
const topicStatusSpan = document.getElementById('topicStatus');
|
| 511 |
+
const progressStatusSpan = document.getElementById('progressStatus');
|
| 512 |
+
const summaryStatusSpan = document.getElementById('summaryStatus');
|
| 513 |
+
const resultsContainer = document.getElementById('conversationResults');
|
| 514 |
+
const showingCountSpan = document.getElementById('showingCount');
|
| 515 |
+
const totalCountSpan = document.getElementById('totalCount');
|
| 516 |
+
const analysisModal = document.getElementById('analysisModal');
|
| 517 |
+
const closeModalBtn = document.getElementById('closeModal');
|
| 518 |
+
const modalSummary = document.getElementById('modalSummary');
|
| 519 |
+
const successIndicatorsDiv = document.getElementById('successIndicators');
|
| 520 |
+
const struggleIndicatorsDiv = document.getElementById('struggleIndicators');
|
| 521 |
+
const mainTopicsDiv = document.getElementById('mainTopics');
|
| 522 |
+
const sentimentTrendSpan = document.getElementById('sentimentTrend');
|
| 523 |
+
const sentimentTrendIcon = document.querySelector('#sentimentTrend').previousElementSibling;
|
| 524 |
+
const keyLearningPara = document.getElementById('keyLearning');
|
| 525 |
+
const downloadBtn = document.getElementById('downloadBtn');
|
| 526 |
+
const pauseBtn = document.getElementById('pauseBtn');
|
| 527 |
+
const stopBtn = document.getElementById('stopBtn');
|
| 528 |
+
const modelSelect = document.getElementById('modelSelect');
|
| 529 |
+
const sentimentChartDiv = document.getElementById('sentimentChart');
|
| 530 |
+
const topicChartDiv = document.getElementById('topicChart');
|
| 531 |
+
|
| 532 |
+
// Sample data for demonstration
|
| 533 |
+
const sampleResults = [
|
| 534 |
+
{
|
| 535 |
+
filename: "Customer Support Call - 2023-05-15",
|
| 536 |
+
date: "May 15, 2023",
|
| 537 |
+
sentiment: "Positive",
|
| 538 |
+
summary_snippet: "The customer was satisfied with the resolution provided and appreciated the quick response time.",
|
| 539 |
+
topics: ["Support", "Resolution", "Response Time"],
|
| 540 |
+
exchanges: 12,
|
| 541 |
+
has_details: true
|
| 542 |
+
},
|
| 543 |
+
{
|
| 544 |
+
filename: "Sales Demo - Acme Corp",
|
| 545 |
+
date: "May 10, 2023",
|
| 546 |
+
sentiment: "Neutral",
|
| 547 |
+
summary_snippet: "The prospect showed interest in the product but had concerns about pricing and implementation timeline.",
|
| 548 |
+
topics: ["Pricing", "Implementation", "Features"],
|
| 549 |
+
exchanges: 24,
|
| 550 |
+
has_details: true
|
| 551 |
+
},
|
| 552 |
+
{
|
| 553 |
+
filename: "Team Meeting - Project Kickoff",
|
| 554 |
+
date: "May 5, 2023",
|
| 555 |
+
sentiment: "Positive",
|
| 556 |
+
summary_snippet: "The team aligned on project goals and timelines with clear action items assigned.",
|
| 557 |
+
topics: ["Goals", "Timelines", "Action Items"],
|
| 558 |
+
exchanges: 18,
|
| 559 |
+
has_details: true
|
| 560 |
+
}
|
| 561 |
+
];
|
| 562 |
+
|
| 563 |
+
// Event Listeners
|
| 564 |
+
mobileMenuBtn.addEventListener('click', function() {
|
| 565 |
+
sidebar.classList.toggle('sidebar-open');
|
| 566 |
+
});
|
| 567 |
+
|
| 568 |
+
// Close sidebar when clicking outside on mobile
|
| 569 |
+
document.addEventListener('click', function(event) {
|
| 570 |
+
if (!sidebar.contains(event.target) && !mobileMenuBtn.contains(event.target) && sidebar.classList.contains('sidebar-open') && window.innerWidth <= 768) {
|
| 571 |
+
sidebar.classList.remove('sidebar-open');
|
| 572 |
+
}
|
| 573 |
+
});
|
| 574 |
+
|
| 575 |
+
// Worker count display
|
| 576 |
+
workerCountInput.addEventListener('input', function() {
|
| 577 |
+
workerCountDisplay.textContent = this.value;
|
| 578 |
+
});
|
| 579 |
+
|
| 580 |
+
// File upload modal
|
| 581 |
+
uploadBtn.addEventListener('click', function() {
|
| 582 |
+
uploadModal.classList.remove('hidden');
|
| 583 |
+
});
|
| 584 |
+
|
| 585 |
+
closeUploadModalBtn.addEventListener('click', function() {
|
| 586 |
+
uploadModal.classList.add('hidden');
|
| 587 |
+
resetUploadModal();
|
| 588 |
+
});
|
| 589 |
+
|
| 590 |
+
// Reset upload modal UI
|
| 591 |
+
function resetUploadModal() {
|
| 592 |
+
fileUploadInput.value = '';
|
| 593 |
+
fileNameDisplay.textContent = 'None';
|
| 594 |
+
confirmUploadBtn.disabled = true;
|
| 595 |
+
uploadProgress.style.width = '0%';
|
| 596 |
+
}
|
| 597 |
+
|
| 598 |
+
// File selection handler
|
| 599 |
+
fileUploadInput.addEventListener('change', function(e) {
|
| 600 |
+
const file = e.target.files[0];
|
| 601 |
+
if (file) {
|
| 602 |
+
fileNameDisplay.textContent = file.name;
|
| 603 |
+
confirmUploadBtn.disabled = false;
|
| 604 |
+
} else {
|
| 605 |
+
resetUploadModal();
|
| 606 |
+
}
|
| 607 |
+
});
|
| 608 |
+
|
| 609 |
+
// Confirm upload button
|
| 610 |
+
confirmUploadBtn.addEventListener('click', function() {
|
| 611 |
+
const file = fileUploadInput.files[0];
|
| 612 |
+
if (!file) return;
|
| 613 |
+
|
| 614 |
+
// Simulate upload progress
|
| 615 |
+
let progress = 0;
|
| 616 |
+
const interval = setInterval(() => {
|
| 617 |
+
progress += 10;
|
| 618 |
+
uploadProgress.style.width = `${progress}%`;
|
| 619 |
+
|
| 620 |
+
if (progress >= 100) {
|
| 621 |
+
clearInterval(interval);
|
| 622 |
+
setTimeout(() => {
|
| 623 |
+
// Update UI with sample data
|
| 624 |
+
totalCountSpan.textContent = sampleResults.length;
|
| 625 |
+
showingCountSpan.textContent = 0;
|
| 626 |
+
|
| 627 |
+
// Reset analysis progress
|
| 628 |
+
completedCountSpan.textContent = '0';
|
| 629 |
+
errorCountSpan.textContent = '0';
|
| 630 |
+
processingCountSpan.textContent = '0';
|
| 631 |
+
progressPercentSpan.textContent = '0';
|
| 632 |
+
progressPercentSpan2.textContent = '0';
|
| 633 |
+
progressBarDiv.style.width = '0%';
|
| 634 |
+
|
| 635 |
+
// Update agent statuses
|
| 636 |
+
updateAgentStatuses("Ready");
|
| 637 |
+
|
| 638 |
+
// Close modal after upload
|
| 639 |
+
uploadModal.classList.add('hidden');
|
| 640 |
+
resetUploadModal();
|
| 641 |
+
|
| 642 |
+
// Show success message
|
| 643 |
+
alert('File uploaded successfully! 3 conversations extracted.');
|
| 644 |
+
}, 500);
|
| 645 |
+
}
|
| 646 |
+
}, 200);
|
| 647 |
+
});
|
| 648 |
+
|
| 649 |
+
// Update agent statuses
|
| 650 |
+
function updateAgentStatuses(status) {
|
| 651 |
+
const statusInfo = {
|
| 652 |
+
"Idle": { text: "Idle", color: "gray" },
|
| 653 |
+
"Ready": { text: "Ready", color: "blue" },
|
| 654 |
+
"Active": { text: "Active", color: "blue" },
|
| 655 |
+
"Completed": { text: "Completed", color: "green" },
|
| 656 |
+
"Error": { text: "Error", color: "red" }
|
| 657 |
+
};
|
| 658 |
+
|
| 659 |
+
const currentStatus = statusInfo[status] || statusInfo["Idle"];
|
| 660 |
+
|
| 661 |
+
sentimentStatusSpan.textContent = currentStatus.text;
|
| 662 |
+
sentimentStatusSpan.className = `text-sm font-medium text-${currentStatus.color}-600`;
|
| 663 |
+
|
| 664 |
+
topicStatusSpan.textContent = currentStatus.text;
|
| 665 |
+
topicStatusSpan.className = `text-sm font-medium text-${currentStatus.color}-600`;
|
| 666 |
+
|
| 667 |
+
progressStatusSpan.textContent = currentStatus.text;
|
| 668 |
+
progressStatusSpan.className = `text-sm font-medium text-${currentStatus.color}-600`;
|
| 669 |
+
|
| 670 |
+
summaryStatusSpan.textContent = currentStatus.text;
|
| 671 |
+
summaryStatusSpan.className = `text-sm font-medium text-${currentStatus.color}-600`;
|
| 672 |
+
}
|
| 673 |
+
|
| 674 |
+
// Start analysis button
|
| 675 |
+
startAnalysisBtn.addEventListener('click', function() {
|
| 676 |
+
const model = modelSelect.value;
|
| 677 |
+
|
| 678 |
+
// Disable button and show loading
|
| 679 |
+
startAnalysisBtn.disabled = true;
|
| 680 |
+
startAnalysisBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Starting...';
|
| 681 |
+
startAnalysisBtn.classList.add('opacity-50', 'cursor-not-allowed');
|
| 682 |
+
|
| 683 |
+
// Simulate analysis progress
|
| 684 |
+
setTimeout(() => {
|
| 685 |
+
// Update last run time
|
| 686 |
+
lastRunTimeSpan.textContent = new Date().toLocaleString();
|
| 687 |
+
|
| 688 |
+
// Update agent statuses
|
| 689 |
+
updateAgentStatuses("Active");
|
| 690 |
+
|
| 691 |
+
// Enable pause/stop buttons
|
| 692 |
+
pauseBtn.disabled = false;
|
| 693 |
+
stopBtn.disabled = false;
|
| 694 |
+
|
| 695 |
+
// Simulate analysis progress
|
| 696 |
+
let progress = 0;
|
| 697 |
+
const analysisInterval = setInterval(() => {
|
| 698 |
+
progress += 5;
|
| 699 |
+
|
| 700 |
+
// Update progress
|
| 701 |
+
progressPercentSpan.textContent = progress;
|
| 702 |
+
progressPercentSpan2.textContent = progress;
|
| 703 |
+
progressBarDiv.style.width = `${progress}%`;
|
| 704 |
+
|
| 705 |
+
// Update counts
|
| 706 |
+
completedCountSpan.textContent = Math.floor(progress / 100 * sampleResults.length);
|
| 707 |
+
processingCountSpan.textContent = sampleResults.length - Math.floor(progress / 100 * sampleResults.length);
|
| 708 |
+
|
| 709 |
+
// Update agent counts
|
| 710 |
+
sentimentCountSpan.textContent = Math.floor(progress / 30);
|
| 711 |
+
topicCountSpan.textContent = Math.floor(progress / 25);
|
| 712 |
+
progressCountSpan.textContent = Math.floor(progress / 20);
|
| 713 |
+
summaryCountSpan.textContent = Math.floor(progress / 35);
|
| 714 |
+
|
| 715 |
+
// When analysis completes
|
| 716 |
+
if (progress >= 100) {
|
| 717 |
+
clearInterval(analysisInterval);
|
| 718 |
+
|
| 719 |
+
// Update UI for completion
|
| 720 |
+
updateAgentStatuses("Completed");
|
| 721 |
+
startAnalysisBtn.disabled = false;
|
| 722 |
+
startAnalysisBtn.innerHTML = '<i class="fas fa-check mr-2"></i> Analysis Complete';
|
| 723 |
+
startAnalysisBtn.classList.remove('opacity-50', 'cursor-not-allowed');
|
| 724 |
+
startAnalysisBtn.classList.add('bg-green-100', 'text-green-700', 'hover:bg-green-200');
|
| 725 |
+
|
| 726 |
+
// Disable pause/stop buttons
|
| 727 |
+
pauseBtn.disabled = true;
|
| 728 |
+
stopBtn.disabled = true;
|
| 729 |
+
|
| 730 |
+
// Show results
|
| 731 |
+
updateResultsList();
|
| 732 |
+
updateVisualizations();
|
| 733 |
+
}
|
| 734 |
+
}, 300);
|
| 735 |
+
}, 1000);
|
| 736 |
+
});
|
| 737 |
+
|
| 738 |
+
// Update results list
|
| 739 |
+
function updateResultsList() {
|
| 740 |
+
resultsContainer.innerHTML = '';
|
| 741 |
+
|
| 742 |
+
sampleResults.forEach((result, index) => {
|
| 743 |
+
const card = document.createElement('div');
|
| 744 |
+
card.className = 'conversation-card bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm transition cursor-pointer mb-4 fade-in';
|
| 745 |
+
card.style.animationDelay = `${index * 0.1}s`;
|
| 746 |
+
|
| 747 |
+
// Determine sentiment color
|
| 748 |
+
let sentimentClass = 'bg-gray-100 text-gray-800';
|
| 749 |
+
if (result.sentiment.toLowerCase() === 'positive') {
|
| 750 |
+
sentimentClass = 'bg-green-100 text-green-800';
|
| 751 |
+
} else if (result.sentiment.toLowerCase() === 'negative') {
|
| 752 |
+
sentimentClass = 'bg-red-100 text-red-800';
|
| 753 |
+
}
|
| 754 |
+
|
| 755 |
+
card.innerHTML = `
|
| 756 |
+
<div class="p-4 border-b border-gray-200">
|
| 757 |
+
<div class="flex justify-between items-start">
|
| 758 |
+
<div>
|
| 759 |
+
<h4 class="font-medium text-gray-900 truncate">${result.filename}</h4>
|
| 760 |
+
<p class="text-xs text-gray-500 mt-1">${result.date}</p>
|
| 761 |
+
</div>
|
| 762 |
+
<span class="px-2 py-1 rounded-full text-xs font-medium ${sentimentClass}">
|
| 763 |
+
${result.sentiment}
|
| 764 |
+
</span>
|
| 765 |
+
</div>
|
| 766 |
+
</div>
|
| 767 |
+
<div class="p-4">
|
| 768 |
+
<p class="text-sm text-gray-600 line-clamp-2">${result.summary_snippet}</p>
|
| 769 |
+
<div class="mt-3 flex flex-wrap gap-1">
|
| 770 |
+
${result.topics.map(topic =>
|
| 771 |
+
`<span class="inline-block bg-gray-100 px-2 py-1 rounded-md text-xs">${topic}</span>`
|
| 772 |
+
).join('')}
|
| 773 |
+
</div>
|
| 774 |
+
</div>
|
| 775 |
+
<div class="px-4 py-2 bg-gray-50 border-t border-gray-200 flex justify-between items-center">
|
| 776 |
+
<span class="text-xs text-gray-500">${result.exchanges} exchanges</span>
|
| 777 |
+
<button class="text-blue-600 hover:text-blue-800 text-sm font-medium" data-filename="${result.filename}">
|
| 778 |
+
View Details
|
| 779 |
+
</button>
|
| 780 |
+
</div>
|
| 781 |
+
`;
|
| 782 |
+
|
| 783 |
+
// Add click handler to show modal
|
| 784 |
+
const detailButton = card.querySelector('button');
|
| 785 |
+
detailButton.addEventListener('click', function(e) {
|
| 786 |
+
e.stopPropagation();
|
| 787 |
+
showAnalysisModal(result.filename);
|
| 788 |
+
});
|
| 789 |
+
|
| 790 |
+
card.addEventListener('click', function() {
|
| 791 |
+
showAnalysisModal(result.filename);
|
| 792 |
+
});
|
| 793 |
+
|
| 794 |
+
resultsContainer.appendChild(card);
|
| 795 |
+
});
|
| 796 |
+
|
| 797 |
+
showingCountSpan.textContent = sampleResults.length;
|
| 798 |
+
totalCountSpan.textContent = sampleResults.length;
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
// Show analysis modal with details
|
| 802 |
+
function showAnalysisModal(filename) {
|
| 803 |
+
// Find the selected conversation
|
| 804 |
+
const conversation = sampleResults.find(r => r.filename === filename);
|
| 805 |
+
if (!conversation) return;
|
| 806 |
+
|
| 807 |
+
// Update modal content
|
| 808 |
+
modalSummary.textContent = conversation.summary_snippet;
|
| 809 |
+
|
| 810 |
+
// Update success indicators
|
| 811 |
+
successIndicatorsDiv.innerHTML = `
|
| 812 |
+
<div class="bg-green-50 border border-green-100 p-3 rounded-lg">
|
| 813 |
+
<p class="text-sm text-green-800">Positive engagement throughout the conversation</p>
|
| 814 |
+
</div>
|
| 815 |
+
<div class="bg-green-50 border border-green-100 p-3 rounded-lg">
|
| 816 |
+
<p class="text-sm text-green-800">Clear communication of key points</p>
|
| 817 |
+
</div>
|
| 818 |
+
`;
|
| 819 |
+
|
| 820 |
+
// Update struggle indicators
|
| 821 |
+
struggleIndicatorsDiv.innerHTML = `
|
| 822 |
+
<div class="bg-red-50 border border-red-100 p-3 rounded-lg">
|
| 823 |
+
<p class="text-sm text-red-800">Some confusion around technical terms</p>
|
| 824 |
+
</div>
|
| 825 |
+
`;
|
| 826 |
+
|
| 827 |
+
// Update main topics
|
| 828 |
+
mainTopicsDiv.innerHTML = conversation.topics.map(topic =>
|
| 829 |
+
`<span class="inline-block bg-white px-2 py-1 rounded-md text-xs mr-1 mb-1">${topic}</span>`
|
| 830 |
+
).join('');
|
| 831 |
+
|
| 832 |
+
// Update sentiment trend
|
| 833 |
+
let sentimentIcon, sentimentColor, sentimentText;
|
| 834 |
+
if (conversation.sentiment.toLowerCase() === 'positive') {
|
| 835 |
+
sentimentIcon = 'fa-smile text-green-600';
|
| 836 |
+
sentimentColor = 'text-green-600';
|
| 837 |
+
sentimentText = 'Mostly Positive';
|
| 838 |
+
} else if (conversation.sentiment.toLowerCase() === 'negative') {
|
| 839 |
+
sentimentIcon = 'fa-frown text-red-600';
|
| 840 |
+
sentimentColor = 'text-red-600';
|
| 841 |
+
sentimentText = 'Mostly Negative';
|
| 842 |
+
} else {
|
| 843 |
+
sentimentIcon = 'fa-meh text-gray-500';
|
| 844 |
+
sentimentColor = 'text-gray-600';
|
| 845 |
+
sentimentText = 'Neutral';
|
| 846 |
+
}
|
| 847 |
+
|
| 848 |
+
sentimentTrendIcon.className = `fas ${sentimentIcon} text-sm mr-2`;
|
| 849 |
+
sentimentTrendSpan.textContent = sentimentText;
|
| 850 |
+
sentimentTrendSpan.className = `text-sm font-medium ${sentimentColor}`;
|
| 851 |
+
|
| 852 |
+
// Update key learning
|
| 853 |
+
keyLearningPara.textContent = "The customer was particularly interested in integration capabilities with their existing systems.";
|
| 854 |
+
|
| 855 |
+
// Show modal
|
| 856 |
+
analysisModal.classList.remove('hidden');
|
| 857 |
+
}
|
| 858 |
+
|
| 859 |
+
// Close modal
|
| 860 |
+
closeModalBtn.addEventListener('click', function() {
|
| 861 |
+
analysisModal.classList.add('hidden');
|
| 862 |
+
});
|
| 863 |
+
|
| 864 |
+
// Download results
|
| 865 |
+
downloadBtn.addEventListener('click', function() {
|
| 866 |
+
alert('Downloading analysis results...');
|
| 867 |
+
});
|
| 868 |
+
|
| 869 |
+
// Pause analysis
|
| 870 |
+
pauseBtn.addEventListener('click', function() {
|
| 871 |
+
alert('Analysis paused. Click Start to resume.');
|
| 872 |
+
updateAgentStatuses("Ready");
|
| 873 |
+
});
|
| 874 |
+
|
| 875 |
+
// Stop analysis
|
| 876 |
+
stopBtn.addEventListener('click', function() {
|
| 877 |
+
if (confirm('Are you sure you want to stop the analysis? Progress will be saved.')) {
|
| 878 |
+
alert('Analysis stopped.');
|
| 879 |
+
updateAgentStatuses("Ready");
|
| 880 |
+
}
|
| 881 |
+
});
|
| 882 |
+
|
| 883 |
+
// Update visualizations
|
| 884 |
+
function updateVisualizations() {
|
| 885 |
+
// Sentiment distribution
|
| 886 |
+
const sentimentCounts = sampleResults.reduce((acc, res) => {
|
| 887 |
+
const sentiment = res.sentiment || 'N/A';
|
| 888 |
+
acc[sentiment] = (acc[sentiment] || 0) + 1;
|
| 889 |
+
return acc;
|
| 890 |
+
}, {});
|
| 891 |
+
|
| 892 |
+
let sentimentHtml = '<h4 class="font-medium text-gray-900 mb-3">Sentiment Distribution</h4>';
|
| 893 |
+
sentimentHtml += '<ul class="list-disc list-inside text-sm text-gray-700">';
|
| 894 |
+
for (const [sentiment, count] of Object.entries(sentimentCounts)) {
|
| 895 |
+
let colorClass = 'text-gray-600';
|
| 896 |
+
if (sentiment.toLowerCase() === 'positive') colorClass = 'text-green-600';
|
| 897 |
+
else if (sentiment.toLowerCase() === 'negative') colorClass = 'text-red-600';
|
| 898 |
+
|
| 899 |
+
sentimentHtml += `<li class="${colorClass}">${sentiment}: ${count}</li>`;
|
| 900 |
+
}
|
| 901 |
+
sentimentHtml += '</ul>';
|
| 902 |
+
sentimentChartDiv.innerHTML = sentimentHtml;
|
| 903 |
+
|
| 904 |
+
// Top topics
|
| 905 |
+
const allTopics = sampleResults.flatMap(res => res.topics || []);
|
| 906 |
+
const topicCounts = allTopics.reduce((acc, topic) => {
|
| 907 |
+
acc[topic] = (acc[topic] || 0) + 1;
|
| 908 |
+
return acc;
|
| 909 |
+
}, {});
|
| 910 |
+
|
| 911 |
+
const sortedTopics = Object.entries(topicCounts).sort(([, a], [, b]) => b - a);
|
| 912 |
+
|
| 913 |
+
let topicHtml = '<h4 class="font-medium text-gray-900 mb-3">Top Topics</h4>';
|
| 914 |
+
topicHtml += '<ul class="list-disc list-inside text-sm text-gray-700">';
|
| 915 |
+
sortedTopics.slice(0, 5).forEach(([topic, count]) => {
|
| 916 |
+
topicHtml += `<li>${topic}: ${count}</li>`;
|
| 917 |
+
});
|
| 918 |
+
topicHtml += '</ul>';
|
| 919 |
+
topicChartDiv.innerHTML = topicHtml;
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
// Initialize
|
| 923 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 924 |
+
// Set initial values
|
| 925 |
+
workerCountDisplay.textContent = workerCountInput.value;
|
| 926 |
+
lastRunTimeSpan.textContent = 'Never';
|
| 927 |
+
|
| 928 |
+
// Add fade-in class to cards when they're added
|
| 929 |
+
const observer = new MutationObserver(function(mutations) {
|
| 930 |
+
mutations.forEach(function(mutation) {
|
| 931 |
+
mutation.addedNodes.forEach(function(node) {
|
| 932 |
+
if (node.classList && node.classList.contains('conversation-card')) {
|
| 933 |
+
node.classList.add('fade-in');
|
| 934 |
+
}
|
| 935 |
+
});
|
| 936 |
+
});
|
| 937 |
+
});
|
| 938 |
+
|
| 939 |
+
observer.observe(resultsContainer, {
|
| 940 |
+
childList: true,
|
| 941 |
+
subtree: true
|
| 942 |
+
});
|
| 943 |
+
});
|
| 944 |
+
</script>
|
| 945 |
+
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Ultronprime/conv" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
|
| 946 |
+
</html>
|
prompts.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
<script> const mobileMenuBtn = document.getElementById('mobileMenuBtn'); const sidebar = document.getElementById('sidebar'); const workerCountInput = document.getElementById('workerCount'); const workerCountDisplay = document.getElementById('workerCountDisplay'); const uploadBtn = document.getElementById('uploadBtn'); const uploadModal = document.getElementById('uploadModal'); const closeUploadModalBtn = document.getElementById('closeUploadModal'); const fileUploadInput = document.getElementById('fileUpload'); const fileNameDisplay = document.getElementById('fileNameDisplay'); const confirmUploadBtn = document.getElementById('confirmUpload'); const uploadProgress = document.getElementById('uploadProgress'); const startAnalysisBtn = document.getElementById('startAnalysis'); const lastRunTimeSpan = document.getElementById('lastRunTime'); const completedCountSpan = document.getElementById('completedCount'); const errorCountSpan = document.getElementById('errorCount'); const processingCountSpan = document.getElementById('processingCount'); const progressPercentSpan = document.getElementById('progressPercent'); const progressBarDiv = document.getElementById('progressBar'); const sentimentCountSpan = document.getElementById('sentimentCount'); const topicCountSpan = document.getElementById('topicCount'); const progressCountSpan = document.getElementById('progressCount'); const summaryCountSpan = document.getElementById('summaryCount'); const sentimentStatusSpan = document.getElementById('sentimentStatus'); const topicStatusSpan = document.getElementById('topicStatus'); const progressStatusSpan = document.getElementById('progressStatus'); const summaryStatusSpan = document.getElementById('summaryStatus'); const resultsContainer = document.getElementById('conversationResults'); const showingCountSpan = document.getElementById('showingCount'); const totalCountSpan = document.getElementById('totalCount'); const analysisModal = document.getElementById('analysisModal'); const closeModalBtn = document.getElementById('closeModal'); const modalSummary = document.getElementById('modalSummary'); const successIndicatorsDiv = document.getElementById('successIndicators'); const struggleIndicatorsDiv = document.getElementById('struggleIndicators'); const mainTopicsDiv = document.getElementById('mainTopics'); const sentimentTrendSpan = document.getElementById('sentimentTrend'); const sentimentTrendIcon = sentimentTrendSpan.previousElementSibling.querySelector('i'); // Assuming the icon is the previous sibling const keyLearningPara = document.getElementById('keyLearning'); const downloadBtn = document.getElementById('downloadBtn'); const pauseBtn = document.getElementById('pauseBtn'); const stopBtn = document.getElementById('stopBtn'); const modelSelect = document.getElementById('modelSelect'); let statusInterval = null; // To hold the interval for status polling // Initial state update updateStatusDisplay(); // Call once on load // Mobile menu toggle mobileMenuBtn.addEventListener('click', function() { sidebar.classList.toggle('sidebar-open'); }); // Close sidebar on click outside (simple example, might need refinement) document.addEventListener('click', function(event) { if (!sidebar.contains(event.target) && !mobileMenuBtn.contains(event.target) && sidebar.classList.contains('sidebar-open') && window.innerWidth <= 768) { sidebar.classList.remove('sidebar-open'); } }); // Update worker count display (still useful for UI config) workerCountInput.addEventListener('input', function() { // Use input for live updates workerCountDisplay.textContent = this.value; // Note: Changing this *during* analysis won't change the actual worker count // A real app would need to signal the backend to resize the pool }); // File upload modal uploadBtn.addEventListener('click', function() { uploadModal.classList.remove('hidden'); }); closeUploadModalBtn.addEventListener('click', function() { uploadModal.classList.add('hidden'); resetUploadModal(); // Reset modal state on close }); // Reset upload modal UI function resetUploadModal() { fileUploadInput.value = ''; // Clear selected file fileNameDisplay.textContent = 'None'; confirmUploadBtn.disabled = true; uploadProgress.style.width = '0%'; } // File upload handling fileUploadInput.addEventListener('change', function(e) { const file = e.target.files[0]; if (file) { fileNameDisplay.textContent = file.name; confirmUploadBtn.disabled = false; } else { resetUploadModal(); } }); // Confirm Upload button (sends file to backend) confirmUploadBtn.addEventListener('click', async function() { const file = fileUploadInput.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); // Simulate progress visually for the upload itself (optional, backend handles processing) // We won't do a real upload progress bar here as it's complex uploadProgress.style.width = '0%'; confirmUploadBtn.disabled = true; confirmUploadBtn.textContent = 'Uploading...'; try { const response = await fetch('/upload', { method: 'POST', body: formData, }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Upload failed'); } const result = await response.json(); console.log('Upload successful:', result); uploadProgress.style.width = '100%'; alert(result.message); // Update total count based on files extracted totalCountSpan.textContent = result.file_count; showingCountSpan.textContent = 0; // No results shown yet // Reset analysis progress UI completedCountSpan.textContent = '0'; errorCountSpan.textContent = '0'; processingCountSpan.textContent = '0'; progressPercentSpan.textContent = '0'; progressBarDiv.style.width = '0%'; sentimentCountSpan.textContent = '0'; topicCountSpan.textContent = '0'; progressCountSpan.textContent = '0'; summaryCountSpan.textContent = '0'; // Clear previous results from display resultsContainer.innerHTML = ` <div class="text-center py-10 text-gray-400"> <i class="fas fa-comments text-4xl mb-2"></i> <p>Files uploaded. Start analysis to see results here.</p> </div> `; // Update agent statuses to 'Ready' or 'Idle' after upload updateAgentStatuses("Ready"); setTimeout(() => { uploadModal.classList.add('hidden'); resetUploadModal(); }, 500); // Give user a moment to read alert } catch (error) { console.error('Upload error:', error); alert('Upload failed: ' + error.message); uploadProgress.style.width = '0%'; // Reset progress on error confirmUploadBtn.disabled = false; confirmUploadBtn.textContent = 'Upload and Extract'; updateAgentStatuses("Idle"); // Stay idle on error } }); // Function to update agent status text and color function updateAgentStatuses(status) { const statusMapping = { "Idle": { text: "Idle", color: "gray" }, "Ready": { text: "Ready", color: "blue" }, // New status after upload "Active": { text: "Active", color: "blue" }, "Completed": { text: "Completed", color: "green" }, "Error": { text: "Error", color: "red" } // For individual agent errors }; const s = statusMapping[status] || statusMapping["Idle"]; sentimentStatusSpan.textContent = s.text; sentimentStatusSpan.className = `text-sm font-medium text-${s.color}-600`; topicStatusSpan.textContent = s.text; topicStatusSpan.className = `text-sm font-medium text-${s.color}-600`; progressStatusSpan.textContent = s.text; progressStatusSpan.className = `text-sm font-medium text-${s.color}-600`; summaryStatusSpan.textContent = s.text; summaryStatusSpan.className = `text-sm font-medium text-${s.color}-600`; } // Start Analysis button startAnalysisBtn.addEventListener('click', async function() { const model = modelSelect.value; // Disable button and indicate starting startAnalysisBtn.disabled = true; startAnalysisBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <span>Starting...</span>'; startAnalysisBtn.classList.add('opacity-50', 'cursor-not-allowed'); try { const response = await fetch('/start_analysis', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ model: model }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to start analysis'); } const result = await response.json(); console.log('Analysis start message:', result.message); // Update UI immediately to reflect starting lastRunTimeSpan.textContent = new Date().toLocaleString(); updateAgentStatuses("Active"); // Polling will update counts and progress // Start polling for status and results if (statusInterval) clearInterval(statusInterval); // Clear any existing interval statusInterval = setInterval(updateStatusDisplay, 2000); // Poll every 2 seconds // Enable pause/stop buttons pauseBtn.disabled = false; stopBtn.disabled = false; } catch (error) { console.error('Error starting analysis:', error); alert('Failed to start analysis: ' + error.message); // Re-enable button and reset text startAnalysisBtn.disabled = false; startAnalysisBtn.innerHTML = '<i class="fas fa-play"></i> <span>Start Analysis</span>'; startAnalysisBtn.classList.remove('opacity-50', 'cursor-not-allowed'); updateAgentStatuses("Idle"); } }); // Function to poll backend status and update UI async function updateStatusDisplay() { try { const statusResponse = await fetch('/status'); const status = await statusResponse.json(); completedCountSpan.textContent = status.completed_files; errorCountSpan.textContent = status.error_files; processingCountSpan.textContent = status.processing_files_count; progressPercentSpan.textContent = status.progress_percent; progressBarDiv.style.width = status.progress_percent + '%'; lastRunTimeSpan.textContent = status.last_run_time; totalCountSpan.textContent = status.total_files; // Ensure total is correct // Update agent counts sentimentCountSpan.textContent = status.agent_counts.sentiment; topicCountSpan.textContent = status.agent_counts.topic; progressCountSpan.textContent = status.agent_counts.progress; summaryCountSpan.textContent = status.agent_counts.summary; // Update agent statuses based on overall status updateAgentStatuses(status.status); // Uses the main status from backend // Update Start button based on status if (status.status === "Running") { startAnalysisBtn.disabled = true; startAnalysisBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> <span>Analyzing...</span>'; startAnalysisBtn.classList.add('opacity-50', 'cursor-not-allowed'); startAnalysisBtn.classList.remove('bg-white', 'text-purple-700', 'hover:bg-purple-50'); startAnalysisBtn.classList.add('bg-gray-200', 'text-gray-700'); // Indicate disabled running state } else if (status.status === "Completed") { if (statusInterval) clearInterval(statusInterval); // Stop polling on completion startAnalysisBtn.disabled = false; startAnalysisBtn.innerHTML = '<i class="fas fa-check"></i> <span>Analysis Complete</span>'; startAnalysisBtn.classList.remove('opacity-50', 'cursor-not-allowed', 'bg-gray-200', 'text-gray-700', 'bg-white', 'text-purple-700', 'hover:bg-purple-50'); startAnalysisBtn.classList.add('bg-green-100', 'text-green-700', 'hover:bg-green-200'); // Indicate success // Disable pause/stop on completion pauseBtn.disabled = true; stopBtn.disabled = true; } else if (status.status === "Idle" || status.status === "Ready") { startAnalysisBtn.disabled = status.total_files === 0; // Disable if no files startAnalysisBtn.innerHTML = '<i class="fas fa-play"></i> <span>Start Analysis</span>'; startAnalysisBtn.classList.remove('opacity-50', 'cursor-not-allowed', 'bg-green-100', 'text-green-700', 'hover:bg-green-200', 'bg-gray-200', 'text-gray-700'); startAnalysisBtn.classList.add('bg-white', 'text-purple-700', 'hover:bg-purple-50'); // Ensure buttons are disabled if not running if (status.status === "Idle") { // Truly idle, before upload pauseBtn.disabled = true; stopBtn.disabled = true; } else { // Ready after upload pauseBtn.disabled = true; // Can't pause before starting stopBtn.disabled = false; // Can 'stop' to clear files } } // Fetch and display results list if analysis is running or completed if (status.status === "Running" || status.status === "Completed") { await updateResultsList(); // Charts would be updated here based on aggregated data from /status or a new endpoint } } catch (error) { console.error('Error fetching status:', error); // If status fetch fails repeatedly, stop polling and indicate error // For simplicity, we just log here. } } // Function to fetch and display the list of results async function updateResultsList() { try { const resultsResponse = await fetch('/results'); const results = await resultsResponse.json(); // Clear existing results, but keep the placeholder if no results yet if (results.length > 0) { const emptyMessage = resultsContainer.querySelector('.text-center.py-10.text-gray-400'); if (emptyMessage) { emptyMessage.remove(); } resultsContainer.innerHTML = ''; // Clear only if there are results to show results.forEach(result => { const card = document.createElement('div'); card.className = 'conversation-card bg-white border border-gray-200 rounded-lg overflow-hidden shadow-sm transition cursor-pointer fade-in'; // Determine sentiment class for badge let sentimentClass = 'bg-gray-100 text-gray-800'; if (result.sentiment.toLowerCase() === 'positive') { sentimentClass = 'bg-green-100 text-green-800'; } else if (result.sentiment.toLowerCase() === 'negative') { sentimentClass = 'bg-red-100 text-red-800'; } card.innerHTML = ` <div class="p-4 border-b border-gray-200"> <div class="flex justify-between items-start"> <div> <h4 class="font-medium text-gray-900 truncate">${result.filename}</h4> <p class="text-xs text-gray-500 mt-1">${result.date || 'N/A'}</p> </div> <span class="px-2 py-1 rounded-full text-xs font-medium ${sentimentClass}"> ${result.sentiment || 'N/A'} </span> </div> </div> <div class="p-4"> <p class="text-sm text-gray-600 line-clamp-2">${result.summary_snippet || 'No summary snippet available.'}</p> <div class="mt-3 flex flex-wrap gap-1"> ${(result.topics || []).map(topic => `<span class="inline-block bg-gray-100 px-2 py-1 rounded-md text-xs">${topic}</span>` ).join('') || '<span class="inline-block text-xs text-gray-500">No topics identified.</span>'} </div> </div> <div class="px-4 py-2 bg-gray-50 border-t border-gray-200 flex justify-between items-center"> <span class="text-xs text-gray-500">${result.exchanges || 'N/A'} exchanges</span> ${result.has_details ? ` <button class="text-blue-600 hover:text-blue-800 text-sm font-medium" data-filename="${result.filename}"> View Details </button> ` : `<span class="text-xs text-red-500">Analysis Failed</span>`} </div> `; // Add click handler to show modal (on button or card) if(result.has_details) { const detailButton = card.querySelector('button'); detailButton.addEventListener('click', function(e) { e.stopPropagation(); // Prevent card click from triggering showAnalysisModal(detailButton.dataset.filename); }); card.addEventListener('click', function() { showAnalysisModal(result.filename); }); } resultsContainer.appendChild(card); // Use appendChild to add in order }); showingCountSpan.textContent = results.length; // Update showing count // totalCountSpan should already be set by upload } else if (totalCountSpan.textContent > 0) { // Files uploaded, but no results yet (analysis not started or still processing first files) const emptyMessage = resultsContainer.querySelector('.text-center.py-10.text-gray-400'); if (!emptyMessage) { resultsContainer.innerHTML = ` <div class="text-center py-10 text-gray-400"> <i class="fas fa-comments text-4xl mb-2"></i> <p>Analysis in progress or no results yet. Results will appear here.</p> </div> `; } showingCountSpan.textContent = 0; } else { // No files uploaded const emptyMessage = resultsContainer.querySelector('.text-center.py-10.text-gray-400'); if (!emptyMessage) { resultsContainer.innerHTML = ` <div class="text-center py-10 text-gray-400"> <i class="fas fa-comments text-4xl mb-2"></i> <p>No analysis results yet. Start the analysis to see results here.</p> </div> `; } showingCountSpan.textContent = 0; } } catch (error) { console.error('Error fetching results list:', error); resultsContainer.innerHTML = ` <div class="text-center py-10 text-red-400"> <i class="fas fa-exclamation-circle text-4xl mb-2"></i> <p>Error loading results.</p> </div> `; } } // Show analysis modal with details fetched from backend async function showAnalysisModal(filename) { try { // Clear previous modal content and show loading modalSummary.textContent = 'Loading...'; successIndicatorsDiv.innerHTML = '<p class="text-gray-500">Loading...</p>'; struggleIndicatorsDiv.innerHTML = '<p class="text-gray-500">Loading...</p>'; mainTopicsDiv.innerHTML = '<p class="text-gray-500">Loading...</p>'; sentimentTrendSpan.textContent = 'Loading...'; sentimentTrendIcon.className = 'fas fa-spinner fa-spin text-gray-500 text-sm'; // Loading icon keyLearningPara.textContent = 'Loading...'; const response = await fetch(`/analyze/${encodeURIComponent(filename)}`); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to load analysis details'); } const details = await response.json(); console.log('Analysis details:', details); // Populate modal content modalSummary.textContent = details.summary || 'No summary available.'; // Success Indicators successIndicatorsDiv.innerHTML = ''; if (details.success_indicators && details.success_indicators.length > 0) { details.success_indicators.forEach(indicator => { const div = document.createElement('div'); div.className = 'bg-green-50 border border-green-100 p-3 rounded-lg'; div.innerHTML = `<p class="text-sm text-green-800">${indicator}</p>`; // Assuming indicator is the text itself successIndicatorsDiv.appendChild(div); }); } else { successIndicatorsDiv.innerHTML = '<p class="text-gray-500 text-sm">No specific success indicators identified.</p>'; } // Struggle Indicators struggleIndicatorsDiv.innerHTML = ''; if (details.struggle_indicators && details.struggle_indicators.length > 0) { details.struggle_indicators.forEach(indicator => { const div = document.createElement('div'); div.className = 'bg-red-50 border border-red-100 p-3 rounded-lg'; div.innerHTML = `<p class="text-sm text-red-800">${indicator}</p>`; // Assuming indicator is the text itself struggleIndicatorsDiv.appendChild(div); }); } else { struggleIndicatorsDiv.innerHTML = '<p class="text-gray-500 text-sm">No specific struggle indicators identified.</p>'; } // Main Topics mainTopicsDiv.innerHTML = ''; if (details.topics && details.topics.length > 0) { details.topics.forEach(topic => { const span = document.createElement('span'); span.className = 'inline-block bg-white px-2 py-1 rounded-md text-xs mr-1 mb-1'; span.textContent = topic; mainTopicsDiv.appendChild(span); }); } else { mainTopicsDiv.innerHTML = '<p class="text-gray-500 text-sm">No main topics identified.</p>'; } // Sentiment Trend (simplified mapping from main sentiment) const sentiment = details.sentiment?.label || 'N/A'; let sentimentTrendText = 'N/A'; let sentimentIconClass = 'fas fa-question text-gray-500'; let sentimentColorClass = 'text-gray-600'; if (sentiment.toLowerCase() === 'positive') { sentimentTrendText = 'Mostly Positive'; sentimentIconClass = 'fas fa-smile text-green-600'; sentimentColorClass = 'text-green-600'; } else if (sentiment.toLowerCase() === 'negative') { sentimentTrendText = 'Mostly Negative'; sentimentIconClass = 'fas fa-frown text-red-600'; sentimentColorClass = 'text-red-600'; } else if (sentiment.toLowerCase() === 'neutral') { sentimentTrendText = 'Neutral'; sentimentIconClass = 'fas fa-meh text-gray-500'; sentimentColorClass = 'text-gray-600'; } sentimentTrendSpan.textContent = sentimentTrendText; sentimentTrendSpan.className = `text-sm font-medium ${sentimentColorClass}`; sentimentTrendIcon.className = sentimentIconClass + ' text-sm'; // Key Learning keyLearningPara.textContent = details.key_learning || 'No key learning identified.'; // Store filename for download button in modal (if needed) // The current download button downloads ALL results, you'd need to add a new endpoint for single file download // Show modal analysisModal.classList.remove('hidden'); } catch (error) { console.error('Error fetching analysis details:', error); // Display error in modal if fetch fails modalSummary.textContent = `Error loading details: ${error.message}`; successIndicatorsDiv.innerHTML = ''; struggleIndicatorsDiv.innerHTML = ''; mainTopicsDiv.innerHTML = ''; sentimentTrendSpan.textContent = 'Error'; sentimentTrendIcon.className = 'fas fa-times-circle text-red-600 text-sm'; keyLearningPara.textContent = 'Could not load detailed analysis.'; analysisModal.classList.remove('hidden'); } } // Close modal closeModalBtn.addEventListener('click', function() { analysisModal.classList.add('hidden'); }); // Download results (calls backend endpoint) downloadBtn.addEventListener('click', function() { // The backend handles generating and sending the zip file window.location.href = '/download_results'; // This will trigger the download in the browser // Optional: Add visual feedback like a spinner or message alert('Generating and downloading results...'); // Simple feedback }); // Pause/stop buttons (call backend endpoints - functionality depends on backend implementation) pauseBtn.addEventListener('click', async function() { try { const response = await fetch('/pause_analysis', { method: 'POST' }); const result = await response.json(); console.log(result.message); alert(result.message); // UI might need updating to show paused state (backend status should handle this) } catch (error) { console.error('Error pausing analysis:', error); alert('Failed to pause analysis: ' + error.message); } }); stopBtn.addEventListener('click', async function() { const confirmStop = confirm("Are you sure you want to stop the analysis? Progress will be saved."); if (!confirmStop) return; try { const response = await fetch('/stop_analysis', { method: 'POST' }); const result = await response.json(); console.log(result.message); alert(result.message); // UI needs to update to indicate stopped state and potentially stop polling if (statusInterval) clearInterval(statusInterval); updateStatusDisplay(); // Final status update } catch (error) { console.error('Error stopping analysis:', error); alert('Failed to stop analysis: ' + error.message); } }); // Initial call to update status display on page load // This fetches the state in case the app was restarted or already had files document.addEventListener('DOMContentLoaded', () => { updateStatusDisplay(); }); // Helper function to update the chart areas with simple text/lists // This replaces the static HTML visualization simulation function updateVisualizations() { // This function is called by updateStatusDisplay when analysis is completed (or periodically) // Fetch aggregate data from the backend or use data from state.get_status() + results const status = state.get_status(); // Access state directly on frontend (not ideal) or fetch aggregate data // --- Sentiment Distribution (Simple Text Summary) --- const sentimentChartDiv = document.getElementById('sentimentChart'); const resultsList = state.get_results_list(); // Or fetch /results and process const sentimentCounts = resultsList.reduce((acc, res) => { const sentiment = res.sentiment || 'N/A'; acc[sentiment] = (acc[sentiment] || 0) + 1; return acc; }, {}); let sentimentHtml = '<h4 class="font-medium text-gray-900 mb-3">Sentiment Distribution</h4>'; if (resultsList.length === 0) { sentimentHtml += `<div class="text-center py-10 text-gray-400"> <i class="fas fa-chart-pie text-2xl mb-2"></i> <p class="text-sm">No sentiment data available yet</p> </div>`; } else { sentimentHtml += '<ul class="list-disc list-inside text-sm text-gray-700">'; for (const [sentiment, count] of Object.entries(sentimentCounts)) { let colorClass = 'text-gray-600'; if (sentiment.toLowerCase() === 'positive') colorClass = 'text-green-600'; else if (sentiment.toLowerCase() === 'negative') colorClass = 'text-red-600'; sentimentHtml += `<li class="${colorClass}">${sentiment}: ${count}</li>`; } sentimentHtml += '</ul>'; } sentimentChartDiv.innerHTML = sentimentHtml; // --- Top Topics (Simple List) --- const topicChartDiv = document.getElementById('topicChart'); const allTopics = resultsList.flatMap(res => res.topics || []).filter(topic => topic !== 'Topic extraction pipeline failed to load.'); const topicCounts = allTopics.reduce((acc, topic) => { acc[topic] = (acc[topic] || 0) + 1; return acc; }, {}); const sortedTopics = Object.entries(topicCounts).sort(([, a], [, b]) => b - a); let topicHtml = '<h4 class="font-medium text-gray-900 mb-3">Top Topics</h4>'; if (sortedTopics.length === 0) { topicHtml += `<div class="text-center py-10 text-gray-400"> <i class="fas fa-chart-bar text-2xl mb-2"></i> <p class="text-sm">No topic data available yet</p> </div>`; } else { topicHtml += '<ul class="list-disc list-inside text-sm text-gray-700">'; sortedTopics.slice(0, 5).forEach(([topic, count]) => { // Show top 5 topicHtml += `<li>${topic}: ${count}</li>`; }); topicHtml += '</ul>'; } topicChartDiv.innerHTML = topicHtml; } // Call update visualizations after results are updated, especially on completion // The updateStatusDisplay could call updateVisualizations periodically or on completion // For now, let's add a manual call example or call it when analysis is detected as completed. // A better approach is to fetch aggregate data from a backend endpoint dedicated to charts. // For this simplified version, let's update charts only when analysis completes. // (Modified the updateStatusDisplay to call updateVisualizations on "Completed") </script>
|