roshnn24's picture
Create templates/index.html
1e4f197 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Chat Interface</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
<style>
.chat-area {
height: calc(100vh - 200px);
overflow-y: auto;
background-color: #1a1a1a;
color: #e6e6e6;
padding: 20px;
gap: 15px; /* Space between messages */
display: flex;
flex-direction: column;
}
.message-bubble {
max-width: 80%;
margin: 10px;
padding: 15px;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.2s ease;
}
.assistant-message {
background-color: #797D7F; /* Tailwind's gray-800 - a nice light shade for dark theme */
margin-right: auto;
max-width: 80%;
margin: 10px;
padding: 15px;
border-radius: 10px;
border: 1px solid #4a5568; /* Subtle border for definition */
color: #e5e7eb; /* Light text color for contrast */
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
/* For user messages (to maintain contrast) */
.user-message {
background-color: #283747; /* Slightly darker than assistant messages */
margin-left: auto;
max-width: 80%;
margin: 10px;
padding: 15px;
border-radius: 10px;
border: 1px solid #4a5568;
color: #e5e7eb;
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.chat-list {
max-height: calc(100vh - 100px);
overflow-y: auto;
background-color: #1a1a1a;
border-right: 1px solid #4a5568;
color: #e6e6e6;
}
.chat-item {
padding: 10px 15px;
border-bottom: 1px solid #2d3748;
color: #e6e6e6;
}
.chat-item:hover {
background-color: #2d3748;
transition: background-color 0.2s ease;
}
.active-chat {
background-color: #3b82f6;
color: #ffffff;
}
.copy-button {
position: absolute;
right: 10px;
top: 5px;
padding: 4px 8px;
background-color: #3b82f6;
color: #ffffff;
border-radius: 4px;
font-size: 12px;
opacity: 0.9;
cursor: pointer;
transition: all 0.2s ease;
border: 1px solid #60a5fa;
}
.copy-button:hover {
opacity: 1;
background-color: #60a5fa;
}
.code-block-wrapper {
position: relative;
margin: 1em 0;
background-color: #1e293b;
border: 1px solid #4a5568;
border-radius: 6px;
}
.copy-tooltip {
position: absolute;
background-color: #2d3748;
color: #ffffff;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
right: 10px;
top: -25px;
opacity: 0;
transition: opacity 0.2s;
border: 1px solid #4a5568;
}
.copy-tooltip.show {
opacity: 1;
}
.message-footer {
display: flex;
justify-content: flex-end;
margin-top: 10px;
padding-top: 8px;
border-top: 1px solid #4a5568;
}
.retry-button {
display: inline-flex;
align-items: center;
padding: 6px 12px;
background-color: #2d3748;
color: #e6e6e6;
border: 1px solid #4a5568;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s ease;
}
.retry-button:hover {
background-color: #4a5568;
color: #ffffff;
}
.retry-button svg {
width: 14px;
height: 14px;
margin-right: 4px;
}
.retry-button.loading {
opacity: 0.7;
cursor: not-allowed;
}
.code-block-wrapper {
position: relative;
margin: 1em 0;
padding-top: 40px;
background-color: #1e293b;
border: 1px solid #4a5568;
border-radius: 6px;
}
.test-button {
position: absolute;
top: 5px;
right: 120px;
background-color: #3b82f6;
color: #ffffff;
padding: 6px 12px;
border-radius: 4px;
font-size: 12px;
border: 1px solid #60a5fa;
cursor: pointer;
transition: all 0.2s ease;
}
.test-button:hover {
background-color: #60a5fa;
}
.test-results {
margin-top: 8px;
padding: 12px;
background-color: #1e293b;
border-left: 4px solid #4a5568;
font-family: monospace;
white-space: pre-wrap;
display: none;
color: #e6e6e6;
}
.test-results.show {
display: block;
}
.test-results.error {
border-left-color: #ef4444;
background-color: #2d1f1f;
}
.test-results.success {
border-left-color: #10b981;
background-color: #1a2e1f;
}
.loading-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #ffffff;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.upload-button {
position: relative;
overflow: hidden;
display: inline-block;
background-color: #3b82f6;
color: #ffffff;
border: 1px solid #60a5fa;
border-radius: 4px;
transition: all 0.2s ease;
padding: 6px 12px;
}
.upload-button:hover {
background-color: #60a5fa;
}
.upload-button input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
opacity: 0;
cursor: pointer;
}
/* New Chat button specific styling */
.new-chat-button {
background-color: #3b82f6;
color: #ffffff;
padding: 8px 16px;
border-radius: 6px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.new-chat-button:hover {
background-color: #60a5fa;
}
/* Chat title styles */
.chat-title {
color: #e6e6e6;
font-weight: 600;
margin-bottom: 4px;
}
/* Chat date styles */
.chat-date {
color: #9ca3af;
font-size: 0.875rem;
}
/* Message input area */
.message-input {
background-color: #1e293b;
border: 1px solid #4a5568;
color: #e6e6e6;
border-radius: 6px;
padding: 12px;
width: 100%;
margin-bottom: 10px;
}
.message-input:focus {
outline: none;
border-color: #60a5fa;
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
}
/* Send button */
.send-button {
background-color: #3b82f6;
color: #ffffff;
padding: 8px 16px;
border-radius: 6px;
border: none;
cursor: pointer;
transition: all 0.2s ease;
font-weight: 500;
}
.send-button:hover {
background-color: #60a5fa;
}
.header-container {
text-align: center;
padding: 1rem 0;
margin-bottom: 2rem;
position: relative;
overflow: hidden;
}
.title-wrapper {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 1.5rem;
background: linear-gradient(135deg, rgba(59, 130, 246, 0.1), rgba(147, 197, 253, 0.1));
border-radius: 12px;
border: 1px solid rgba(59, 130, 246, 0.2);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.title {
font-size: 1.75rem;
font-weight: 700;
background: linear-gradient(135deg, #60A5FA, #3B82F6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
position: relative;
}
.logo-icon {
width: 32px;
height: 32px;
animation: pulse 2s infinite;
}
.status-dot {
width: 8px;
height: 8px;
background-color: #10B981;
border-radius: 50%;
position: relative;
display: inline-block;
margin-left: 0.5rem;
animation: blink 2s infinite;
}
/* Floating particles */
.particle {
position: absolute;
width: 4px;
height: 4px;
background: rgba(59, 130, 246, 0.2);
border-radius: 50%;
pointer-events: none;
}
.particle:nth-child(1) { animation: float-1 8s infinite; }
.particle:nth-child(2) { animation: float-2 10s infinite; }
.particle:nth-child(3) { animation: float-3 7s infinite; }
.particle:nth-child(4) { animation: float-4 9s infinite; }
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
@keyframes float-1 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(20px, -20px); }
}
@keyframes float-2 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-15px, -25px); }
}
@keyframes float-3 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(25px, -15px); }
}
@keyframes float-4 {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(-20px, -10px); }
}
.model-label {
position: fixed;
top: 1rem;
right: 1rem;
background-color: #f3f4f6;
color: #4b5563;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
z-index: 50;
}
.loading-indicator {
position: sticky;
bottom: 0;
left: 0;
right: 0;
background: rgba(17, 24, 39, 0.95);
padding: 1rem;
border-top: 1px solid #4b5563;
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.loading-indicator.hidden {
display: none;
}
.loading-bar {
width: 200px;
height: 4px;
background: #374151;
border-radius: 2px;
overflow: hidden;
}
.loading-progress {
width: 40%;
height: 100%;
background: #3b82f6;
border-radius: 2px;
animation: moveProgress 1.5s infinite ease-in-out;
}
.loading-text {
color: #9ca3af;
font-size: 0.875rem;
font-weight: 500;
}
@keyframes moveProgress {
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}
</style>
</head>
<body class="bg-gray-900">
<div class="model-label">Mistral:7b</div>
<div class="container mx-auto px-4 py-8">
<h1 class="text-2xl font-bold text-blue-500 text-center mb-6">Figr AI Code Assistant</h1>
<div class="flex gap-4">
<!-- Sidebar with chat list -->
<div class="w-1/4 bg-gray-800 rounded-lg shadow-lg p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-gray-100">Chats</h2>
<button onclick="createNewChat()" class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600">
New Chat
</button>
</div>
<div id="chatList" class="chat-list">
<!-- Chat items will be dynamically added here -->
</div>
</div>
<!-- Main chat area -->
<div class="w-3/4 bg-gray-800 rounded-lg shadow-lg p-4">
<div id="chatArea" class="chat-area mb-4">
<!-- Messages will be dynamically added here -->
</div>
<!-- Input area -->
<div class="flex gap-2">
<textarea
id="userInput"
class="w-full p-2 border rounded-lg resize-none bg-gray-700 text-gray-100"
rows="3"
placeholder="Type your message here..."
></textarea>
<div class="flex flex-col gap-2">
<label class="relative cursor-pointer bg-gray-700 hover:bg-gray-600 p-2 rounded-lg flex items-center justify-center">
<input type="file"
id="fileInput"
accept=".py"
class="hidden"
onchange="handleFileUpload(event)"/>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-blue-400">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
</label>
<button
onclick="sendMessage()"
class="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600"
>
Send
</button>
</div>
</div>
<div id="loadingIndicator" class="loading-indicator hidden">
<div class="loading-bar">
<div class="loading-progress"></div>
</div>
<div class="loading-text">Generating...</div>
</div>
<!-- Important Information -->
<div class="mt-4">
<h3 class="text-lg font-bold mb-2 text-gray-100">Important Information</h3>
<div id="importantInfo" class="bg-gray-800 p-4 rounded-lg text-gray-100 border border-gray-700">
<!-- Important info will be added here -->
</div>
</div>
</div>
</div>
</div>
<script>
let currentSessionId = null;
// Initialize marked with syntax highlighting
marked.setOptions({
highlight: function(code, lang) {
return hljs.highlight(code, {language: lang || 'plaintext'}).value;
}
});
const renderer = new marked.Renderer();
renderer.code = function(code, language) {
const highlightedCode = language ? hljs.highlight(code, {language}).value : hljs.highlightAuto(code).value;
return `
<div class="code-block-wrapper">
<button class="test-button">Test Code</button>
<button class="copy-button">Copy Code</button>
<pre><code class="hljs ${language || ''}">${highlightedCode}</code></pre>
<div class="test-results"></div>
</div>
`;
};
marked.setOptions({ renderer });
// Create new chat
async function createNewChat() {
try {
const response = await fetch('/api/new-chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (data.success) {
currentSessionId = data.chat.id;
await loadChatList();
clearChatArea();
}
} catch (error) {
console.error('Error creating new chat:', error);
}
}
// Load chat list
async function loadChatList() {
try {
const response = await fetch('/api/chat-list');
const data = await response.json();
const chatListElement = document.getElementById('chatList');
chatListElement.innerHTML = '';
data.chats.forEach(chat => {
const chatElement = document.createElement('div');
chatElement.className = `chat-item p-3 cursor-pointer rounded ${
chat.id === currentSessionId ? 'active-chat' : ''
}`;
chatElement.onclick = () => loadChat(chat.id);
chatElement.innerHTML = `
<div class="font-medium">${chat.title || 'New Chat'}</div>
<div class="text-sm text-gray-500">${new Date(chat.date).toLocaleDateString()}</div>
`;
chatListElement.appendChild(chatElement);
});
} catch (error) {
console.error('Error loading chat list:', error);
}
}
// Load specific chat
async function loadChat(sessionId) {
currentSessionId = sessionId;
try {
const response = await fetch(`/api/chat-history?sessionId=${sessionId}`);
const data = await response.json();
displayChatHistory(data.history);
displayImportantInfo(data.important_info);
await loadChatList(); // Refresh chat list to update active state
} catch (error) {
console.error('Error loading chat:', error);
}
}
// Display chat history
function displayChatHistory(history) {
const chatArea = document.getElementById('chatArea');
chatArea.innerHTML = '';
history.forEach(message => {
displayMessage(message.role, message.content);
});
scrollToBottom();
}
// Display important information
function displayImportantInfo(info) {
const infoElement = document.getElementById('importantInfo');
infoElement.innerHTML = info.length > 0
? info.map(item => `<p>• ${item}</p>`).join('')
: '<p>No important information yet</p>';
}
// Send message
async function sendMessage() {
const userInput = document.getElementById('userInput');
const message = userInput.value.trim();
const loadingIndicator = document.getElementById('loadingIndicator');
if (!message) return;
if (!currentSessionId) {
await createNewChat();
}
displayMessage('user', message);
userInput.value = '';
scrollToBottom();
// Show loading indicator
loadingIndicator.classList.remove('hidden');
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: message,
sessionId: currentSessionId
})
});
const data = await response.json();
// Hide loading indicator
loadingIndicator.classList.add('hidden');
if (data.success) {
displayMessage('assistant', data.response);
displayImportantInfo(data.important_info);
await loadChatList();
} else {
displayMessage('assistant', 'Error: ' + data.response);
}
} catch (error) {
// Hide loading indicator on error too
loadingIndicator.classList.add('hidden');
console.error('Error sending message:', error);
displayMessage('assistant', 'Error sending message');
}
scrollToBottom();
}
async function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
if (!file.name.endsWith('.py')) {
alert('Please upload only Python (.py) files');
return;
}
const formData = new FormData();
formData.append('file', file);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.success) {
// Add the file content and analysis to the chat
const message = `I've uploaded a Python file named "${data.filename}". Here's the code:\n\n\`\`\`python\n${data.content}\n\`\`\``;
document.getElementById('userInput').value = message;
await sendMessage();
// Send the analysis as an assistant message
const analysisMessage = data.analysis;
displayMessage('assistant', analysisMessage);
// Clear the file input
event.target.value = '';
} else {
alert(data.error || 'Error uploading file');
}
} catch (error) {
console.error('Error:', error);
alert('Error uploading file');
}
}
// Display a single message
function displayMessage(role, content) {
const chatArea = document.getElementById('chatArea');
const messageDiv = document.createElement('div');
messageDiv.className = `message-bubble ${role}-message`;
// Create a div for message content
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
// Custom renderer for marked to wrap code blocks (same as before)
const renderer = new marked.Renderer();
renderer.code = function(code, language) {
const highlightedCode = language ? hljs.highlight(code, {language}).value : hljs.highlightAuto(code).value;
// Simplified HTML structure with both buttons
const buttonsHtml = `
<button class="test-button">Test Code</button>
<button class="copy-button">Copy Code</button>
`;
return `
<div class="code-block-wrapper">
${buttonsHtml}
<pre><code class="hljs ${language || ''}">${highlightedCode}</code></pre>
<div class="test-results"></div>
</div>
`;
};
marked.setOptions({ renderer });
// Parse markdown and render HTML
contentDiv.innerHTML = marked.parse(content);
messageDiv.appendChild(contentDiv);
// Immediately initialize code blocks after adding content
messageDiv.querySelectorAll('.code-block-wrapper').forEach(wrapper => {
// Ensure buttons exist
if (!wrapper.querySelector('.test-button') || !wrapper.querySelector('.copy-button')) {
const buttonsDiv = document.createElement('div');
buttonsDiv.innerHTML = `
<button class="test-button">Test Code</button>
<button class="copy-button">Copy Code</button>
`;
wrapper.insertBefore(buttonsDiv, wrapper.firstChild);
}
const copyButton = wrapper.querySelector('.copy-button');
const testButton = wrapper.querySelector('.test-button');
const codeElement = wrapper.querySelector('code');
const resultsElement = wrapper.querySelector('.test-results') || (() => {
const div = document.createElement('div');
div.className = 'test-results';
wrapper.appendChild(div);
return div;
})();
// Copy button handler
copyButton.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(codeElement.textContent);
copyButton.textContent = 'Copied!';
setTimeout(() => {
copyButton.textContent = 'Copy Code';
}, 2000);
} catch (err) {
console.error('Failed to copy:', err);
}
});
// Test button handler
testButton.addEventListener('click', async () => {
testButton.textContent = 'Testing...';
testButton.disabled = true;
try {
const response = await fetch('/api/test-code', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
code: codeElement.textContent
})
});
const data = await response.json();
resultsElement.textContent = data.output || 'Code executed successfully with no output';
resultsElement.className = `test-results show ${data.success ? 'success' : 'error'}`;
} catch (error) {
resultsElement.textContent = `Error: ${error.message}`;
resultsElement.className = 'test-results show error';
} finally {
testButton.textContent = 'Test Code';
testButton.disabled = false;
}
});
});
// Add retry button for assistant messages
if (role === 'assistant') {
const footer = document.createElement('div');
footer.className = 'message-footer';
footer.innerHTML = `
<button class="retry-button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M2.5 2v6h6M21.5 22v-6h-6"/>
<path d="M22 11.5A10 10 0 003.2 7.2M2 12.5a10 10 0 0018.8 4.2"/>
</svg>
Retry
</button>
`;
messageDiv.appendChild(footer);
// Add click handler for retry button
const retryButton = footer.querySelector('.retry-button');
const lastUserMessage = findLastUserMessage();
if (lastUserMessage) {
retryButton.addEventListener('click', () => retryMessage(lastUserMessage, messageDiv));
}
}
// Add copy button functionality
addCopyButtons(contentDiv);
chatArea.appendChild(messageDiv);
}
// Helper function to find the last user message before an assistant message
function findLastUserMessage() {
const chatArea = document.getElementById('chatArea');
const messages = chatArea.querySelectorAll('.message-bubble');
let lastUserMessage = null;
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].classList.contains('user-message')) {
const contentDiv = messages[i].querySelector('.message-content');
if (contentDiv) {
lastUserMessage = contentDiv.textContent;
break;
}
}
}
return lastUserMessage;
}
// Clear chat area
function clearChatArea() {
document.getElementById('chatArea').innerHTML = '';
document.getElementById('importantInfo').innerHTML = '<p>No important information yet</p>';
document.getElementById('userInput').value = '';
}
// Scroll chat area to bottom
function scrollToBottom() {
const chatArea = document.getElementById('chatArea');
chatArea.scrollTop = chatArea.scrollHeight;
}
// Handle Enter key in textarea
document.getElementById('userInput').addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
// Initialize
window.onload = async function() {
await loadChatList();
};
async function copyCode(codeElement, tooltipElement) {
try {
await navigator.clipboard.writeText(codeElement.textContent);
tooltipElement.textContent = 'Copied!';
tooltipElement.classList.add('show');
setTimeout(() => {
tooltipElement.classList.remove('show');
}, 1500);
} catch (err) {
console.error('Failed to copy code:', err);
tooltipElement.textContent = 'Failed to copy';
tooltipElement.classList.add('show');
setTimeout(() => {
tooltipElement.classList.remove('show');
}, 1500);
}
}
async function retryMessage(originalMessage, messageElement) {
if (!currentSessionId) return;
const retryButton = messageElement.querySelector('.retry-button');
// Show loading state
retryButton.classList.add('loading');
retryButton.innerHTML = `
<svg class="animate-spin" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
Regenerating...
`;
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: originalMessage,
sessionId: currentSessionId
})
});
const data = await response.json();
if (data.success) {
// Replace the old message content with new content
const contentDiv = messageElement.querySelector('.message-content');
contentDiv.innerHTML = marked.parse(data.response);
// Reapply syntax highlighting
contentDiv.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
});
// Add copy buttons to new code blocks
addCopyButtons(contentDiv);
// Update important info
displayImportantInfo(data.important_info);
} else {
alert('Error regenerating response: ' + data.response);
}
} catch (error) {
console.error('Error regenerating message:', error);
alert('Error regenerating response');
} finally {
// Restore retry button
retryButton.classList.remove('loading');
retryButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor">
<path d="M2.5 2v6h6M21.5 22v-6h-6"/>
<path d="M22 11.5A10 10 0 003.2 7.2M2 12.5a10 10 0 0018.8 4.2"/>
</svg>
Retry
`;
}
}
// Helper function to add copy buttons to code blocks
function addCopyButtons(element) {
element.querySelectorAll('.code-block-wrapper').forEach(wrapper => {
const copyButton = wrapper.querySelector('.copy-button');
const codeElement = wrapper.querySelector('code');
const tooltipElement = wrapper.querySelector('.copy-tooltip');
copyButton.addEventListener('click', () => copyCode(codeElement, tooltipElement));
copyButton.addEventListener('mouseenter', () => {
tooltipElement.textContent = 'Copy';
tooltipElement.classList.add('show');
});
copyButton.addEventListener('mouseleave', () => {
if (tooltipElement.textContent === 'Copy') {
tooltipElement.classList.remove('show');
}
});
});
}
</script>
</body>
</html>