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
33 changes: 16 additions & 17 deletions js/chat/chat-init.js
Original file line number Diff line number Diff line change
Expand Up @@ -648,23 +648,22 @@ document.addEventListener("DOMContentLoaded", () => {
const updateImage = () => {
const seed = randomSeed();
try {
if (window.polliLib && window.polliClient) {
const url = window.polliLib.mcp.generateImageUrl(window.polliClient, {
prompt: imagePrompt,
width: 512,
height: 512,
seed,
nologo: true
});
voiceChatImage.src = url;
} else {
voiceChatImage.src = "https://via.placeholder.com/512?text=Image+Unavailable";
}
} catch (e) {
console.warn('polliLib generateImageUrl failed', e);
voiceChatImage.src = "https://via.placeholder.com/512?text=Image+Unavailable";
}
};
if (window.polliLib && window.polliClient) {
const url = window.polliLib.mcp.generateImageUrl(window.polliClient, {
prompt: imagePrompt,
width: 512,
height: 512,
seed,
nologo: true
});
voiceChatImage.src = url;
} else {
console.error("polliLib not available; cannot generate voice chat image");
}
} catch (e) {
console.warn('polliLib generateImageUrl failed', e);
}
};
updateImage();
slideshowInterval = setInterval(updateImage, 10000);
};
Expand Down
31 changes: 15 additions & 16 deletions js/chat/chat-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -646,22 +646,21 @@ document.addEventListener("DOMContentLoaded", () => {
const imageId = `voice-img-${Date.now()}`;
localStorage.setItem(`voiceImageId_${imageId}`, imageId);
try {
if (window.polliLib && window.polliClient) {
const url = window.polliLib.mcp.generateImageUrl(window.polliClient, {
prompt: imagePrompt,
width: 512,
height: 512,
seed,
nologo: true
});
voiceChatImage.src = url;
} else {
voiceChatImage.src = "https://via.placeholder.com/512?text=Image+Unavailable";
}
} catch (e) {
console.warn('polliLib generateImageUrl failed', e);
voiceChatImage.src = "https://via.placeholder.com/512?text=Image+Unavailable";
}
if (window.polliLib && window.polliClient) {
const url = window.polliLib.mcp.generateImageUrl(window.polliClient, {
prompt: imagePrompt,
width: 512,
height: 512,
seed,
nologo: true
});
voiceChatImage.src = url;
} else {
console.error("polliLib not available; cannot generate voice chat image");
}
} catch (e) {
console.warn('polliLib generateImageUrl failed', e);
}
voiceChatImage.dataset.imageId = imageId;
voiceChatImage.onload = () => {
attachImageButtons(voiceChatImage, imageId);
Expand Down
51 changes: 23 additions & 28 deletions js/ui/screensaver.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,10 +269,10 @@ document.addEventListener("DOMContentLoaded", () => {
enhance: !!enhance
});
}
} catch (e) { console.warn('polliLib generateImageUrl failed', e); }
// Fallback to placeholder if polliLib not available
return "https://via.placeholder.com/512?text=Image+Unavailable";
})();
} catch (e) { console.warn('polliLib generateImageUrl failed', e); }
console.error('polliLib not available; no image generated');
return "";
})();
console.log("Generated new image URL via polliLib:", url);

const nextImage = currentImage === 'image1' ? 'image2' : 'image1';
Expand All @@ -296,24 +296,19 @@ document.addEventListener("DOMContentLoaded", () => {

nextImgElement.onload = () => handleImageLoad("Image loaded successfully, added to history:");

nextImgElement.onerror = () => {
const fallbackUrl = "https://via.placeholder.com/512?text=Image+Failed";
nextImgElement.src = fallbackUrl;
nextImgElement.onload = () => handleImageLoad("Image failed, added fallback to history:");
nextImgElement.onerror = () => {
console.error("Fallback image also failed to load.");
};
};

try {
await preloadImage(url);
nextImgElement.src = url;
} catch (err) {
const fallbackUrl = "https://via.placeholder.com/512?text=Image+Failed";
nextImgElement.src = fallbackUrl;
} finally {
isTransitioning = false;
}
nextImgElement.onerror = () => {
console.error("Image failed to load.");
};

try {
if (!url) throw new Error("No image URL");
await preloadImage(url);
nextImgElement.src = url;
} catch (err) {
console.error("Failed to fetch new image:", err);
} finally {
isTransitioning = false;
}
}

function addToHistory(imageUrl, prompt) {
Expand Down Expand Up @@ -380,12 +375,12 @@ document.addEventListener("DOMContentLoaded", () => {
currentImage = nextImage;
updateThumbnailHistory();
};
nextImgElement.onerror = () => {
nextImgElement.src = "https://via.placeholder.com/512?text=Image+Failed";
nextImgElement.style.opacity = '1';
currentImage = nextImage;
updateThumbnailHistory();
};
nextImgElement.onerror = () => {
console.error("Historical image failed to load.");
nextImgElement.style.opacity = '1';
currentImage = nextImage;
updateThumbnailHistory();
};
nextImgElement.src = imageUrl;
nextImgElement.alt = "Screensaver Image";
if (nextImgElement.complete && nextImgElement.naturalWidth !== 0) {
Expand Down
8 changes: 4 additions & 4 deletions tests/ai-response.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ const client = new PolliClientWeb({ referrer: 'unityailab.com' });
const response = [
'Hello',
'```image',
'tiny red square',
'an apple',
'```',
'```audio',
'say ok',
'say apple',
'```',
'```ui',
'console.log("ui done")',
Expand Down Expand Up @@ -79,11 +79,11 @@ const sanitized = sanitizeMarkdown(content);
const html = marked.parse(sanitized);

assert(imageUrls.length === 1 && imageUrls[0].startsWith('http'), 'Image URL via polliLib');
assert(!sanitized.includes('tiny red square'), 'Image prompt hidden');
assert(!sanitized.includes('an apple'), 'Image prompt hidden');

const blob = audioBlobs[0];
assert(blob && typeof blob.size === 'number' && blob.size > 0, 'Audio blob generated');
assert(!sanitized.includes('say ok'), 'Audio prompt hidden');
assert(!sanitized.includes('say apple'), 'Audio prompt hidden');

assert(uiExecuted, 'UI command executed');
assert(!sanitized.includes('console.log("ui done")'), 'UI command hidden');
Expand Down
4 changes: 2 additions & 2 deletions tests/json-tools.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ async function dispatch(json) {
return await fn(obj);
}

await dispatch('{"tool":"image","prompt":"tiny green square"}');
await dispatch('{"tool":"tts","text":"ok"}');
await dispatch('{"tool":"image","prompt":"apple"}');
await dispatch('{"tool":"tts","text":"apple"}');
await dispatch('{"tool":"ui","command":"ping"}');

assert(imageUrl && imageUrl.startsWith('http'), 'image url via polliLib');
Expand Down
24 changes: 12 additions & 12 deletions tests/pollilib-smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ await step('textModels returns JSON', async () => {
});

await step('text(prompt) returns string', async () => {
const out = await textGet('Say ok', { model: 'openai-mini' }, client);
const out = await textGet('Tell me about an apple', { model: 'openai-mini' }, client);
if (typeof out !== 'string' || !out.length) throw new Error('empty text output');
return `len=${out.length}`;
});

await step('chat basic response', async () => {
const messages = [
{ role: 'system', content: 'You are concise.' },
{ role: 'user', content: 'Reply with the word: ok' }
{ role: 'user', content: 'Reply with the word: apple' }
];
const data = await chat({ messages, /* model omitted to use server default */ }, client);
const content = data?.choices?.[0]?.message?.content;
Expand All @@ -71,7 +71,7 @@ await step('chat basic response', async () => {
});

await step('search convenience returns text', async () => {
const out = await search('2+2=?', 'searchgpt', client);
const out = await search('apple', 'searchgpt', client);
if (typeof out !== 'string' || !out.length) throw new Error('empty search output');
return `len=${out.length}`;
});
Expand All @@ -85,13 +85,13 @@ await step('imageModels returns JSON', async () => {
});

await step('mcp generateImageUrl builds URL', async () => {
const url = mcp.generateImageUrl(client, { prompt: 'simple red square icon', width: 32, height: 32, private: true, nologo: true });
const url = mcp.generateImageUrl(client, { prompt: 'apple icon', width: 32, height: 32, private: true, nologo: true });
if (typeof url !== 'string' || !url.startsWith('http')) throw new Error('bad url');
return url.slice(0, 80) + '…';
});

await step('image fetch small blob', async () => {
const blob = await image('tiny test pixel art red square', { width: 32, height: 32, private: true, nologo: true, safe: true }, client);
const blob = await image('tiny pixel art apple', { width: 32, height: 32, private: true, nologo: true, safe: true }, client);
if (!blob || typeof blob.size !== 'number' || blob.size <= 0) throw new Error('empty image blob');
return `blob size=${blob.size}`;
});
Expand All @@ -106,23 +106,23 @@ async function blobToBase64(b) {
}

await step('mcp generateImageBase64 returns base64', async () => {
const b64 = await mcp.generateImageBase64(client, { prompt: 'tiny blue square icon', width: 16, height: 16, private: true, nologo: true, safe: true });
const b64 = await mcp.generateImageBase64(client, { prompt: 'tiny apple icon', width: 16, height: 16, private: true, nologo: true, safe: true });
if (typeof b64 !== 'string' || b64.length < 20) throw new Error('short base64');
return `len=${b64.length}`;
});

await step('vision with data URL', async () => {
const blob = await image('tiny green square icon', { width: 16, height: 16, private: true, nologo: true, safe: true }, client);
const blob = await image('tiny apple icon', { width: 16, height: 16, private: true, nologo: true, safe: true }, client);
const b64 = await blobToBase64(blob);
const dataUrl = `data:image/png;base64,${b64}`;
const resp = await vision({ imageUrl: dataUrl, question: 'One word color name only.' }, client);
const resp = await vision({ imageUrl: dataUrl, question: 'One word fruit name only.' }, client);
const msg = resp?.choices?.[0]?.message?.content;
if (!msg || typeof msg !== 'string') throw new Error('vision no content');
return `len=${msg.length}`;
});

await step('audio.tts returns audio blob', async () => {
const blob = await tts('ok', { voice: 'alloy', model: 'openai-audio' }, client);
const blob = await tts('apple', { voice: 'alloy', model: 'openai-audio' }, client);
if (!blob || typeof blob.size !== 'number' || blob.size <= 0) throw new Error('empty tts blob');
return `blob size=${blob.size}`;
});
Expand All @@ -148,9 +148,9 @@ await step('tools.functionTool and ToolBox shape', async () => {

await step('pipeline end-to-end', async () => {
const p = new pipeline.Pipeline()
.step(new pipeline.TextGetStep({ prompt: 'Say ok', outKey: 't', params: { model: 'openai-mini' } }))
.step(new pipeline.ImageStep({ prompt: 'tiny emoji like red dot', outKey: 'img', params: { width: 16, height: 16, private: true, nologo: true, safe: true } }))
.step(new pipeline.TtsStep({ text: 'ok', outKey: 'snd', params: { model: 'openai-audio' } }));
.step(new pipeline.TextGetStep({ prompt: 'Say apple', outKey: 't', params: { model: 'openai-mini' } }))
.step(new pipeline.ImageStep({ prompt: 'tiny emoji like apple', outKey: 'img', params: { width: 16, height: 16, private: true, nologo: true, safe: true } }))
.step(new pipeline.TtsStep({ text: 'apple', outKey: 'snd', params: { model: 'openai-audio' } }));
const ctx = await p.execute({ client });
if (!ctx.get('t') || !ctx.get('img')?.blob || !ctx.get('snd')?.blob) throw new Error('pipeline missing outputs');
return 'ok';
Expand Down
Loading