G.E.N.I.E / index.html
nihalaninihal's picture
Upload 11 files
f6f98ea verified
<!DOCTYPE html>
<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>