const userInput = document.getElementById('userInput'); const sendButton = document.getElementById('sendButton'); const historyArea = document.getElementById('historyArea'); const liveResponseArea = document.getElementById('liveResponseArea'); const fileInput = document.getElementById('fileAttachment'); const fileAttachmentInfo = document.getElementById('fileAttachmentInfo'); const fileAttachmentClear = document.getElementById('fileAttachmentClear'); const clearHistoryButton = document.getElementById('clearHistoryButton'); const PHP_API_ENDPOINT = 'chat_api.php'; const STORAGE_KEY = 'IslamicAI24thMay05'; let conversationHistory = []; let attachedFileContent = null; let initialContextProcessed = false; const ICON_COPY = ''; const ICON_COPIED = ''; const ICON_COPY_FAILED = ''; function createMessageElement(role, contentParts) { const messageElement = document.createElement('div'); messageElement.classList.add('message', `${role}-message`); const contentContainer = document.createElement('div'); let modelMessageTextContentForCopy = ""; contentParts.forEach(part => { if (part.inline_data && part.inline_data.data && part.inline_data.mime_type && part.inline_data.mime_type.startsWith('image/')) { const imgElement = document.createElement('img'); imgElement.src = `data:${part.inline_data.mime_type};base64,${part.inline_data.data}`; imgElement.alt = 'Attached Image'; contentContainer.appendChild(imgElement); } else if (part.text !== undefined) { const textToParse = part.text || ''; if (role === 'model') { modelMessageTextContentForCopy += textToParse + "\n"; } if (role === 'user' && textToParse.startsWith('## Attached File:')) { const fileDisplayDiv = document.createElement('div'); fileDisplayDiv.className = 'file-content-display'; const headerEndIndex = textToParse.indexOf('\n\n'); let fileNameLine = 'Attached Text File'; let actualFileContent = textToParse; if (headerEndIndex > '## Attached File: '.length) { fileNameLine = textToParse.substring('## Attached File: '.length, headerEndIndex).trim(); actualFileContent = textToParse.substring(headerEndIndex + 2); } else if (headerEndIndex === -1) { fileNameLine = textToParse.substring('## Attached File: '.length).trim(); actualFileContent = ''; } const suffixToRemove = '\n\n---\n\n'; if (actualFileContent.endsWith(suffixToRemove)) { actualFileContent = actualFileContent.substring(0, actualFileContent.length - suffixToRemove.length); } else if (actualFileContent.endsWith('\n---\n')) { actualFileContent = actualFileContent.substring(0, actualFileContent.length - '\n---\n'.length); } else if (actualFileContent.endsWith('---')) { actualFileContent = actualFileContent.substring(0, actualFileContent.length - '---'.length); } const fileNameHeaderElement = document.createElement('p'); fileNameHeaderElement.style.fontWeight = 'bold'; fileNameHeaderElement.style.marginBottom = '5px'; fileNameHeaderElement.textContent = `Preview: ${fileNameLine}`; fileDisplayDiv.appendChild(fileNameHeaderElement); const preElement = document.createElement('pre'); const codeElement = document.createElement('code'); codeElement.textContent = actualFileContent; preElement.appendChild(codeElement); fileDisplayDiv.appendChild(preElement); contentContainer.appendChild(fileDisplayDiv); } else { const tempDiv = document.createElement('div'); try { tempDiv.innerHTML = marked.parse(textToParse, { breaks: true }); } catch (e) { console.error("Markdown Parsing Error:", e); const p = document.createElement('p'); p.textContent = textToParse; tempDiv.appendChild(p); } while (tempDiv.firstChild) { contentContainer.appendChild(tempDiv.firstChild); } } } }); messageElement.appendChild(contentContainer); if (role === 'model' && modelMessageTextContentForCopy.trim() !== "") { const finalModelTextToCopy = modelMessageTextContentForCopy.trim(); const copyButton = document.createElement('button'); copyButton.innerHTML = ICON_COPY; copyButton.classList.add('copy-button'); copyButton.setAttribute('title', 'Copy Text'); copyButton.type = 'button'; copyButton.addEventListener('click', (e) => { e.stopPropagation(); navigator.clipboard.writeText(finalModelTextToCopy) .then(() => { copyButton.innerHTML = ICON_COPIED; copyButton.setAttribute('title', 'Copied!'); setTimeout(() => { copyButton.innerHTML = ICON_COPY; copyButton.setAttribute('title', 'Copy Text'); }, 2000); }) .catch(err => { console.error('Could not copy text: ', err); copyButton.innerHTML = ICON_COPY_FAILED; copyButton.setAttribute('title', 'Copy Failed!'); setTimeout(() => { copyButton.innerHTML = ICON_COPY; copyButton.setAttribute('title', 'Copy Text'); }, 2000); }); }); messageElement.appendChild(copyButton); } return messageElement; } function saveHistory() { try { const historyToSave = conversationHistory.map(entry => ({ role: entry.role, parts: (entry.parts && Array.isArray(entry.parts)) ? entry.parts.map(part => { if (part.text !== undefined) { return { text: part.text }; } else if (part.inline_data && part.inline_data.data && part.inline_data.mime_type) { return { inline_data: { mime_type: part.inline_data.mime_type, data: part.inline_data.data } }; } return null; }).filter(part => part !== null) : [] })).filter(entry => entry.parts.length > 0); localStorage.setItem(STORAGE_KEY, JSON.stringify(historyToSave)); } catch (e) { console.error("Error Saving History To Local Storage:", e); } } function loadHistory() { try { const storedHistory = localStorage.getItem(STORAGE_KEY); if (storedHistory) { const parsedHistory = JSON.parse(storedHistory); if (Array.isArray(parsedHistory)) { conversationHistory = parsedHistory; if (conversationHistory.length > 0) { const fragment = document.createDocumentFragment(); conversationHistory.forEach(entry => { if (entry.parts && entry.parts.length > 0) { fragment.appendChild(createMessageElement(entry.role, entry.parts)); } }); historyArea.appendChild(fragment); initialContextProcessed = true; } else { initialContextProcessed = false; } return true; } else { console.warn("Loaded History Is Not An Array. Starting Fresh..."); conversationHistory = []; initialContextProcessed = false; return false; } } else { conversationHistory = []; initialContextProcessed = false; return false; } } catch (e) { console.error("Error Loading History From Local Storage:", e); conversationHistory = []; initialContextProcessed = false; return false; } } function clearHistory() { localStorage.removeItem(STORAGE_KEY); conversationHistory = []; historyArea.innerHTML = ''; liveResponseArea.innerHTML = ''; clearAttachedFile(); initialContextProcessed = false; userInput.focus(); } function displayMessage(role, contentParts) { const messageElement = createMessageElement(role, contentParts); historyArea.appendChild(messageElement); requestAnimationFrame(() => { historyArea.scrollTo({ top: historyArea.scrollHeight, behavior: 'smooth' }); }); } const MAX_IMAGE_SIZE_MB = 5; const MAX_TEXT_SIZE_KB = 100; if (fileInput && fileAttachmentInfo && fileAttachmentClear) { function clearAttachedFile() { fileInput.value = ''; attachedFileContent = null; fileAttachmentInfo.textContent = ''; fileAttachmentClear.style.display = 'none'; liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; } function handleFileReadError(message, error, file = null) { const fileName = file ? `: ${file.name}` : ''; liveResponseArea.textContent = message + fileName; liveResponseArea.className = 'error'; console.error("File Process Error:", error, file); clearAttachedFile(); } fileInput.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) { clearAttachedFile(); return; } clearAttachedFile(); fileAttachmentInfo.textContent = `Processing: ${file.name}`; fileAttachmentClear.style.display = 'inline'; const validImageTypes = ['image/jpg', 'image/jpeg', 'image/png', 'image/gif', 'image/webp']; const isImage = validImageTypes.includes(file.type); const isText = file.type === 'text/plain'; if (!isImage && !isText) { handleFileReadError(`Invalid File Type. Image (${validImageTypes.join(', ')}) or text (.txt) only.`, null, file); fileAttachmentInfo.textContent = `Invalid Type: ${file.name}`; return; } liveResponseArea.innerHTML = 'Reading File...'; liveResponseArea.className = 'info'; const reader = new FileReader(); reader.onloadstart = () => { liveResponseArea.textContent = 'Reading File...'; liveResponseArea.className = 'info'; }; reader.onerror = (error) => handleFileReadError(`Error Reading File ${file.name}.`, error, file); reader.onabort = () => handleFileReadError(`File Reading Aborted For ${file.name}.`, null, file); if (isImage) { reader.onloadend = () => { if (reader.readyState === FileReader.DONE && reader.result) { try { const base64String = reader.result.split(',')[1]; const estimatedBase64Size = base64String.length * 0.75; const maxBase64ByteSize = MAX_IMAGE_SIZE_MB * 1024 * 1024; if (estimatedBase64Size > maxBase64ByteSize) { handleFileReadError(`Image Size Too Large (~${(estimatedBase64Size/1024/1024).toFixed(2)}MB). Max approx ${MAX_IMAGE_SIZE_MB}MB.`, null, file); fileAttachmentInfo.textContent = `Size Too Large: ${file.name}`; } else { attachedFileContent = { type: 'image', mime_type: file.type, data: base64String, name: file.name }; liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; fileAttachmentInfo.textContent = `Image: ${file.name}`; fileAttachmentClear.style.display = 'inline'; } } catch (e) { handleFileReadError(`Error Processing Image Data For ${file.name}.`, e, file); fileAttachmentInfo.textContent = `Processing Error: ${file.name}`; } } else { handleFileReadError(`Failed To Read Image File ${file.name}.`, null, file); fileAttachmentInfo.textContent = `Read Failed: ${file.name}`; } }; reader.readAsDataURL(file); } else if (isText) { reader.onloadend = () => { if (reader.readyState === FileReader.DONE && reader.result !== null) { if (reader.result.length > MAX_TEXT_SIZE_KB * 1024) { handleFileReadError(`Text File Size Too Large (${(reader.result.length/1024).toFixed(2)}KB). Max ${MAX_TEXT_SIZE_KB}KB.`, null, file); fileAttachmentInfo.textContent = `Size Too Large: ${file.name}`; } else { attachedFileContent = { type: 'text', name: file.name, content: reader.result }; liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; fileAttachmentInfo.textContent = `Text File: ${file.name}`; fileAttachmentClear.style.display = 'inline'; } } else { handleFileReadError(`Failed To Read Text File ${file.name}.`, null, file); fileAttachmentInfo.textContent = `Read Failed: ${file.name}`; } }; reader.readAsText(file); } }); fileAttachmentClear.addEventListener('click', () => { clearAttachedFile(); }); userInput.addEventListener('paste', (event) => { const items = event.clipboardData?.items; if (items) { for (const item of items) { if (item.type.startsWith('image/')) { event.preventDefault(); const file = item.getAsFile(); if (file) { clearAttachedFile(); fileAttachmentInfo.textContent = `Processing Pasted Image (${file.type})...`; fileAttachmentClear.style.display = 'inline'; const reader = new FileReader(); reader.onloadstart = () => { liveResponseArea.textContent = 'Reading Pasted Image...'; liveResponseArea.className = 'info'; }; reader.onerror = (error) => handleFileReadError("Error Reading Pasted Image.", error, file); reader.onabort = () => handleFileReadError("Pasted Image Reading Aborted.", null, file); reader.onloadend = () => { if (reader.readyState === FileReader.DONE && reader.result) { try { const base64String = reader.result.split(',')[1]; const estimatedBase64Size = base64String.length * 0.75; const maxBase64ByteSize = MAX_IMAGE_SIZE_MB * 1024 * 1024; if (estimatedBase64Size > maxBase64ByteSize) { handleFileReadError(`Pasted Image Data Too Large (~${(estimatedBase64Size/1024/1024).toFixed(2)}MB). Max approx ${MAX_IMAGE_SIZE_MB}MB.`, null, file); fileAttachmentInfo.textContent = `Size Too Large`; } else { attachedFileContent = { type: 'image', mime_type: file.type, data: base64String, name: 'pasted_image.png' }; liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; fileAttachmentInfo.textContent = `Pasted Image Ready (${file.type}).`; } fileAttachmentClear.style.display = 'inline'; } catch(e) { handleFileReadError("Error Processing Pasted Image Data.", e, file); fileAttachmentInfo.textContent = `Pasted Image Processing Error`; fileAttachmentClear.style.display = 'inline'; } } else { handleFileReadError("Failed To Read Pasted Image Data.", null, file); fileAttachmentInfo.textContent = `Pasted Image Read Error`; fileAttachmentClear.style.display = 'inline'; } }; reader.readAsDataURL(file); return; } } } } }); } else { } async function sendMessage() { const userMessageText = userInput.value.trim(); if (!userMessageText && !attachedFileContent) { if (navigator.vibrate) navigator.vibrate(50); userInput.classList.add('shake-on-empty'); setTimeout(() => { userInput.classList.remove('shake-on-empty'); }, 500); liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; return; } const currentUserParts = []; if (attachedFileContent) { if (attachedFileContent.type === 'image') { currentUserParts.push({ inline_data: { mime_type: attachedFileContent.mime_type, data: attachedFileContent.data } }); } else if (attachedFileContent.type === 'text' && attachedFileContent.content !== undefined) { currentUserParts.push({ text: `## Attached File: ${attachedFileContent.name}\n\n${attachedFileContent.content}\n\n---` }); } } if (userMessageText) { currentUserParts.push({ text: userMessageText }); } conversationHistory.push({ role: 'user', parts: currentUserParts }); displayMessage('user', currentUserParts); userInput.value = ''; userInput.disabled = true; liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; sendButton.disabled = true; const dataToSendToPHP = { userInput: userMessageText, history: conversationHistory, attachedFile: attachedFileContent, isInitialContextProcessed: initialContextProcessed }; console.log("Data Sent To PHP:", JSON.stringify(dataToSendToPHP, null, 2)); try { const response = await fetch(PHP_API_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(dataToSendToPHP) }); if (!response.ok) { let errorDetails = `PHP API HTTP Error: ${response.status} ${response.statusText}`; try { const errorJson = await response.json(); if (errorJson.error) errorDetails += ` - ${errorJson.error}`; } catch (e) { try { const rawText = await response.text(); if (rawText) errorDetails += ` - Raw: ${rawText.substring(0, 150)}...`; } catch(e2) { } } liveResponseArea.innerHTML = errorDetails; liveResponseArea.className = 'error'; console.error('PHP API Request Failed', { status: response.status, statusText: response.statusText, errorDetails }); return; } const data = await response.json(); if (data.success) { const modelResponseParts = data.parts; const finishReason = data.finishReason || 'STOP'; if (finishReason !== 'STOP') { console.warn(`AI Finished With Reason ${finishReason}. Response May Be Incomplete.`); } conversationHistory.push({ role: 'model', parts: modelResponseParts }); displayMessage('model', modelResponseParts); liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; saveHistory(); } else if (data.warning) { let warningText = data.warning; if (data.blockReason) warningText += ` (Reason: ${data.blockReason})`; if (data.blockMessage) warningText += `: ${data.blockMessage}`; liveResponseArea.innerHTML = marked.parse(`**${warningText}**`); liveResponseArea.className = 'warning'; if(data.safetyRatings) liveResponseArea.innerHTML += marked.parse(`\n\nSafety Ratings: ${JSON.stringify(data.safetyRatings, null, 2)}`); console.warn("Response Warning From PHP:", data); } else if (data.error) { let errorText = data.error; liveResponseArea.innerHTML = marked.parse(`**Error:** ${errorText}`); liveResponseArea.className = 'error'; console.error("Error Response From PHP:", data); } else { liveResponseArea.innerHTML = 'Received An Unexpected Response Structure From Your Server.'; liveResponseArea.className = 'error'; console.error("Unexpected Response Structure From PHP:", data); } initialContextProcessed = true; } catch (error) { console.error('Fetch Error Or Unexpected Issue:', error); liveResponseArea.innerHTML = `An Error Occurred During The Request: ${error.message || error}`; liveResponseArea.className = 'error'; } finally { clearAttachedFile(); sendButton.disabled = false; userInput.disabled = false; userInput.focus(); } } sendButton.addEventListener('click', sendMessage); userInput.addEventListener('keypress', function(event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); if (!sendButton.disabled && (userInput.value.trim() || attachedFileContent)) { sendMessage(); } else if (!userInput.value.trim() && !attachedFileContent) { if (navigator.vibrate) navigator.vibrate(50); userInput.classList.add('shake-on-empty'); setTimeout(() => { userInput.classList.remove('shake-on-empty'); }, 500); liveResponseArea.innerHTML = ''; liveResponseArea.className = ''; } } }); if (clearHistoryButton) { clearHistoryButton.addEventListener('click', clearHistory); } else { } loadHistory(); userInput.focus();