Skip to content
Closed
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
44 changes: 15 additions & 29 deletions chat-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ async function pollinationsFetch(url, options = {}, { timeoutMs = 45000 } = {})
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(new DOMException('timeout', 'AbortError')), timeoutMs);
try {
const res = await fetch(
url,
{ ...options, signal: controller.signal, cache: 'no-store' }
);
const res = await fetch(url, { ...options, signal: controller.signal, cache: 'no-store' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res;
} finally {
Expand Down Expand Up @@ -472,50 +469,39 @@ document.addEventListener("DOMContentLoaded", () => {
}
}

const messages = [];
if (window.aiInstructions) {
messages.push({ role: "system", content: window.aiInstructions });
}
let prompt = window.aiInstructions;
const memories = Memory.getMemories();
if (memories?.length) {
messages.push({ role: "system", content: `Relevant memory:\n${memories.join("\n")}\nUse it in your response.` });
prompt += `\nRelevant memory:\n${memories.join("\n")}\nUse it in your response.`;
}

const HISTORY = 10;
const end = currentSession.messages.length - 1;
const start = Math.max(0, end - HISTORY);
for (let i = start; i < end; i++) {
messages.push(currentSession.messages[i]);
const m = currentSession.messages[i];
prompt += `\n${m.role === "ai" ? "AI" : "User"}: ${m.content}`;
}

const lastUser = overrideContent || currentSession.messages[end]?.content;
if (lastUser) {
messages.push({ role: "user", content: lastUser });
prompt += `\nUser: ${lastUser}`;
}

const modelSelectEl = document.getElementById("model-select");
const model = modelSelectEl?.value || currentSession.model || Storage.getDefaultModel();
if (!model) {
loadingDiv.textContent = "Error: No model selected.";
setTimeout(() => loadingDiv.remove(), 3000);
const btn = window._chatInternals?.sendButton || document.getElementById("send-button");
const input = window._chatInternals?.chatInput || document.getElementById("chat-input");
if (btn) btn.disabled = false;
if (input) input.disabled = false;
showToast("Please select a model before sending a message.");
if (callback) callback();
return;
}
const model = modelSelectEl?.value || currentSession.model;
if (!model) throw new Error("No model selected");
const apiUrl = `https://text.pollinations.ai/${encodeURIComponent(prompt)}?model=${encodeURIComponent(model)}`;

try {
const res = await window.pollinationsFetch("https://text.pollinations.ai/openai", {
method: "POST",
headers: { "Content-Type": "application/json", Accept: "application/json" },
body: JSON.stringify({ model, messages })
const res = await window.pollinationsFetch(apiUrl, {
method: "GET",
headers: { "Accept": "text/plain" }
}, { timeoutMs: 45000 });
const data = await res.json();
const aiContentRaw = await res.text();

loadingDiv.remove();
const aiContentRaw = data?.choices?.[0]?.message?.content || "";

let aiContent = aiContentRaw;

const memRegex = /\[memory\]([\s\S]*?)\[\/memory\]/gi;
Expand Down
31 changes: 13 additions & 18 deletions chat-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -565,24 +565,19 @@ document.addEventListener("DOMContentLoaded", () => {
chatInput.disabled = true;
};
window._chatInternals.handleSendMessage = handleSendMessage;
chatInput.addEventListener("input", () => {
sendButton.disabled = chatInput.value.trim() === "";
chatInput.style.height = "auto";
chatInput.style.height = chatInput.scrollHeight + "px";
});
sendButton.addEventListener("click", handleSendMessage);

// Send on Enter, allow newline with Shift+Enter
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
if (e.shiftKey) return; // allow newline
e.preventDefault();
// Directly invoke the send handler so the message is processed
// even if the button state would block programmatic clicks.
handleSendMessage();
}
});
sendButton.disabled = chatInput.value.trim() === "";
chatInput.addEventListener("input", () => {
sendButton.disabled = chatInput.value.trim() === "";
chatInput.style.height = "auto";
chatInput.style.height = chatInput.scrollHeight + "px";
});
chatInput.addEventListener("keydown", e => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
});
sendButton.addEventListener("click", handleSendMessage);
sendButton.disabled = chatInput.value.trim() === "";
chatInput.dispatchEvent(new Event("input"));
const initialSession = Storage.getCurrentSession();
if (initialSession.messages?.length > 0) renderStoredMessages(initialSession.messages);
Expand Down
84 changes: 30 additions & 54 deletions chat-storage.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,7 @@
document.addEventListener("DOMContentLoaded", () => {
const { chatBox, chatInput, clearChatBtn, voiceToggleBtn, modelSelect, synth, autoSpeakEnabled, speakMessage, stopSpeaking, showToast, toggleSpeechRecognition, initSpeechRecognition, handleVoiceCommand, speakSentences } = window._chatInternals;
const imagePatterns = window.imagePatterns;

function openImageModal(imageUrl) {
window.open(imageUrl, "_blank");
}

function addImageToGallery(imageUrl) {
const gallery = document.getElementById('past-image-gallery');
if (!gallery) return;
if ([...gallery.querySelectorAll('img.thumbnail')].some(img => img.src === imageUrl)) return;
const wrapper = gallery.parentElement;
const img = document.createElement('img');
img.src = imageUrl;
img.className = 'thumbnail';
img.addEventListener('click', () => {
openImageModal(imageUrl);
});
gallery.appendChild(img);
if (wrapper && wrapper.classList.contains('hidden')) {
wrapper.classList.remove('hidden');
}
if (window.Memory && typeof window.Memory.saveImage === 'function') {
window.Memory.saveImage(imageUrl);
}
}

if (window.Memory && typeof window.Memory.loadPastImages === 'function') {
window.Memory.loadPastImages(addImageToGallery);
}

function generateSessionTitle(messages) {
function generateSessionTitle(messages) {
let title = "";
for (let i = 0; i < messages.length; i++) {
if (messages[i].role === "ai") {
Expand Down Expand Up @@ -234,20 +205,19 @@ document.addEventListener("DOMContentLoaded", () => {
img.style.display = "block";
attachImageButtons(img, imageId);
};
img.onerror = () => {
loadingDiv.innerHTML = "⚠️ Failed to load image";
loadingDiv.style.display = "flex";
loadingDiv.style.justifyContent = "center";
loadingDiv.style.alignItems = "center";
};
imageContainer.appendChild(img);
addImageToGallery(url);
const imgButtonContainer = document.createElement("div");
imgButtonContainer.className = "image-button-container";
imgButtonContainer.dataset.imageId = imageId;
imageContainer.appendChild(imgButtonContainer);
return imageContainer;
}
img.onerror = () => {
loadingDiv.innerHTML = "⚠️ Failed to load image";
loadingDiv.style.display = "flex";
loadingDiv.style.justifyContent = "center";
loadingDiv.style.alignItems = "center";
};
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) {
const imgButtonContainer = document.querySelector(`.image-button-container[data-image-id="${imageId}"]`);
if (!imgButtonContainer) {
Expand Down Expand Up @@ -621,16 +591,22 @@ document.addEventListener("DOMContentLoaded", () => {
sendButton.disabled = true;
chatInput.disabled = true;
}
chatInput.addEventListener("input", () => {
sendButton.disabled = chatInput.value.trim() === "";
chatInput.style.height = "auto";
chatInput.style.height = chatInput.scrollHeight + "px";
});
sendButton.addEventListener("click", () => {
handleSendMessage();
});
sendButton.disabled = chatInput.value.trim() === "";
const initialSession = Storage.getCurrentSession();
chatInput.addEventListener("input", () => {
sendButton.disabled = chatInput.value.trim() === "";
chatInput.style.height = "auto";
chatInput.style.height = chatInput.scrollHeight + "px";
});
chatInput.addEventListener("keydown", (e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
handleSendMessage();
}
});
sendButton.addEventListener("click", () => {
handleSendMessage();
});
sendButton.disabled = chatInput.value.trim() === "";
const initialSession = Storage.getCurrentSession();
if (initialSession.messages && initialSession.messages.length > 0) {
renderStoredMessages(initialSession.messages);
} else {
Expand Down
77 changes: 25 additions & 52 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,20 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Unity AI Lab Chat</title>
<title>Unity Chat UI 0.14.7</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="stylesScreensaver.css">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div id="screensaver-container" class="screensaver hidden">
<img id="screensaver-image1" alt="Screensaver Image" style="opacity: 0;">
<img id="screensaver-image2" alt="Screensaver Image" style="opacity: 0;">
<div id="screensaver-thumbnails-wrapper" class="screensaver-thumbnails-wrapper">
<button id="screensaver-thumb-left" class="thumb-nav" aria-label="Scroll thumbnails left">&#9664;</button>
<div id="screensaver-thumbnails" class="screensaver-thumbnails">
<!-- Predefined slots for up to ten thumbnails -->
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
<img class="thumbnail placeholder" alt="Empty slot" src="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
</div>
<button id="screensaver-thumb-right" class="thumb-nav" aria-label="Scroll thumbnails right">&#9654;</button>
</div>
<div id="screensaver-thumbnails" class="screensaver-thumbnails"></div>
<div class="screensaver-controls">
<div class="screensaver-settings">
<label>Prompt:
Expand All @@ -47,9 +31,9 @@
<option value="portrait">9:16</option>
</select>
</label>
<label>Image Model:
<select id="screensaver-model"></select>
</label>
<label>Image Model:
<select id="screensaver-model"></select>
</label>
<label>Transition Duration (s):
<input type="number" id="screensaver-transition-duration" value="1" min="0.1" step="0.1">
</label>
Expand Down Expand Up @@ -147,11 +131,6 @@ <h2>Sessions</h2>
</button>
</div>
</div>
<div class="gallery-wrapper hidden">
<div id="past-image-gallery" class="screensaver-thumbnails">
<!-- JS will populate thumbnails here -->
</div>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -370,18 +349,18 @@ <h3 class="modal-title">Voice Settings</h3>
<input type="range" id="voice-speed" class="form-range" min="0.5" max="2.0" step="0.1" value="1.0">
<span id="voice-speed-value">1.0x</span>
</div>
<div class="form-group mb-3">
<label for="voice-pitch" class="form-label">
<i class="fas fa-music"></i> Pitch:
</label>
<input type="range" id="voice-pitch" class="form-range" min="0.5" max="2.0" step="0.1" value="1.0">
<span id="voice-pitch-value">1.0x</span>
</div>
</div>
<div class="modal-footer">
<button id="voice-settings-save" class="btn btn-primary">Save</button>
<button id="voice-settings-cancel" class="btn btn-secondary">Cancel</button>
</div>
<div class="form-group mb-3">
<label for="voice-pitch" class="form-label">
<i class="fas fa-music"></i> Pitch:
</label>
<input type="range" id="voice-pitch" class="form-range" min="0.5" max="2.0" step="0.1" value="1.0">
<span id="voice-pitch-value">1.0x</span>
</div>
</div>
<div class="modal-footer">
<button id="voice-settings-save" class="btn btn-primary">Save</button>
<button id="voice-settings-cancel" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
<div id="voice-chat-modal" class="modal-backdrop hidden">
Expand Down Expand Up @@ -424,23 +403,17 @@ <h3 class="modal-title">Voice Chat</h3>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-css.min.js" defer></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markup.min.js" defer></script>
<script defer src="screensaver.js"></script>
<script defer src="storage.js"></script>
<script defer src="memory-api.js"></script>

<!-- chat-core FIRST so pollinationsFetch exists -->
<script defer src="chat-core.js"></script>

<!-- UI now safely uses pollinationsFetch -->
<script defer src="ui.js"></script>
<script defer src="chat-storage.js"></script>
<script defer src="chat-init.js"></script>
<script defer src="simple.js"></script>
</body>
</html>
<script defer src="chat-core.js"></script>
<script defer src="chat-storage.js"></script>
<script defer src="chat-init.js"></script>
<script defer src="simple.js"></script>
</body>

</html>
Loading