src/tiny_cats/index.ts (99 lines of code) (raw):

/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import {GoogleGenAI} from '@google/genai'; import {marked} from 'marked'; const ai = new GoogleGenAI({apiKey: process.env.API_KEY}); const chat = ai.chats.create({ model: 'gemini-2.0-flash-exp', config: { responseModalities: ['TEXT', 'IMAGE'], }, history: [], }); const userInput = document.querySelector('#input') as HTMLTextAreaElement; const modelOutput = document.querySelector('#output') as HTMLDivElement; const slideshow = document.querySelector('#slideshow') as HTMLDivElement; const error = document.querySelector('#error') as HTMLDivElement; const additionalInstructions = ` Use a fun story about lots of tiny cats as a metaphor. Keep sentences short but conversational, casual, and engaging. Generate a cute, minimal illustration for each sentence with black ink on white background. No commentary, just begin your explanation. Keep going until you're done.`; async function addSlide(text: string, image: HTMLImageElement) { const slide = document.createElement('div'); slide.className = 'slide'; const caption = document.createElement('div') as HTMLDivElement; caption.innerHTML = await marked.parse(text); slide.append(image); slide.append(caption); slideshow.append(slide); } async function generate(message: string) { userInput.disabled = true; chat.history.length = 0; modelOutput.innerHTML = ''; slideshow.innerHTML = ''; error.innerHTML = ''; error.toggleAttribute('hidden', true); try { const userTurn = document.createElement('div') as HTMLDivElement; userTurn.innerHTML = await marked.parse(message); userTurn.className = 'user-turn'; modelOutput.append(userTurn); userInput.value = ''; const result = await chat.sendMessageStream({ message: message + additionalInstructions, }); let text = ''; let img = null; for await (const chunk of result) { for (const candidate of chunk.candidates) { for (const part of candidate.content.parts ?? []) { if (part.text) { text += part.text; } else { try { const data = part.inlineData; if (data) { img = document.createElement('img'); img.src = `data:image/png;base64,` + data.data; } else { console.log('no data', chunk); } } catch (e) { console.log('no data', chunk); } } if (text && img) { await addSlide(text, img); slideshow.removeAttribute('hidden'); text = ''; img = null; } } } } if (img) { await addSlide(text, img); slideshow.removeAttribute('hidden'); text = ''; } } catch (e) { error.innerHTML = 'Something went wrong.'; error.removeAttribute('hidden'); } userInput.disabled = false; userInput.focus(); } userInput.addEventListener('keydown', async (e: KeyboardEvent) => { if (e.code === 'Enter') { e.preventDefault(); const message = userInput.value; await generate(message); } }); const examples = document.querySelectorAll('#examples li'); examples.forEach((li) => li.addEventListener('click', async (e) => { await generate(li.textContent); }), );