forked from Doriandarko/claude-engineer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bfe3b9f
commit f49b300
Showing
5 changed files
with
434 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
|
||
uploads/ | ||
*.pyc | ||
.DS_Store | ||
.env | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from flask import Flask, render_template, request, jsonify, url_for | ||
from ce3 import Assistant | ||
import os | ||
from werkzeug.utils import secure_filename | ||
import base64 | ||
|
||
app = Flask(__name__, static_folder='static') | ||
app.config['UPLOAD_FOLDER'] = 'uploads' | ||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max file size | ||
|
||
# Ensure upload directory exists | ||
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | ||
|
||
# Initialize the assistant | ||
assistant = Assistant() | ||
|
||
@app.route('/') | ||
def home(): | ||
return render_template('index.html') | ||
|
||
@app.route('/chat', methods=['POST']) | ||
def chat(): | ||
data = request.json | ||
message = data.get('message', '') | ||
|
||
# Handle the chat message | ||
response = assistant.chat(message) | ||
|
||
# Return both the response and thinking state | ||
return jsonify({ | ||
'response': response, | ||
'thinking': False | ||
}) | ||
|
||
@app.route('/upload', methods=['POST']) | ||
def upload_file(): | ||
if 'file' not in request.files: | ||
return jsonify({'error': 'No file part'}), 400 | ||
|
||
file = request.files['file'] | ||
if file.filename == '': | ||
return jsonify({'error': 'No selected file'}), 400 | ||
|
||
if file and file.filename.lower().endswith(('.png', '.jpg', '.jpeg', '.gif')): | ||
filename = secure_filename(file.filename) | ||
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) | ||
file.save(filepath) | ||
|
||
# Convert image to base64 | ||
with open(filepath, "rb") as image_file: | ||
encoded_string = base64.b64encode(image_file.read()).decode('utf-8') | ||
|
||
# Clean up the file | ||
os.remove(filepath) | ||
|
||
return jsonify({ | ||
'success': True, | ||
'image_data': encoded_string | ||
}) | ||
|
||
return jsonify({'error': 'Invalid file type'}), 400 | ||
|
||
if __name__ == '__main__': | ||
app.run(debug=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* Custom scrollbar */ | ||
::-webkit-scrollbar { | ||
width: 8px; | ||
} | ||
::-webkit-scrollbar-track { | ||
background: transparent; | ||
} | ||
::-webkit-scrollbar-thumb { | ||
background: #cbd5e1; | ||
border-radius: 4px; | ||
} | ||
::-webkit-scrollbar-thumb:hover { | ||
background: #94a3b8; | ||
} | ||
|
||
/* Code block styling */ | ||
pre { | ||
background: #f8fafc; | ||
border-radius: 6px; | ||
padding: 1rem; | ||
margin: 0.5rem 0; | ||
overflow-x: auto; | ||
} | ||
code { | ||
font-family: ui-monospace, monospace; | ||
font-size: 0.9em; | ||
} | ||
|
||
/* Chat container styles */ | ||
.chat-container { | ||
display: flex; | ||
flex-direction: column; | ||
height: 100vh; | ||
overflow: hidden; | ||
} | ||
|
||
.messages-container { | ||
flex: 1; | ||
overflow-y: auto; | ||
padding: 1rem; | ||
padding-bottom: 2rem; | ||
} | ||
|
||
.input-container { | ||
position: sticky; | ||
bottom: 0; | ||
background-color: white; | ||
border-top: 1px solid #e5e7eb; | ||
padding: 1rem; | ||
margin: 0 -1rem; | ||
box-shadow: 0 -4px 6px -1px rgb(0 0 0 / 0.05); | ||
} | ||
|
||
@keyframes pulse { | ||
0%, 100% { opacity: 1; } | ||
50% { opacity: 0.5; } | ||
} | ||
|
||
.thinking { | ||
display: flex; | ||
align-items: center; | ||
gap: 0.5rem; | ||
color: #6b7280; | ||
font-size: 0.875rem; | ||
} | ||
|
||
.thinking::before { | ||
content: ''; | ||
width: 0.5rem; | ||
height: 0.5rem; | ||
background-color: currentColor; | ||
border-radius: 50%; | ||
animation: pulse 1.5s ease-in-out infinite; | ||
} | ||
|
||
.thinking-dots span { | ||
animation: pulse 1.5s ease-in-out infinite; | ||
display: inline-block; | ||
margin-right: 2px; | ||
} | ||
|
||
.thinking-dots span:nth-child(2) { | ||
animation-delay: 0.2s; | ||
} | ||
|
||
.thinking-dots span:nth-child(3) { | ||
animation-delay: 0.4s; | ||
} | ||
|
||
/* Update the messages spacing */ | ||
.message-wrapper { | ||
margin-bottom: 1.5rem; /* Increased vertical spacing */ | ||
} | ||
|
||
.message-wrapper:last-child { | ||
margin-bottom: 0; | ||
} | ||
|
||
/* Update the primary color to Tailwind black */ | ||
.ai-avatar { | ||
background-color: #111827; /* Tailwind black-900 */ | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
let currentImageData = null; | ||
|
||
// Auto-resize textarea | ||
const textarea = document.getElementById('message-input'); | ||
textarea.addEventListener('input', function() { | ||
this.style.height = '28px'; | ||
this.style.height = (this.scrollHeight) + 'px'; | ||
}); | ||
|
||
function appendMessage(content, isUser = false) { | ||
const messagesDiv = document.getElementById('chat-messages'); | ||
const messageWrapper = document.createElement('div'); | ||
messageWrapper.className = 'message-wrapper'; | ||
|
||
const messageDiv = document.createElement('div'); | ||
messageDiv.className = 'flex items-start space-x-4'; | ||
|
||
// Avatar | ||
const avatarDiv = document.createElement('div'); | ||
if (isUser) { | ||
avatarDiv.className = 'w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center text-gray-600 font-bold text-sm'; | ||
avatarDiv.textContent = 'You'; | ||
} else { | ||
avatarDiv.className = 'w-8 h-8 rounded-full ai-avatar flex items-center justify-center text-white font-bold text-sm'; | ||
avatarDiv.textContent = 'CE'; | ||
} | ||
|
||
// Message content | ||
const contentDiv = document.createElement('div'); | ||
contentDiv.className = 'flex-1'; | ||
|
||
const innerDiv = document.createElement('div'); | ||
innerDiv.className = 'prose prose-slate max-w-none'; | ||
|
||
if (!isUser && content) { | ||
try { | ||
innerDiv.innerHTML = marked.parse(content); | ||
} catch (e) { | ||
console.error('Error parsing markdown:', e); | ||
innerDiv.textContent = content; | ||
} | ||
} else { | ||
innerDiv.textContent = content || ''; | ||
} | ||
|
||
contentDiv.appendChild(innerDiv); | ||
messageDiv.appendChild(avatarDiv); | ||
messageDiv.appendChild(contentDiv); | ||
messageWrapper.appendChild(messageDiv); | ||
messagesDiv.appendChild(messageWrapper); | ||
messagesDiv.scrollTop = messagesDiv.scrollHeight; | ||
} | ||
|
||
// Event Listeners | ||
document.getElementById('upload-btn').addEventListener('click', () => { | ||
document.getElementById('file-input').click(); | ||
}); | ||
|
||
document.getElementById('file-input').addEventListener('change', async (e) => { | ||
const file = e.target.files[0]; | ||
if (file) { | ||
const formData = new FormData(); | ||
formData.append('file', file); | ||
|
||
try { | ||
const response = await fetch('/upload', { | ||
method: 'POST', | ||
body: formData | ||
}); | ||
const data = await response.json(); | ||
|
||
if (data.success) { | ||
currentImageData = data.image_data; | ||
document.getElementById('preview-img').src = `data:image/png;base64,${data.image_data}`; | ||
document.getElementById('image-preview').classList.remove('hidden'); | ||
} | ||
} catch (error) { | ||
console.error('Error uploading image:', error); | ||
} | ||
} | ||
}); | ||
|
||
document.getElementById('remove-image').addEventListener('click', () => { | ||
currentImageData = null; | ||
document.getElementById('image-preview').classList.add('hidden'); | ||
document.getElementById('file-input').value = ''; | ||
}); | ||
|
||
function appendThinkingIndicator() { | ||
const messagesDiv = document.getElementById('chat-messages'); | ||
const messageWrapper = document.createElement('div'); | ||
messageWrapper.className = 'message-wrapper thinking-message'; | ||
|
||
const messageDiv = document.createElement('div'); | ||
messageDiv.className = 'flex items-start space-x-4'; | ||
|
||
// AI Avatar | ||
const avatarDiv = document.createElement('div'); | ||
avatarDiv.className = 'w-8 h-8 rounded-full ai-avatar flex items-center justify-center text-white font-bold text-sm'; | ||
avatarDiv.textContent = 'CE'; | ||
|
||
// Thinking content | ||
const contentDiv = document.createElement('div'); | ||
contentDiv.className = 'flex-1'; | ||
|
||
const thinkingDiv = document.createElement('div'); | ||
thinkingDiv.className = 'thinking'; | ||
thinkingDiv.innerHTML = 'Thinking<span class="thinking-dots"><span>.</span><span>.</span><span>.</span></span>'; | ||
|
||
contentDiv.appendChild(thinkingDiv); | ||
messageDiv.appendChild(avatarDiv); | ||
messageDiv.appendChild(contentDiv); | ||
messageWrapper.appendChild(messageDiv); | ||
messagesDiv.appendChild(messageWrapper); | ||
messagesDiv.scrollTop = messagesDiv.scrollHeight; | ||
|
||
return messageWrapper; | ||
} | ||
|
||
document.getElementById('chat-form').addEventListener('submit', async (e) => { | ||
e.preventDefault(); | ||
|
||
const messageInput = document.getElementById('message-input'); | ||
const message = messageInput.value.trim(); | ||
|
||
if (!message && !currentImageData) return; | ||
|
||
// Append user message | ||
appendMessage(message, true); | ||
|
||
// Clear input and reset height | ||
messageInput.value = ''; | ||
resetTextarea(); | ||
|
||
try { | ||
// Add thinking indicator | ||
const thinkingMessage = appendThinkingIndicator(); | ||
|
||
const response = await fetch('/chat', { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify({ | ||
message, | ||
image: currentImageData | ||
}) | ||
}); | ||
|
||
const data = await response.json(); | ||
|
||
// Remove thinking indicator | ||
if (thinkingMessage) { | ||
thinkingMessage.remove(); | ||
} | ||
|
||
// Show response if we have one | ||
if (data && data.response) { | ||
appendMessage(data.response); | ||
} else { | ||
appendMessage('Error: No response received'); | ||
} | ||
|
||
// Clear image after sending | ||
currentImageData = null; | ||
document.getElementById('image-preview').classList.add('hidden'); | ||
document.getElementById('file-input').value = ''; | ||
|
||
} catch (error) { | ||
console.error('Error sending message:', error); | ||
// Remove thinking indicator if it exists | ||
document.querySelector('.thinking-message')?.remove(); | ||
appendMessage('Error: Failed to send message'); | ||
} | ||
}); | ||
|
||
function resetTextarea() { | ||
const textarea = document.getElementById('message-input'); | ||
textarea.style.height = '28px'; | ||
} | ||
|
||
document.getElementById('chat-form').addEventListener('reset', () => { | ||
resetTextarea(); | ||
}); |
Oops, something went wrong.