ai-code-chat / index.html
fredthehack's picture
Add 2 files
b86f562 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Code Chat</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.code-block {
font-family: 'Courier New', monospace;
background-color: #f3f4f6;
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
white-space: pre;
margin-bottom: 1rem;
border-left: 4px solid #3b82f6;
}
.chat-message.user {
background-color: #eef2ff;
border-radius: 1rem 1rem 0 1rem;
}
.chat-message.ai {
background-color: #f8fafc;
border-radius: 1rem 1rem 1rem 0;
}
.loader {
border: 3px solid #f3f3f3;
border-top: 3px solid #3b82f6;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
display: inline-block;
vertical-align: middle;
margin-left: 8px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.file-tree-item {
transition: all 0.2s;
}
.file-tree-item:hover {
background-color: #f1f5f9;
cursor: pointer;
}
.repo-info-card {
transition: all 0.3s;
}
.repo-info-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-10">
<h1 class="text-4xl font-bold text-blue-600 mb-2">
<i class="fas fa-robot mr-2"></i> AI Code Chat
</h1>
<p class="text-gray-600 max-w-2xl mx-auto">
Analyze any GitHub repository with AI. Ask questions about the codebase and get insightful answers.
</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Configuration Panel -->
<div class="bg-white p-6 rounded-xl shadow-md lg:col-span-1">
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-cog mr-2"></i>Settings
</h2>
<div class="mb-4">
<label for="apiKey" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fas fa-key mr-1"></i>OpenAI API Key
</label>
<div class="relative">
<input
type="password"
id="apiKey"
placeholder="sk-..."
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<button class="absolute right-2 top-2 text-gray-400 hover:text-gray-600 focus:outline-none" id="toggleKey">
<i class="fas fa-eye"></i>
</button>
</div>
<p class="text-xs text-gray-500 mt-1">
Your API key is stored locally and never sent to our servers.
</p>
</div>
<div class="mb-6">
<label for="githubRepo" class="block text-sm font-medium text-gray-700 mb-1">
<i class="fab fa-github mr-1"></i>GitHub Repository URL
</label>
<input
type="text"
id="githubRepo"
placeholder="https://github.com/username/repo"
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<button
id="loadRepoBtn"
class="mt-3 w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md flex items-center justify-center transition duration-200"
>
<i class="fas fa-download mr-2"></i>Load & Index Code
</button>
</div>
<div id="repoInfo" class="hidden bg-gray-50 p-4 rounded-lg repo-info-card">
<div class="flex justify-between items-start mb-2">
<div>
<h3 id="repoName" class="font-semibold text-blue-600"></h3>
<p id="repoDescription" class="text-sm text-gray-600"></p>
</div>
<span id="repoLang" class="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full"></span>
</div>
<div class="flex justify-between text-xs text-gray-500 mt-3">
<span id="repoStars"><i class="fas fa-star mr-1"></i></span>
<span id="repoIssues"><i class="fas fa-exclamation-circle mr-1"></i></span>
<span id="repoSize"><i class="fas fa-code mr-1"></i></span>
</div>
</div>
<div id="processingStatus" class="hidden mt-4">
<div class="flex flex-col space-y-3">
<div class="flex items-center">
<span class="w-4 h-4 rounded-full bg-blue-600 mr-2 flex-shrink-0"></span>
<span class="text-sm font-medium text-gray-700">Processing files...</span>
</div>
<div class="flex items-center">
<span class="w-4 h-4 rounded-full bg-blue-600 mr-2 flex-shrink-0"></span>
<span class="text-sm font-medium text-gray-700">Building code index...</span>
</div>
<div class="flex items-center">
<span class="w-4 h-4 rounded-full bg-blue-600 mr-2 flex-shrink-0"></span>
<span class="text-sm font-medium text-gray-700">Ready for questions!</span>
</div>
</div>
<div class="mt-3 relative pt-1">
<div class="flex mb-2 items-center justify-between">
<div>
<span class="text-xs font-semibold inline-block text-blue-600">
Progress
</span>
</div>
<div class="text-right">
<span class="text-xs font-semibold inline-block text-blue-600" id="progressPercent">
0%
</span>
</div>
</div>
<div class="overflow-hidden h-2 mb-4 text-xs flex rounded bg-blue-200">
<div id="progressBar" style="width:0%" class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-blue-500"></div>
</div>
</div>
</div>
<div id="fileTree" class="hidden max-h-96 overflow-y-auto mt-4 border border-gray-200 rounded-lg p-3">
<h4 class="font-medium text-gray-700 mb-2">
<i class="fas fa-folder-open mr-1"></i>Repository Structure
</h4>
<div id="treeContent" class="space-y-1"></div>
</div>
</div>
<!-- Chat Interface -->
<div class="bg-white p-6 rounded-xl shadow-md lg:col-span-2 flex flex-col">
<h2 class="text-xl font-semibold mb-4 text-blue-600">
<i class="fas fa-comments mr-2"></i>Code Chat
</h2>
<div id="chatContainer" class="flex-grow mb-4 overflow-y-auto max-h-[500px] space-y-3">
<div class="text-center text-gray-500 py-10 hidden" id="noRepoMessage">
<i class="fas fa-code-branch text-4xl mb-4 text-blue-200"></i>
<p class="text-lg">Load a GitHub repository to start chatting about its code</p>
</div>
<div id="chatMessages"></div>
</div>
<div class="mt-auto">
<form id="chatForm" class="flex">
<input
type="text"
id="userInput"
placeholder="Ask about the code..."
class="flex-grow px-4 py-2 border border-gray-300 rounded-l-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
disabled
>
<button
type="submit"
id="sendBtn"
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-r-md transition duration-200 disabled:opacity-50"
disabled
>
<i class="fas fa-paper-plane"></i>
</button>
</form>
<div class="flex items-center mt-2 text-xs text-gray-500">
<span>Model: <span id="modelName">gpt-4o-mini</span></span>
<span class="mx-2">|</span>
<span>Built with <i class="fas fa-heart text-red-400 ml-1"></i></span>
</div>
</div>
</div>
</div>
</div>
<script>
// State management
const state = {
apiKey: '',
githubRepo: '',
indexedFiles: [],
chatHistory: []
};
// DOM elements
const elements = {
apiKey: document.getElementById('apiKey'),
githubRepo: document.getElementById('githubRepo'),
loadRepoBtn: document.getElementById('loadRepoBtn'),
repoInfo: document.getElementById('repoInfo'),
repoName: document.getElementById('repoName'),
repoDescription: document.getElementById('repoDescription'),
repoLang: document.getElementById('repoLang'),
repoStars: document.getElementById('repoStars'),
repoIssues: document.getElementById('repoIssues'),
repoSize: document.getElementById('repoSize'),
processingStatus: document.getElementById('processingStatus'),
progressBar: document.getElementById('progressBar'),
progressPercent: document.getElementById('progressPercent'),
fileTree: document.getElementById('fileTree'),
treeContent: document.getElementById('treeContent'),
chatContainer: document.getElementById('chatContainer'),
noRepoMessage: document.getElementById('noRepoMessage'),
chatMessages: document.getElementById('chatMessages'),
chatForm: document.getElementById('chatForm'),
userInput: document.getElementById('userInput'),
sendBtn: document.getElementById('sendBtn'),
toggleKey: document.getElementById('toggleKey')
};
// Initialize the application
function init() {
// Load saved API key if exists
const savedKey = localStorage.getItem('openai_api_key');
if (savedKey) {
state.apiKey = savedKey;
elements.apiKey.value = savedKey;
toggleKeyVisibility(true);
}
// Load saved repo if exists
const savedRepo = localStorage.getItem('github_repo');
if (savedRepo) {
state.githubRepo = savedRepo;
elements.githubRepo.value = savedRepo;
}
// Event listeners
elements.apiKey.addEventListener('change', handleApiKeyChange);
elements.githubRepo.addEventListener('change', handleGithubRepoChange);
elements.loadRepoBtn.addEventListener('click', handleLoadRepo);
elements.chatForm.addEventListener('submit', handleChatSubmit);
elements.toggleKey.addEventListener('click', toggleKeyVisibility);
// Show initial state
toggleNoRepoMessage(true);
}
// Toggle API key visibility
let keyVisible = false;
function toggleKeyVisibility(forceShow = false) {
if (forceShow) {
elements.apiKey.type = 'text';
elements.toggleKey.innerHTML = '<i class="fas fa-eye-slash"></i>';
keyVisible = true;
return;
}
keyVisible = !keyVisible;
elements.apiKey.type = keyVisible ? 'text' : 'password';
elements.toggleKey.innerHTML = keyVisible ? '<i class="fas fa-eye-slash"></i>' : '<i class="fas fa-eye"></i>';
}
// Handle API key change
function handleApiKeyChange(e) {
state.apiKey = e.target.value.trim();
localStorage.setItem('openai_api_key', state.apiKey);
}
// Handle GitHub repo URL change
function handleGithubRepoChange(e) {
state.githubRepo = e.target.value.trim();
localStorage.setItem('github_repo', state.githubRepo);
}
// Toggle "no repo" message visibility
function toggleNoRepoMessage(show) {
if (show) {
elements.noRepoMessage.classList.remove('hidden');
elements.userInput.disabled = true;
elements.sendBtn.disabled = true;
} else {
elements.noRepoMessage.classList.add('hidden');
elements.userInput.disabled = false;
elements.sendBtn.disabled = false;
}
}
// Handle load repository button click
async function handleLoadRepo() {
if (!state.apiKey) {
showAlert('Please enter your OpenAI API key', 'error');
return;
}
if (!state.githubRepo) {
showAlert('Please enter a GitHub repository URL', 'error');
return;
}
// Validate GitHub URL
const repoParts = state.githubRepo.match(/github\.com\/([^\/]+)\/([^\/]+)/);
if (!repoParts) {
showAlert('Invalid GitHub repository URL. Format should be: https://github.com/username/repo', 'error');
return;
}
const [_, owner, repo] = repoParts;
try {
// Update UI for loading
elements.loadRepoBtn.disabled = true;
elements.loadRepoBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Loading...';
// Get repository info
const repoInfo = await fetchRepoInfo(owner, repo);
displayRepoInfo(repoInfo);
// Show processing UI
elements.processingStatus.classList.remove('hidden');
// Simulate processing (in a real app, this would be the actual indexing process)
simulateProcessing();
// Display file tree (simulated - in a real app, this would come from actual indexing)
displayFileTree(simulateFileTree());
// Enable chat
toggleNoRepoMessage(false);
} catch (error) {
console.error('Error loading repository:', error);
showAlert(`Failed to load repository: ${error.message}`, 'error');
} finally {
// Reset the button
elements.loadRepoBtn.disabled = false;
elements.loadRepoBtn.innerHTML = '<i class="fas fa-download mr-2"></i>Load & Index Code';
}
}
// Simulate processing (in a real app, this would be replaced with actual processing)
function simulateProcessing() {
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress >= 100) {
progress = 100;
clearInterval(interval);
}
updateProgress(progress);
}, 300);
}
// Update progress bar
function updateProgress(percent) {
elements.progressBar.style.width = `${percent}%`;
elements.progressPercent.textContent = `${Math.round(percent)}%`;
}
// Display repository information
function displayRepoInfo(repoInfo) {
elements.repoName.textContent = repoInfo.full_name;
elements.repoDescription.textContent = repoInfo.description || 'No description provided';
elements.repoLang.textContent = repoInfo.language || 'Unknown';
elements.repoStars.innerHTML = `${repoInfo.stargazers_count.toLocaleString()} stars`;
elements.repoIssues.innerHTML = `${repoInfo.open_issues.toLocaleString()} open issues`;
elements.repoSize.innerHTML = `${(repoInfo.size / 1024).toFixed(1)} MB`;
// Set language color
const langColor = getLanguageColor(repoInfo.language);
elements.repoLang.style.backgroundColor = `${langColor}20`;
elements.repoLang.style.color = langColor;
elements.repoInfo.classList.remove('hidden');
}
// Get color for programming language
function getLanguageColor(lang) {
if (!lang) return '#64748b';
const colors = {
'JavaScript': '#f1e05a',
'TypeScript': '#3178c6',
'Python': '#3572A5',
'Java': '#b07219',
'C++': '#f34b7d',
'C#': '#178600',
'Ruby': '#701516',
'Go': '#00ADD8',
'PHP': '#4F5D95',
'Swift': '#F05138'
};
return colors[lang] || '#64748b';
}
// Simulate file tree (in a real app, this would come from actual repository)
function simulateFileTree() {
return {
name: 'root',
type: 'dir',
children: [
{
name: 'src',
type: 'dir',
children: [
{
name: 'components',
type: 'dir',
children: [
{ name: 'Header.js', type: 'file', lang: 'JavaScript', size: '4.2 KB' },
{ name: 'Footer.js', type: 'file', lang: 'JavaScript', size: '3.1 KB' },
]
},
{
name: 'pages',
type: 'dir',
children: [
{ name: 'Home.js', type: 'file', lang: 'JavaScript', size: '7.5 KB' },
{ name: 'About.js', type: 'file', lang: 'JavaScript', size: '5.2 KB' },
]
},
{ name: 'App.js', type: 'file', lang: 'JavaScript', size: '8.7 KB' },
]
},
{
name: 'public',
type: 'dir',
children: [
{ name: 'index.html', type: 'file', lang: 'HTML', size: '1.8 KB' },
{ name: 'styles.css', type: 'file', lang: 'CSS', size: '3.4 KB' },
]
},
{ name: 'package.json', type: 'file', lang: 'JSON', size: '1.2 KB' },
{ name: 'README.md', type: 'file', lang: 'Markdown', size: '2.5 KB' },
]
};
}
// Display file tree
function displayFileTree(tree) {
elements.treeContent.innerHTML = '';
renderTreeItem(tree, elements.treeContent);
elements.fileTree.classList.remove('hidden');
}
// Render a tree item recursively
function renderTreeItem(item, parentEl, level = 0) {
const itemEl = document.createElement('div');
itemEl.className = `file-tree-item pl-${level * 4}`;
itemEl.style.marginLeft = `${level * 12}px`;
const icon = item.type === 'dir' ? 'fa-folder text-blue-400' : `fa-file text-gray-400`;
const size = item.type === 'file' ? `<span class="text-xs text-gray-500 ml-2">${item.size}</span>` : '';
itemEl.innerHTML = `
<div class="flex items-center py-1 px-2 rounded">
<i class="fas ${icon} mr-2 text-sm"></i>
<span class="text-sm">${item.name}</span>
${size}
</div>
`;
parentEl.appendChild(itemEl);
if (item.children) {
item.children.forEach(child => {
renderTreeItem(child, parentEl, level + 1);
});
}
}
// Handle chat form submission
async function handleChatSubmit(e) {
e.preventDefault();
const userMessage = elements.userInput.value.trim();
if (!userMessage) return;
// Add user message to chat
addMessage('user', userMessage);
elements.userInput.value = '';
// Create a placeholder for the AI response
const aiMessageId = addMessage('ai', '<div class="loader"></div>', true);
try {
// Simulate API call (in a real app, this would call the OpenAI API)
elements.sendBtn.disabled = true;
// Simulate processing delay
await new Promise(resolve => setTimeout(resolve, 1500));
// Generate a mock response (in a real app, this would come from OpenAI)
const mockResponse = generateMockResponse(userMessage);
// Update the AI message
updateMessage(aiMessageId, mockResponse);
} catch (error) {
console.error('Error getting AI response:', error);
updateMessage(aiMessageId, 'Sorry, I encountered an error processing your request.');
} finally {
elements.sendBtn.disabled = false;
}
}
// Add a message to the chat
function addMessage(role, content, isPending = false) {
const messageId = `msg-${Date.now()}`;
const messageEl = document.createElement('div');
messageEl.id = messageId;
messageEl.className = `chat-message ${role} p-4`;
if (typeof content === 'string') {
messageEl.innerHTML = content;
} else {
messageEl.appendChild(content);
}
elements.chatMessages.appendChild(messageEl);
elements.chatContainer.scrollTop = elements.chatContainer.scrollHeight;
return isPending ? messageId : null;
}
// Update an existing message
function updateMessage(messageId, newContent) {
const messageEl = document.getElementById(messageId);
if (messageEl) {
if (typeof newContent === 'string') {
messageEl.innerHTML = newContent;
} else {
messageEl.innerHTML = '';
messageEl.appendChild(newContent);
}
elements.chatContainer.scrollTop = elements.chatContainer.scrollHeight;
}
}
// Show an alert message
function showAlert(message, type = 'info') {
const alertEl = document.createElement('div');
alertEl.className = `fixed top-4 right-4 px-6 py-3 rounded-md shadow-md text-white ${
type === 'error' ? 'bg-red-500' : 'bg-blue-500'
} animate-fade-in`;
alertEl.textContent = message;
document.body.appendChild(alertEl);
// Remove after 4 seconds
setTimeout(() => {
alertEl.classList.add('animate-fade-out');
setTimeout(() => alertEl.remove(), 300);
}, 4000);
}
// Generate a mock response (in a real app, this would come from OpenAI API)
function generateMockResponse(userMessage) {
const lowerMsg = userMessage.toLowerCase();
if (lowerMsg.includes('component') || lowerMsg.includes('file') || lowerMsg.includes('code')) {
return `
<div>
<p>Based on the repository structure, I found these relevant components:</p>
<div class="mt-2 mb-4">
<ul class="list-disc pl-5 space-y-1">
<li><span class="font-medium">App.js</span> - Main application entry point</li>
<li><span class="font-medium">Header.js</span> - Navigation component</li>
<li><span class="font-medium">Footer.js</span> - Page footer component</li>
</ul>
</div>
<p>Would you like me to show you the code for any specific file?</p>
</div>
`;
} else if (lowerMsg.includes('html') || lowerMsg.includes('markup')) {
return `
<div>
<p>The public/index.html file contains the base HTML structure:</p>
<pre class="code-block mt-2">
&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
&lt;title&gt;React App&lt;/title&gt;
&lt;link rel="stylesheet" href="styles.css"&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="root"&gt;&lt;/div&gt;
&lt;script src="../src/App.js"&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<p class="mt-2">This is a standard HTML5 template with a root div where the React application mounts.</p>
</div>
`;
} else if (lowerMsg.includes('function') || lowerMsg.includes('method')) {
return `
<div>
<p>Here's an example function from one of the components:</p>
<pre class="code-block mt-2">
// Example function from Header.js
function handleNavigation(route) {
// Check if route is valid
if (!routes.includes(route)) {
console.error('Invalid navigation route:', route);
return;
}
// Update state and navigate
setCurrentRoute(route);
window.history.pushState({}, route, \`/\${route}\`);
}</pre>
<p class="mt-2">This function handles navigation between different routes in the application.</p>
</div>
`;
} else if (lowerMsg.includes('dependencies') || lowerMsg.includes('package.json')) {
return `
<div>
<p>The package.json shows these main dependencies:</p>
<div class="mt-2 mb-4">
<ul class="list-disc pl-5 space-y-1">
<li><span class="font-medium">react</span> (^18.2.0) - Core library</li>
<li><span class="font-medium">react-dom</span> (^18.2.0) - DOM rendering</li>
<li><span class="font-medium">react-router-dom</span> (^6.4.3) - Routing</li>
</ul>
</div>
<p>The project is built with React and uses functional components with hooks.</p>
</div>
`;
} else {
return `
<div>
<p>I'm analyzing the repository and can help you understand:</p>
<ul class="list-disc pl-5 space-y-1 mt-2">
<li>The code structure and architecture</li>
<li>Specific components or functions</li>
<li>Dependencies and configuration</li>
</ul>
<p class="mt-3">Could you be more specific about what you'd like to know?</p>
</div>
`;
}
}
// Simulate fetching repository info (in a real app, this would call GitHub API)
function fetchRepoInfo(owner, repo) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
full_name: `${owner}/${repo}`,
description: "A sample repository for the AI Code Chat application.",
language: "JavaScript",
stargazers_count: 1284,
open_issues: 23,
size: 5042
});
}, 800);
});
}
// Initialize the app
init();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - <a href="https://enzostvs-deepsite.hf.space?remix=fredthehack/ai-code-chat" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body>
</html>