Skip to content

Commit

Permalink
new flask app
Browse files Browse the repository at this point in the history
  • Loading branch information
Doriandarko committed Dec 6, 2024
1 parent bfe3b9f commit f49b300
Show file tree
Hide file tree
Showing 5 changed files with 434 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

uploads/
*.pyc
.DS_Store
.env
Expand Down
64 changes: 64 additions & 0 deletions app.py
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)
102 changes: 102 additions & 0 deletions static/css/style.css
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 */
}
184 changes: 184 additions & 0 deletions static/js/chat.js
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();
});
Loading

0 comments on commit f49b300

Please sign in to comment.