nihalaninihal commited on
Commit
b049a63
·
verified ·
1 Parent(s): a67e387

Update index.html

Browse files
Files changed (1) hide show
  1. 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 rel="preconnect" href="https://fonts.googleapis.com">
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
- /* Custom styles for the futuristic/magical look and animations */
14
  body {
15
  font-family: 'Inter', sans-serif;
16
- background-color: #0f0a1f; /* Slightly more purple dark background */
17
  color: #e0e0e0;
18
- overflow: hidden;
19
  }
20
 
21
- /* Base Styling for the G.E.N.I.E. orb */
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: #1a203a; border-radius: 10px; }
122
- /* Updated scrollbar thumb to match purple theme */
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
- /* Updated border color slightly */
129
- background: rgba(26, 32, 58, 0.6); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
130
- border: 1px solid rgba(138, 43, 226, 0.3); border-radius: 1rem; box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
 
 
 
131
  }
132
 
133
  /* Input field styling */
134
- .futuristic-input {
135
- /* Updated border color */
136
- background-color: rgba(15, 10, 31, 0.7); border: 1px solid #8a2be280; color: #e0e0e0;
137
- border-radius: 0.5rem; padding: 0.75rem 1rem; transition: border-color 0.3s, box-shadow 0.3s;
 
 
 
 
 
 
 
 
 
 
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 - Kept blue gradient for now, contrasts well */
143
  .futuristic-button {
144
- background: linear-gradient(90deg, #0077ff, #00aaff); color: white; font-weight: 600;
145
- padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; cursor: pointer;
146
- transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(0, 127, 255, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 - Kept green theme for listening indication */
154
  .mic-button {
155
- background: #1f3a6e; color: #90ee90; padding: 0.75rem; width: 50px; height: 50px;
156
- display: flex; align-items: center; justify-content: center; font-size: 1.25rem;
 
 
 
 
 
 
 
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 (Kept red for warning) */
163
  .interrupt-button {
164
- background: linear-gradient(90deg, #ff6b6b, #ff8e8e);
165
- color: white; font-weight: 600; padding: 0.5rem 1rem;
166
- border-radius: 0.5rem; border: none; cursor: pointer;
167
- transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4);
 
 
 
 
 
168
  opacity: 1;
169
  }
170
- .interrupt-button:hover { box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6); transform: translateY(-2px); background: linear-gradient(90deg, #ff8e8e, #ffa7a7); }
171
- .interrupt-button:active { transform: translateY(0); box-shadow: 0 2px 10px rgba(255, 107, 107, 0.3); }
172
- .interrupt-button.hidden { opacity: 0; pointer-events: none; transform: scale(0.8); }
173
-
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  /* Chat bubble styling */
176
- .chat-bubble { padding: 0.75rem 1rem; border-radius: 0.75rem; max-width: 75%; word-wrap: break-word; }
177
- /* User bubble - keep blue or maybe a dark purple? Keep blue for contrast. */
178
- .user-bubble { background-color: #0055aa; margin-left: auto; border-bottom-right-radius: 0.25rem; }
179
- /* Bot bubble - Use a darker purple */
180
- .bot-bubble { background-color: #4b0082; margin-right: auto; border-bottom-left-radius: 0.25rem; }
181
- .bot-bubble.thinking { font-style: italic; color: #a0aec0; display: flex; align-items: center; background-color: #2d1f4a; /* Slightly lighter purple for thinking */}
182
- .bot-bubble.interrupted { font-style: italic; color: #f6ad55; background-color: #5c3d1c; } /* Orange tone */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
  /* Dot animation for thinking */
185
- .dot-flashing { position: relative; width: 5px; height: 5px; border-radius: 5px; background-color: #a0aec0; color: #a0aec0; animation: dotFlashing 1s infinite linear alternate; animation-delay: .5s; margin-left: 5px; }
186
- .dot-flashing::before, .dot-flashing::after { content: ''; display: inline-block; position: absolute; top: 0; width: 5px; height: 5px; border-radius: 5px; background-color: #a0aec0; color: #a0aec0; }
 
 
 
 
 
 
 
 
 
 
 
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="flex h-screen">
194
-
195
- <div class="w-2/3 flex flex-col p-6 space-y-4">
196
- <!-- Updated Heading -->
197
- <h1 class="text-3xl font-bold text-center text-purple-300 mb-2">G.E.N.I.E.</h1>
198
- <!-- Added Subtitle/Description -->
199
- <p class="text-center text-purple-100 text-sm mb-4">GitHub Enhanced Natural Intelligence Engine <br> A voice-controlled AI that “grants your GitHub wishes.”</p>
200
-
201
- <div class="glass-panel p-4 space-y-3">
202
- <input type="url" id="repoUrl" placeholder="Enter GitHub Repository URL (e.g., https://github.com/owner/repo)" class="w-full futuristic-input">
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
- <div id="chatHistory" class="flex-grow glass-panel p-4 space-y-3 overflow-y-auto custom-scrollbar">
211
- <!-- Updated initial message -->
212
- <div class="chat-bubble bot-bubble">
213
- Greetings! I am G.E.N.I.E. Provide a repo URL and make your wish (ask a question). Click 🎙️ or start typing to interrupt me.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- <button id="interruptButton" class="interrupt-button hidden mb-4">Interrupt G.E.N.I.E.</button>
 
 
 
 
 
225
 
226
- <div class="w-full glass-panel p-4 h-40 overflow-y-auto custom-scrollbar">
227
- <!-- Updated Output Title Color -->
228
- <h3 class="text-lg font-medium text-purple-300 mb-2 border-b border-purple-700 pb-1">G.E.N.I.E. Output:</h3>
229
- <!-- Updated Text Output ID -->
230
- <p id="genieOutput" class="text-sm whitespace-pre-wrap">Awaiting your command...</p>
 
 
 
 
 
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
- // Updated orb variable
291
- genieOrb.classList.remove('listening');
292
- micButton.classList.remove('listening');
293
  if (!isBotActive) {
294
- // Updated status text
295
  agentStatusTitle.textContent = "G.E.N.I.E. Status";
296
- // Updated function call and text check
297
  if (genieOutput.textContent.startsWith("Listening...")) {
298
- updateGenieText("Awaiting your command...");
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
- alert('Please state your wish (enter a query).'); return;
 
324
  }
325
  if (!isValidHttpUrl(repoUrl)) {
326
- alert('Please provide a valid GitHub repository URL (starting with http:// or https://).'); return;
 
327
  }
328
 
 
329
  addMessageToChat(query, 'user');
330
- userQueryInput.value = '';
331
- simulateBotResponse(repoUrl, query);
 
 
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 active.");
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
- if (type === 'user') {
366
- messageElement.classList.add('user-bubble');
367
- messageElement.textContent = message;
368
- } else if (type === 'bot') {
369
- messageElement.classList.add('bot-bubble');
370
- messageElement.textContent = message;
371
- // Updated function call
372
- updateGenieText(message);
373
- if (botResponseTimeoutId) {
374
- clearTimeout(botResponseTimeoutId);
375
- botResponseTimeoutId = null;
376
- }
377
- setTimeout(() => {
378
- if(isBotActive) setBotActiveState(false);
379
- }, 300);
380
- } else if (type === 'thinking') {
381
- messageElement.classList.add('bot-bubble', 'thinking');
382
- messageElement.innerHTML = `Consulting the digital ether... <div class="dot-flashing"></div>`; // Updated thinking text
383
- // Updated function call and text
384
- updateGenieText("Processing your wish...");
385
- setBotActiveState(true);
386
- thinkingMessageElement = messageElement;
387
- } else if (type === 'interrupted') {
388
- messageElement.classList.add('bot-bubble', 'interrupted');
389
- messageElement.textContent = message;
390
- // Genie text updated in handleInterrupt
391
- } else {
392
- return null;
 
 
 
393
  }
394
 
395
  chatHistory.appendChild(messageElement);
396
- setTimeout(() => { chatHistory.scrollTop = chatHistory.scrollHeight; }, 0);
397
- return messageElement;
 
 
 
 
 
398
  }
399
 
400
- function simulateBotResponse(repoUrl, query) {
 
401
  thinkingMessageElement = addMessageToChat('', 'thinking');
402
- const delay = 2000 + Math.random() * 3500;
 
 
 
 
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
- if (thinkingMessageElement) thinkingMessageElement.remove();
414
- thinkingMessageElement = null;
415
- botResponseTimeoutId = null;
 
 
 
416
 
417
- const botResponse = generateSampleResponse(repoUrl, query);
 
 
 
418
  addMessageToChat(botResponse, 'bot');
 
419
 
420
  }, delay);
421
  }
422
 
423
- // generateSampleResponse logic remains the same internally,
424
- // only the output text might be slightly rephrased if desired,
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
- // Added a touch of "genie" flavor to responses
430
- if (query.includes("what is this repo") || query.includes("summarize")) { return `As you wish! The repository '${repoName}' appears to be [briefly describe purpose based on a hypothetical analysis, e.g., 'a JavaScript library for creating charts', 'a Python backend for a web application', 'a collection of configuration files']. My insights suggest it focuses on [mention a key technology or area].`; }
431
- if (query.includes("main language") || query.includes("written in")) { const languages = ["JavaScript", "Python", "Java", "TypeScript", "Go", "CSS"]; const randomLang = languages[Math.floor(Math.random() * languages.length)]; return `Gazing into the code of '${repoName}', I reveal the primary language appears to be ${randomLang}. Hints of [mention another language if applicable] may also be present.`; }
432
- if (query.includes("how many files") || query.includes("file count")) { const fileCount = Math.floor(Math.random() * 500) + 50; return `By my count, the main branch of '${repoName}' contains approximately ${fileCount} files.`; }
433
- if (query.includes("docker") || query.includes("container")) { return Math.random() > 0.5 ? `Indeed! A Dockerfile has manifested in '${repoName}', indicating readiness for containerization.` : `Alas, a standard Dockerfile is hidden from my sight in the root of '${repoName}', but its containerization magic might be conjured elsewhere.`; }
434
- if (query.includes("test") || query.includes("coverage")) { return `The repository '${repoName}' reveals a '/tests' directory and scrolls for [mention testing framework like Jest, Pytest, etc.], showing a commitment to testing charms.`; }
435
- if (query.includes("hello") || query.includes("hi") || query.includes("greetings")) { return `Greetings, Master! How may G.E.N.I.E. assist you with the '${repoName}' repository today? State your wish!`; }
436
- return `I have considered your wish regarding '${repoName}' concerning "${query.substring(0, 30)}...". My vision suggests that [provide a generic but relevant-sounding statement, e.g., 'the file structure seems well-organized', 'recent commit activity implies active development', 'it utilizes common libraries for its domain']. For deeper secrets, please phrase your wish more specifically.`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
 
439
- /** Updates the G.E.N.I.E. output text */
440
- // Renamed function
441
- function updateGenieText(text) { genieOutput.textContent = text; }
 
442
 
 
443
  function setBotActiveState(isActive) {
444
  isBotActive = isActive;
 
 
 
 
 
 
 
 
445
 
446
  if (isActive) {
447
- // Updated orb variable
448
- genieOrb.classList.remove('listening');
449
- micButton.classList.remove('listening');
450
- isListening = false;
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
- sendButton.disabled = true;
458
  } else {
459
- // Updated orb variable
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
- sendButton.disabled = false;
467
- if (botResponseTimeoutId) {
 
 
468
  clearTimeout(botResponseTimeoutId);
469
  botResponseTimeoutId = null;
470
- }
471
- thinkingMessageElement = null;
472
  }
473
  }
474
 
 
475
  function isValidHttpUrl(string) {
476
- let url; try { url = new URL(string); } catch (_) { return false; }
477
- return url.protocol === "http:" || url.protocol === "https:";
 
 
 
 
478
  }
479
 
480
- // Initial setup
481
- // Updated function call and text
482
- updateGenieText("Standing by. Please provide a GitHub repo URL and state your wish, or click 🎙️.");
483
- interruptButton.classList.add('hidden');
484
- sendButton.disabled = false;
 
 
 
 
 
 
 
 
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>