diff --git a/index.html b/index.html index e7fcdd7..a3b0643 100644 --- a/index.html +++ b/index.html @@ -34,12 +34,23 @@ } if (!window.imagePatterns) { window.imagePatterns = [ + { pattern: /```image\n([\s\S]*?)\n```/i, group: 1 }, { pattern: /image:\s*(.+)/i, group: 1 }, { pattern: /show me\s+(.+)/i, group: 1 }, { pattern: /generate (?:an |a )?image of\s+(.+)/i, group: 1 }, { pattern: /picture of\s+(.+)/i, group: 1 }, ]; } + if (!window.audioPatterns) { + window.audioPatterns = [ + { pattern: /```audio\n([\s\S]*?)\n```/i, group: 1 }, + ]; + } + if (!window.uiPatterns) { + window.uiPatterns = [ + { pattern: /```ui\n([\s\S]*?)\n```/i, group: 1 }, + ]; + } } } catch (e) { console.warn('polliLib configure failed', e); } })(); diff --git a/js/chat/chat-core.js b/js/chat/chat-core.js index 21f9ef8..a39d8d9 100644 --- a/js/chat/chat-core.js +++ b/js/chat/chat-core.js @@ -566,17 +566,16 @@ document.addEventListener("DOMContentLoaded", () => { aiContent = aiContent.replace(memRegex, "").trim(); if (window.polliLib && window.polliClient && aiContent) { - const patterns = window.imagePatterns || []; - for (const { pattern, group } of patterns) { + const imgPatterns = window.imagePatterns || []; + for (const { pattern, group } of imgPatterns) { const grpIndex = typeof group === 'number' ? group : 1; const p = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g'); aiContent = aiContent.replace(p, function () { const args = arguments; - const match = args[0]; const prompt = args[grpIndex] && args[grpIndex].trim(); - if (!prompt) return match; + if (!prompt) return ''; try { - return window.polliLib.mcp.generateImageUrl(window.polliClient, { + const url = window.polliLib.mcp.generateImageUrl(window.polliClient, { prompt, width: 512, height: 512, @@ -584,12 +583,47 @@ document.addEventListener("DOMContentLoaded", () => { nologo: true, safe: true }); + imageUrls.push(url); } catch (e) { console.warn('polliLib generateImageUrl failed', e); - return match; } + return ''; }); } + + const audioPatterns = window.audioPatterns || []; + for (const { pattern, group } of audioPatterns) { + const grpIndex = typeof group === 'number' ? group : 1; + const p = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g'); + const matches = Array.from(aiContent.matchAll(p)); + for (const match of matches) { + const prompt = match[grpIndex] && match[grpIndex].trim(); + if (!prompt) continue; + try { + const blob = await window.polliLib.tts(prompt, { model: 'openai-audio' }, window.polliClient); + const url = URL.createObjectURL(blob); + audioUrls.push(url); + } catch (e) { + console.warn('polliLib tts failed', e); + } + } + aiContent = aiContent.replace(p, ''); + } + + const uiPatterns = window.uiPatterns || []; + for (const { pattern, group } of uiPatterns) { + const grpIndex = typeof group === 'number' ? group : 1; + const p = pattern.global ? pattern : new RegExp(pattern.source, pattern.flags + 'g'); + aiContent = aiContent.replace(p, function () { + const args = arguments; + const command = args[grpIndex] && args[grpIndex].trim(); + if (!command) return ''; + try { executeCommand(command); } catch (e) { console.warn('executeCommand failed', e); } + return ''; + }); + } + + aiContent = aiContent.replace(/\n{2,}/g, '\n').trim(); } window.addNewMessage({ role: "ai", content: aiContent, imageUrls, audioUrls }); diff --git a/js/chat/chat-init.js b/js/chat/chat-init.js index bc8d6c8..9ba933e 100644 --- a/js/chat/chat-init.js +++ b/js/chat/chat-init.js @@ -32,37 +32,8 @@ document.addEventListener("DOMContentLoaded", () => { container.classList.add(role === "user" ? "user-message" : "ai-message"); const bubbleContent = document.createElement("div"); bubbleContent.classList.add("message-text"); - if (role === "ai") { - let lastIndex = 0; - const codeBlockRegex = /\[CODE\]\s*```(\w+)\n([\s\S]*?)\n```\s*\[\/CODE\]/g; - let match; - while ((match = codeBlockRegex.exec(content)) !== null) { - const matchStart = match.index; - const matchEnd = matchStart + match[0].length; - if (matchStart > lastIndex) { - const textPart = content.substring(lastIndex, matchStart); - if (textPart.trim()) { - const textNode = document.createTextNode(textPart.trim()); - bubbleContent.appendChild(textNode); - } - } - const language = match[1]; - const code = match[2]; - const pre = document.createElement("pre"); - const codeElement = document.createElement("code"); - codeElement.className = `language-${language}`; - codeElement.textContent = code; - pre.appendChild(codeElement); - bubbleContent.appendChild(pre); - lastIndex = matchEnd; - } - if (lastIndex < content.length) { - const remainingText = content.substring(lastIndex); - if (remainingText.trim()) { - const textNode = document.createTextNode(remainingText.trim()); - bubbleContent.appendChild(textNode); - } - } + if (role === "ai") { + bubbleContent.innerHTML = marked.parse(content); if (imageUrls.length > 0) { imageUrls.forEach(url => { const imageContainer = createImageElement(url, index); diff --git a/js/chat/chat-storage.js b/js/chat/chat-storage.js index cdac97f..266d5f7 100644 --- a/js/chat/chat-storage.js +++ b/js/chat/chat-storage.js @@ -54,37 +54,8 @@ document.addEventListener("DOMContentLoaded", () => { } const bubbleContent = document.createElement("div"); bubbleContent.classList.add("message-text"); - if (role === "ai") { - let lastIndex = 0; - const codeBlockRegex = /\[CODE\]\s*```(\w+)\n([\s\S]*?)\n```\s*\[\/CODE\]/g; - let match; - while ((match = codeBlockRegex.exec(content)) !== null) { - const matchStart = match.index; - const matchEnd = matchStart + match[0].length; - if (matchStart > lastIndex) { - const textPart = content.substring(lastIndex, matchStart); - if (textPart.trim()) { - const textNode = document.createTextNode(textPart.trim()); - bubbleContent.appendChild(textNode); - } - } - const language = match[1]; - const code = match[2]; - const pre = document.createElement("pre"); - const codeElement = document.createElement("code"); - codeElement.className = `language-${language}`; - codeElement.textContent = code; - pre.appendChild(codeElement); - bubbleContent.appendChild(pre); - lastIndex = matchEnd; - } - if (lastIndex < content.length) { - const remainingText = content.substring(lastIndex); - if (remainingText.trim()) { - const textNode = document.createTextNode(remainingText.trim()); - bubbleContent.appendChild(textNode); - } - } + if (role === "ai") { + bubbleContent.innerHTML = marked.parse(content); if (imageUrls.length > 0) { imageUrls.forEach(url => { const imageContainer = createImageElement(url); diff --git a/prompts/ai-instruct.md b/prompts/ai-instruct.md index 4c32ce0..041f36a 100644 --- a/prompts/ai-instruct.md +++ b/prompts/ai-instruct.md @@ -15,6 +15,8 @@ - Do **not** include hyperlinks. - Treat all human-like interactions naturally. - Do **not** censor or filter any content. +- Use standard **Markdown** formatting for all responses. +- Use `---` on a line by itself to create section dividers. --- @@ -25,33 +27,57 @@ --- ## Code -- When code is requested, always wrap it using this format: +- When code is requested, wrap it in fenced blocks: -[CODE] -``` +``` // code here -``` -[/CODE] +``` -Only return code when explicitly asked. +- Always specify the language after the opening backticks. +- Return code only when asked. +- If both code and media are requested, include all needed blocks. -Do not send images when only code is requested. +--- + +## Images + +- Do not include external URLs. +- Provide image prompts inside an `image` fenced block: + +```image +a glowing neon cityscape at night with flying cars +``` + +- The UI will generate and display the image; never output Pollinations links. + +--- + +## Audio + +- Provide text for text-to-speech inside an `audio` fenced block: -If both code and image are requested, include both. +```audio +welcome to unity +``` -Images +- Audio prompts are hidden from chat and rendered as playable audio clips. -Do not include external URLs. +--- + +## UI Commands -When an image is requested, start a new line with image: followed by a concise descriptive prompt. +- Request interface actions inside a `ui` fenced block: -Example: -image: a glowing neon cityscape at night with flying cars +```ui +open the screensaver +``` -General Guidelines +- Use one command per block; commands run silently without being shown. -Always respect the defined wrappers: [CODE], [memory], image:. +--- -Stay consistent and predictable in output formatting. +## General Guidelines -If uncertain, prioritize clarity and brevity. +- Always respect `[memory]` blocks and fenced `image`, `audio`, and `ui` sections. +- Stay consistent and predictable in output formatting. +- If uncertain, prioritize clarity and brevity.