diff --git a/js/chat/chat-core.js b/js/chat/chat-core.js index 15f1186..21f9ef8 100644 --- a/js/chat/chat-core.js +++ b/js/chat/chat-core.js @@ -537,11 +537,31 @@ document.addEventListener("DOMContentLoaded", () => { // Use polliLib OpenAI-compatible chat endpoint const data = await (window.polliLib?.chat?.({ model, messages }) ?? Promise.reject(new Error('polliLib not loaded'))); loadingDiv.remove(); - const aiContentRaw = data?.choices?.[0]?.message?.content || ""; - let aiContent = aiContentRaw; - - const memRegex = /\[memory\]([\s\S]*?)\[\/memory\]/gi; - let m; + + const messageObj = data?.choices?.[0]?.message || {}; + const imageUrls = []; + const audioUrls = []; + let aiContent = ""; + + if (Array.isArray(messageObj.content)) { + for (const part of messageObj.content) { + if (!part) continue; + if (typeof part === 'string') { + aiContent += part; + } else if (part.type === 'text' && part.text) { + aiContent += part.text; + } else if (part.type === 'image_url' && part.image_url?.url) { + imageUrls.push(part.image_url.url); + } else if (part.type === 'audio' && part.audio?.url) { + audioUrls.push(part.audio.url); + } + } + } else { + aiContent = messageObj.content || ""; + } + + const memRegex = /\[memory\]([\s\S]*?)\[\/memory\]/gi; + let m; while ((m = memRegex.exec(aiContent)) !== null) Memory.addMemoryEntry(m[1].trim()); aiContent = aiContent.replace(memRegex, "").trim(); @@ -572,12 +592,12 @@ document.addEventListener("DOMContentLoaded", () => { } } - window.addNewMessage({ role: "ai", content: aiContent }); - if (autoSpeakEnabled) { - const sentences = aiContent.split(/(?<=[.!?])\s+/).filter(s => s.trim().length > 0); - speakSentences(sentences); - } else { - stopSpeaking(); + window.addNewMessage({ role: "ai", content: aiContent, imageUrls, audioUrls }); + if (autoSpeakEnabled) { + const sentences = aiContent.split(/(?<=[.!?])\s+/).filter(s => s.trim().length > 0); + speakSentences(sentences); + } else { + stopSpeaking(); } if (callback) callback(); } catch (err) { diff --git a/js/chat/chat-init.js b/js/chat/chat-init.js index f08085f..bc8d6c8 100644 --- a/js/chat/chat-init.js +++ b/js/chat/chat-init.js @@ -17,7 +17,7 @@ document.addEventListener("DOMContentLoaded", () => { if (!window.Prism) return; chatBox.querySelectorAll("pre code").forEach(block => Prism.highlightElement(block)); }; - const appendMessage = ({ role, content, index, imageUrls = [] }) => { + const appendMessage = ({ role, content, index, imageUrls = [], audioUrls = [] }) => { const container = document.createElement("div"); container.classList.add("message"); container.dataset.index = index; @@ -63,15 +63,21 @@ document.addEventListener("DOMContentLoaded", () => { bubbleContent.appendChild(textNode); } } - if (imageUrls.length > 0) { - imageUrls.forEach(url => { - const imageContainer = createImageElement(url, index); - bubbleContent.appendChild(imageContainer); - }); - } - } else { - bubbleContent.textContent = content; - } + if (imageUrls.length > 0) { + imageUrls.forEach(url => { + const imageContainer = createImageElement(url, index); + bubbleContent.appendChild(imageContainer); + }); + } + if (audioUrls.length > 0) { + audioUrls.forEach(url => { + const audioEl = createAudioElement(url); + bubbleContent.appendChild(audioEl); + }); + } + } else { + bubbleContent.textContent = content; + } container.appendChild(bubbleContent); const actionsDiv = document.createElement("div"); actionsDiv.className = "message-actions"; @@ -281,8 +287,8 @@ document.addEventListener("DOMContentLoaded", () => { window.open(img.src, "_blank"); showToast("Image opened in new tab"); }; - const createImageElement = (url, msgIndex) => { - const imageId = `img-${msgIndex}-${Date.now()}`; + const createImageElement = (url, msgIndex) => { + const imageId = `img-${msgIndex}-${Date.now()}`; localStorage.setItem(`imageId_${msgIndex}`, imageId); const imageContainer = document.createElement("div"); imageContainer.className = "ai-image-container"; @@ -315,11 +321,19 @@ document.addEventListener("DOMContentLoaded", () => { imageContainer.appendChild(img); const imgButtonContainer = document.createElement("div"); imgButtonContainer.className = "image-button-container"; - imgButtonContainer.dataset.imageId = imageId; - imageContainer.appendChild(imgButtonContainer); - return imageContainer; - }; - const attachImageButtonListeners = (img, imageId) => { + imgButtonContainer.dataset.imageId = imageId; + imageContainer.appendChild(imgButtonContainer); + return imageContainer; + }; + const createAudioElement = (url) => { + const audio = document.createElement("audio"); + audio.controls = true; + audio.src = url; + audio.className = "ai-generated-audio"; + audio.style.display = "block"; + return audio; + }; + const attachImageButtonListeners = (img, imageId) => { const imgButtonContainer = document.querySelector(`.image-button-container[data-image-id="${imageId}"]`); if (!imgButtonContainer) { console.warn(`No image button container found for image ID: ${imageId}`); @@ -372,58 +386,64 @@ document.addEventListener("DOMContentLoaded", () => { }); imgButtonContainer.appendChild(openImgBtn); }; - const renderStoredMessages = messages => { - console.log("Rendering stored messages..."); - chatBox.innerHTML = ""; - messages.forEach((msg, idx) => { - console.log(`Appending message at index ${idx}: ${msg.role}`); - if (!window.polliClient || !window.polliClient.imageBase) return appendMessage({ role: msg.role, content: msg.content, index: idx, imageUrls: [] }); - const base = window.polliClient.imageBase; - const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const imgRegex = new RegExp(`(${escaped}\/prompt\/[^ ]+)`, 'g'); - const imgMatches = msg.content.match(imgRegex) || []; - appendMessage({ - role: msg.role, - content: msg.content, - index: idx, - imageUrls: imgMatches - }); - }); - messages.forEach((msg, idx) => { - const storedImageId = localStorage.getItem(`imageId_${idx}`); - if (storedImageId) { - const img = chatBox.querySelector(`img[data-image-id="${storedImageId}"]`); - if (img) { - console.log(`Re-attaching image button listeners for stored image ID: ${storedImageId}`); - attachImageButtonListeners(img, storedImageId); - } else { - console.warn(`Image with ID ${storedImageId} not found in DOM`); - } - } - }); - highlightAllCodeBlocks(); - }; - window.addNewMessage = ({ role, content }) => { - const currentSession = Storage.getCurrentSession(); - currentSession.messages.push({ role, content }); - Storage.updateSessionMessages(currentSession.id, currentSession.messages); + const renderStoredMessages = messages => { + console.log("Rendering stored messages..."); + chatBox.innerHTML = ""; + messages.forEach((msg, idx) => { + console.log(`Appending message at index ${idx}: ${msg.role}`); + const storedImages = Array.isArray(msg.imageUrls) ? msg.imageUrls : []; + const storedAudio = Array.isArray(msg.audioUrls) ? msg.audioUrls : []; + let imgMatches = storedImages; + if (imgMatches.length === 0 && msg.content && window.polliClient && window.polliClient.imageBase) { + const base = window.polliClient.imageBase; + const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const imgRegex = new RegExp(`(${escaped}\/prompt\/[^ ]+)`, 'g'); + imgMatches = msg.content.match(imgRegex) || []; + } + appendMessage({ + role: msg.role, + content: msg.content, + index: idx, + imageUrls: imgMatches, + audioUrls: storedAudio + }); + }); + messages.forEach((msg, idx) => { + const storedImageId = localStorage.getItem(`imageId_${idx}`); + if (storedImageId) { + const img = chatBox.querySelector(`img[data-image-id="${storedImageId}"]`); + if (img) { + console.log(`Re-attaching image button listeners for stored image ID: ${storedImageId}`); + attachImageButtonListeners(img, storedImageId); + } else { + console.warn(`Image with ID ${storedImageId} not found in DOM`); + } + } + }); + highlightAllCodeBlocks(); + }; + window.addNewMessage = ({ role, content, imageUrls = [], audioUrls = [] }) => { + const currentSession = Storage.getCurrentSession(); + currentSession.messages.push({ role, content, imageUrls, audioUrls }); + Storage.updateSessionMessages(currentSession.id, currentSession.messages); if (!window.polliClient || !window.polliClient.imageBase) { - appendMessage({ role, content, index: currentSession.messages.length - 1, imageUrls: [] }); + appendMessage({ role, content, index: currentSession.messages.length - 1, imageUrls, audioUrls }); if (role === "ai") checkAndUpdateSessionTitle(); return; } const base = window.polliClient.imageBase; - const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const imgRegex = new RegExp(`(${escaped}\/prompt\/[^ ]+)`, 'g'); - const imgMatches = content.match(imgRegex) || []; - appendMessage({ - role, - content, - index: currentSession.messages.length - 1, - imageUrls: imgMatches - }); - if (role === "ai") checkAndUpdateSessionTitle(); - }; + const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const imgRegex = new RegExp(`(${escaped}\/prompt\/[^ ]+)`, 'g'); + const imgMatches = imageUrls.length > 0 ? imageUrls : (content.match(imgRegex) || []); + appendMessage({ + role, + content, + index: currentSession.messages.length - 1, + imageUrls: imgMatches, + audioUrls + }); + if (role === "ai") checkAndUpdateSessionTitle(); + }; const editMessage = msgIndex => { const currentSession = Storage.getCurrentSession(); const oldMessage = currentSession.messages[msgIndex]; diff --git a/js/chat/chat-storage.js b/js/chat/chat-storage.js index ad5e8b8..cdac97f 100644 --- a/js/chat/chat-storage.js +++ b/js/chat/chat-storage.js @@ -34,7 +34,7 @@ document.addEventListener("DOMContentLoaded", () => { Prism.highlightElement(block); }); } - function appendMessage({ role, content, index, imageUrls = [] }) { + function appendMessage({ role, content, index, imageUrls = [], audioUrls = [] }) { const container = document.createElement("div"); container.classList.add("message"); container.dataset.index = index; @@ -85,15 +85,21 @@ document.addEventListener("DOMContentLoaded", () => { bubbleContent.appendChild(textNode); } } - if (imageUrls.length > 0) { - imageUrls.forEach(url => { - const imageContainer = createImageElement(url); - bubbleContent.appendChild(imageContainer); - }); - } - } else { - bubbleContent.textContent = content; - } + if (imageUrls.length > 0) { + imageUrls.forEach(url => { + const imageContainer = createImageElement(url); + bubbleContent.appendChild(imageContainer); + }); + } + if (audioUrls.length > 0) { + audioUrls.forEach(url => { + const audioEl = createAudioElement(url); + bubbleContent.appendChild(audioEl); + }); + } + } else { + bubbleContent.textContent = content; + } container.appendChild(bubbleContent); if (role === "ai") { const actionsDiv = document.createElement("div"); @@ -183,8 +189,8 @@ document.addEventListener("DOMContentLoaded", () => { URL.revokeObjectURL(url); showToast("Code downloaded as .txt"); } - function createImageElement(url) { - const imageId = `voice-img-${Date.now()}`; + function createImageElement(url) { + const imageId = `voice-img-${Date.now()}`; localStorage.setItem(`voiceImageId_${imageId}`, imageId); const imageContainer = document.createElement("div"); imageContainer.className = "ai-image-container"; @@ -216,12 +222,21 @@ document.addEventListener("DOMContentLoaded", () => { }; imageContainer.appendChild(img); const imgButtonContainer = document.createElement("div"); - imgButtonContainer.className = "image-button-container"; - imgButtonContainer.dataset.imageId = imageId; - imageContainer.appendChild(imgButtonContainer); - return imageContainer; - } - function attachImageButtons(img, imageId) { + imgButtonContainer.className = "image-button-container"; + imgButtonContainer.dataset.imageId = imageId; + imageContainer.appendChild(imgButtonContainer); + return imageContainer; + } + + function createAudioElement(url) { + const audio = document.createElement("audio"); + audio.controls = true; + audio.src = url; + audio.className = "ai-generated-audio"; + audio.style.display = "block"; + return audio; + } + function attachImageButtons(img, imageId) { const imgButtonContainer = document.querySelector(`.image-button-container[data-image-id="${imageId}"]`); if (!imgButtonContainer) { console.warn(`No image button container found for image ID: ${imageId}`); @@ -376,48 +391,58 @@ document.addEventListener("DOMContentLoaded", () => { window.open(img.src, "_blank"); showToast("Image opened in new tab"); } - function renderStoredMessages(messages) { - console.log("Rendering stored messages..."); - chatBox.innerHTML = ""; - messages.forEach((msg, idx) => { - console.log(`Appending message at index ${idx}: ${msg.role}`); - if (!window.polliClient || !window.polliClient.imageBase) return appendMessage({ role: msg.role, content: msg.content, index: idx, imageUrls: [] }); - const baseList = [ window.polliClient.imageBase ]; - const escaped = baseList.map(b => b.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - const imgRegex = new RegExp(`(${escaped.join('|')})/prompt/[^ ]+`, 'g'); - const imgMatches = msg.content.match(imgRegex) || []; - appendMessage({ - role: msg.role, - content: msg.content, - index: idx, - imageUrls: imgMatches - }); - }); - highlightAllCodeBlocks(); - chatInput.disabled = false; - chatInput.focus(); - } - window.addNewMessage = function ({ role, content }) { - const currentSession = Storage.getCurrentSession(); - currentSession.messages.push({ role, content }); - Storage.updateSessionMessages(currentSession.id, currentSession.messages); + function renderStoredMessages(messages) { + console.log("Rendering stored messages..."); + chatBox.innerHTML = ""; + messages.forEach((msg, idx) => { + console.log(`Appending message at index ${idx}: ${msg.role}`); + const storedImages = Array.isArray(msg.imageUrls) ? msg.imageUrls : []; + const storedAudio = Array.isArray(msg.audioUrls) ? msg.audioUrls : []; + let imgMatches = storedImages; + if (imgMatches.length === 0 && msg.content) { + if (window.polliClient && window.polliClient.imageBase) { + const baseList = [ window.polliClient.imageBase ]; + const escaped = baseList.map(b => b.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); + const imgRegex = new RegExp(`(${escaped.join('|')})/prompt/[^ ]+`, 'g'); + imgMatches = msg.content.match(imgRegex) || []; + } else { + imgMatches = []; + } + } + appendMessage({ + role: msg.role, + content: msg.content, + index: idx, + imageUrls: imgMatches, + audioUrls: storedAudio + }); + }); + highlightAllCodeBlocks(); + chatInput.disabled = false; + chatInput.focus(); + } + window.addNewMessage = function ({ role, content, imageUrls = [], audioUrls = [] }) { + const currentSession = Storage.getCurrentSession(); + currentSession.messages.push({ role, content, imageUrls, audioUrls }); + Storage.updateSessionMessages(currentSession.id, currentSession.messages); if (!window.polliClient || !window.polliClient.imageBase) { - appendMessage({ role, content, index: currentSession.messages.length - 1, imageUrls: [] }); + appendMessage({ role, content, index: currentSession.messages.length - 1, imageUrls, audioUrls }); if (role === "ai") checkAndUpdateSessionTitle(); return; } const base = window.polliClient.imageBase; - const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const imgRegex = new RegExp(`(${escaped}\/prompt\/[^ ]+)`, 'g'); - const imgMatches = content.match(imgRegex) || []; - appendMessage({ - role, - content, - index: currentSession.messages.length - 1, - imageUrls: imgMatches - }); - if (role === "ai") checkAndUpdateSessionTitle(); - }; + const escaped = base.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const imgRegex = new RegExp(`(${escaped}\/prompt\/[^ ]+)`, 'g'); + const imgMatches = imageUrls.length > 0 ? imageUrls : (content.match(imgRegex) || []); + appendMessage({ + role, + content, + index: currentSession.messages.length - 1, + imageUrls: imgMatches, + audioUrls + }); + if (role === "ai") checkAndUpdateSessionTitle(); + }; function editMessage(msgIndex) { const currentSession = Storage.getCurrentSession(); const oldMessage = currentSession.messages[msgIndex];