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();