AI_tool / templates /index.html
TarSh8654's picture
Update templates/index.html
145e34f verified
Raw
History Blame Contribute Delete
30.9 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Own AI Assistant</title> <!-- Changed title -->
<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Inter font from Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #1a202c 0%, #2d3748 100%); /* Subtle gradient background */
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
color: #e2e8f0;
}
.chat-container {
display: flex;
flex-direction: column;
width: 100%;
max-width: 800px;
height: 90vh;
background-color: #202a36; /* Slightly darker than body for depth */
border-radius: 1.5rem; /* More rounded corners */
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.5); /* Deeper shadow */
overflow: hidden;
border: 1px solid rgba(74, 85, 104, 0.3); /* Subtle border */
}
.chat-header {
padding: 1.5rem 2rem; /* More padding */
border-bottom: 1px solid #3a4759; /* Darker border */
text-align: center;
font-size: 2rem; /* Larger title */
font-weight: 700;
color: #e2e8f0;
background: linear-gradient(90deg, #2d3748, #3a4759); /* Header gradient */
position: relative;
z-index: 10; /* Ensure header is above messages */
display: flex;
justify-content: space-between;
align-items: center;
}
.messages-area {
flex-grow: 1;
overflow-y: auto;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem; /* Slightly less gap */
scroll-behavior: smooth; /* Smooth scrolling */
}
.message-bubble {
max-width: 85%; /* Slightly wider bubbles */
padding: 0.85rem 1.2rem; /* More padding */
border-radius: 1.2rem; /* More rounded */
line-height: 1.6;
word-wrap: break-word;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); /* Subtle bubble shadow */
opacity: 0; /* For animation */
transform: translateY(10px); /* For animation */
animation: fadeInSlideUp 0.3s ease-out forwards; /* Animation */
}
.user-message {
background-color: #4299e1; /* Blue-400 */
color: white;
align-self: flex-end;
border-bottom-right-radius: 0.4rem; /* Sharpen one corner for bubble effect */
}
.ai-message {
background-color: #3a4759; /* Darker gray for AI */
color: #e2e8f0;
align-self: flex-start;
border-bottom-left-radius: 0.4rem; /* Sharpen one corner for bubble effect */
}
@keyframes fadeInSlideUp {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.input-area {
padding: 1.5rem;
border-top: 1px solid #3a4759;
background-color: #2d3748; /* Matches body gradient start */
display: flex;
flex-direction: column; /* Stack file inputs above text input */
gap: 0.75rem;
}
textarea {
resize: none;
flex-grow: 1;
min-height: 2.75rem; /* Slightly taller */
max-height: 8rem;
overflow-y: auto;
background-color: #202a36; /* Matches chat container */
color: #e2e8f0;
border: 1px solid #4a5568;
border-radius: 0.75rem; /* More rounded */
padding: 0.75rem 1rem;
}
textarea:focus {
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.4); /* Stronger shadow */
outline: none;
}
/* Custom spinner for loading */
.spinner {
border: 3px solid rgba(255, 255, 255, 0.2);
border-left-color: #4299e1;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-message {
align-self: flex-start;
background-color: #3a4759;
color: #e2e8f0;
padding: 0.75rem 1rem;
border-radius: 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.file-input-group {
display: flex;
gap: 0.75rem; /* Increased gap */
width: 100%;
}
.file-input-group label {
cursor: pointer;
background-color: #4a5568;
color: #e2e8f0;
padding: 0.6rem 1rem; /* More padding */
border-radius: 0.75rem; /* More rounded */
transition: background-color 0.2s, transform 0.1s;
font-size: 0.9rem; /* Slightly larger font */
display: inline-flex;
align-items: center;
gap: 0.35rem;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.file-input-group label:hover {
background-color: #5a6a7e;
transform: translateY(-1px); /* Subtle lift */
}
.file-input-group input[type="file"] {
display: none;
}
.clear-file-button {
background-color: #e53e3e;
color: white;
padding: 0.4rem 0.75rem; /* Adjusted padding */
border-radius: 0.75rem;
font-size: 0.8rem;
margin-left: 0.5rem;
cursor: pointer;
transition: background-color 0.2s, transform 0.1s;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.clear-file-button:hover {
background-color: #c53030;
transform: translateY(-1px);
}
.file-name-display {
font-size: 0.875rem;
color: #a0aec0;
margin-left: 0.5rem;
display: flex;
align-items: center;
flex-grow: 1; /* Allow it to take available space */
white-space: nowrap; /* Prevent wrapping */
overflow: hidden; /* Hide overflow */
text-overflow: ellipsis; /* Add ellipsis */
}
.send-button {
background: linear-gradient(90deg, #4299e1, #3182ce); /* Gradient button */
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 0.75rem;
transition: transform 0.2s, box-shadow 0.2s;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.send-button:hover {
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
}
.send-button:disabled {
background: #4a5568;
cursor: not-allowed;
transform: none;
box-shadow: none;
opacity: 0.7;
}
.new-chat-button {
background-color: #63b3ed; /* Light blue for new chat */
color: #1a202c; /* Dark text */
padding: 0.5rem 1rem;
border-radius: 0.75rem;
font-size: 0.9rem;
font-weight: 600;
transition: background-color 0.2s, transform 0.1s;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
display: flex;
align-items: center;
gap: 0.25rem;
}
.new-chat-button:hover {
background-color: #4299e1;
transform: translateY(-1px);
}
</style>
</head>
<body class="p-4">
<div class="chat-container">
<div class="chat-header">
<span>Own AI Assistant</span> <!-- Changed header text -->
<button id="newChatButton" class="new-chat-button">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path></svg>
New Chat
</button>
</div>
<div id="messagesArea" class="messages-area">
<!-- Chat messages will be appended here -->
<div class="ai-message message-bubble">
Hello! How can I assist you today? You can type a message, or upload an image, a text document, or a PDF.
</div>
</div>
<div class="input-area flex-col items-stretch">
<div class="file-input-group w-full">
<label for="imageUpload" class="flex-shrink-0">
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
Image
</label>
<input type="file" id="imageUpload" accept="image/*">
<label for="documentUpload" class="flex-shrink-0">
<svg class="w-5 h-5 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>
Document
</label>
<input type="file" id="documentUpload" accept=".txt, .pdf, .docx">
<span id="fileNameDisplay" class="file-name-display flex-grow"></span>
<button id="clearFileButton" class="clear-file-button hidden">Clear</button>
</div>
<div id="filePreview" class="w-full mb-2 hidden">
<!-- Image or document preview will appear here -->
</div>
<div class="flex w-full gap-3">
<textarea
id="messageInput"
class="p-3 border rounded-lg focus:outline-none transition duration-200"
placeholder="Type your message..."
rows="1"
oninput="this.style.height = 'auto'; this.style.height = (this.scrollHeight) + 'px';"
></textarea>
<button
id="sendButton"
class="send-button flex items-center justify-center"
>
Send
</button>
</div>
</div>
<!-- Message Box for alerts -->
<div id="messageBox" class="fixed inset-0 bg-gray-600 bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white p-6 rounded-lg shadow-xl max-w-sm w-full text-gray-800">
<h3 class="text-lg font-bold mb-4" id="messageBoxTitle"></h3>
<p class="text-gray-700 mb-6" id="messageBoxContent"></p>
<button id="messageBoxClose" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg w-full">
OK
</button>
</div>
</div>
</div>
<script>
// DOM elements
const messagesArea = document.getElementById('messagesArea');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const imageUpload = document.getElementById('imageUpload');
const documentUpload = document.getElementById('documentUpload');
const filePreview = document.getElementById('filePreview');
const fileNameDisplay = document.getElementById('fileNameDisplay');
const clearFileButton = document.getElementById('clearFileButton');
const messageBox = document.getElementById('messageBox');
const messageBoxTitle = document.getElementById('messageBoxTitle');
const messageBoxContent = document.getElementById('messageBoxContent');
const messageBoxClose = document.getElementById('messageBoxClose');
const newChatButton = document.getElementById('newChatButton'); // New button
let sessionId = localStorage.getItem('aiChatSessionId');
if (!sessionId) {
sessionId = crypto.randomUUID();
localStorage.setItem('aiChatSessionId', sessionId);
console.log("New session ID generated:", sessionId);
} else {
console.log("Existing session ID loaded:", sessionId);
}
let uploadedFile = null; // Stores the selected file object
let uploadedFileType = null; // 'image', 'text_document', or 'pdf_document'
let uploadedFileBase64 = null; // For images and PDFs
let uploadedFileText = null; // For text documents
// Function to show custom message box
function showMessage(title, content) {
messageBoxTitle.textContent = title;
messageBoxContent.textContent = content;
messageBox.classList.remove('hidden');
}
// Event listener for message box close button
messageBoxClose.addEventListener('click', () => {
messageBox.classList.add('hidden');
});
// Function to add a message to the chat display
function addMessage(content, sender, isTyping = false) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message-bubble');
if (sender === 'user') {
messageDiv.classList.add('user-message');
} else if (sender === 'ai') {
messageDiv.classList.add('ai-message');
} else if (sender === 'loading') {
messageDiv.classList.add('loading-message');
messageDiv.innerHTML = `<div class="spinner"></div><span>Thinking...</span>`;
messageDiv.id = 'loadingMessage'; // Give it an ID to easily remove later
}
if (isTyping) {
// Content is already handled for loading
} else if (typeof content === 'string') {
messageDiv.innerHTML = marked.parse(content); // Render Markdown for text
} else if (typeof content === 'object' && content.type === 'image') {
// This block is now effectively unused for user messages as displayContent will be a string
// But keeping it here for potential future use or if AI sends image back
const img = document.createElement('img');
img.src = content.src;
img.alt = "Uploaded image";
img.classList.add('message-image-preview');
messageDiv.appendChild(img);
if (content.text) { // Add accompanying text if any
const textDiv = document.createElement('div');
textDiv.innerHTML = marked.parse(content.text);
messageDiv.appendChild(textDiv);
}
} else if (typeof content === 'object' && content.type === 'document') {
// This block is now effectively unused for user messages as displayContent will be a string
const docInfoDiv = document.createElement('div');
docInfoDiv.innerHTML = `<strong>Document Uploaded:</strong> ${content.name}<br>`;
if (content.textPreview) {
docInfoDiv.innerHTML += `<pre class="whitespace-pre-wrap text-sm max-h-32 overflow-y-auto bg-gray-800 p-2 rounded-md">${content.textPreview.substring(0, 500)}...</pre>`;
} else {
docInfoDiv.innerHTML += `<em>(Content too large or not plain text for preview)</em>`;
}
if (content.text) { // Add accompanying text if any
const textDiv = document.createElement('div');
textDiv.innerHTML = marked.parse(content.text);
messageDiv.appendChild(textDiv);
}
messageDiv.appendChild(docInfoDiv);
}
messagesArea.appendChild(messageDiv);
messagesArea.scrollTop = messagesArea.scrollHeight; // Auto-scroll to bottom
return messageDiv; // Return the created element, especially for loading message
}
// Clear file selection
function clearFileSelection() {
uploadedFile = null;
uploadedFileType = null;
uploadedFileBase64 = null;
uploadedFileText = null;
imageUpload.value = ''; // Clear file input
documentUpload.value = ''; // Clear file input
filePreview.innerHTML = '';
filePreview.classList.add('hidden');
fileNameDisplay.textContent = '';
clearFileButton.classList.add('hidden');
console.log("DEBUG: File selection cleared.");
}
// Handle image upload
imageUpload.addEventListener('change', (event) => {
console.log("DEBUG: Image input change event triggered.");
console.log("DEBUG: event.target.files:", event.target.files);
const file = event.target.files[0];
if (!file) {
console.log("DEBUG: No image file selected or file input was cancelled.");
clearFileSelection(); // Clear UI if user cancelled
return;
}
console.log("DEBUG: Selected image file:", file);
if (!file.type.startsWith('image/')) {
showMessage("Invalid File Type", "Please upload an image file.");
imageUpload.value = ''; // Clear input
console.error("DEBUG: Invalid image file type:", file.type);
return;
}
if (file.size > 5 * 1024 * 1024) { // 5MB limit for images
showMessage("File Too Large", "Please upload an image smaller than 5MB.");
imageUpload.value = '';
console.error("DEBUG: Image file too large:", file.size);
return;
}
uploadedFile = file;
uploadedFileType = 'image';
fileNameDisplay.textContent = `Image: ${file.name} (Ready to send)`;
clearFileButton.classList.remove('hidden');
const reader = new FileReader();
reader.onload = (e) => {
uploadedFileBase64 = e.target.result.split(',')[1]; // Get base64 part
console.log("DEBUG: Image FileReader loaded. Base64 length:", uploadedFileBase64.length);
filePreview.innerHTML = '';
filePreview.classList.add('hidden');
};
reader.onerror = (e) => {
console.error("DEBUG: FileReader error for image:", e);
showMessage("File Read Error", "Could not read the image file.");
clearFileSelection();
};
reader.readAsDataURL(file);
});
// Handle document upload
documentUpload.addEventListener('change', (event) => {
console.log("DEBUG: Document input change event triggered.");
console.log("DEBUG: event.target.files:", event.target.files);
const file = event.target.files[0];
if (!file) {
console.log("DEBUG: No document file selected or file input was cancelled.");
clearFileSelection(); // Clear UI if user cancelled
return;
}
console.log("DEBUG: Selected document file:", file);
uploadedFile = file;
fileNameDisplay.textContent = `Document: ${file.name} (Ready to send)`;
clearFileButton.classList.remove('hidden');
// Determine file type and read accordingly
if (file.type === 'text/plain') {
uploadedFileType = 'text_document';
const reader = new FileReader();
reader.onload = (e) => {
uploadedFileText = e.target.result;
console.log("DEBUG: Text Document FileReader loaded. Text length:", uploadedFileText.length);
filePreview.innerHTML = '';
filePreview.classList.add('hidden');
};
reader.onerror = (e) => {
console.error("DEBUG: FileReader error for text document:", e);
showMessage("File Read Error", "Could not read the text document file.");
clearFileSelection();
};
reader.readAsText(file);
} else if (file.type === 'application/pdf') {
uploadedFileType = 'pdf_document';
if (file.size > 10 * 1024 * 1024) { // 10MB limit for PDFs (adjust as needed)
showMessage("File Too Large", "Please upload a PDF smaller than 10MB.");
documentUpload.value = '';
console.error("DEBUG: PDF file too large:", file.size);
clearFileSelection();
return;
}
const reader = new FileReader();
reader.onload = (e) => {
uploadedFileBase64 = e.target.result.split(',')[1]; // Get base64 part
console.log("DEBUG: PDF FileReader loaded. Base64 length:", uploadedFileBase64.length);
filePreview.innerHTML = '';
filePreview.classList.add('hidden');
};
reader.onerror = (e) => {
console.error("DEBUG: FileReader error for PDF:", e);
showMessage("File Read Error", "Could not read the PDF file.");
clearFileSelection();
};
reader.readAsDataURL(file); // Read PDF as Data URL (base64)
} else {
// For other document types like .docx, inform the user
uploadedFileType = 'unsupported_document';
showMessage("Unsupported Document", "Only plain text (.txt) and PDF (.pdf) files are directly processed. For DOCX, etc., advanced backend parsing is required.");
console.warn("DEBUG: Unsupported document type selected:", file.type);
uploadedFileText = null;
uploadedFileBase64 = null;
filePreview.innerHTML = '';
filePreview.classList.add('hidden');
}
});
clearFileButton.addEventListener('click', clearFileSelection);
// Main function to handle sending messages
async function sendMessage() {
const userQuery = messageInput.value.trim();
if (!userQuery && !uploadedFile) {
showMessage("Input Required", "Please enter your message or upload a file.");
console.warn("DEBUG: Attempted to send empty message with no file.");
return;
}
// Prepare data to send
const requestBody = {
session_id: sessionId,
query: userQuery,
};
// Add uploaded file data if available
let displayContent = userQuery; // What to show in the chat bubble
if (uploadedFileType === 'image' && uploadedFileBase64) {
requestBody.image_data = {
mimeType: uploadedFile.type,
data: uploadedFileBase64
};
displayContent = `Image uploaded: ${uploadedFile.name}. ${userQuery ? `\n\n${userQuery}` : ''}`;
console.log("DEBUG: Prepared image data for requestBody. MimeType:", requestBody.image_data.mimeType, "Data length:", requestBody.image_data.data.length);
} else if (uploadedFileType === 'text_document' && uploadedFileText) {
requestBody.document_text = uploadedFileText;
displayContent = `Document uploaded: ${uploadedFile.name}. ${userQuery ? `\n\n${userQuery}` : ''}`;
console.log("DEBUG: Prepared text document text for requestBody. Text length:", requestBody.document_text.length);
} else if (uploadedFileType === 'pdf_document' && uploadedFileBase64) {
requestBody.pdf_data = {
mimeType: uploadedFile.type,
data: uploadedFileBase64
};
displayContent = `PDF uploaded: ${uploadedFile.name}. ${userQuery ? `\n\n${userQuery}` : ''}`;
console.log("DEBUG: Prepared PDF data for requestBody. MimeType:", requestBody.pdf_data.mimeType, "Data length:", requestBody.pdf_data.data.length);
} else if (uploadedFileType === 'unsupported_document' && uploadedFile) {
// For unsupported document types, send filename and a note
requestBody.query = `${userQuery}\n\nUser uploaded a document named "${uploadedFile.name}". Please note: Content extraction for this file type is not supported in this demo.`;
displayContent = `Document uploaded: ${uploadedFile.name} (unsupported type). ${userQuery ? `\n\n${userQuery}` : ''}`;
console.log("DEBUG: Prepared unsupported document type as text query:", requestBody.query);
}
console.log("DEBUG: Sending requestBody:", requestBody);
// Add user message (or file preview) to chat
addMessage(displayContent, 'user');
messageInput.value = ''; // Clear input
messageInput.style.height = 'auto'; // Reset textarea height
// Clear file selection after adding to chat
clearFileSelection();
// Show loading indicator
const loadingMessageElement = addMessage('', 'loading', true);
sendButton.disabled = true;
messageInput.disabled = true;
imageUpload.disabled = true;
documentUpload.disabled = true;
try {
const response = await fetch('/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
// Remove loading indicator
if (loadingMessageElement && loadingMessageElement.parentNode) {
loadingMessageElement.parentNode.removeChild(loadingMessageElement);
}
if (!response.ok) {
const errorData = await response.json(); // Try to parse error JSON
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.solution) {
addMessage(result.solution, 'ai');
if (result.session_id && result.session_id !== sessionId) {
sessionId = result.session_id;
localStorage.setItem('aiChatSessionId', sessionId);
console.log("DEBUG: Session ID updated by backend:", sessionId);
}
} else {
addMessage("I'm sorry, I couldn't generate a response.", 'ai');
}
} catch (error) {
console.error("DEBUG: Error generating solution:", error);
// Remove loading indicator on error too
if (loadingMessageElement && loadingMessageElement.parentNode) {
loadingMessageElement.parentNode.removeChild(loadingMessageElement);
}
showMessage("Generation Error", `An error occurred: ${error.message}. Please try again.`);
addMessage(`An error occurred: ${error.message}`, 'ai'); // Display error in chat
} finally {
sendButton.disabled = false;
messageInput.disabled = false;
imageUpload.disabled = false;
documentUpload.disabled = false;
messageInput.focus(); // Focus input for next message
}
}
// Event listeners for sending message
sendButton.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { // Send on Enter, new line on Shift+Enter
e.preventDefault(); // Prevent default Enter behavior (new line)
sendMessage();
}
});
// New Chat Button functionality
newChatButton.addEventListener('click', () => {
if (confirm("Are you sure you want to start a new chat? This will clear the current conversation history.")) {
localStorage.removeItem('aiChatSessionId'); // Clear session ID
sessionId = crypto.randomUUID(); // Generate new one
localStorage.setItem('aiChatSessionId', sessionId);
messagesArea.innerHTML = `
<div class="ai-message message-bubble">
Hello! How can I assist you today? You can type a message, or upload an image, a text document, or a PDF.
</div>
`; // Clear chat display
clearFileSelection(); // Clear any pending file uploads
messageInput.value = ''; // Clear text input
messageInput.style.height = 'auto'; // Reset textarea height
messageInput.focus();
console.log("New chat started. New session ID:", sessionId);
}
});
// Marked.js for Markdown rendering (simple CDN inclusion)
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
script.onload = () => console.log('Marked.js loaded.');
document.head.appendChild(script);
</script>
</body>
</html>