Skip to content

Commit 9007478

Browse files
authored
Merge pull request #21 from Unity-Lab-AI/develop
Develop
2 parents c80a067 + b6912b0 commit 9007478

File tree

9 files changed

+1103
-291
lines changed

9 files changed

+1103
-291
lines changed

ai/chat-part1.js

Lines changed: 223 additions & 93 deletions
Large diffs are not rendered by default.

ai/chat-part2.js

Lines changed: 593 additions & 38 deletions
Large diffs are not rendered by default.

ai/chat-part3.js

Lines changed: 97 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,25 @@
11
document.addEventListener("DOMContentLoaded", () => {
2-
const { chatBox, chatInput, clearChatBtn, voiceToggleBtn, modelSelect, synth, autoSpeakEnabled, speakMessage, stopSpeaking, showToast, toggleSpeechRecognition, initSpeechRecognition } = window._chatInternals;
3-
2+
const {
3+
chatBox,
4+
chatInput,
5+
clearChatBtn,
6+
voiceToggleBtn,
7+
modelSelect,
8+
synth,
9+
autoSpeakEnabled,
10+
speakMessage,
11+
stopSpeaking,
12+
showToast,
13+
toggleSpeechRecognition,
14+
initSpeechRecognition
15+
} = window._chatInternals;
16+
417
// No static currentSession; we'll fetch it fresh each time
18+
519
function randomSeed() {
620
return Math.floor(Math.random() * 1000000).toString();
721
}
8-
22+
923
function generateSessionTitle(messages) {
1024
let title = "";
1125
for (let i = 0; i < messages.length; i++) {
@@ -18,7 +32,7 @@ document.addEventListener("DOMContentLoaded", () => {
1832
if (title.length > 50) title = title.substring(0, 50) + "...";
1933
return title;
2034
}
21-
35+
2236
function checkAndUpdateSessionTitle() {
2337
const currentSession = Storage.getCurrentSession();
2438
if (!currentSession.name || currentSession.name === "New Chat") {
@@ -28,12 +42,23 @@ document.addEventListener("DOMContentLoaded", () => {
2842
}
2943
}
3044
}
31-
45+
46+
// Wait for Prism.js to load before applying highlighting
3247
function waitForPrism(callback) {
3348
if (window.Prism) callback();
3449
else setTimeout(() => waitForPrism(callback), 100);
3550
}
36-
51+
52+
// Highlight all code blocks inside chatBox
53+
function highlightAllCodeBlocks() {
54+
waitForPrism(() => {
55+
const codeBlocks = chatBox.querySelectorAll("pre code");
56+
codeBlocks.forEach((block) => {
57+
Prism.highlightElement(block);
58+
});
59+
});
60+
}
61+
3762
function appendMessage({ role, content, index }) {
3863
const container = document.createElement("div");
3964
container.classList.add("message");
@@ -97,9 +122,11 @@ document.addEventListener("DOMContentLoaded", () => {
97122
copyBtn.className = "message-action-btn";
98123
copyBtn.textContent = "Copy";
99124
copyBtn.addEventListener("click", () => {
100-
navigator.clipboard.writeText(content).then(() => showToast("AI response copied to clipboard")).catch(() => {
101-
showToast("Failed to copy to clipboard");
102-
});
125+
navigator.clipboard.writeText(content)
126+
.then(() => showToast("AI response copied to clipboard"))
127+
.catch(() => {
128+
showToast("Failed to copy to clipboard");
129+
});
103130
});
104131
actionsDiv.appendChild(copyBtn);
105132
const speakBtn = document.createElement("button");
@@ -147,11 +174,13 @@ document.addEventListener("DOMContentLoaded", () => {
147174
copyCodeBtn.textContent = "Copy Code";
148175
copyCodeBtn.style.fontSize = "12px";
149176
copyCodeBtn.addEventListener("click", () => {
150-
navigator.clipboard.writeText(codeContent).then(() => {
151-
showToast("Code copied to clipboard");
152-
}).catch(() => {
153-
showToast("Failed to copy code");
154-
});
177+
navigator.clipboard.writeText(codeContent)
178+
.then(() => {
179+
showToast("Code copied to clipboard");
180+
})
181+
.catch(() => {
182+
showToast("Failed to copy code");
183+
});
155184
});
156185
buttonContainer.appendChild(copyCodeBtn);
157186
const downloadCodeBtn = document.createElement("button");
@@ -171,7 +200,7 @@ document.addEventListener("DOMContentLoaded", () => {
171200
speakMessage(content);
172201
}
173202
}
174-
203+
175204
function downloadCodeAsTxt(codeContent, language) {
176205
const blob = new Blob([codeContent], { type: "text/plain" });
177206
const url = URL.createObjectURL(blob);
@@ -184,7 +213,7 @@ document.addEventListener("DOMContentLoaded", () => {
184213
URL.revokeObjectURL(url);
185214
showToast("Code downloaded as .txt");
186215
}
187-
216+
188217
function createImageElement(url) {
189218
const imageContainer = document.createElement("div");
190219
imageContainer.className = "ai-image-container";
@@ -227,7 +256,7 @@ document.addEventListener("DOMContentLoaded", () => {
227256
imageContainer.appendChild(buttonContainer);
228257
return imageContainer;
229258
}
230-
259+
231260
function renderMarkdown(mdText) {
232261
if (window.marked) {
233262
marked.setOptions({
@@ -242,28 +271,34 @@ document.addEventListener("DOMContentLoaded", () => {
242271
return mdText.replace(/\n/g, "<br>");
243272
}
244273
}
245-
274+
246275
function escapeHTML(html) {
247-
return html.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
276+
return html.replace(/&/g, "&amp;")
277+
.replace(/</g, "&lt;")
278+
.replace(/>/g, "&gt;")
279+
.replace(/"/g, "&quot;")
280+
.replace(/'/g, "&#039;");
248281
}
249-
282+
250283
function renderStoredMessages(messages) {
251284
chatBox.innerHTML = "";
252285
messages.forEach((msg, idx) => appendMessage({ role: msg.role, content: msg.content, index: idx }));
286+
highlightAllCodeBlocks();
253287
}
254-
288+
255289
window.addNewMessage = function ({ role, content }) {
256290
const currentSession = Storage.getCurrentSession();
257291
currentSession.messages.push({ role, content });
258292
Storage.updateSessionMessages(currentSession.id, currentSession.messages);
259293
appendMessage({ role, content, index: currentSession.messages.length - 1 });
260294
if (role === "ai") checkAndUpdateSessionTitle();
261295
};
262-
296+
263297
function editMessage(msgIndex) {
264298
const currentSession = Storage.getCurrentSession();
265299
const oldMessage = currentSession.messages[msgIndex];
266300
if (!oldMessage) return;
301+
window._chatInternals.stopSpeaking();
267302
const newContent = prompt("Edit this message:", oldMessage.content);
268303
if (newContent === null || newContent === oldMessage.content) return;
269304
if (oldMessage.role === "user") {
@@ -285,17 +320,20 @@ document.addEventListener("DOMContentLoaded", () => {
285320
sendToPollinations(() => {
286321
const loadingMsg = document.getElementById(loadingMsgId);
287322
if (loadingMsg) loadingMsg.remove();
323+
highlightAllCodeBlocks();
288324
}, newContent);
289325
showToast("User message updated and new response generated");
290326
} else {
291327
currentSession.messages[msgIndex].content = newContent;
292328
Storage.updateSessionMessages(currentSession.id, currentSession.messages);
293329
renderStoredMessages(currentSession.messages);
330+
highlightAllCodeBlocks();
294331
showToast("AI message updated");
295332
}
296333
}
297-
334+
298335
function reGenerateAIResponse(aiIndex) {
336+
window._chatInternals.stopSpeaking();
299337
const currentSession = Storage.getCurrentSession();
300338
if (aiIndex < 0 || aiIndex >= currentSession.messages.length) return;
301339
let userIndex = -1;
@@ -327,10 +365,11 @@ document.addEventListener("DOMContentLoaded", () => {
327365
sendToPollinations(() => {
328366
const loadingMsg = document.getElementById(loadingMsgId);
329367
if (loadingMsg) loadingMsg.remove();
368+
highlightAllCodeBlocks();
330369
showToast("Response regenerated successfully");
331370
}, userMessage);
332371
}
333-
372+
334373
window.sendToPollinations = function (callback = null, overrideContent = null) {
335374
const currentSession = Storage.getCurrentSession();
336375
const loadingMsgId = "loading-" + Date.now();
@@ -344,7 +383,7 @@ document.addEventListener("DOMContentLoaded", () => {
344383
loadingDiv.textContent = "Thinking...";
345384
chatBox.appendChild(loadingDiv);
346385
chatBox.scrollTop = chatBox.scrollHeight;
347-
const memoryInstructions = "You are the user's AI. Be yourself, the 'you' and name and gender you know is you the AI. Do not mix up your name and gender with the user's. Write user 'memories' you make in [memory]your_text_memory[/memory] format, and include an additional plain text description in your response. When sharing code, always use [CODE]language\ncode[/CODE] for proper syntax highlighting (e.g., [CODE]python\nprint('Hello')\n[/CODE]). Always wrap code in these blocks for readability. Images can be generated via https://image.pollinations.ai/prompt/your_detailed_prompt_here.";
386+
const memoryInstructions = "You are the user's AI. Be yourself, the 'you' and name and gender you know is you the AI. Do not mix up your name and gender with the user's. Write user 'memories' you make in [memory]your_text_memory[/memory] format, and include an additional plain text description in your response. When sharing code, always use [CODE]language\ncode[/CODE] for proper syntax highlighting (e.g., [CODE]python\nprint('Hello')\n[/CODE]). Always wrap code in these blocks for readability. Images can be generated via https://image.pollinations.ai/openai/prompt/your_detailed_prompt_here.";
348387
const messages = [];
349388
if (memoryInstructions) messages.push({ role: "system", content: memoryInstructions });
350389
const memories = Memory.getMemories();
@@ -361,8 +400,9 @@ document.addEventListener("DOMContentLoaded", () => {
361400
if (overrideContent && messages[messages.length - 1].content !== overrideContent) {
362401
messages.push({ role: "user", content: overrideContent });
363402
}
403+
const safeParam = window._pollinationsAPIConfig ? `safe=${window._pollinationsAPIConfig.safe}` : "safe=false";
364404
const body = { messages, model: currentSession.model || modelSelect.value || "unity", stream: false };
365-
fetch("https://text.pollinations.ai/openai", {
405+
fetch(`https://text.pollinations.ai/openai?${safeParam}`, {
366406
method: "POST",
367407
headers: { "Content-Type": "application/json", Accept: "application/json" },
368408
body: JSON.stringify(body)
@@ -375,28 +415,23 @@ document.addEventListener("DOMContentLoaded", () => {
375415
const loadingMsg = document.getElementById(loadingMsgId);
376416
if (loadingMsg) loadingMsg.remove();
377417
let aiContent = extractAIContent(data);
378-
379-
// Check if the user's prompt is requesting an image
380418
const lastUserMsg = messages[messages.length - 1].content.toLowerCase();
381-
const isImageRequest = lastUserMsg.includes("image") || lastUserMsg.includes("picture") || lastUserMsg.includes("show me") || lastUserMsg.includes("generate an image");
382-
383-
// If the prompt suggests an image request but no image URL is in the response, generate one
419+
const isImageRequest = lastUserMsg.includes("image") ||
420+
lastUserMsg.includes("picture") ||
421+
lastUserMsg.includes("show me") ||
422+
lastUserMsg.includes("generate an image");
384423
if (aiContent && isImageRequest && !aiContent.includes("https://image.pollinations.ai")) {
385-
let imagePrompt = lastUserMsg
386-
.replace(/show me|generate|image of|picture of|image|picture/gi, "")
387-
.trim();
388-
424+
let imagePrompt = lastUserMsg.replace(/show me|generate|image of|picture of|image|picture/gi, "").trim();
389425
if (imagePrompt.length < 5 && aiContent.toLowerCase().includes("image")) {
390-
imagePrompt = aiContent
391-
.toLowerCase()
392-
.replace(/here's an image of|image|to enjoy visually/gi, "")
393-
.trim();
426+
imagePrompt = aiContent.toLowerCase().replace(/here's an image of|image|to enjoy visually/gi, "").trim();
427+
}
428+
if (imagePrompt.length > 100) {
429+
imagePrompt = imagePrompt.substring(0, 100);
394430
}
395-
396-
const imageUrl = `https://image.pollinations.ai/prompt/${encodeURIComponent(imagePrompt)}?width=512&height=512&seed=${randomSeed()}`;
431+
imagePrompt += ", photographic";
432+
const imageUrl = `https://image.pollinations.ai/prompt/${encodeURIComponent(imagePrompt)}?width=512&height=512&seed=${randomSeed()}&${safeParam}&nolog=true`;
397433
aiContent += `\n\n**Generated Image:**\n${imageUrl}`;
398434
}
399-
400435
if (aiContent) {
401436
const foundMemories = parseMemoryBlocks(aiContent);
402437
foundMemories.forEach((m) => Memory.addMemoryEntry(m));
@@ -415,28 +450,32 @@ document.addEventListener("DOMContentLoaded", () => {
415450
}
416451
});
417452
};
418-
453+
419454
function extractAIContent(response) {
420455
if (response.choices && response.choices.length > 0) {
421-
if (response.choices[0].message && response.choices[0].message.content) return response.choices[0].message.content;
422-
else if (response.choices[0].text) return response.choices[0].text;
423-
} else if (response.response) return response.response;
424-
else if (typeof response === "string") return response;
456+
if (response.choices[0].message && response.choices[0].message.content)
457+
return response.choices[0].message.content;
458+
else if (response.choices[0].text)
459+
return response.choices[0].text;
460+
} else if (response.response)
461+
return response.response;
462+
else if (typeof response === "string")
463+
return response;
425464
return "Sorry, I couldn't process that response.";
426465
}
427-
466+
428467
function parseMemoryBlocks(text) {
429468
const memRegex = /\[memory\]([\s\S]*?)\[\/memory\]/gi;
430469
const found = [];
431470
let match;
432471
while ((match = memRegex.exec(text)) !== null) found.push(match[1].trim());
433472
return found;
434473
}
435-
474+
436475
function removeMemoryBlocks(text) {
437476
return text.replace(/\[memory\][\s\S]*?\[\/memory\]/gi, "");
438477
}
439-
478+
440479
if (voiceToggleBtn) {
441480
voiceToggleBtn.addEventListener("click", window._chatInternals.toggleAutoSpeak);
442481
window._chatInternals.updateVoiceToggleUI();
@@ -455,7 +494,7 @@ document.addEventListener("DOMContentLoaded", () => {
455494
}
456495
}, 2000);
457496
}
458-
497+
459498
if (clearChatBtn) {
460499
clearChatBtn.addEventListener("click", () => {
461500
const currentSession = Storage.getCurrentSession();
@@ -467,7 +506,7 @@ document.addEventListener("DOMContentLoaded", () => {
467506
}
468507
});
469508
}
470-
509+
471510
function checkFirstLaunch() {
472511
const firstLaunch = localStorage.getItem("firstLaunch") === "0";
473512
if (firstLaunch) {
@@ -498,7 +537,7 @@ document.addEventListener("DOMContentLoaded", () => {
498537
}
499538
}
500539
checkFirstLaunch();
501-
540+
502541
function setupVoiceInputButton() {
503542
if ("webkitSpeechRecognition" in window || "SpeechRecognition" in window) {
504543
const inputButtonsContainer = document.querySelector(".input-buttons-container");
@@ -514,7 +553,7 @@ document.addEventListener("DOMContentLoaded", () => {
514553
}
515554
}
516555
setupVoiceInputButton();
517-
556+
518557
const sendButton = document.getElementById("send-button");
519558
function handleSendMessage() {
520559
const message = chatInput.value.trim();
@@ -525,24 +564,24 @@ document.addEventListener("DOMContentLoaded", () => {
525564
window.sendToPollinations();
526565
sendButton.disabled = true;
527566
}
528-
567+
529568
chatInput.addEventListener("input", () => {
530569
sendButton.disabled = chatInput.value.trim() === "";
531570
chatInput.style.height = "auto";
532571
chatInput.style.height = chatInput.scrollHeight + "px";
533572
});
534-
573+
535574
chatInput.addEventListener("keydown", (e) => {
536575
if (e.key === "Enter" && !e.shiftKey) {
537576
e.preventDefault();
538577
handleSendMessage();
539578
}
540579
});
541-
580+
542581
sendButton.addEventListener("click", () => {
543582
handleSendMessage();
544583
});
545-
584+
546585
const initialSession = Storage.getCurrentSession();
547586
if (initialSession.messages && initialSession.messages.length > 0) {
548587
renderStoredMessages(initialSession.messages);

ai/index.html

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<title>Unity Chat UI 9.0</title>
5+
<title>Unity Chat UI 0.9.63</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<!-- Favicon -->
88
<link rel="icon" href="favicon.ico" type="image/x-icon" />
@@ -293,6 +293,15 @@ <h3 class="modal-title">Settings</h3>
293293
<!-- Will be populated by JS -->
294294
</select>
295295
</div>
296+
297+
<div class="form-group mb-3">
298+
<label for="voice-select" class="form-label">
299+
<i class="fas fa-headset"></i> Voice Selection:
300+
</label>
301+
<select id="voice-select" class="form-control">
302+
<!-- Voices will dynamically populate here -->
303+
</select>
304+
</div>
296305

297306
<div class="d-grid gap-2">
298307
<button
@@ -653,4 +662,4 @@ <h2 class="welcome-heading">Welcome to Unity Chat!</h2>
653662
})();
654663
</script>
655664
</body>
656-
</html>
665+
</html>

0 commit comments

Comments
 (0)