Spaces:
Sleeping
Sleeping
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>G.E.N.I.E. - GitHub Enhanced Natural Intelligence Engine</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet"> | |
<style> | |
/* Base styles */ | |
body { | |
font-family: 'Inter', sans-serif; | |
background: linear-gradient(135deg, #0f0a1f 0%, #1a102f 50%, #2c1c4a 100%); | |
color: #e0e0e0; | |
overflow: hidden; /* Prevent body scroll */ | |
} | |
/* Custom Scrollbar */ | |
.custom-scrollbar::-webkit-scrollbar { width: 8px; } | |
.custom-scrollbar::-webkit-scrollbar-track { background: rgba(26, 32, 58, 0.3); border-radius: 10px; } | |
.custom-scrollbar::-webkit-scrollbar-thumb { background-color: #8a2be2; border-radius: 10px; border: 2px solid transparent; background-clip: content-box; } | |
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9932cc; } | |
/* Glassmorphism effect */ | |
.glass-panel { | |
background: rgba(26, 32, 58, 0.6); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
border: 1px solid rgba(138, 43, 226, 0.3); /* Purple border */ | |
border-radius: 1rem; | |
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2); | |
} | |
/* Input field styling */ | |
.futuristic-input, .futuristic-select { | |
background-color: rgba(15, 10, 31, 0.7); | |
border: 1px solid rgba(138, 43, 226, 0.5); /* Slightly stronger purple border */ | |
color: #e0e0e0; | |
border-radius: 0.5rem; | |
padding: 0.75rem 1rem; | |
transition: border-color 0.3s, box-shadow 0.3s; | |
width: 100%; /* Ensure inputs take full width */ | |
box-sizing: border-box; /* Include padding and border in element's total width and height */ | |
} | |
.futuristic-input:focus, .futuristic-select:focus { | |
outline: none; | |
border-color: #ffd700; /* Gold focus */ | |
box-shadow: 0 0 10px rgba(255, 215, 0, 0.25); | |
} | |
.futuristic-input::placeholder { color: #718096; } | |
.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 */ } | |
/* Button styling */ | |
.futuristic-button { | |
background: linear-gradient(90deg, #0077ff, #00aaff); | |
color: white; | |
font-weight: 600; | |
padding: 0.75rem 1.5rem; | |
border-radius: 0.5rem; | |
border: none; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
box-shadow: 0 4px 15px rgba(0, 127, 255, 0.3); | |
white-space: nowrap; /* Prevent text wrapping */ | |
} | |
.futuristic-button:hover { | |
box-shadow: 0 6px 20px rgba(0, 170, 255, 0.5); | |
transform: translateY(-2px); | |
background: linear-gradient(90deg, #00aaff, #00ccff); | |
} | |
.futuristic-button:active { | |
transform: translateY(0); | |
box-shadow: 0 2px 10px rgba(0, 127, 255, 0.2); | |
} | |
.futuristic-button:disabled { | |
background: #555; | |
cursor: not-allowed; | |
box-shadow: none; | |
transform: none; | |
opacity: 0.7; | |
} | |
/* Mic button specific style */ | |
.mic-button { | |
background: #1f3a6e; | |
color: #90ee90; /* Light green mic */ | |
padding: 0.75rem; | |
width: 50px; /* Fixed width */ | |
height: 50px; /* Fixed height */ | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 1.25rem; | |
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); | |
flex-shrink: 0; /* Prevent shrinking */ | |
} | |
.mic-button:hover { | |
background: #2c5282; | |
box-shadow: 0 4px 15px rgba(144, 238, 144, 0.4); | |
transform: translateY(-1px); | |
color: #adffad; | |
} | |
.mic-button.listening { | |
background: linear-gradient(90deg, #3cb371, #5fbf5f); /* Green gradient */ | |
color: white; | |
box-shadow: 0 4px 15px rgba(60, 179, 113, 0.5); | |
} | |
/* Interrupt button specific style */ | |
.interrupt-button { | |
background: linear-gradient(90deg, #ff6b6b, #ff8e8e); /* Red gradient */ | |
color: white; | |
font-weight: 600; | |
padding: 0.5rem 1rem; | |
border-radius: 0.5rem; | |
border: none; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4); | |
opacity: 1; | |
} | |
.interrupt-button:hover { | |
box-shadow: 0 6px 20px rgba(255, 107, 107, 0.6); | |
transform: translateY(-2px); | |
background: linear-gradient(90deg, #ff8e8e, #ffa7a7); | |
} | |
.interrupt-button:active { | |
transform: translateY(0); | |
box-shadow: 0 2px 10px rgba(255, 107, 107, 0.3); | |
} | |
.interrupt-button.hidden { | |
opacity: 0; | |
pointer-events: none; | |
transform: scale(0.8); | |
transition: opacity 0.3s ease, transform 0.3s ease; | |
} | |
/* Chat bubble styling */ | |
.chat-bubble { | |
padding: 0.75rem 1rem; | |
border-radius: 0.75rem; | |
max-width: 85%; /* Slightly wider max width */ | |
word-wrap: break-word; | |
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); | |
} | |
/* User bubble */ | |
.user-bubble { | |
background-color: #0055aa; /* Kept blue */ | |
margin-left: auto; | |
border-bottom-right-radius: 0.25rem; | |
color: #f0f0f0; | |
} | |
/* Bot bubble */ | |
.bot-bubble { | |
background-color: #4b0082; /* Dark purple */ | |
margin-right: auto; | |
border-bottom-left-radius: 0.25rem; | |
color: #f0f0f0; | |
} | |
.bot-bubble.thinking { | |
font-style: italic; | |
color: #a0aec0; | |
display: flex; | |
align-items: center; | |
background-color: #2d1f4a; /* Lighter purple for thinking */ | |
} | |
.bot-bubble.interrupted { | |
font-style: italic; | |
color: #f6ad55; /* Orange tone */ | |
background-color: #5c3d1c; /* Dark orange/brown */ | |
} | |
/* Dot animation for thinking */ | |
.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: 8px; /* Increased spacing */ | |
} | |
.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; | |
} | |
.dot-flashing::before { left: -10px; animation: dotFlashing 1s infinite alternate; animation-delay: 0s; } | |
.dot-flashing::after { left: 10px; animation: dotFlashing 1s infinite alternate; animation-delay: 1s; } | |
@keyframes dotFlashing { 0% { background-color: #a0aec0; } 50%, 100% { background-color: rgba(160, 174, 192, 0.2); } } | |
/* Genie Orb Styling */ | |
.genie-orb { | |
width: 100px; height: 100px; | |
border-radius: 50%; | |
background: radial-gradient(circle, rgba(138, 43, 226, 0.7) 0%, rgba(75, 0, 130, 0.9) 70%); | |
box-shadow: 0 0 25px rgba(138, 43, 226, 0.6), inset 0 0 15px rgba(255, 255, 255, 0.2); | |
position: relative; | |
transition: all 0.5s ease; | |
border: 2px solid rgba(255, 215, 0, 0.3); /* Subtle gold border */ | |
} | |
.genie-orb::before { /* Inner glow */ | |
content: ''; position: absolute; | |
top: 10%; left: 10%; width: 80%; height: 80%; | |
border-radius: 50%; | |
background: radial-gradient(circle, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0) 70%); | |
} | |
.genie-orb.listening { | |
box-shadow: 0 0 35px rgba(60, 179, 113, 0.8), inset 0 0 20px rgba(144, 238, 144, 0.4); | |
background: radial-gradient(circle, rgba(60, 179, 113, 0.8) 0%, rgba(46, 139, 87, 1) 70%); | |
border-color: rgba(144, 238, 144, 0.5); | |
} | |
.genie-orb.active { | |
box-shadow: 0 0 35px rgba(0, 170, 255, 0.8), inset 0 0 20px rgba(0, 204, 255, 0.4); | |
background: radial-gradient(circle, rgba(0, 170, 255, 0.8) 0%, rgba(0, 127, 255, 1) 70%); | |
border-color: rgba(0, 204, 255, 0.5); | |
animation: pulse 1.5s infinite ease-in-out; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.05); } | |
100% { transform: scale(1); } | |
} | |
/* Responsive Design */ | |
/* Stack columns on smaller screens */ | |
.main-container { | |
display: flex; | |
flex-direction: column; /* Default: Stack vertically */ | |
height: 100vh; /* Full viewport height */ | |
} | |
.chat-section { | |
flex-grow: 1; /* Takes available space */ | |
display: flex; | |
flex-direction: column; | |
padding: 1rem; /* Padding for mobile */ | |
overflow: hidden; /* Prevent overflow */ | |
} | |
.status-section { | |
padding: 1rem; /* Padding for mobile */ | |
height: auto; /* Adjust height automatically */ | |
flex-shrink: 0; /* Prevent shrinking */ | |
background: linear-gradient(to bottom, #1a102f, #0f0a1f); /* Gradient for status section */ | |
} | |
/* Apply row layout on medium screens and up */ | |
@media (min-width: 768px) { /* md breakpoint */ | |
.main-container { | |
flex-direction: row; /* Side-by-side layout */ | |
} | |
.chat-section { | |
width: 66.666667%; /* 2/3 width */ | |
padding: 1.5rem; /* Larger padding */ | |
height: 100vh; /* Full height */ | |
} | |
.status-section { | |
width: 33.333333%; /* 1/3 width */ | |
padding: 1.5rem; /* Larger padding */ | |
height: 100vh; /* Full height */ | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; /* Center vertically */ | |
} | |
.status-content { | |
width: 100%; | |
max-width: 400px; /* Max width for status content */ | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
gap: 1.5rem; /* Space between items */ | |
} | |
.chat-input-area { | |
display: flex; /* Keep input and button side-by-side */ | |
align-items: center; | |
gap: 0.75rem; /* Space between mic, input, send */ | |
} | |
} | |
/* Ensure chat history scrolls within its container */ | |
#chatHistory { | |
flex-grow: 1; /* Takes up remaining space in chat-section */ | |
overflow-y: auto; /* Enable vertical scroll */ | |
} | |
/* Adjust input area layout for smaller screens */ | |
.chat-input-area { | |
display: flex; | |
flex-wrap: wrap; /* Allow wrapping */ | |
gap: 0.5rem; /* Space between elements */ | |
} | |
#userQuery { | |
flex-grow: 1; /* Take available space */ | |
min-width: 150px; /* Minimum width before wrapping */ | |
} | |
.preferences-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); /* Responsive grid */ | |
gap: 0.75rem; /* Space between grid items */ | |
} | |
</style> | |
</head> | |
<body class="text-sm md:text-base"> <div class="main-container"> | |
<div class="chat-section"> | |
<div class="text-center mb-3 md:mb-4"> | |
<h1 class="text-2xl md:text-3xl font-bold text-purple-300">G.E.N.I.E.</h1> | |
<p class="text-purple-100 text-xs md:text-sm"> | |
GitHub Enhanced Natural Intelligence Engine <br class="md:hidden"> | |
<span class="hidden md:inline"> - </span> | |
A voice-controlled AI that “grants your GitHub wishes.” | |
</p> | |
</div> | |
<div class="glass-panel p-3 md:p-4 space-y-3 mb-3 md:mb-4"> | |
<input type="url" id="repoUrl" placeholder="GitHub Repository URL (e.g., https://github.com/owner/repo)" class="futuristic-input"> | |
<div class="preferences-grid"> | |
<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"> | |
<select id="userType" class="futuristic-select" title="Select your role for tailored responses."> | |
<option value="coder">Role: Coder</option> | |
<option value="manager">Role: Manager</option> | |
<option value="researcher">Role: Researcher</option> | |
<option value="student">Role: Student</option> | |
</select> | |
<select id="responseDetail" class="futuristic-select" title="Select the desired level of detail for G.E.N.I.E.'s responses."> | |
<option value="concise">Detail: Concise</option> | |
<option value="normal" selected>Detail: Normal</option> | |
<option value="detailed">Detail: Detailed</option> | |
</select> | |
</div> | |
<div class="chat-input-area"> | |
<button id="micButton" title="Toggle Listening Mode / Interrupt" class="futuristic-button mic-button">🎙️</button> | |
<input type="text" id="userQuery" placeholder="Make a wish about the repository..." class="futuristic-input flex-grow"> | |
<button id="sendButton" class="futuristic-button">Ask G.E.N.I.E.</button> | |
</div> | |
</div> | |
<div id="chatHistory" class="flex-grow glass-panel p-3 md:p-4 space-y-3 overflow-y-auto custom-scrollbar"> | |
<div class="chat-bubble bot-bubble"> | |
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. | |
</div> | |
</div> | |
</div> | |
<div class="status-section"> | |
<div class="status-content"> {/* Wrapper for centering content */} | |
<h2 id="agentStatusTitle" class="text-lg md:text-xl font-semibold text-purple-400 text-center">G.E.N.I.E. Status</h2> | |
<div id="genieOrb" class="genie-orb"></div> | |
<button id="interruptButton" class="interrupt-button hidden">Interrupt G.E.N.I.E.</button> | |
<div class="w-full glass-panel p-3 md:p-4 h-32 md:h-40 overflow-y-auto custom-scrollbar"> | |
<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> | |
<p id="genieOutput" class="text-xs md:text-sm whitespace-pre-wrap">Awaiting your command...</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// --- DOM Elements --- | |
const repoUrlInput = document.getElementById('repoUrl'); | |
const githubTokenInput = document.getElementById('githubToken'); // New | |
const userTypeSelect = document.getElementById('userType'); // New | |
const responseDetailSelect = document.getElementById('responseDetail'); // New | |
const userQueryInput = document.getElementById('userQuery'); | |
const sendButton = document.getElementById('sendButton'); | |
const micButton = document.getElementById('micButton'); | |
const interruptButton = document.getElementById('interruptButton'); | |
const chatHistory = document.getElementById('chatHistory'); | |
const genieOrb = document.getElementById('genieOrb'); | |
const genieOutput = document.getElementById('genieOutput'); | |
const agentStatusTitle = document.getElementById('agentStatusTitle'); | |
// --- State --- | |
let isListening = false; | |
let botResponseTimeoutId = null; | |
let thinkingMessageElement = null; | |
let isBotActive = false; | |
// --- Event Listeners --- | |
sendButton.addEventListener('click', handleSendQuery); | |
userQueryInput.addEventListener('keypress', (event) => { | |
if (event.key === 'Enter' && !event.shiftKey) { // Send on Enter, allow Shift+Enter for newline | |
event.preventDefault(); // Prevent default newline behavior | |
handleSendQuery(); | |
} | |
}); | |
userQueryInput.addEventListener('input', handleTypingInterrupt); | |
micButton.addEventListener('click', handleMicButtonClick); | |
interruptButton.addEventListener('click', handleInterrupt); | |
// --- Functions --- | |
function handleMicButtonClick() { | |
// If the bot is actively processing, the mic button acts as an interrupt | |
if (isBotActive) { | |
handleInterrupt(); | |
} else { | |
// Otherwise, toggle listening mode | |
toggleListeningMode(); | |
} | |
} | |
function toggleListeningMode() { | |
if (isBotActive) return; // Don't toggle if bot is busy | |
isListening = !isListening; | |
micButton.classList.toggle('listening', isListening); | |
genieOrb.classList.toggle('listening', isListening); | |
if (isListening) { | |
setBotActiveState(false); // Ensure bot is not marked active | |
agentStatusTitle.textContent = "G.E.N.I.E. Status: Listening..."; | |
updateGenieText("Listening... (Simulation - Type your wish and press Ask)"); | |
userQueryInput.focus(); | |
micButton.title = "Stop Listening / Interrupt"; // Update tooltip | |
} else { | |
micButton.title = "Toggle Listening Mode / Interrupt"; // Reset tooltip | |
// Only reset status if the bot isn't currently active (e.g., thinking) | |
if (!isBotActive) { | |
agentStatusTitle.textContent = "G.E.N.I.E. Status"; | |
// Only reset output if it was showing the listening message | |
if (genieOutput.textContent.startsWith("Listening...")) { | |
updateGenieText("Awaiting your command..."); | |
} | |
} | |
} | |
} | |
function handleTypingInterrupt() { | |
// Interrupt if the bot is thinking and the user starts typing | |
if (isBotActive && botResponseTimeoutId) { | |
handleInterrupt(); | |
} | |
} | |
function handleSendQuery() { | |
// Stop listening mode if active when sending query | |
if (isListening) { | |
toggleListeningMode(); | |
} | |
// Prevent sending if bot is already active | |
if (isBotActive) { | |
console.log("G.E.N.I.E. is active, cannot send new query yet."); | |
// Maybe add a subtle visual cue here later | |
return; | |
} | |
const repoUrl = repoUrlInput.value.trim(); | |
const query = userQueryInput.value.trim(); | |
const githubToken = githubTokenInput.value.trim(); // Get token (optional) | |
const userType = userTypeSelect.value; // Get user type | |
const responseDetail = responseDetailSelect.value; // Get detail level | |
// Basic Validations | |
if (!query) { | |
showTemporaryAlert(userQueryInput, 'Please state your wish (enter a query).'); | |
return; | |
} | |
if (!isValidHttpUrl(repoUrl)) { | |
showTemporaryAlert(repoUrlInput, 'Please provide a valid GitHub repository URL (starting with http:// or https://).'); | |
return; | |
} | |
// Add user message to chat | |
addMessageToChat(query, 'user'); | |
userQueryInput.value = ''; // Clear input field | |
// Simulate bot processing and response | |
simulateBotResponse(repoUrl, query, userType, responseDetail, githubToken); | |
} | |
function handleInterrupt() { | |
if (botResponseTimeoutId) { | |
console.log("Interrupt triggered by user."); | |
clearTimeout(botResponseTimeoutId); | |
botResponseTimeoutId = null; | |
// Remove the "thinking" message if it exists | |
if (thinkingMessageElement) { | |
thinkingMessageElement.remove(); | |
thinkingMessageElement = null; | |
} | |
// Reset bot state | |
setBotActiveState(false); | |
// Add an interrupted message to chat | |
addMessageToChat("Processing interrupted by user.", 'interrupted'); | |
updateGenieText("Interrupted. Ready for your next wish."); | |
// Refocus input for convenience | |
userQueryInput.focus(); | |
} else { | |
console.log("Interrupt called but G.E.N.I.E. was not actively processing."); | |
} | |
} | |
function addMessageToChat(message, type) { | |
const messageElement = document.createElement('div'); | |
messageElement.classList.add('chat-bubble'); | |
// Stop listening visual cues if a message is added | |
if (isListening) { | |
genieOrb.classList.remove('listening'); | |
micButton.classList.remove('listening'); | |
isListening = false; // Ensure state is updated | |
micButton.title = "Toggle Listening Mode / Interrupt"; // Reset tooltip | |
} | |
switch (type) { | |
case 'user': | |
messageElement.classList.add('user-bubble'); | |
messageElement.textContent = message; | |
break; | |
case 'bot': | |
messageElement.classList.add('bot-bubble'); | |
messageElement.textContent = message; | |
updateGenieText(message); // Update status output | |
// Short delay before resetting active state to allow UI update | |
if (botResponseTimeoutId) clearTimeout(botResponseTimeoutId); // Clear any pending timeout | |
botResponseTimeoutId = null; // Ensure timeout ID is cleared | |
setTimeout(() => { | |
if(isBotActive) setBotActiveState(false); // Reset state after response is shown | |
}, 100); // Small delay | |
break; | |
case 'thinking': | |
messageElement.classList.add('bot-bubble', 'thinking'); | |
messageElement.innerHTML = `Consulting the digital ether... <div class="dot-flashing"></div>`; | |
updateGenieText("Processing your wish..."); // Update status output | |
setBotActiveState(true); // Set bot to active state | |
thinkingMessageElement = messageElement; // Store reference to remove later | |
break; | |
case 'interrupted': | |
messageElement.classList.add('bot-bubble', 'interrupted'); | |
messageElement.textContent = message; | |
// Status text is updated in handleInterrupt | |
break; | |
default: | |
console.error("Unknown message type:", type); | |
return null; // Don't add unknown types | |
} | |
chatHistory.appendChild(messageElement); | |
// Scroll to the bottom smoothly after adding message | |
// Use setTimeout to ensure element is rendered before scrolling | |
setTimeout(() => { | |
chatHistory.scrollTo({ top: chatHistory.scrollHeight, behavior: 'smooth' }); | |
}, 50); | |
return messageElement; // Return the created element | |
} | |
function simulateBotResponse(repoUrl, query, userType, responseDetail, githubToken) { | |
// Start the thinking state | |
thinkingMessageElement = addMessageToChat('', 'thinking'); | |
// Simulate network delay/processing time | |
const delay = 2000 + Math.random() * 3500; // 2 to 5.5 seconds | |
// Clear any previous timeout just in case | |
if (botResponseTimeoutId) clearTimeout(botResponseTimeoutId); | |
botResponseTimeoutId = setTimeout(() => { | |
// Check if it was interrupted *before* this timeout fired | |
const wasInterrupted = !thinkingMessageElement; | |
if (wasInterrupted) { | |
console.log("G.E.N.I.E. response timeout fired, but was already interrupted."); | |
botResponseTimeoutId = null; // Clear ID as it's no longer needed | |
// No need to reset state here, handleInterrupt already did | |
return; | |
} | |
// If not interrupted, remove the thinking message | |
if (thinkingMessageElement) { | |
thinkingMessageElement.remove(); | |
thinkingMessageElement = null; | |
} | |
botResponseTimeoutId = null; // Clear the timeout ID | |
// Generate a sample response (incorporating preferences cosmetically) | |
const botResponse = generateSampleResponse(repoUrl, query, userType, responseDetail, githubToken); | |
// Add the actual bot response to the chat | |
addMessageToChat(botResponse, 'bot'); | |
// The 'bot' case in addMessageToChat now handles resetting the active state | |
}, delay); | |
} | |
// Updated to include preferences (cosmetic for now) | |
function generateSampleResponse(repoUrl, query, userType, responseDetail, githubToken) { | |
const repoName = repoUrl.substring(repoUrl.lastIndexOf('/') + 1) || "[Unknown Repo]"; | |
query = query.toLowerCase(); | |
let responsePrefix = ""; | |
let detailModifier = 1; // 1 = normal, <1 = concise, >1 = detailed | |
// Adjust detail level (simple multiplier for example length) | |
if (responseDetail === 'concise') detailModifier = 0.6; | |
if (responseDetail === 'detailed') detailModifier = 1.5; | |
// Add flavor based on user type | |
switch (userType) { | |
case 'manager': responsePrefix = "From a high-level view, "; break; | |
case 'researcher': responsePrefix = "Digging into the details, "; break; | |
case 'student': responsePrefix = "To help with your learning, "; break; | |
// Coder gets no specific prefix (default) | |
} | |
// Acknowledge token if provided (SIMULATION ONLY) | |
if (githubToken) { | |
console.log("Simulating use of GitHub Token (not actually used):", githubToken.substring(0, 4) + "..."); | |
// You could add a note in the response, but be careful not to leak info | |
// responsePrefix += "(Using provided token access) "; | |
} | |
// Basic query matching with some flavor | |
if (query.includes("what is this repo") || query.includes("summarize")) { | |
let base = `As you wish! The repository '${repoName}' appears to be `; | |
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'}].`; | |
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.`; | |
if (detailModifier < 1) example = `[a ${Math.random() > 0.5 ? 'JS UI library' : 'Python data backend'}].`; | |
return responsePrefix + base + example; | |
} | |
if (query.includes("main language") || query.includes("written in")) { | |
const languages = ["JavaScript", "Python", "Java", "TypeScript", "Go", "CSS", "Ruby", "PHP"]; | |
const randomLang = languages[Math.floor(Math.random() * languages.length)]; | |
let base = `Gazing into the code of '${repoName}', I reveal the primary language appears to be ${randomLang}. `; | |
let example = `Hints of [${languages[Math.floor(Math.random() * languages.length)]}] may also be present.`; | |
if (detailModifier > 1) example += ` Analysis suggests around ${Math.floor(Math.random()*30+60)}% of the codebase is ${randomLang}.`; | |
if (detailModifier < 1) example = ""; // Concise just gives the main language | |
return responsePrefix + base + example; | |
} | |
if (query.includes("how many files") || query.includes("file count")) { | |
const fileCount = Math.floor(Math.random() * (800 * detailModifier) + 50); | |
return responsePrefix + `By my count, the main branch of '${repoName}' contains approximately ${fileCount} files and directories.`; | |
} | |
if (query.includes("docker") || query.includes("container")) { | |
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}'`; | |
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.`; | |
if (detailModifier < 1) example = Math.random() > 0.4 ? ", suggesting container support." : ", no obvious Dockerfile found."; | |
return responsePrefix + base + example; | |
} | |
if (query.includes("test") || query.includes("coverage")) { | |
let base = `The repository '${repoName}' reveals signs of testing charms. `; | |
let example = `I see evidence of a '/tests' or '/spec' directory, possibly utilizing frameworks like [Jest/Pytest/RSpec].`; | |
if (detailModifier > 1) example += ` Configuration files suggest integration with CI/CD pipelines for automated testing. Code coverage analysis might be configured.`; | |
if (detailModifier < 1) example = `Testing files seem present.`; | |
return responsePrefix + base + example; | |
} | |
if (query.includes("hello") || query.includes("hi") || query.includes("greetings")) { | |
return `Greetings, Master ${userType}! How may G.E.N.I.E. assist you with the '${repoName}' repository today? State your wish!`; | |
} | |
// Default fallback response | |
let fallback = `I have considered your wish regarding '${repoName}' concerning "${query.substring(0, 30)}...". `; | |
let example = `My vision suggests that [${Math.random() > 0.5 ? 'the file structure seems reasonably organized' : 'recent commit activity implies active maintenance'}].`; | |
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].`; | |
if (detailModifier < 1) example = `It appears to be a standard project.`; | |
fallback += example + ` For deeper secrets, phrase your wish more specifically.`; | |
return responsePrefix + fallback; | |
} | |
/** Updates the G.E.N.I.E. output text in the status panel */ | |
function updateGenieText(text) { | |
genieOutput.textContent = text; | |
} | |
/** Sets the visual state indicating if the bot is actively processing */ | |
function setBotActiveState(isActive) { | |
isBotActive = isActive; | |
genieOrb.classList.toggle('active', isActive); | |
interruptButton.classList.toggle('hidden', !isActive); | |
sendButton.disabled = isActive; // Disable send button while active | |
userQueryInput.disabled = isActive; // Optionally disable query input too | |
repoUrlInput.disabled = isActive; // Disable repo URL input | |
githubTokenInput.disabled = isActive; // Disable token input | |
userTypeSelect.disabled = isActive; // Disable user type select | |
responseDetailSelect.disabled = isActive; // Disable detail select | |
if (isActive) { | |
// If activating, ensure listening mode is off | |
if (isListening) { | |
toggleListeningMode(); // Turn off listening visuals/state | |
} | |
agentStatusTitle.textContent = "G.E.N.I.E. Status: Fulfilling Wish..."; | |
micButton.title = "Interrupt G.E.N.I.E."; // Mic becomes interrupt | |
} else { | |
// Reset status title only if not currently listening | |
if (!isListening) { | |
agentStatusTitle.textContent = "G.E.N.I.E. Status"; | |
updateGenieText("Awaiting your command..."); // Reset output text | |
} | |
micButton.title = "Toggle Listening Mode / Interrupt"; // Reset mic title | |
// Clear any lingering timeout or thinking message reference | |
if (botResponseTimeoutId) { | |
clearTimeout(botResponseTimeoutId); | |
botResponseTimeoutId = null; | |
} | |
thinkingMessageElement = null; | |
} | |
} | |
/** Checks if a string is a valid HTTP/HTTPS URL */ | |
function isValidHttpUrl(string) { | |
try { | |
const url = new URL(string); | |
return url.protocol === "http:" || url.protocol === "https:"; | |
} catch (_) { | |
return false; // Invalid URL format | |
} | |
} | |
/** Shows a temporary red border and message on an input element */ | |
function showTemporaryAlert(element, message) { | |
element.style.borderColor = '#ff6b6b'; // Red border | |
element.style.boxShadow = '0 0 10px rgba(255, 107, 107, 0.4)'; | |
// Optionally, display the message near the element or use a dedicated alert area | |
console.warn("Validation Error:", message); // Log for debugging | |
// You could add a small temporary message element below the input | |
setTimeout(() => { | |
element.style.borderColor = ''; // Reset border color | |
element.style.boxShadow = ''; // Reset box shadow | |
}, 2500); // Reset after 2.5 seconds | |
element.focus(); | |
} | |
// --- Initial Setup --- | |
updateGenieText("Standing by. Please provide a GitHub repo URL, set preferences, and state your wish, or click 🎙️."); | |
interruptButton.classList.add('hidden'); // Start hidden | |
sendButton.disabled = false; // Start enabled | |
// Ensure initial state is not active | |
setBotActiveState(false); | |
</script> | |
</body> | |
</html> | |