Skip to content

Commit 7b76078

Browse files
authored
Merge pull request #10 from Unity-Lab-AI/feature/v0.5
Fixed chat settings / JSON Errors
2 parents bc8f4ee + d12c58d commit 7b76078

File tree

3 files changed

+232
-87
lines changed

3 files changed

+232
-87
lines changed

chat.js

Lines changed: 195 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -310,48 +310,161 @@ document.addEventListener("DOMContentLoaded", () => {
310310
});
311311

312312
// Generate chat title.
313-
function generateChatTitle(initialInput) {
314-
const prompt = `Generate a creative, 3-8 word chat title based on this conversation:\nUser: ${initialInput}`;
315-
const body = {
316-
messages: [
317-
{
318-
role: "system",
319-
content: "You are yourself, interact and conversate with the user as yourself. Any code you create shall be wrapped in [CODE] and [/CODE] tags containing all the code. You can generate images using: https://image.pollinations.ai/prompt/your_prompt_here\n\n" + initialInput
320-
}
321-
],
322-
model: "flux",
323-
seed: randomSeed(),
324-
jsonMode: true,
325-
private: true
326-
};
313+
// Replace the entire generateChatTitle function with this improved version
314+
function generateChatTitle(initialInput) {
315+
const trimmedInput = initialInput.length > 50
316+
? initialInput.substring(0, 47) + "..."
317+
: initialInput;
318+
319+
// Create a more direct prompt that explicitly asks for plain text
320+
const body = {
321+
messages: [
322+
{
323+
role: "system",
324+
content: "You are a helpful assistant that creates short, descriptive titles. When asked to create a title, respond with ONLY 3-4 words that capture the essence of the conversation. Do not include any formatting, quotes, JSON, or explanations."
325+
},
326+
{
327+
role: "user",
328+
content: "Create a title for this conversation: " + trimmedInput
329+
}
330+
],
331+
model: "flux", // or whatever model you're using
332+
seed: randomSeed(),
333+
private: true
334+
};
327335

328-
console.debug("Generating chat title with payload:", body);
329-
fetch("https://text.pollinations.ai/openai", {
330-
method: "POST",
331-
headers: { "Content-Type": "application/json" },
332-
body: JSON.stringify(body)
336+
console.debug("Generating chat title with payload:", body);
337+
338+
// Set a loading title while waiting for the AI response
339+
const tempTitle = "Creating title...";
340+
Storage.renameSession(currentSession.id, tempTitle);
341+
342+
fetch("https://text.pollinations.ai/openai", { // Using /openai endpoint instead of /unity for more reliable plain text
343+
method: "POST",
344+
headers: { "Content-Type": "application/json" },
345+
body: JSON.stringify(body)
346+
})
347+
.then(res => {
348+
if (!res.ok) {
349+
throw new Error(`HTTP error! Status: ${res.status}`);
350+
}
351+
return res.text(); // Get raw text instead of parsing as JSON
333352
})
334-
.then(res => res.json())
335-
.then(data => {
336-
console.debug("Raw title generation response:", data);
337-
let title = "Untitled Chat";
338-
if (data?.choices && data.choices.length > 0) {
339-
const titleJSON = data.choices[0].message.content.trim();
340-
try {
341-
const parsedTitle = JSON.parse(titleJSON);
342-
title = parsedTitle.response || parsedTitle.chatTitle || titleJSON;
343-
} catch (e) {
344-
console.error("Error parsing chat title JSON:", e);
345-
title = titleJSON;
346-
}
353+
.then(rawData => {
354+
console.debug("Raw title generation response:", rawData);
355+
356+
let title = "Untitled Chat";
357+
358+
// Try to extract a clean title from various formats
359+
try {
360+
// First check if it's parseable as JSON
361+
const data = JSON.parse(rawData);
362+
363+
// Try to get from choices[0].message.content (OpenAI format)
364+
if (data.choices && data.choices.length > 0 && data.choices[0].message && data.choices[0].message.content) {
365+
title = data.choices[0].message.content.trim();
366+
}
367+
// Try to get from common response fields
368+
else if (data.response) {
369+
title = data.response;
347370
}
348-
console.debug("Extracted chat title:", title);
349-
Storage.renameSession(currentSession.id, title);
350-
currentSession = Storage.getCurrentSession();
351-
})
352-
.catch(err => {
353-
console.error("Error generating chat title:", err);
354-
});
371+
else if (data.message) {
372+
title = data.message;
373+
}
374+
else if (data.content) {
375+
title = data.content;
376+
}
377+
// If it's just a string in the JSON
378+
else if (typeof data === "string") {
379+
title = data;
380+
}
381+
// If we got here but couldn't find a recognizable field, try stringifying
382+
else {
383+
const str = JSON.stringify(data);
384+
if (str.length < 50) title = str;
385+
}
386+
} catch (e) {
387+
// Not valid JSON, so treat the raw response as plain text
388+
// This should be the most common case with our improved prompt
389+
if (rawData && rawData.length < 100) {
390+
title = rawData.trim();
391+
}
392+
}
393+
394+
// Clean up title
395+
title = title
396+
.replace(/["""]/g, '') // Remove all types of quotes
397+
.replace(/^Title: /i, '') // Remove "Title:" prefix if present
398+
.replace(/[\{\}]/g, '') // Remove curly braces
399+
.replace(/^title:?\s*/i, '') // Remove "title:" prefix
400+
.replace(/^chat title:?\s*/i, '') // Remove "chat title:" prefix
401+
.replace(/^conversation title:?\s*/i, '') // Remove "conversation title:" prefix
402+
.trim();
403+
404+
// If title is still too long or empty, create a default
405+
if (title.length > 30) {
406+
title = title.substring(0, 27) + "...";
407+
} else if (!title || title.length < 2) {
408+
// Generate a simple topic-based title from the user's input
409+
const words = trimmedInput.split(/\s+/).filter(w => w.length > 3);
410+
if (words.length >= 2) {
411+
title = words.slice(0, 2).join(" ");
412+
} else {
413+
title = "Chat about " + (words[0] || "topic");
414+
}
415+
}
416+
417+
console.debug("Final chat title:", title);
418+
Storage.renameSession(currentSession.id, title);
419+
currentSession = Storage.getCurrentSession();
420+
})
421+
.catch(err => {
422+
console.error("Error generating chat title:", err);
423+
// Extract meaningful words from the user's input as a fallback
424+
const words = trimmedInput.split(/\s+/).filter(word =>
425+
word.length > 3 && !["what", "when", "where", "which", "this", "that", "with", "your"].includes(word.toLowerCase())
426+
);
427+
428+
let defaultTitle;
429+
if (words.length >= 2) {
430+
defaultTitle = words.slice(0, 3).join(" ");
431+
if (defaultTitle.length > 30) {
432+
defaultTitle = defaultTitle.substring(0, 27) + "...";
433+
}
434+
} else {
435+
defaultTitle = "Chat " + new Date().toLocaleTimeString();
436+
}
437+
438+
Storage.renameSession(currentSession.id, defaultTitle);
439+
currentSession = Storage.getCurrentSession();
440+
});
441+
}
442+
443+
// 5. Improvements to renderSessions in storage.js
444+
// Add this to clean up session titles in renderSessions:
445+
446+
// Inside the renderSessions function, replace the code that sets displayName:
447+
let displayName = session.name;
448+
if (typeof displayName === "object") {
449+
// Handle object names
450+
displayName = "Chat " + new Date(session.lastUpdated).toLocaleString();
451+
} else if (typeof displayName === "string") {
452+
// Handle JSON-looking strings
453+
if (displayName.startsWith("{") && displayName.endsWith("}")) {
454+
try {
455+
const parsed = JSON.parse(displayName);
456+
displayName = parsed.response || parsed.chatTitle || parsed.title ||
457+
"Chat " + new Date(session.lastUpdated).toLocaleString();
458+
} catch (e) {
459+
// If parsing fails, truncate the string
460+
displayName = displayName.length > 20
461+
? displayName.substring(0, 17) + "..."
462+
: displayName;
463+
}
464+
}
465+
466+
// Remove quotes if present
467+
displayName = displayName.replace(/^"(.*)"$/, "$1");
355468
}
356469

357470
// Send message to Pollinations API.
@@ -365,7 +478,7 @@ document.addEventListener("DOMContentLoaded", () => {
365478
const systemPrompt = {
366479
role: "system",
367480
content: "You are yourself, interact and conversate with the user as yourself using plain text only. " +
368-
"Do not format your responses in JSON" +
481+
"Do not format your responses in JSON. " +
369482
"When providing code, you MUST wrap it in one of these formats:\n" +
370483
"1. [CODE] and [/CODE] tags (e.g., [CODE]console.log('hello')[/CODE])\n" +
371484
"2. Markdown code blocks with language specification (preferred):\n" +
@@ -375,9 +488,9 @@ document.addEventListener("DOMContentLoaded", () => {
375488
"Always specify the language in markdown code blocks for proper syntax highlighting. " +
376489
"Never use both formats for the same code block. " +
377490
"For multi-file responses, use separate code blocks for each file, each with appropriate language specification. " +
378-
"You can generate images using this: https://image.pollinations.ai/prompt/your_prompt_here&width=1080&height=1920&enhance=true&private=true&seed=497283&nologo=true&model=flux"
379-
}; "You use %20 for spaces when creating 'your_prompt_here', use a random generated 6 digit number for seed, and use standard height witdh for aspect ratios or user specified. You can use this method to generate likness' or anything you and/or the user come up with. respect the user's wishes and requests. these generations can be called an image, pic, photo, art and any variation there-in and more."
380-
491+
"You can generate images using this: https://image.pollinations.ai/prompt/your_prompt_here&width=1080&height=1920&enhance=true&private=true&seed=497283&nologo=true&model=flux. " +
492+
"You use %20 for spaces when creating 'your_prompt_here', use a random generated 6 digit number for seed, and use standard height width for aspect ratios or user specified. You can use this method to generate likeness' or anything you and/or the user come up with. respect the user's wishes and requests. these generations can be called an image, pic, photo, art and any variation there-in and more."
493+
};
381494
const body = {
382495
messages: [systemPrompt, ...normalizedMessages],
383496
model: modelName,
@@ -410,40 +523,52 @@ document.addEventListener("DOMContentLoaded", () => {
410523
}
411524
// Format AI response.
412525
function formatAIResponse(data) {
413-
let rawResponse;
526+
// Case 1: If it's already a string, try to extract JSON from it
414527
if (typeof data === "string") {
415-
rawResponse = extractJSON(data);
416-
console.debug("Extracted JSON from raw response:", rawResponse);
417528
try {
418-
data = JSON.parse(rawResponse);
529+
// Try to parse it as JSON
530+
const jsonData = JSON.parse(data);
531+
// If successful, extract the content
532+
return extractContentFromJSON(jsonData);
419533
} catch (e) {
420-
console.error("Failed to parse JSON from response:", e);
421-
return rawResponse;
534+
// If not valid JSON, just return the string
535+
return data;
422536
}
423537
}
424-
console.debug("Parsed JSON response:", data);
425-
426-
let content = "No response available";
427-
if (data?.response) {
428-
content = data.response;
429-
} else if (data?.message) {
430-
content = data.message;
431-
} else if (data?.choices && data.choices.length > 0 && data.choices[0].message && data.choices[0].message.content) {
432-
const messageContent = data.choices[0].message.content.trim();
433-
try {
434-
const parsedContent = JSON.parse(messageContent);
435-
content = parsedContent.response || messageContent;
436-
} catch (e) {
437-
content = messageContent;
538+
539+
// Case 2: It's already a JSON object
540+
return extractContentFromJSON(data);
541+
542+
// Helper function to extract content from various JSON formats
543+
function extractContentFromJSON(json) {
544+
if (!json) return "No response available";
545+
546+
// Check various common formats
547+
if (typeof json.response === "string") return json.response;
548+
if (typeof json.message === "string") return json.message;
549+
if (typeof json.content === "string") return json.content;
550+
if (json.choices && json.choices.length > 0) {
551+
const choice = json.choices[0];
552+
if (typeof choice === "string") return choice;
553+
if (choice.message && typeof choice.message.content === "string") {
554+
try {
555+
// Sometimes content is a JSON string itself
556+
const nestedJson = JSON.parse(choice.message.content);
557+
if (typeof nestedJson.response === "string") return nestedJson.response;
558+
if (typeof nestedJson.content === "string") return nestedJson.content;
559+
if (typeof nestedJson.message === "string") return nestedJson.message;
560+
// If nested JSON has no recognizable fields, stringify it nicely
561+
return JSON.stringify(nestedJson, null, 2);
562+
} catch (e) {
563+
// If not valid nested JSON, just return the content
564+
return choice.message.content;
565+
}
566+
}
438567
}
439-
} else if (typeof data === "string") {
440-
content = data;
441-
} else {
442-
console.debug("No valid content field in response, returning full JSON.");
443-
return JSON.stringify(data, null, 2);
568+
569+
// If we couldn't extract content in a recognizable format, stringify the whole object
570+
return JSON.stringify(json, null, 2);
444571
}
445-
446-
return content;
447572
}
448573

449574
// Additional event listeners.

index.html

Lines changed: 1 addition & 1 deletion
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 U1 5.0</title>
5+
<title>Unity Chat U1 5.1</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" />

storage.js

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,25 +58,45 @@ document.addEventListener("DOMContentLoaded", () => {
5858
}
5959

6060
function renameSession(sessionId, newName) {
61-
const session = sessions.find(s => s.id === sessionId);
62-
if (session) {
63-
let cleanName = newName;
64-
if (typeof newName === "object") {
65-
cleanName = JSON.stringify(newName);
66-
} else if (newName && newName.startsWith("{") && newName.endsWith("}")) {
67-
try {
68-
const parsed = JSON.parse(newName);
69-
cleanName = parsed.response || parsed.chatTitle || newName;
70-
} catch (e) {
71-
console.error("Error parsing session name JSON:", e);
72-
}
61+
const session = sessions.find(s => s.id === sessionId);
62+
if (session) {
63+
let cleanName = newName;
64+
65+
// Handle if newName is an object
66+
if (typeof newName === "object") {
67+
cleanName = "New Chat"; // Default fallback
68+
// Try to extract a title from the object
69+
if (newName.response) cleanName = newName.response;
70+
else if (newName.chatTitle) cleanName = newName.chatTitle;
71+
else if (newName.title) cleanName = newName.title;
72+
else cleanName = "Chat " + new Date().toLocaleString();
73+
}
74+
// Handle if newName looks like JSON
75+
else if (typeof newName === "string" &&
76+
newName.trim().startsWith("{") &&
77+
newName.trim().endsWith("}")) {
78+
try {
79+
const parsed = JSON.parse(newName);
80+
cleanName = parsed.response || parsed.chatTitle || parsed.title ||
81+
"Chat " + new Date().toLocaleString();
82+
} catch (e) {
83+
console.error("Error parsing session name JSON:", e);
84+
// If parsing fails, use the string as is but limit length
85+
cleanName = newName.substring(0, 30);
7386
}
74-
session.name = cleanName;
75-
session.lastUpdated = Date.now();
76-
saveSessions();
77-
renderSessions();
7887
}
88+
89+
// Ensure name isn't too long
90+
if (cleanName.length > 30) {
91+
cleanName = cleanName.substring(0, 27) + "...";
92+
}
93+
94+
session.name = cleanName;
95+
session.lastUpdated = Date.now();
96+
saveSessions();
97+
renderSessions();
7998
}
99+
}
80100

81101
// FIXED: Ensure a valid session is always returned.
82102
function getCurrentSession() {

0 commit comments

Comments
 (0)