Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions js/chat/chat-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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) {
Expand Down
148 changes: 84 additions & 64 deletions js/chat/chat-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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}`);
Expand Down Expand Up @@ -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];
Expand Down
Loading
Loading