GratiCraft / static /index.html
yasserrmd's picture
Update static/index.html
64e9f52 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Gratitude Journal</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.11.1/font/bootstrap-icons.min.css">
<style>
:root {
--bg-primary: #0A0B1E;
--bg-secondary: #12142B;
--accent-primary: #7B68EE;
--accent-secondary: #00D4FF;
--accent-tertiary: #FF69B4;
--text-primary: #E2E8F0;
--text-secondary: #94A3B8;
--border-light: rgba(255, 255, 255, 0.08);
--glass: rgba(255, 255, 255, 0.03);
--card-bg: rgba(18, 20, 43, 0.95);
}
@keyframes gradientFlow {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes floatingGlow {
0% { box-shadow: 0 0 40px rgba(123, 104, 238, 0.2); }
50% { box-shadow: 0 0 60px rgba(0, 212, 255, 0.2); }
100% { box-shadow: 0 0 40px rgba(123, 104, 238, 0.2); }
}
body {
font-family: 'SF Pro Display', -apple-system, BlinkMacSystemFont, sans-serif;
margin: 0;
min-height: 100vh;
background: linear-gradient(135deg, var(--bg-primary), var(--bg-secondary));
color: var(--text-primary);
padding: 2rem;
line-height: 1.6;
}
.container {
max-width: 1600px;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2.5rem;
}
.header {
grid-column: 1 / -1;
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2.5rem;
background: linear-gradient(180deg,
rgba(123, 104, 238, 0.05) 0%,
rgba(0, 212, 255, 0.05) 100%);
border-radius: 2rem;
border: 1px solid var(--border-light);
backdrop-filter: blur(20px);
animation: floatingGlow 6s infinite;
}
.title-section {
flex: 1;
}
.header h1 {
font-size: 2.5rem;
margin: 0;
background: linear-gradient(135deg, #FFFFFF 0%, #7B68EE 50%, #00D4FF 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 200% 200%;
animation: gradientFlow 6s ease infinite;
letter-spacing: -1px;
font-weight: 800;
}
.header p {
color: var(--text-secondary);
font-size: 1rem;
margin: 0.5rem 0 0 0;
font-weight: 300;
letter-spacing: 0.5px;
}
.powered-by {
display: flex;
align-items: center;
gap: 2rem;
margin-left: 2rem;
padding-left: 2rem;
border-left: 1px solid var(--border-light);
}
.powered-by a {
opacity: 0.7;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
transform: translateY(0);
}
.powered-by a:hover {
opacity: 1;
transform: translateY(-3px);
}
.powered-by img {
height: 24px;
/*filter: brightness(0) invert(1);*/
}
.input-card {
background: var(--card-bg);
border-radius: 2rem;
padding: 2.5rem;
border: 1px solid var(--border-light);
backdrop-filter: blur(20px);
height: calc(100vh - 200px);
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
.input-card:hover {
box-shadow: 0 25px 50px rgba(123, 104, 238, 0.1);
}
textarea {
flex: 1;
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-light);
border-radius: 1.5rem;
padding: 1.5rem;
color: var(--text-primary);
font-size: 1.1rem;
resize: none;
transition: all 0.3s ease;
margin-bottom: 2rem;
}
textarea:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(123, 104, 238, 0.1);
}
.media-section {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
margin-bottom: 2rem;
}
.media-upload {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-light);
border-radius: 1.5rem;
padding: 2rem;
text-align: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.media-upload:hover {
border-color: var(--accent-primary);
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(123, 104, 238, 0.1);
}
.media-upload i {
font-size: 2.5rem;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 1rem;
display: block;
}
.generate-button {
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: white;
border: none;
padding: 1.75rem;
border-radius: 1.5rem;
font-size: 1.25rem;
font-weight: 600;
letter-spacing: 1.5px;
text-transform: uppercase;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
}
.generate-button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.2),
transparent
);
transition: transform 0.6s;
}
.generate-button:hover {
transform: translateY(-5px);
box-shadow: 0 20px 40px rgba(123, 104, 238, 0.3);
}
.generate-button:hover::before {
transform: translateX(200%);
}
.messages {
background: var(--card-bg);
border-radius: 2rem;
padding: 2.5rem;
border: 1px solid var(--border-light);
backdrop-filter: blur(20px);
height: calc(100vh - 200px);
overflow-y: auto;
}
.message {
background: rgba(255, 255, 255, 0.02);
border: 1px solid var(--border-light);
border-radius: 1.5rem;
padding: 2rem;
margin-bottom: 1.5rem;
transition: all 0.3s ease;
}
.message:hover {
transform: translateX(5px);
border-color: var(--accent-primary);
}
.message.user {
border-left: 4px solid var(--accent-secondary);
}
.message.ai {
border-left: 4px solid var(--accent-primary);
}
.message.img {
border-left: 4px solid var(--accent-tertiary);
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.02);
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: rgba(123, 104, 238, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(123, 104, 238, 0.5);
}
@media (max-width: 1200px) {
.container {
grid-template-columns: 1fr;
}
.header {
flex-direction: column;
text-align: center;
padding: 2rem;
}
.powered-by {
margin: 1.5rem 0 0 0;
padding: 1.5rem 0 0 0;
border-left: none;
border-top: 1px solid var(--border-light);
}
.input-card, .messages {
height: 600px;
}
}
@media (max-width: 768px) {
.header h1 {
font-size: 2rem;
}
.powered-by {
flex-wrap: wrap;
justify-content: center;
gap: 1.5rem;
}
.media-section {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="title-section">
<h1>AI Gratitude Journal</h1>
<p>Transform your thoughts into meaningful reflections</p>
</div>
<div class="powered-by">
<a href="https://groq.com" target="_blank" rel="noopener noreferrer">
<img src="https://groq.com/wp-content/uploads/2024/03/PBG-mark1-color.svg" alt="Powered by Groq">
</a>
<a href="https://huggingface.co" target="_blank" rel="noopener noreferrer" >
<img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="Hugging Face" >
</a>
<a href="https://falcons.ai" target="_blank" rel="noopener noreferrer">
<img src="https://falcons.ai/assets/images/icons-red/logo.png" alt="Falcons.ai">
</a>
<a href="https://blackforestlabs.ai/" target="_blank" rel="noopener noreferrer">
<img src="https://blackforestlabs.ai/wp-content/uploads/2024/08/white_logo-1280x1213.png" alt="Black Forest Lab">
</a>
</div>
</div>
<div class="input-card">
<form id="journalForm">
<textarea
placeholder="Share your gratitude..."
id="textInput"
style="width: 90%;"></textarea>
<div class="media-section" style="display: none;">
<div class="media-upload" id="imageUpload">
<i class="bi bi-image"></i>
<span>Add Images</span>
<input type="file" id="imageInput" multiple accept="image/*" style="display: none;">
</div>
<div class="media-upload" id="audioUpload">
<i class="bi bi-mic"></i>
<span>Add Voice Note</span>
<input type="file" id="audioInput" accept="audio/*" style="display: none;">
</div>
</div>
<button type="submit" class="generate-button">
<i class="bi bi-stars"></i>
Generate
</button>
</form>
</div>
<div class="messages" id="messages"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('journalForm');
const textInput = document.getElementById('textInput');
const imageInput = document.getElementById('imageInput');
const audioInput = document.getElementById('audioInput');
const imageUpload = document.getElementById('imageUpload');
const audioUpload = document.getElementById('audioUpload');
const messages = document.querySelector('.messages');
let imageFiles = [];
let audioFile = null;
let ws;
// Function to connect/reconnect WebSocket
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
ws = new WebSocket(`${protocol}//${window.location.host}/ws`);
ws.onopen = () => {
addMessage('Connected to the server.', 'system');
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === 'journal') {
const htmlContent = marked.parse(message.content); // Convert markdown to HTML
addMessage(htmlContent, 'ai', true);
} else if (message.type === 'info') {
const htmlContent = marked.parse(message.content); // Convert markdown to HTML
addMessage(htmlContent, 'ai', true);
} else if (message.type === 'image') {
const img = document.createElement('img');
const imgDiv = document.createElement('div');
imgDiv.className = `message img`;
if (img){
img.src = `data:image/png;base64,${message.image}`; // Set Base64 as the image source
img.alt = 'Generated Image';
img.style.width = '100%';
imgDiv.appendChild(img);
messages.appendChild(imgDiv);
} else{
const htmlContent = "The generated image might be inappropraite"; // Convert markdown to HTML
addMessage(htmlContent, 'ai', true);
}
}
};
ws.onclose = () => {
addMessage('Connection closed. Attempting to reconnect...', 'system');
setTimeout(connectWebSocket, 5000); // Attempt reconnection after 5 seconds
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
// Initialize WebSocket connection
connectWebSocket();
// Handle image upload button
imageUpload.addEventListener('click', () => {
imageInput.click();
});
// Handle audio upload button
audioUpload.addEventListener('click', () => {
audioInput.click();
});
// Track selected image files
imageInput.addEventListener('change', (e) => {
imageFiles = Array.from(e.target.files);
});
// Track selected audio file
audioInput.addEventListener('change', (e) => {
audioFile = e.target.files[0];
});
// Handle form submission
form.addEventListener('submit', (e) => {
e.preventDefault();
const data = { text: textInput.value, images: [], audio: null };
const tasks = [];
// Process images
if (imageFiles.length > 0) {
imageFiles.forEach((file) => {
const reader = new FileReader();
const promise = new Promise((resolve) => {
reader.onload = () => {
data.images.push(reader.result);
resolve();
};
reader.readAsDataURL(file);
});
tasks.push(promise);
});
}
// Process audio
if (audioFile) {
const reader = new FileReader();
const promise = new Promise((resolve) => {
reader.onload = () => {
data.audio = reader.result;
resolve();
};
reader.readAsDataURL(audioFile);
});
tasks.push(promise);
}
// Once all files are processed
Promise.all(tasks).then(() => {
ws.send(JSON.stringify(data));
addMessage('Your input is being processed...', 'user');
});
});
// Add messages to the UI
function addMessage(content, type, isHtml = false) {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${type}`;
if (isHtml) {
messageDiv.innerHTML = content; // Render HTML for markdown
} else {
messageDiv.textContent = content;
}
messages.appendChild(messageDiv);
messages.scrollTop = messages.scrollHeight;
}
});
</script>
</body>
</html>