Spaces:
Sleeping
Sleeping
Update index.html
Browse files- index.html +534 -284
index.html
CHANGED
|
@@ -3,245 +3,370 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<!-- Updated Title -->
|
| 7 |
<title>G.E.N.I.E. - GitHub Enhanced Natural Intelligence Engine</title>
|
| 8 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
-
<link
|
| 10 |
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 11 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 12 |
<style>
|
| 13 |
-
/*
|
| 14 |
body {
|
| 15 |
font-family: 'Inter', sans-serif;
|
| 16 |
-
background
|
| 17 |
color: #e0e0e0;
|
| 18 |
-
overflow: hidden;
|
| 19 |
}
|
| 20 |
|
| 21 |
-
/*
|
| 22 |
-
/* Renamed class */
|
| 23 |
-
.genie-orb {
|
| 24 |
-
width: 150px;
|
| 25 |
-
height: 150px;
|
| 26 |
-
border-radius: 50%;
|
| 27 |
-
/* Updated gradient - Purple/Gold theme */
|
| 28 |
-
background: radial-gradient(circle, #ffd700 10%, #8a2be2 50%, #4b0082 75%);
|
| 29 |
-
/* Updated shadow - Purple/Gold theme */
|
| 30 |
-
box-shadow: 0 0 20px #8a2be2, 0 0 40px #4b0082, inset 0 0 15px #ffffff40;
|
| 31 |
-
position: relative;
|
| 32 |
-
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out, background 0.5s ease;
|
| 33 |
-
/* Default animation state - using updated pulse */
|
| 34 |
-
animation: genie-pulse 3s infinite ease-in-out, subtle-move 10s infinite alternate ease-in-out;
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
/* Glossy highlight - Pale gold */
|
| 38 |
-
/* Updated selector */
|
| 39 |
-
.genie-orb::before {
|
| 40 |
-
content: '';
|
| 41 |
-
position: absolute;
|
| 42 |
-
top: 10%;
|
| 43 |
-
left: 20%;
|
| 44 |
-
width: 20%;
|
| 45 |
-
height: 20%;
|
| 46 |
-
/* Pale gold-ish highlight */
|
| 47 |
-
background: rgba(255, 248, 220, 0.6); /* Cornsilk with alpha */
|
| 48 |
-
border-radius: 50%;
|
| 49 |
-
filter: blur(5px);
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
/* Animation for default pulsing effect - Updated Colors */
|
| 53 |
-
/* Renamed animation */
|
| 54 |
-
@keyframes genie-pulse {
|
| 55 |
-
0%, 100% {
|
| 56 |
-
transform: scale(1);
|
| 57 |
-
/* Updated shadow */
|
| 58 |
-
box-shadow: 0 0 20px #8a2be2, 0 0 40px #4b0082, inset 0 0 15px #ffffff40;
|
| 59 |
-
}
|
| 60 |
-
50% {
|
| 61 |
-
transform: scale(1.05);
|
| 62 |
-
/* Updated shadow */
|
| 63 |
-
box-shadow: 0 0 30px #9932cc, 0 0 60px #8a2be2, inset 0 0 20px #ffffff60;
|
| 64 |
-
}
|
| 65 |
-
}
|
| 66 |
-
|
| 67 |
-
/* Animation for default subtle movement (Kept as is) */
|
| 68 |
-
@keyframes subtle-move {
|
| 69 |
-
0% { transform: translate(0, 0) scale(var(--orb-scale, 1)); }
|
| 70 |
-
25% { transform: translate(5px, -3px) scale(var(--orb-scale, 1.02)); }
|
| 71 |
-
50% { transform: translate(-4px, 4px) scale(var(--orb-scale, 0.98)); }
|
| 72 |
-
75% { transform: translate(3px, 2px) scale(var(--orb-scale, 1.01)); }
|
| 73 |
-
100% { transform: translate(0, 0) scale(var(--orb-scale, 1)); }
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
/* --- Listening State --- */
|
| 77 |
-
/* Updated selector */
|
| 78 |
-
.genie-orb.listening {
|
| 79 |
-
/* Kept ripple animation, updated background/shadow slightly */
|
| 80 |
-
animation: ripple 2s infinite ease-out;
|
| 81 |
-
background: radial-gradient(circle, #adffad 0%, #90ee90 50%, #0f0a1f 75%); /* Light green focus */
|
| 82 |
-
box-shadow: 0 0 25px #90ee90, 0 0 50px #3cb371, inset 0 0 15px #ffffff60;
|
| 83 |
-
}
|
| 84 |
-
|
| 85 |
-
/* Ripple animation kept green - it's intuitive for listening */
|
| 86 |
-
@keyframes ripple {
|
| 87 |
-
0% {
|
| 88 |
-
transform: scale(0.95);
|
| 89 |
-
box-shadow: 0 0 0 0 rgba(144, 238, 144, 0.7), 0 0 25px #90ee90, 0 0 50px #3cb371, inset 0 0 15px #ffffff60;
|
| 90 |
-
}
|
| 91 |
-
70% {
|
| 92 |
-
transform: scale(1.05);
|
| 93 |
-
box-shadow: 0 0 0 15px rgba(144, 238, 144, 0), 0 0 30px #adffad, 0 0 60px #5fbf5f, inset 0 0 20px #ffffff80;
|
| 94 |
-
}
|
| 95 |
-
100% {
|
| 96 |
-
transform: scale(0.95);
|
| 97 |
-
box-shadow: 0 0 0 0 rgba(144, 238, 144, 0), 0 0 25px #90ee90, 0 0 50px #3cb371, inset 0 0 15px #ffffff60;
|
| 98 |
-
}
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
/* --- Active/Thinking/Talking State --- */
|
| 103 |
-
/* Updated selector */
|
| 104 |
-
.genie-orb.active {
|
| 105 |
-
/* Using updated pulse and glow animations */
|
| 106 |
-
animation: genie-pulse 1.5s infinite ease-in-out, subtle-move 5s infinite alternate ease-in-out, genie-glow 1.5s infinite alternate;
|
| 107 |
-
/* Reset background to default purple/gold */
|
| 108 |
-
background: radial-gradient(circle, #ffd700 10%, #8a2be2 50%, #4b0082 75%);
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
/* Updated glow animation colors */
|
| 112 |
-
/* Renamed animation */
|
| 113 |
-
@keyframes genie-glow {
|
| 114 |
-
0%, 100% { box-shadow: 0 0 30px #8a2be2, 0 0 60px #4b0082, 0 0 80px #9932cc, inset 0 0 20px #fff8dc60; } /* Cornsilk inset */
|
| 115 |
-
50% { box-shadow: 0 0 40px #9932cc, 0 0 80px #8a2be2, 0 0 100px #b19cd9, inset 0 0 25px #fff8dc80; } /* Light pastel purple + Cornsilk */
|
| 116 |
-
}
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
/* Custom scrollbar for chat */
|
| 120 |
.custom-scrollbar::-webkit-scrollbar { width: 8px; }
|
| 121 |
-
.custom-scrollbar::-webkit-scrollbar-track { background:
|
| 122 |
-
|
| 123 |
-
.custom-scrollbar::-webkit-scrollbar-thumb { background-color: #8a2be2; border-radius: 10px; border: 2px solid #1a203a; }
|
| 124 |
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9932cc; }
|
| 125 |
|
| 126 |
/* Glassmorphism effect */
|
| 127 |
.glass-panel {
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
| 132 |
|
| 133 |
/* Input field styling */
|
| 134 |
-
.futuristic-input {
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
}
|
| 139 |
-
.futuristic-input:focus { outline: none; border-color: #ffd700; box-shadow: 0 0 10px #ffd70040; } /* Gold focus */
|
| 140 |
.futuristic-input::placeholder { color: #718096; }
|
|
|
|
| 141 |
|
| 142 |
-
/* Button styling
|
| 143 |
.futuristic-button {
|
| 144 |
-
background: linear-gradient(90deg, #0077ff, #00aaff);
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
}
|
| 148 |
-
.futuristic-button:hover { box-shadow: 0 6px 20px rgba(0, 170, 255, 0.5); transform: translateY(-2px); background: linear-gradient(90deg, #00aaff, #00ccff); }
|
| 149 |
-
.futuristic-button:active { transform: translateY(0); box-shadow: 0 2px 10px rgba(0, 127, 255, 0.2); }
|
| 150 |
-
.futuristic-button:disabled { background: #555; cursor: not-allowed; box-shadow: none; transform: none; }
|
| 151 |
-
|
| 152 |
|
| 153 |
-
/* Mic button specific style
|
| 154 |
.mic-button {
|
| 155 |
-
background: #1f3a6e;
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
}
|
| 159 |
-
.mic-button:hover { background: #2c5282; box-shadow: 0 4px 15px rgba(144, 238, 144, 0.4); transform: translateY(-1px); color: #adffad; }
|
| 160 |
-
.mic-button.listening { background: linear-gradient(90deg, #3cb371, #5fbf5f); color: white; box-shadow: 0 4px 15px rgba(60, 179, 113, 0.5); }
|
| 161 |
|
| 162 |
-
/* Interrupt button specific style
|
| 163 |
.interrupt-button {
|
| 164 |
-
background: linear-gradient(90deg, #ff6b6b, #ff8e8e);
|
| 165 |
-
color: white;
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
opacity: 1;
|
| 169 |
}
|
| 170 |
-
.interrupt-button:hover {
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
/* Chat bubble styling */
|
| 176 |
-
.chat-bubble {
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
|
| 184 |
/* Dot animation for thinking */
|
| 185 |
-
.dot-flashing {
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
.dot-flashing::before { left: -10px; animation: dotFlashing 1s infinite alternate; animation-delay: 0s; }
|
| 188 |
.dot-flashing::after { left: 10px; animation: dotFlashing 1s infinite alternate; animation-delay: 1s; }
|
| 189 |
@keyframes dotFlashing { 0% { background-color: #a0aec0; } 50%, 100% { background-color: rgba(160, 174, 192, 0.2); } }
|
| 190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
</style>
|
| 192 |
</head>
|
| 193 |
-
<body class="
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
<div class="flex items-center space-x-3">
|
| 204 |
-
<button id="micButton" title="Toggle Listening Mode / Interrupt" class="futuristic-button mic-button">🎙️</button>
|
| 205 |
-
<input type="text" id="userQuery" placeholder="Make a wish about the repository..." class="flex-grow futuristic-input">
|
| 206 |
-
<button id="sendButton" class="futuristic-button">Ask G.E.N.I.E.</button>
|
| 207 |
</div>
|
| 208 |
-
</div>
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
</div>
|
| 215 |
-
</div>
|
| 216 |
-
</div>
|
| 217 |
-
|
| 218 |
-
<div class="w-1/3 flex flex-col items-center justify-center p-6 space-y-6 bg-gradient-to-b from-[#0f0a1f] to-[#1a102f]">
|
| 219 |
-
<!-- Updated Status Title Color -->
|
| 220 |
-
<h2 id="agentStatusTitle" class="text-xl font-semibold text-purple-400">G.E.N.I.E. Status</h2>
|
| 221 |
-
<!-- Updated Orb ID and Class -->
|
| 222 |
-
<div id="genieOrb" class="genie-orb mb-4"></div>
|
| 223 |
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
-
<div class="
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
</div>
|
| 232 |
</div>
|
| 233 |
|
| 234 |
<script>
|
| 235 |
// --- DOM Elements ---
|
| 236 |
const repoUrlInput = document.getElementById('repoUrl');
|
|
|
|
|
|
|
|
|
|
| 237 |
const userQueryInput = document.getElementById('userQuery');
|
| 238 |
const sendButton = document.getElementById('sendButton');
|
| 239 |
const micButton = document.getElementById('micButton');
|
| 240 |
const interruptButton = document.getElementById('interruptButton');
|
| 241 |
const chatHistory = document.getElementById('chatHistory');
|
| 242 |
-
// Updated variable name and ID
|
| 243 |
const genieOrb = document.getElementById('genieOrb');
|
| 244 |
-
// Updated variable name and ID
|
| 245 |
const genieOutput = document.getElementById('genieOutput');
|
| 246 |
const agentStatusTitle = document.getElementById('agentStatusTitle');
|
| 247 |
|
|
@@ -254,7 +379,8 @@
|
|
| 254 |
// --- Event Listeners ---
|
| 255 |
sendButton.addEventListener('click', handleSendQuery);
|
| 256 |
userQueryInput.addEventListener('keypress', (event) => {
|
| 257 |
-
if (event.key === 'Enter') {
|
|
|
|
| 258 |
handleSendQuery();
|
| 259 |
}
|
| 260 |
});
|
|
@@ -265,90 +391,108 @@
|
|
| 265 |
// --- Functions ---
|
| 266 |
|
| 267 |
function handleMicButtonClick() {
|
|
|
|
| 268 |
if (isBotActive) {
|
| 269 |
handleInterrupt();
|
| 270 |
} else {
|
|
|
|
| 271 |
toggleListeningMode();
|
| 272 |
}
|
| 273 |
}
|
| 274 |
|
| 275 |
function toggleListeningMode() {
|
| 276 |
-
if (isBotActive) return;
|
| 277 |
|
| 278 |
isListening = !isListening;
|
|
|
|
|
|
|
|
|
|
| 279 |
if (isListening) {
|
| 280 |
-
setBotActiveState(false);
|
| 281 |
-
// Updated orb variable
|
| 282 |
-
genieOrb.classList.add('listening');
|
| 283 |
-
micButton.classList.add('listening');
|
| 284 |
-
// Updated status text
|
| 285 |
agentStatusTitle.textContent = "G.E.N.I.E. Status: Listening...";
|
| 286 |
-
// Updated function call and text
|
| 287 |
updateGenieText("Listening... (Simulation - Type your wish and press Ask)");
|
| 288 |
userQueryInput.focus();
|
|
|
|
| 289 |
} else {
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
micButton.classList.remove('listening');
|
| 293 |
if (!isBotActive) {
|
| 294 |
-
// Updated status text
|
| 295 |
agentStatusTitle.textContent = "G.E.N.I.E. Status";
|
| 296 |
-
//
|
| 297 |
if (genieOutput.textContent.startsWith("Listening...")) {
|
| 298 |
-
|
| 299 |
}
|
| 300 |
}
|
| 301 |
}
|
| 302 |
}
|
| 303 |
|
| 304 |
function handleTypingInterrupt() {
|
|
|
|
| 305 |
if (isBotActive && botResponseTimeoutId) {
|
| 306 |
handleInterrupt();
|
| 307 |
}
|
| 308 |
}
|
| 309 |
|
| 310 |
function handleSendQuery() {
|
|
|
|
| 311 |
if (isListening) {
|
| 312 |
toggleListeningMode();
|
| 313 |
}
|
|
|
|
|
|
|
| 314 |
if (isBotActive) {
|
| 315 |
console.log("G.E.N.I.E. is active, cannot send new query yet.");
|
|
|
|
| 316 |
return;
|
| 317 |
}
|
| 318 |
|
| 319 |
const repoUrl = repoUrlInput.value.trim();
|
| 320 |
const query = userQueryInput.value.trim();
|
|
|
|
|
|
|
|
|
|
| 321 |
|
|
|
|
| 322 |
if (!query) {
|
| 323 |
-
|
|
|
|
| 324 |
}
|
| 325 |
if (!isValidHttpUrl(repoUrl)) {
|
| 326 |
-
|
|
|
|
| 327 |
}
|
| 328 |
|
|
|
|
| 329 |
addMessageToChat(query, 'user');
|
| 330 |
-
userQueryInput.value = '';
|
| 331 |
-
|
|
|
|
|
|
|
| 332 |
}
|
| 333 |
|
| 334 |
function handleInterrupt() {
|
| 335 |
if (botResponseTimeoutId) {
|
| 336 |
-
console.log("Interrupt triggered.");
|
| 337 |
clearTimeout(botResponseTimeoutId);
|
| 338 |
botResponseTimeoutId = null;
|
| 339 |
|
|
|
|
| 340 |
if (thinkingMessageElement) {
|
| 341 |
thinkingMessageElement.remove();
|
| 342 |
thinkingMessageElement = null;
|
| 343 |
}
|
|
|
|
|
|
|
| 344 |
setBotActiveState(false);
|
| 345 |
|
|
|
|
| 346 |
addMessageToChat("Processing interrupted by user.", 'interrupted');
|
| 347 |
-
// Updated function call and text
|
| 348 |
updateGenieText("Interrupted. Ready for your next wish.");
|
|
|
|
|
|
|
| 349 |
userQueryInput.focus();
|
| 350 |
} else {
|
| 351 |
-
console.log("Interrupt called but G.E.N.I.E. was not
|
| 352 |
}
|
| 353 |
}
|
| 354 |
|
|
@@ -356,134 +500,240 @@
|
|
| 356 |
const messageElement = document.createElement('div');
|
| 357 |
messageElement.classList.add('chat-bubble');
|
| 358 |
|
|
|
|
| 359 |
if (isListening) {
|
| 360 |
-
// Updated orb variable
|
| 361 |
genieOrb.classList.remove('listening');
|
| 362 |
micButton.classList.remove('listening');
|
|
|
|
|
|
|
| 363 |
}
|
| 364 |
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
botResponseTimeoutId
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
| 393 |
}
|
| 394 |
|
| 395 |
chatHistory.appendChild(messageElement);
|
| 396 |
-
|
| 397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
}
|
| 399 |
|
| 400 |
-
function simulateBotResponse(repoUrl, query) {
|
|
|
|
| 401 |
thinkingMessageElement = addMessageToChat('', 'thinking');
|
| 402 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
if (botResponseTimeoutId) clearTimeout(botResponseTimeoutId);
|
| 404 |
|
| 405 |
botResponseTimeoutId = setTimeout(() => {
|
|
|
|
| 406 |
const wasInterrupted = !thinkingMessageElement;
|
| 407 |
if (wasInterrupted) {
|
| 408 |
console.log("G.E.N.I.E. response timeout fired, but was already interrupted.");
|
| 409 |
-
botResponseTimeoutId = null;
|
|
|
|
| 410 |
return;
|
| 411 |
}
|
| 412 |
|
| 413 |
-
|
| 414 |
-
thinkingMessageElement
|
| 415 |
-
|
|
|
|
|
|
|
|
|
|
| 416 |
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
| 418 |
addMessageToChat(botResponse, 'bot');
|
|
|
|
| 419 |
|
| 420 |
}, delay);
|
| 421 |
}
|
| 422 |
|
| 423 |
-
//
|
| 424 |
-
|
| 425 |
-
// but the examples are generic enough.
|
| 426 |
-
function generateSampleResponse(repoUrl, query) {
|
| 427 |
const repoName = repoUrl.substring(repoUrl.lastIndexOf('/') + 1) || "[Unknown Repo]";
|
| 428 |
query = query.toLowerCase();
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
if (
|
| 434 |
-
if (
|
| 435 |
-
|
| 436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
}
|
| 438 |
|
| 439 |
-
/** Updates the G.E.N.I.E. output text */
|
| 440 |
-
|
| 441 |
-
|
|
|
|
| 442 |
|
|
|
|
| 443 |
function setBotActiveState(isActive) {
|
| 444 |
isBotActive = isActive;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
|
| 446 |
if (isActive) {
|
| 447 |
-
//
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
// Updated orb variable
|
| 453 |
-
genieOrb.classList.add('active');
|
| 454 |
-
interruptButton.classList.remove('hidden');
|
| 455 |
-
// Updated status text
|
| 456 |
agentStatusTitle.textContent = "G.E.N.I.E. Status: Fulfilling Wish...";
|
| 457 |
-
|
| 458 |
} else {
|
| 459 |
-
//
|
| 460 |
-
genieOrb.classList.remove('active');
|
| 461 |
-
interruptButton.classList.add('hidden');
|
| 462 |
if (!isListening) {
|
| 463 |
-
// Updated status text
|
| 464 |
agentStatusTitle.textContent = "G.E.N.I.E. Status";
|
|
|
|
| 465 |
}
|
| 466 |
-
|
| 467 |
-
|
|
|
|
|
|
|
| 468 |
clearTimeout(botResponseTimeoutId);
|
| 469 |
botResponseTimeoutId = null;
|
| 470 |
-
|
| 471 |
-
|
| 472 |
}
|
| 473 |
}
|
| 474 |
|
|
|
|
| 475 |
function isValidHttpUrl(string) {
|
| 476 |
-
|
| 477 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
}
|
| 479 |
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
|
| 486 |
-
</script>
|
| 487 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 488 |
</body>
|
| 489 |
-
</html>
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
| 6 |
<title>G.E.N.I.E. - GitHub Enhanced Natural Intelligence Engine</title>
|
| 7 |
<script src="https://cdn.tailwindcss.com"></script>
|
| 8 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
|
|
|
|
|
|
|
| 9 |
<style>
|
| 10 |
+
/* Base styles */
|
| 11 |
body {
|
| 12 |
font-family: 'Inter', sans-serif;
|
| 13 |
+
background: linear-gradient(135deg, #0f0a1f 0%, #1a102f 50%, #2c1c4a 100%);
|
| 14 |
color: #e0e0e0;
|
| 15 |
+
overflow: hidden; /* Prevent body scroll */
|
| 16 |
}
|
| 17 |
|
| 18 |
+
/* Custom Scrollbar */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
.custom-scrollbar::-webkit-scrollbar { width: 8px; }
|
| 20 |
+
.custom-scrollbar::-webkit-scrollbar-track { background: rgba(26, 32, 58, 0.3); border-radius: 10px; }
|
| 21 |
+
.custom-scrollbar::-webkit-scrollbar-thumb { background-color: #8a2be2; border-radius: 10px; border: 2px solid transparent; background-clip: content-box; }
|
|
|
|
| 22 |
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9932cc; }
|
| 23 |
|
| 24 |
/* Glassmorphism effect */
|
| 25 |
.glass-panel {
|
| 26 |
+
background: rgba(26, 32, 58, 0.6);
|
| 27 |
+
backdrop-filter: blur(10px);
|
| 28 |
+
-webkit-backdrop-filter: blur(10px);
|
| 29 |
+
border: 1px solid rgba(138, 43, 226, 0.3); /* Purple border */
|
| 30 |
+
border-radius: 1rem;
|
| 31 |
+
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2);
|
| 32 |
}
|
| 33 |
|
| 34 |
/* Input field styling */
|
| 35 |
+
.futuristic-input, .futuristic-select {
|
| 36 |
+
background-color: rgba(15, 10, 31, 0.7);
|
| 37 |
+
border: 1px solid rgba(138, 43, 226, 0.5); /* Slightly stronger purple border */
|
| 38 |
+
color: #e0e0e0;
|
| 39 |
+
border-radius: 0.5rem;
|
| 40 |
+
padding: 0.75rem 1rem;
|
| 41 |
+
transition: border-color 0.3s, box-shadow 0.3s;
|
| 42 |
+
width: 100%; /* Ensure inputs take full width */
|
| 43 |
+
box-sizing: border-box; /* Include padding and border in element's total width and height */
|
| 44 |
+
}
|
| 45 |
+
.futuristic-input:focus, .futuristic-select:focus {
|
| 46 |
+
outline: none;
|
| 47 |
+
border-color: #ffd700; /* Gold focus */
|
| 48 |
+
box-shadow: 0 0 10px rgba(255, 215, 0, 0.25);
|
| 49 |
}
|
|
|
|
| 50 |
.futuristic-input::placeholder { color: #718096; }
|
| 51 |
+
.futuristic-select { appearance: none; /* Remove default arrow */ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke-width='1.5' stroke='%23e0e0e0' class='w-6 h-6'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='m19.5 8.25-7.5 7.5-7.5-7.5' /%3E%3C/svg%3E%0A"); background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 1.25em; padding-right: 2.5rem; /* Space for arrow */ }
|
| 52 |
|
| 53 |
+
/* Button styling */
|
| 54 |
.futuristic-button {
|
| 55 |
+
background: linear-gradient(90deg, #0077ff, #00aaff);
|
| 56 |
+
color: white;
|
| 57 |
+
font-weight: 600;
|
| 58 |
+
padding: 0.75rem 1.5rem;
|
| 59 |
+
border-radius: 0.5rem;
|
| 60 |
+
border: none;
|
| 61 |
+
cursor: pointer;
|
| 62 |
+
transition: all 0.3s ease;
|
| 63 |
+
box-shadow: 0 4px 15px rgba(0, 127, 255, 0.3);
|
| 64 |
+
white-space: nowrap; /* Prevent text wrapping */
|
| 65 |
+
}
|
| 66 |
+
.futuristic-button:hover {
|
| 67 |
+
box-shadow: 0 6px 20px rgba(0, 170, 255, 0.5);
|
| 68 |
+
transform: translateY(-2px);
|
| 69 |
+
background: linear-gradient(90deg, #00aaff, #00ccff);
|
| 70 |
+
}
|
| 71 |
+
.futuristic-button:active {
|
| 72 |
+
transform: translateY(0);
|
| 73 |
+
box-shadow: 0 2px 10px rgba(0, 127, 255, 0.2);
|
| 74 |
+
}
|
| 75 |
+
.futuristic-button:disabled {
|
| 76 |
+
background: #555;
|
| 77 |
+
cursor: not-allowed;
|
| 78 |
+
box-shadow: none;
|
| 79 |
+
transform: none;
|
| 80 |
+
opacity: 0.7;
|
| 81 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
|
| 83 |
+
/* Mic button specific style */
|
| 84 |
.mic-button {
|
| 85 |
+
background: #1f3a6e;
|
| 86 |
+
color: #90ee90; /* Light green mic */
|
| 87 |
+
padding: 0.75rem;
|
| 88 |
+
width: 50px; /* Fixed width */
|
| 89 |
+
height: 50px; /* Fixed height */
|
| 90 |
+
display: flex;
|
| 91 |
+
align-items: center;
|
| 92 |
+
justify-content: center;
|
| 93 |
+
font-size: 1.25rem;
|
| 94 |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
| 95 |
+
flex-shrink: 0; /* Prevent shrinking */
|
| 96 |
+
}
|
| 97 |
+
.mic-button:hover {
|
| 98 |
+
background: #2c5282;
|
| 99 |
+
box-shadow: 0 4px 15px rgba(144, 238, 144, 0.4);
|
| 100 |
+
transform: translateY(-1px);
|
| 101 |
+
color: #adffad;
|
| 102 |
+
}
|
| 103 |
+
.mic-button.listening {
|
| 104 |
+
background: linear-gradient(90deg, #3cb371, #5fbf5f); /* Green gradient */
|
| 105 |
+
color: white;
|
| 106 |
+
box-shadow: 0 4px 15px rgba(60, 179, 113, 0.5);
|
| 107 |
}
|
|
|
|
|
|
|
| 108 |
|
| 109 |
+
/* Interrupt button specific style */
|
| 110 |
.interrupt-button {
|
| 111 |
+
background: linear-gradient(90deg, #ff6b6b, #ff8e8e); /* Red gradient */
|
| 112 |
+
color: white;
|
| 113 |
+
font-weight: 600;
|
| 114 |
+
padding: 0.5rem 1rem;
|
| 115 |
+
border-radius: 0.5rem;
|
| 116 |
+
border: none;
|
| 117 |
+
cursor: pointer;
|
| 118 |
+
transition: all 0.3s ease;
|
| 119 |
+
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
|
| 120 |
opacity: 1;
|
| 121 |
}
|
| 122 |
+
.interrupt-button:hover {
|
| 123 |
+
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6);
|
| 124 |
+
transform: translateY(-2px);
|
| 125 |
+
background: linear-gradient(90deg, #ff8e8e, #ffa7a7);
|
| 126 |
+
}
|
| 127 |
+
.interrupt-button:active {
|
| 128 |
+
transform: translateY(0);
|
| 129 |
+
box-shadow: 0 2px 10px rgba(255, 107, 107, 0.3);
|
| 130 |
+
}
|
| 131 |
+
.interrupt-button.hidden {
|
| 132 |
+
opacity: 0;
|
| 133 |
+
pointer-events: none;
|
| 134 |
+
transform: scale(0.8);
|
| 135 |
+
transition: opacity 0.3s ease, transform 0.3s ease;
|
| 136 |
+
}
|
| 137 |
|
| 138 |
/* Chat bubble styling */
|
| 139 |
+
.chat-bubble {
|
| 140 |
+
padding: 0.75rem 1rem;
|
| 141 |
+
border-radius: 0.75rem;
|
| 142 |
+
max-width: 85%; /* Slightly wider max width */
|
| 143 |
+
word-wrap: break-word;
|
| 144 |
+
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
|
| 145 |
+
}
|
| 146 |
+
/* User bubble */
|
| 147 |
+
.user-bubble {
|
| 148 |
+
background-color: #0055aa; /* Kept blue */
|
| 149 |
+
margin-left: auto;
|
| 150 |
+
border-bottom-right-radius: 0.25rem;
|
| 151 |
+
color: #f0f0f0;
|
| 152 |
+
}
|
| 153 |
+
/* Bot bubble */
|
| 154 |
+
.bot-bubble {
|
| 155 |
+
background-color: #4b0082; /* Dark purple */
|
| 156 |
+
margin-right: auto;
|
| 157 |
+
border-bottom-left-radius: 0.25rem;
|
| 158 |
+
color: #f0f0f0;
|
| 159 |
+
}
|
| 160 |
+
.bot-bubble.thinking {
|
| 161 |
+
font-style: italic;
|
| 162 |
+
color: #a0aec0;
|
| 163 |
+
display: flex;
|
| 164 |
+
align-items: center;
|
| 165 |
+
background-color: #2d1f4a; /* Lighter purple for thinking */
|
| 166 |
+
}
|
| 167 |
+
.bot-bubble.interrupted {
|
| 168 |
+
font-style: italic;
|
| 169 |
+
color: #f6ad55; /* Orange tone */
|
| 170 |
+
background-color: #5c3d1c; /* Dark orange/brown */
|
| 171 |
+
}
|
| 172 |
|
| 173 |
/* Dot animation for thinking */
|
| 174 |
+
.dot-flashing {
|
| 175 |
+
position: relative;
|
| 176 |
+
width: 5px; height: 5px; border-radius: 5px;
|
| 177 |
+
background-color: #a0aec0; color: #a0aec0;
|
| 178 |
+
animation: dotFlashing 1s infinite linear alternate;
|
| 179 |
+
animation-delay: .5s;
|
| 180 |
+
margin-left: 8px; /* Increased spacing */
|
| 181 |
+
}
|
| 182 |
+
.dot-flashing::before, .dot-flashing::after {
|
| 183 |
+
content: ''; display: inline-block; position: absolute; top: 0;
|
| 184 |
+
width: 5px; height: 5px; border-radius: 5px;
|
| 185 |
+
background-color: #a0aec0; color: #a0aec0;
|
| 186 |
+
}
|
| 187 |
.dot-flashing::before { left: -10px; animation: dotFlashing 1s infinite alternate; animation-delay: 0s; }
|
| 188 |
.dot-flashing::after { left: 10px; animation: dotFlashing 1s infinite alternate; animation-delay: 1s; }
|
| 189 |
@keyframes dotFlashing { 0% { background-color: #a0aec0; } 50%, 100% { background-color: rgba(160, 174, 192, 0.2); } }
|
| 190 |
|
| 191 |
+
/* Genie Orb Styling */
|
| 192 |
+
.genie-orb {
|
| 193 |
+
width: 100px; height: 100px;
|
| 194 |
+
border-radius: 50%;
|
| 195 |
+
background: radial-gradient(circle, rgba(138, 43, 226, 0.7) 0%, rgba(75, 0, 130, 0.9) 70%);
|
| 196 |
+
box-shadow: 0 0 25px rgba(138, 43, 226, 0.6), inset 0 0 15px rgba(255, 255, 255, 0.2);
|
| 197 |
+
position: relative;
|
| 198 |
+
transition: all 0.5s ease;
|
| 199 |
+
border: 2px solid rgba(255, 215, 0, 0.3); /* Subtle gold border */
|
| 200 |
+
}
|
| 201 |
+
.genie-orb::before { /* Inner glow */
|
| 202 |
+
content: ''; position: absolute;
|
| 203 |
+
top: 10%; left: 10%; width: 80%; height: 80%;
|
| 204 |
+
border-radius: 50%;
|
| 205 |
+
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0) 70%);
|
| 206 |
+
}
|
| 207 |
+
.genie-orb.listening {
|
| 208 |
+
box-shadow: 0 0 35px rgba(60, 179, 113, 0.8), inset 0 0 20px rgba(144, 238, 144, 0.4);
|
| 209 |
+
background: radial-gradient(circle, rgba(60, 179, 113, 0.8) 0%, rgba(46, 139, 87, 1) 70%);
|
| 210 |
+
border-color: rgba(144, 238, 144, 0.5);
|
| 211 |
+
}
|
| 212 |
+
.genie-orb.active {
|
| 213 |
+
box-shadow: 0 0 35px rgba(0, 170, 255, 0.8), inset 0 0 20px rgba(0, 204, 255, 0.4);
|
| 214 |
+
background: radial-gradient(circle, rgba(0, 170, 255, 0.8) 0%, rgba(0, 127, 255, 1) 70%);
|
| 215 |
+
border-color: rgba(0, 204, 255, 0.5);
|
| 216 |
+
animation: pulse 1.5s infinite ease-in-out;
|
| 217 |
+
}
|
| 218 |
+
@keyframes pulse {
|
| 219 |
+
0% { transform: scale(1); }
|
| 220 |
+
50% { transform: scale(1.05); }
|
| 221 |
+
100% { transform: scale(1); }
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
/* Responsive Design */
|
| 225 |
+
/* Stack columns on smaller screens */
|
| 226 |
+
.main-container {
|
| 227 |
+
display: flex;
|
| 228 |
+
flex-direction: column; /* Default: Stack vertically */
|
| 229 |
+
height: 100vh; /* Full viewport height */
|
| 230 |
+
}
|
| 231 |
+
.chat-section {
|
| 232 |
+
flex-grow: 1; /* Takes available space */
|
| 233 |
+
display: flex;
|
| 234 |
+
flex-direction: column;
|
| 235 |
+
padding: 1rem; /* Padding for mobile */
|
| 236 |
+
overflow: hidden; /* Prevent overflow */
|
| 237 |
+
}
|
| 238 |
+
.status-section {
|
| 239 |
+
padding: 1rem; /* Padding for mobile */
|
| 240 |
+
height: auto; /* Adjust height automatically */
|
| 241 |
+
flex-shrink: 0; /* Prevent shrinking */
|
| 242 |
+
background: linear-gradient(to bottom, #1a102f, #0f0a1f); /* Gradient for status section */
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
/* Apply row layout on medium screens and up */
|
| 246 |
+
@media (min-width: 768px) { /* md breakpoint */
|
| 247 |
+
.main-container {
|
| 248 |
+
flex-direction: row; /* Side-by-side layout */
|
| 249 |
+
}
|
| 250 |
+
.chat-section {
|
| 251 |
+
width: 66.666667%; /* 2/3 width */
|
| 252 |
+
padding: 1.5rem; /* Larger padding */
|
| 253 |
+
height: 100vh; /* Full height */
|
| 254 |
+
}
|
| 255 |
+
.status-section {
|
| 256 |
+
width: 33.333333%; /* 1/3 width */
|
| 257 |
+
padding: 1.5rem; /* Larger padding */
|
| 258 |
+
height: 100vh; /* Full height */
|
| 259 |
+
display: flex;
|
| 260 |
+
flex-direction: column;
|
| 261 |
+
align-items: center;
|
| 262 |
+
justify-content: center; /* Center vertically */
|
| 263 |
+
}
|
| 264 |
+
.status-content {
|
| 265 |
+
width: 100%;
|
| 266 |
+
max-width: 400px; /* Max width for status content */
|
| 267 |
+
display: flex;
|
| 268 |
+
flex-direction: column;
|
| 269 |
+
align-items: center;
|
| 270 |
+
gap: 1.5rem; /* Space between items */
|
| 271 |
+
}
|
| 272 |
+
.chat-input-area {
|
| 273 |
+
display: flex; /* Keep input and button side-by-side */
|
| 274 |
+
align-items: center;
|
| 275 |
+
gap: 0.75rem; /* Space between mic, input, send */
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
/* Ensure chat history scrolls within its container */
|
| 279 |
+
#chatHistory {
|
| 280 |
+
flex-grow: 1; /* Takes up remaining space in chat-section */
|
| 281 |
+
overflow-y: auto; /* Enable vertical scroll */
|
| 282 |
+
}
|
| 283 |
+
/* Adjust input area layout for smaller screens */
|
| 284 |
+
.chat-input-area {
|
| 285 |
+
display: flex;
|
| 286 |
+
flex-wrap: wrap; /* Allow wrapping */
|
| 287 |
+
gap: 0.5rem; /* Space between elements */
|
| 288 |
+
}
|
| 289 |
+
#userQuery {
|
| 290 |
+
flex-grow: 1; /* Take available space */
|
| 291 |
+
min-width: 150px; /* Minimum width before wrapping */
|
| 292 |
+
}
|
| 293 |
+
.preferences-grid {
|
| 294 |
+
display: grid;
|
| 295 |
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); /* Responsive grid */
|
| 296 |
+
gap: 0.75rem; /* Space between grid items */
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
</style>
|
| 300 |
</head>
|
| 301 |
+
<body class="text-sm md:text-base"> <div class="main-container">
|
| 302 |
+
|
| 303 |
+
<div class="chat-section">
|
| 304 |
+
<div class="text-center mb-3 md:mb-4">
|
| 305 |
+
<h1 class="text-2xl md:text-3xl font-bold text-purple-300">G.E.N.I.E.</h1>
|
| 306 |
+
<p class="text-purple-100 text-xs md:text-sm">
|
| 307 |
+
GitHub Enhanced Natural Intelligence Engine <br class="md:hidden">
|
| 308 |
+
<span class="hidden md:inline"> - </span>
|
| 309 |
+
A voice-controlled AI that “grants your GitHub wishes.”
|
| 310 |
+
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 311 |
</div>
|
|
|
|
| 312 |
|
| 313 |
+
<div class="glass-panel p-3 md:p-4 space-y-3 mb-3 md:mb-4">
|
| 314 |
+
<input type="url" id="repoUrl" placeholder="GitHub Repository URL (e.g., https://github.com/owner/repo)" class="futuristic-input">
|
| 315 |
+
|
| 316 |
+
<div class="preferences-grid">
|
| 317 |
+
<input type="password" id="githubToken" placeholder="GitHub Token (Optional)" title="Enter a GitHub PAT for potentially accessing private repos (stored locally, use with caution)." class="futuristic-input">
|
| 318 |
+
<select id="userType" class="futuristic-select" title="Select your role for tailored responses.">
|
| 319 |
+
<option value="coder">Role: Coder</option>
|
| 320 |
+
<option value="manager">Role: Manager</option>
|
| 321 |
+
<option value="researcher">Role: Researcher</option>
|
| 322 |
+
<option value="student">Role: Student</option>
|
| 323 |
+
</select>
|
| 324 |
+
<select id="responseDetail" class="futuristic-select" title="Select the desired level of detail for G.E.N.I.E.'s responses.">
|
| 325 |
+
<option value="concise">Detail: Concise</option>
|
| 326 |
+
<option value="normal" selected>Detail: Normal</option>
|
| 327 |
+
<option value="detailed">Detail: Detailed</option>
|
| 328 |
+
</select>
|
| 329 |
+
</div>
|
| 330 |
+
|
| 331 |
+
<div class="chat-input-area">
|
| 332 |
+
<button id="micButton" title="Toggle Listening Mode / Interrupt" class="futuristic-button mic-button">🎙️</button>
|
| 333 |
+
<input type="text" id="userQuery" placeholder="Make a wish about the repository..." class="futuristic-input flex-grow">
|
| 334 |
+
<button id="sendButton" class="futuristic-button">Ask G.E.N.I.E.</button>
|
| 335 |
+
</div>
|
| 336 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 337 |
|
| 338 |
+
<div id="chatHistory" class="flex-grow glass-panel p-3 md:p-4 space-y-3 overflow-y-auto custom-scrollbar">
|
| 339 |
+
<div class="chat-bubble bot-bubble">
|
| 340 |
+
Greetings! I am G.E.N.I.E. Provide a repo URL, set preferences (optional), and make your wish (ask a question). Click 🎙️ or start typing to interrupt me.
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</div>
|
| 344 |
|
| 345 |
+
<div class="status-section">
|
| 346 |
+
<div class="status-content"> {/* Wrapper for centering content */}
|
| 347 |
+
<h2 id="agentStatusTitle" class="text-lg md:text-xl font-semibold text-purple-400 text-center">G.E.N.I.E. Status</h2>
|
| 348 |
+
<div id="genieOrb" class="genie-orb"></div>
|
| 349 |
+
<button id="interruptButton" class="interrupt-button hidden">Interrupt G.E.N.I.E.</button>
|
| 350 |
+
<div class="w-full glass-panel p-3 md:p-4 h-32 md:h-40 overflow-y-auto custom-scrollbar">
|
| 351 |
+
<h3 class="text-base md:text-lg font-medium text-purple-300 mb-2 border-b border-purple-700 pb-1">G.E.N.I.E. Output:</h3>
|
| 352 |
+
<p id="genieOutput" class="text-xs md:text-sm whitespace-pre-wrap">Awaiting your command...</p>
|
| 353 |
+
</div>
|
| 354 |
+
</div>
|
| 355 |
</div>
|
| 356 |
</div>
|
| 357 |
|
| 358 |
<script>
|
| 359 |
// --- DOM Elements ---
|
| 360 |
const repoUrlInput = document.getElementById('repoUrl');
|
| 361 |
+
const githubTokenInput = document.getElementById('githubToken'); // New
|
| 362 |
+
const userTypeSelect = document.getElementById('userType'); // New
|
| 363 |
+
const responseDetailSelect = document.getElementById('responseDetail'); // New
|
| 364 |
const userQueryInput = document.getElementById('userQuery');
|
| 365 |
const sendButton = document.getElementById('sendButton');
|
| 366 |
const micButton = document.getElementById('micButton');
|
| 367 |
const interruptButton = document.getElementById('interruptButton');
|
| 368 |
const chatHistory = document.getElementById('chatHistory');
|
|
|
|
| 369 |
const genieOrb = document.getElementById('genieOrb');
|
|
|
|
| 370 |
const genieOutput = document.getElementById('genieOutput');
|
| 371 |
const agentStatusTitle = document.getElementById('agentStatusTitle');
|
| 372 |
|
|
|
|
| 379 |
// --- Event Listeners ---
|
| 380 |
sendButton.addEventListener('click', handleSendQuery);
|
| 381 |
userQueryInput.addEventListener('keypress', (event) => {
|
| 382 |
+
if (event.key === 'Enter' && !event.shiftKey) { // Send on Enter, allow Shift+Enter for newline
|
| 383 |
+
event.preventDefault(); // Prevent default newline behavior
|
| 384 |
handleSendQuery();
|
| 385 |
}
|
| 386 |
});
|
|
|
|
| 391 |
// --- Functions ---
|
| 392 |
|
| 393 |
function handleMicButtonClick() {
|
| 394 |
+
// If the bot is actively processing, the mic button acts as an interrupt
|
| 395 |
if (isBotActive) {
|
| 396 |
handleInterrupt();
|
| 397 |
} else {
|
| 398 |
+
// Otherwise, toggle listening mode
|
| 399 |
toggleListeningMode();
|
| 400 |
}
|
| 401 |
}
|
| 402 |
|
| 403 |
function toggleListeningMode() {
|
| 404 |
+
if (isBotActive) return; // Don't toggle if bot is busy
|
| 405 |
|
| 406 |
isListening = !isListening;
|
| 407 |
+
micButton.classList.toggle('listening', isListening);
|
| 408 |
+
genieOrb.classList.toggle('listening', isListening);
|
| 409 |
+
|
| 410 |
if (isListening) {
|
| 411 |
+
setBotActiveState(false); // Ensure bot is not marked active
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
agentStatusTitle.textContent = "G.E.N.I.E. Status: Listening...";
|
|
|
|
| 413 |
updateGenieText("Listening... (Simulation - Type your wish and press Ask)");
|
| 414 |
userQueryInput.focus();
|
| 415 |
+
micButton.title = "Stop Listening / Interrupt"; // Update tooltip
|
| 416 |
} else {
|
| 417 |
+
micButton.title = "Toggle Listening Mode / Interrupt"; // Reset tooltip
|
| 418 |
+
// Only reset status if the bot isn't currently active (e.g., thinking)
|
|
|
|
| 419 |
if (!isBotActive) {
|
|
|
|
| 420 |
agentStatusTitle.textContent = "G.E.N.I.E. Status";
|
| 421 |
+
// Only reset output if it was showing the listening message
|
| 422 |
if (genieOutput.textContent.startsWith("Listening...")) {
|
| 423 |
+
updateGenieText("Awaiting your command...");
|
| 424 |
}
|
| 425 |
}
|
| 426 |
}
|
| 427 |
}
|
| 428 |
|
| 429 |
function handleTypingInterrupt() {
|
| 430 |
+
// Interrupt if the bot is thinking and the user starts typing
|
| 431 |
if (isBotActive && botResponseTimeoutId) {
|
| 432 |
handleInterrupt();
|
| 433 |
}
|
| 434 |
}
|
| 435 |
|
| 436 |
function handleSendQuery() {
|
| 437 |
+
// Stop listening mode if active when sending query
|
| 438 |
if (isListening) {
|
| 439 |
toggleListeningMode();
|
| 440 |
}
|
| 441 |
+
|
| 442 |
+
// Prevent sending if bot is already active
|
| 443 |
if (isBotActive) {
|
| 444 |
console.log("G.E.N.I.E. is active, cannot send new query yet.");
|
| 445 |
+
// Maybe add a subtle visual cue here later
|
| 446 |
return;
|
| 447 |
}
|
| 448 |
|
| 449 |
const repoUrl = repoUrlInput.value.trim();
|
| 450 |
const query = userQueryInput.value.trim();
|
| 451 |
+
const githubToken = githubTokenInput.value.trim(); // Get token (optional)
|
| 452 |
+
const userType = userTypeSelect.value; // Get user type
|
| 453 |
+
const responseDetail = responseDetailSelect.value; // Get detail level
|
| 454 |
|
| 455 |
+
// Basic Validations
|
| 456 |
if (!query) {
|
| 457 |
+
showTemporaryAlert(userQueryInput, 'Please state your wish (enter a query).');
|
| 458 |
+
return;
|
| 459 |
}
|
| 460 |
if (!isValidHttpUrl(repoUrl)) {
|
| 461 |
+
showTemporaryAlert(repoUrlInput, 'Please provide a valid GitHub repository URL (starting with http:// or https://).');
|
| 462 |
+
return;
|
| 463 |
}
|
| 464 |
|
| 465 |
+
// Add user message to chat
|
| 466 |
addMessageToChat(query, 'user');
|
| 467 |
+
userQueryInput.value = ''; // Clear input field
|
| 468 |
+
|
| 469 |
+
// Simulate bot processing and response
|
| 470 |
+
simulateBotResponse(repoUrl, query, userType, responseDetail, githubToken);
|
| 471 |
}
|
| 472 |
|
| 473 |
function handleInterrupt() {
|
| 474 |
if (botResponseTimeoutId) {
|
| 475 |
+
console.log("Interrupt triggered by user.");
|
| 476 |
clearTimeout(botResponseTimeoutId);
|
| 477 |
botResponseTimeoutId = null;
|
| 478 |
|
| 479 |
+
// Remove the "thinking" message if it exists
|
| 480 |
if (thinkingMessageElement) {
|
| 481 |
thinkingMessageElement.remove();
|
| 482 |
thinkingMessageElement = null;
|
| 483 |
}
|
| 484 |
+
|
| 485 |
+
// Reset bot state
|
| 486 |
setBotActiveState(false);
|
| 487 |
|
| 488 |
+
// Add an interrupted message to chat
|
| 489 |
addMessageToChat("Processing interrupted by user.", 'interrupted');
|
|
|
|
| 490 |
updateGenieText("Interrupted. Ready for your next wish.");
|
| 491 |
+
|
| 492 |
+
// Refocus input for convenience
|
| 493 |
userQueryInput.focus();
|
| 494 |
} else {
|
| 495 |
+
console.log("Interrupt called but G.E.N.I.E. was not actively processing.");
|
| 496 |
}
|
| 497 |
}
|
| 498 |
|
|
|
|
| 500 |
const messageElement = document.createElement('div');
|
| 501 |
messageElement.classList.add('chat-bubble');
|
| 502 |
|
| 503 |
+
// Stop listening visual cues if a message is added
|
| 504 |
if (isListening) {
|
|
|
|
| 505 |
genieOrb.classList.remove('listening');
|
| 506 |
micButton.classList.remove('listening');
|
| 507 |
+
isListening = false; // Ensure state is updated
|
| 508 |
+
micButton.title = "Toggle Listening Mode / Interrupt"; // Reset tooltip
|
| 509 |
}
|
| 510 |
|
| 511 |
+
switch (type) {
|
| 512 |
+
case 'user':
|
| 513 |
+
messageElement.classList.add('user-bubble');
|
| 514 |
+
messageElement.textContent = message;
|
| 515 |
+
break;
|
| 516 |
+
case 'bot':
|
| 517 |
+
messageElement.classList.add('bot-bubble');
|
| 518 |
+
messageElement.textContent = message;
|
| 519 |
+
updateGenieText(message); // Update status output
|
| 520 |
+
// Short delay before resetting active state to allow UI update
|
| 521 |
+
if (botResponseTimeoutId) clearTimeout(botResponseTimeoutId); // Clear any pending timeout
|
| 522 |
+
botResponseTimeoutId = null; // Ensure timeout ID is cleared
|
| 523 |
+
setTimeout(() => {
|
| 524 |
+
if(isBotActive) setBotActiveState(false); // Reset state after response is shown
|
| 525 |
+
}, 100); // Small delay
|
| 526 |
+
break;
|
| 527 |
+
case 'thinking':
|
| 528 |
+
messageElement.classList.add('bot-bubble', 'thinking');
|
| 529 |
+
messageElement.innerHTML = `Consulting the digital ether... <div class="dot-flashing"></div>`;
|
| 530 |
+
updateGenieText("Processing your wish..."); // Update status output
|
| 531 |
+
setBotActiveState(true); // Set bot to active state
|
| 532 |
+
thinkingMessageElement = messageElement; // Store reference to remove later
|
| 533 |
+
break;
|
| 534 |
+
case 'interrupted':
|
| 535 |
+
messageElement.classList.add('bot-bubble', 'interrupted');
|
| 536 |
+
messageElement.textContent = message;
|
| 537 |
+
// Status text is updated in handleInterrupt
|
| 538 |
+
break;
|
| 539 |
+
default:
|
| 540 |
+
console.error("Unknown message type:", type);
|
| 541 |
+
return null; // Don't add unknown types
|
| 542 |
}
|
| 543 |
|
| 544 |
chatHistory.appendChild(messageElement);
|
| 545 |
+
// Scroll to the bottom smoothly after adding message
|
| 546 |
+
// Use setTimeout to ensure element is rendered before scrolling
|
| 547 |
+
setTimeout(() => {
|
| 548 |
+
chatHistory.scrollTo({ top: chatHistory.scrollHeight, behavior: 'smooth' });
|
| 549 |
+
}, 50);
|
| 550 |
+
|
| 551 |
+
return messageElement; // Return the created element
|
| 552 |
}
|
| 553 |
|
| 554 |
+
function simulateBotResponse(repoUrl, query, userType, responseDetail, githubToken) {
|
| 555 |
+
// Start the thinking state
|
| 556 |
thinkingMessageElement = addMessageToChat('', 'thinking');
|
| 557 |
+
|
| 558 |
+
// Simulate network delay/processing time
|
| 559 |
+
const delay = 2000 + Math.random() * 3500; // 2 to 5.5 seconds
|
| 560 |
+
|
| 561 |
+
// Clear any previous timeout just in case
|
| 562 |
if (botResponseTimeoutId) clearTimeout(botResponseTimeoutId);
|
| 563 |
|
| 564 |
botResponseTimeoutId = setTimeout(() => {
|
| 565 |
+
// Check if it was interrupted *before* this timeout fired
|
| 566 |
const wasInterrupted = !thinkingMessageElement;
|
| 567 |
if (wasInterrupted) {
|
| 568 |
console.log("G.E.N.I.E. response timeout fired, but was already interrupted.");
|
| 569 |
+
botResponseTimeoutId = null; // Clear ID as it's no longer needed
|
| 570 |
+
// No need to reset state here, handleInterrupt already did
|
| 571 |
return;
|
| 572 |
}
|
| 573 |
|
| 574 |
+
// If not interrupted, remove the thinking message
|
| 575 |
+
if (thinkingMessageElement) {
|
| 576 |
+
thinkingMessageElement.remove();
|
| 577 |
+
thinkingMessageElement = null;
|
| 578 |
+
}
|
| 579 |
+
botResponseTimeoutId = null; // Clear the timeout ID
|
| 580 |
|
| 581 |
+
// Generate a sample response (incorporating preferences cosmetically)
|
| 582 |
+
const botResponse = generateSampleResponse(repoUrl, query, userType, responseDetail, githubToken);
|
| 583 |
+
|
| 584 |
+
// Add the actual bot response to the chat
|
| 585 |
addMessageToChat(botResponse, 'bot');
|
| 586 |
+
// The 'bot' case in addMessageToChat now handles resetting the active state
|
| 587 |
|
| 588 |
}, delay);
|
| 589 |
}
|
| 590 |
|
| 591 |
+
// Updated to include preferences (cosmetic for now)
|
| 592 |
+
function generateSampleResponse(repoUrl, query, userType, responseDetail, githubToken) {
|
|
|
|
|
|
|
| 593 |
const repoName = repoUrl.substring(repoUrl.lastIndexOf('/') + 1) || "[Unknown Repo]";
|
| 594 |
query = query.toLowerCase();
|
| 595 |
+
let responsePrefix = "";
|
| 596 |
+
let detailModifier = 1; // 1 = normal, <1 = concise, >1 = detailed
|
| 597 |
+
|
| 598 |
+
// Adjust detail level (simple multiplier for example length)
|
| 599 |
+
if (responseDetail === 'concise') detailModifier = 0.6;
|
| 600 |
+
if (responseDetail === 'detailed') detailModifier = 1.5;
|
| 601 |
+
|
| 602 |
+
// Add flavor based on user type
|
| 603 |
+
switch (userType) {
|
| 604 |
+
case 'manager': responsePrefix = "From a high-level view, "; break;
|
| 605 |
+
case 'researcher': responsePrefix = "Digging into the details, "; break;
|
| 606 |
+
case 'student': responsePrefix = "To help with your learning, "; break;
|
| 607 |
+
// Coder gets no specific prefix (default)
|
| 608 |
+
}
|
| 609 |
+
// Acknowledge token if provided (SIMULATION ONLY)
|
| 610 |
+
if (githubToken) {
|
| 611 |
+
console.log("Simulating use of GitHub Token (not actually used):", githubToken.substring(0, 4) + "...");
|
| 612 |
+
// You could add a note in the response, but be careful not to leak info
|
| 613 |
+
// responsePrefix += "(Using provided token access) ";
|
| 614 |
+
}
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
// Basic query matching with some flavor
|
| 618 |
+
if (query.includes("what is this repo") || query.includes("summarize")) {
|
| 619 |
+
let base = `As you wish! The repository '${repoName}' appears to be `;
|
| 620 |
+
let example = `[a ${Math.random() > 0.5 ? 'JavaScript library for UI components' : 'Python backend for data processing'}, focusing on ${Math.random() > 0.5 ? 'performance and modularity' : 'ease of use and integration'}].`;
|
| 621 |
+
if (detailModifier > 1) example += ` It likely utilizes technologies like [React/Vue or Django/Flask] and follows standard project layout conventions. Recent activity suggests ongoing development.`;
|
| 622 |
+
if (detailModifier < 1) example = `[a ${Math.random() > 0.5 ? 'JS UI library' : 'Python data backend'}].`;
|
| 623 |
+
return responsePrefix + base + example;
|
| 624 |
+
}
|
| 625 |
+
if (query.includes("main language") || query.includes("written in")) {
|
| 626 |
+
const languages = ["JavaScript", "Python", "Java", "TypeScript", "Go", "CSS", "Ruby", "PHP"];
|
| 627 |
+
const randomLang = languages[Math.floor(Math.random() * languages.length)];
|
| 628 |
+
let base = `Gazing into the code of '${repoName}', I reveal the primary language appears to be ${randomLang}. `;
|
| 629 |
+
let example = `Hints of [${languages[Math.floor(Math.random() * languages.length)]}] may also be present.`;
|
| 630 |
+
if (detailModifier > 1) example += ` Analysis suggests around ${Math.floor(Math.random()*30+60)}% of the codebase is ${randomLang}.`;
|
| 631 |
+
if (detailModifier < 1) example = ""; // Concise just gives the main language
|
| 632 |
+
return responsePrefix + base + example;
|
| 633 |
+
}
|
| 634 |
+
if (query.includes("how many files") || query.includes("file count")) {
|
| 635 |
+
const fileCount = Math.floor(Math.random() * (800 * detailModifier) + 50);
|
| 636 |
+
return responsePrefix + `By my count, the main branch of '${repoName}' contains approximately ${fileCount} files and directories.`;
|
| 637 |
+
}
|
| 638 |
+
if (query.includes("docker") || query.includes("container")) {
|
| 639 |
+
let base = Math.random() > 0.4 ? `Indeed! A Dockerfile has manifested in '${repoName}'` : `Alas, a standard Dockerfile is hidden from my sight in the root of '${repoName}'`;
|
| 640 |
+
let example = Math.random() > 0.4 ? `, indicating readiness for containerization using standard Docker tooling.` : `, but containerization magic might be conjured via other means like docker-compose or custom scripts.`;
|
| 641 |
+
if (detailModifier < 1) example = Math.random() > 0.4 ? ", suggesting container support." : ", no obvious Dockerfile found.";
|
| 642 |
+
return responsePrefix + base + example;
|
| 643 |
+
}
|
| 644 |
+
if (query.includes("test") || query.includes("coverage")) {
|
| 645 |
+
let base = `The repository '${repoName}' reveals signs of testing charms. `;
|
| 646 |
+
let example = `I see evidence of a '/tests' or '/spec' directory, possibly utilizing frameworks like [Jest/Pytest/RSpec].`;
|
| 647 |
+
if (detailModifier > 1) example += ` Configuration files suggest integration with CI/CD pipelines for automated testing. Code coverage analysis might be configured.`;
|
| 648 |
+
if (detailModifier < 1) example = `Testing files seem present.`;
|
| 649 |
+
return responsePrefix + base + example;
|
| 650 |
+
}
|
| 651 |
+
if (query.includes("hello") || query.includes("hi") || query.includes("greetings")) {
|
| 652 |
+
return `Greetings, Master ${userType}! How may G.E.N.I.E. assist you with the '${repoName}' repository today? State your wish!`;
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
// Default fallback response
|
| 656 |
+
let fallback = `I have considered your wish regarding '${repoName}' concerning "${query.substring(0, 30)}...". `;
|
| 657 |
+
let example = `My vision suggests that [${Math.random() > 0.5 ? 'the file structure seems reasonably organized' : 'recent commit activity implies active maintenance'}].`;
|
| 658 |
+
if (detailModifier > 1) example += ` It likely utilizes common libraries for its domain, such as [mention a relevant library type like 'data visualization' or 'web framework']. Dependencies seem managed via [npm/pip/maven].`;
|
| 659 |
+
if (detailModifier < 1) example = `It appears to be a standard project.`;
|
| 660 |
+
fallback += example + ` For deeper secrets, phrase your wish more specifically.`;
|
| 661 |
+
return responsePrefix + fallback;
|
| 662 |
}
|
| 663 |
|
| 664 |
+
/** Updates the G.E.N.I.E. output text in the status panel */
|
| 665 |
+
function updateGenieText(text) {
|
| 666 |
+
genieOutput.textContent = text;
|
| 667 |
+
}
|
| 668 |
|
| 669 |
+
/** Sets the visual state indicating if the bot is actively processing */
|
| 670 |
function setBotActiveState(isActive) {
|
| 671 |
isBotActive = isActive;
|
| 672 |
+
genieOrb.classList.toggle('active', isActive);
|
| 673 |
+
interruptButton.classList.toggle('hidden', !isActive);
|
| 674 |
+
sendButton.disabled = isActive; // Disable send button while active
|
| 675 |
+
userQueryInput.disabled = isActive; // Optionally disable query input too
|
| 676 |
+
repoUrlInput.disabled = isActive; // Disable repo URL input
|
| 677 |
+
githubTokenInput.disabled = isActive; // Disable token input
|
| 678 |
+
userTypeSelect.disabled = isActive; // Disable user type select
|
| 679 |
+
responseDetailSelect.disabled = isActive; // Disable detail select
|
| 680 |
|
| 681 |
if (isActive) {
|
| 682 |
+
// If activating, ensure listening mode is off
|
| 683 |
+
if (isListening) {
|
| 684 |
+
toggleListeningMode(); // Turn off listening visuals/state
|
| 685 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
agentStatusTitle.textContent = "G.E.N.I.E. Status: Fulfilling Wish...";
|
| 687 |
+
micButton.title = "Interrupt G.E.N.I.E."; // Mic becomes interrupt
|
| 688 |
} else {
|
| 689 |
+
// Reset status title only if not currently listening
|
|
|
|
|
|
|
| 690 |
if (!isListening) {
|
|
|
|
| 691 |
agentStatusTitle.textContent = "G.E.N.I.E. Status";
|
| 692 |
+
updateGenieText("Awaiting your command..."); // Reset output text
|
| 693 |
}
|
| 694 |
+
micButton.title = "Toggle Listening Mode / Interrupt"; // Reset mic title
|
| 695 |
+
|
| 696 |
+
// Clear any lingering timeout or thinking message reference
|
| 697 |
+
if (botResponseTimeoutId) {
|
| 698 |
clearTimeout(botResponseTimeoutId);
|
| 699 |
botResponseTimeoutId = null;
|
| 700 |
+
}
|
| 701 |
+
thinkingMessageElement = null;
|
| 702 |
}
|
| 703 |
}
|
| 704 |
|
| 705 |
+
/** Checks if a string is a valid HTTP/HTTPS URL */
|
| 706 |
function isValidHttpUrl(string) {
|
| 707 |
+
try {
|
| 708 |
+
const url = new URL(string);
|
| 709 |
+
return url.protocol === "http:" || url.protocol === "https:";
|
| 710 |
+
} catch (_) {
|
| 711 |
+
return false; // Invalid URL format
|
| 712 |
+
}
|
| 713 |
}
|
| 714 |
|
| 715 |
+
/** Shows a temporary red border and message on an input element */
|
| 716 |
+
function showTemporaryAlert(element, message) {
|
| 717 |
+
element.style.borderColor = '#ff6b6b'; // Red border
|
| 718 |
+
element.style.boxShadow = '0 0 10px rgba(255, 107, 107, 0.4)';
|
| 719 |
+
// Optionally, display the message near the element or use a dedicated alert area
|
| 720 |
+
console.warn("Validation Error:", message); // Log for debugging
|
| 721 |
+
// You could add a small temporary message element below the input
|
| 722 |
+
setTimeout(() => {
|
| 723 |
+
element.style.borderColor = ''; // Reset border color
|
| 724 |
+
element.style.boxShadow = ''; // Reset box shadow
|
| 725 |
+
}, 2500); // Reset after 2.5 seconds
|
| 726 |
+
element.focus();
|
| 727 |
+
}
|
| 728 |
|
|
|
|
| 729 |
|
| 730 |
+
// --- Initial Setup ---
|
| 731 |
+
updateGenieText("Standing by. Please provide a GitHub repo URL, set preferences, and state your wish, or click 🎙️.");
|
| 732 |
+
interruptButton.classList.add('hidden'); // Start hidden
|
| 733 |
+
sendButton.disabled = false; // Start enabled
|
| 734 |
+
// Ensure initial state is not active
|
| 735 |
+
setBotActiveState(false);
|
| 736 |
+
|
| 737 |
+
</script>
|
| 738 |
</body>
|
| 739 |
+
</html>
|