Output a project based on HTML, css and js only, w...
Created on: October 14, 2025
Answered using GPT-5 Thinking by Chat01
Created on: October 14, 2025
Answered using GPT-5 Thinking by Chat01
Output a project based on HTML, css and js only, which will have a chat interface for Gemini API. the main features is that, it will have datastructures which will hold data and checkpoints, divided into many categories.
-Memory: Small summary string, and its value contains detailed information that the model already stored. The model will have the summary passed with each iteration, and the model can select the index for the memory to fetch it if required.
-Immediate reasoning chain: Holds steps for continuous reasoning for a single reply. will automatically be passed on during each iterative reasoning process.
-Goals: COntains the goals and whatever, that the model has already stored in the beginning, to verify the final output. this will automatically be passed to the model. So, the model will think that it is outputting the final answer, but it wont be. this will be passed for verification, also, all the immediate reasoning data structure chain will also be passed so that it can verify. Then only it will be the final output.
-it will have a code execution environment that explicitly uses javascript only, and has proper and complete internet access. it can use that js environment as much as required. ALso, that will be the script directly executing inside the browser. No sandbox nothing. Directly in the browser. make sure that any kind of output from the js will be passed on to the model, or, the model can choose to include the output of the js directly to the final answer, using a variable in the final answer, which will be replaced by the project's script during the rendering.
-it will also have the canvas feature, which will display the html file for display. it can use that during the intermediate reasoning process too.
-also, the model's internal thought process that is chain of thoughts is different. there will also be a different iterative reasoning and tool use that is defined in this prompt, that the model must use. include proper prompts for each steps. Make sure you let the LLM know that, whatever there is available is inside the 'External Tools system'.
Output the project structure, css file with complete coverage of the required features, and a central js file which will be used to manage all the related functions and stuff. Look, this is a hobby project and we are currently testing the system. So dont make it extremely complex.
Awesome — here’s a compact, front-end-only hobby project that gives you:
⚠️ Heads-up
- You’ll need your own Google AI Studio API key. Paste it in the UI (it’s stored in
localStorage
).- All code executes in your browser. The JS runner uses
eval/new Function
on purpose (per your request). Treat code as untrusted.- Network requests from code you run are subject to CORS.
- The prompts intentionally ask the model not to reveal scratchpad/chain-of-thought; it uses brief rationales instead.
- Gemini REST docs / endpoints used below:
models.generateContent
(v1beta
) withsystemInstruction
andcontents
payloads. See Google’s official docs for details. (Google AI for Developers)
textgemini-chat-hobby/ ├─ index.html ├─ assets/ │ ├─ styles.css │ └─ app.js
html<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" /> <title>Gemini Hobby Chat – Tools, Memory & Canvas</title> <link rel="stylesheet" href="assets/styles.css" /> </head> <body> <header class="app-header"> <div class="brand"> <span class="dot"></span> <h1>Gemini Hobby Chat</h1> </div> <div class="spacer"></div> <details class="settings" id="settings"> <summary>Settings</summary> <label class="field"> <span>API Key</span> <input id="apiKey" type="password" placeholder="AIza..." /> </label> <label class="field"> <span>Model</span> <select id="model"> <!-- keep it simple; you can add more --> <option value="gemini-1.5-flash">gemini-1.5-flash</option> <option value="gemini-1.5-pro">gemini-1.5-pro</option> <option value="gemini-2.5-flash-preview-09-2025">gemini-2.5-flash-preview-09-2025</option> </select> </label> <label class="field"> <span>Temperature</span> <input id="temperature" type="number" min="0" max="1" step="0.1" value="0.3" /> </label> <button id="saveSettings" class="btn primary">Save</button> </details> </header> <main class="layout"> <aside class="sidebar"> <section> <h2>Goals</h2> <ul id="goalsList" class="list"></ul> <div class="inline-form"> <input id="goalTitle" placeholder="Add a goal…" /> <button id="addGoal" class="btn">Add</button> </div> </section> <section> <h2>Memory</h2> <ul id="memoryList" class="list"></ul> <div class="stack"> <input id="memSummary" placeholder="Short summary…" /> <textarea id="memDetail" placeholder="Detailed value…"></textarea> <button id="addMemory" class="btn">Store</button> </div> </section> <section> <h2>Immediate chain</h2> <ol id="chainList" class="list ordered"></ol> <div class="inline-form"> <input id="chainStep" placeholder="Add a step…" /> <button id="addChain" class="btn">Add</button> </div> <button id="clearChain" class="btn danger subtle">Clear chain</button> </section> </aside> <section class="chat-pane"> <div id="messages" class="messages" aria-live="polite"></div> <form id="chatForm" class="composer" autocomplete="off"> <textarea id="userInput" placeholder="Say something…" rows="2"></textarea> <div class="composer-actions"> <button id="sendBtn" class="btn primary" type="submit">Send</button> <button id="resetBtn" class="btn subtle" type="button">Reset</button> </div> </form> </section> <section class="tools-pane"> <div class="tool"> <div class="tool-header"> <h2>External Tools system</h2> <span class="muted">js runner + canvas</span> </div> <details open> <summary>JavaScript Runner (dangerous; no sandbox)</summary> <textarea id="jsCode" class="code" spellcheck="false" // Example: const res = await fetch('https://api.github.com/repos/octocat/Hello-World'); return await res.json(); ></textarea> <div class="inline-form"> <button id="runJs" class="btn">Run JS</button> <label><input type="checkbox" id="autoSendJs" /> Send output back to model</label> </div> <pre id="jsOutput" class="output" aria-live="polite"></pre> </details> <details open> <summary>HTML Canvas (live preview)</summary> <textarea id="canvasHtml" class="code" spellcheck="false"><!doctype html> <html> <head><meta charset="utf-8"><title>Canvas Preview</title></head> <body> <h1>Hello Canvas</h1> <script>console.log("Canvas script running");</script> </body> </html></textarea> <div class="inline-form"> <button id="renderCanvas" class="btn">Render</button> <label><input type="checkbox" id="autoSendCanvas" /> Send status back to model</label> </div> <iframe id="canvasFrame" title="Canvas Preview"></iframe> </details> </div> </section> </main> <footer class="app-footer"> <small> Built for hobby testing. Uses Gemini REST <code>generateContent</code> with <code>systemInstruction</code>. See Google docs for payload shape and model list. ([Google AI for Developers](https://ai.google.dev/api/generate-content?utm_source=chatgpt.com)) </small> </footer> <script src="assets/app.js"></script> </body> </html>
css:root{ --bg: #0f1115; --panel: #171a21; --muted: #8b94a7; --text: #e6e8ee; --primary: #5b9cff; --primary-2: #2f6fe3; --danger: #ff6666; --ring: rgba(91,156,255,.4); --border: #242837; --chip: #1d2230; --shadow: 0 10px 30px rgba(0,0,0,.45); --radius: 12px; --radius-sm: 8px; } *{ box-sizing:border-box } html,body{ height:100% } body{ margin:0; background:var(--bg); color:var(--text); font: 14px/1.4 Inter, system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji"; } .app-header, .app-footer{ display:flex; align-items:center; gap:16px; padding:12px 16px; background:var(--panel); border-bottom:1px solid var(--border); } .app-footer{ border-top:1px solid var(--border); border-bottom:none; justify-content:center } .brand{ display:flex; align-items:center; gap:10px } .brand h1{ font-size:16px; margin:0; font-weight:600 } .brand .dot{ width:10px; height:10px; border-radius:50%; background:var(--primary) } .settings summary{ cursor:pointer; color:var(--muted); font-weight:600 } .settings .field{ display:grid; grid-template-columns:120px 1fr; gap:8px; margin:8px 0 } .settings input, .settings select{ width:100%; padding:10px 12px; border-radius:var(--radius-sm); border:1px solid var(--border); background:#0b0d12; color:var(--text); outline:none; } .settings input:focus, .settings select:focus{ border-color:var(--primary); box-shadow:0 0 0 4px var(--ring) } .btn{ background:#273147; color:var(--text); border:1px solid var(--border); padding:8px 12px; border-radius:var(--radius-sm); cursor:pointer; } .btn:hover{ filter:brightness(1.1) } .btn.primary{ background:var(--primary); border-color:transparent; color:#0c1222; font-weight:700 } .btn.primary:hover{ background:var(--primary-2) } .btn.danger{ background:var(--danger); border-color:transparent; color:#280909 } .btn.subtle{ background:#1a2030 } .btn:disabled{ opacity:.6; cursor:not-allowed } .inline-form{ display:flex; gap:8px; align-items:center; margin:8px 0 } .stack{ display:flex; flex-direction:column; gap:8px } .layout{ display:grid; grid-template-columns: 280px 1fr 420px; height: calc(100vh - 96px); } .sidebar, .chat-pane, .tools-pane{ overflow:auto; border-right:1px solid var(--border); } .tools-pane{ border-right:none; border-left:1px solid var(--border) } .sidebar{ padding:16px; background:var(--panel) } .sidebar h2{ margin:10px 0 } .list{ list-style:none; padding:0; margin:0 0 8px 0; display:flex; flex-direction:column; gap:8px } .list.ordered{ list-style:decimal; margin-left:20px } .list li{ background:var(--chip); border:1px solid var(--border); border-radius:var(--radius-sm); padding:8px 10px; display:flex; gap:8px; align-items:flex-start; justify-content:space-between; } .list li .small{ color:var(--muted); font-size:12px } .list li .x{ cursor:pointer; opacity:.6 } .list li .x:hover{ opacity:1 } .chat-pane{ display:flex; flex-direction:column } .messages{ flex:1; padding:16px; display:flex; flex-direction:column; gap:14px; overflow:auto; } .msg{ display:flex; gap:10px; align-items:flex-start; } .msg .avatar{ width:30px; height:30px; border-radius:50%; background:#1e2536; display:grid; place-items:center; color:#9fb6ff } .bubble{ max-width: min(760px, 80%); padding:10px 12px; border:1px solid var(--border); border-radius:16px; background:#121625; box-shadow: var(--shadow); } .bubble.user{ margin-left:auto; border-top-right-radius:6px; background:#121b2e } .bubble.assistant{ border-top-left-radius:6px } .bubble .meta{ font-size:11px; color:var(--muted); margin-bottom:6px } .bubble pre, .bubble code{ white-space:pre-wrap; word-wrap:break-word; background:#0c0f17; padding:8px; border-radius:8px; border:1px solid #20263a } .bubble .toolcall{ background:#0b121d; border:1px dashed #2e3a58 } .bubble .ok{ color:#9ee6a5 } .bubble .err{ color:#ffadad } .composer{ display:flex; gap:10px; padding:12px; border-top:1px solid var(--border); background:var(--panel) } .composer textarea{ flex:1; min-height:48px; max-height:180px; resize:vertical; padding:12px; border-radius:var(--radius); border:1px solid var(--border); background:#0b0d12; color:var(--text); } .composer textarea:focus{ border-color:var(--primary); box-shadow:0 0 0 4px var(--ring) } .tool{ padding:16px } .tool-header{ display:flex; align-items:center; gap:10px; margin-bottom:8px } .tool-header .muted{ color:var(--muted) } #jsOutput, .output{ background:#0b0f16; border:1px solid var(--border); padding:10px; border-radius:var(--radius-sm); max-height:220px; overflow:auto; } #canvasFrame{ width:100%; height:300px; background:white; border:1px solid var(--border); border-radius:var(--radius-sm) } .code{ width:100%; min-height:120px; max-height:240px; resize:vertical; font: 13px/1.45 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; background:#0b0f16; color:#e6e8ee; border:1px solid var(--border); border-radius:var(--radius-sm); padding:10px; } .spacer{ flex:1 } .muted{ color:var(--muted) } .subtle{ opacity:.85 } .hidden{ display:none }
javascript/* Gemini Hobby Chat – central JS * - Chat UI to Gemini REST generateContent (v1beta) * - External Tools system: JS runner (no sandbox) & HTML canvas renderer * - Memory / Immediate chain / Goals stores * - Variable injection (e.g., {{js:run_123}}) in final answers * * Docs: generateContent + systemInstruction + models list. ([Google AI for Developers](https://ai.google.dev/api/generate-content?utm_source=chatgpt.com)) */ (() => { /** ------------------------------- * Basic state * -------------------------------- */ const els = { apiKey: byId('apiKey'), model: byId('model'), temperature: byId('temperature'), saveSettings: byId('saveSettings'), messages: byId('messages'), chatForm: byId('chatForm'), userInput: byId('userInput'), sendBtn: byId('sendBtn'), resetBtn: byId('resetBtn'), goalsList: byId('goalsList'), goalTitle: byId('goalTitle'), addGoal: byId('addGoal'), memoryList: byId('memoryList'), memSummary: byId('memSummary'), memDetail: byId('memDetail'), addMemory: byId('addMemory'), chainList: byId('chainList'), chainStep: byId('chainStep'), addChain: byId('addChain'), clearChain: byId('clearChain'), jsCode: byId('jsCode'), runJs: byId('runJs'), jsOutput: byId('jsOutput'), autoSendJs: byId('autoSendJs'), canvasHtml: byId('canvasHtml'), renderCanvas: byId('renderCanvas'), autoSendCanvas: byId('autoSendCanvas'), canvasFrame: byId('canvasFrame'), }; const STORAGE = 'gemini_hobby_chat_v1'; const API_ROOT = 'https://generativelanguage.googleapis.com/v1beta'; const state = loadState() || { config: { apiKey: '', model: 'gemini-1.5-flash', temperature: 0.3 }, // App-level stores goals: [], // [{id, title, done:false}] memory: [], // [{id, summary, value}] chain: [], // ["step 1", "step 2", ...] for immediate reasoning steps // Tool execution results (for {{js:...}} injections) toolResults: {}, // { resultId: { type, value, meta } } // Conversation history (for Gemini contents) history: [] // [{role:'user'|'model', parts:[{text}]}] }; // hydrate UI hydrate(); /** ------------------------------- * Event bindings * -------------------------------- */ els.saveSettings.addEventListener('click', () => { state.config.apiKey = els.apiKey.value.trim(); state.config.model = els.model.value; state.config.temperature = Number(els.temperature.value || 0.3); saveState(); toast('Settings saved.'); }); els.addGoal.addEventListener('click', () => { const t = els.goalTitle.value.trim(); if (!t) return; state.goals.push({ id: uid('goal_'), title: t, done: false }); els.goalTitle.value = ''; renderGoals(); saveState(); }); els.addMemory.addEventListener('click', () => { const s = els.memSummary.value.trim(); const v = els.memDetail.value.trim(); if (!s || !v) return; state.memory.push({ id: uid('mem_'), summary: s, value: v }); els.memSummary.value = ''; els.memDetail.value = ''; renderMemory(); saveState(); }); els.addChain.addEventListener('click', () => { const s = els.chainStep.value.trim(); if (!s) return; state.chain.push(s); els.chainStep.value = ''; renderChain(); saveState(); }); els.clearChain.addEventListener('click', () => { state.chain = []; renderChain(); saveState(); }); els.runJs.addEventListener('click', async () => { const code = els.jsCode.value; const { id, summary } = await runJs(code); els.jsOutput.textContent = summary; if (els.autoSendJs.checked) { await addToolResultMessage('js', id, summary); } }); els.renderCanvas.addEventListener('click', async () => { const html = els.canvasHtml.value; const { id, summary } = renderCanvas(html); if (els.autoSendCanvas.checked) { await addToolResultMessage('canvas', id, summary); } }); els.chatForm.addEventListener('submit', async (e) => { e.preventDefault(); const text = els.userInput.value.trim(); if (!text) return; els.userInput.value = ''; await appendUserAndSend(text); }); els.resetBtn.addEventListener('click', () => { state.history = []; state.chain = []; renderChain(); els.messages.innerHTML = ''; toast('Conversation reset.'); saveState(); }); /** ------------------------------- * UI renderers * -------------------------------- */ function hydrate() { els.apiKey.value = state.config.apiKey; els.model.value = state.config.model; els.temperature.value = state.config.temperature; renderGoals(); renderMemory(); renderChain(); renderHistory(); } function renderGoals() { els.goalsList.innerHTML = ''; state.goals.forEach(g => { const li = document.createElement('li'); li.innerHTML = ` <span>${escapeHtml(g.title)}</span> <span> <label class="small"><input type="checkbox" ${g.done ? 'checked' : ''}/> done</label> <span class="x" title="remove">✕</span> </span>`; li.querySelector('input').addEventListener('change', (e) => { g.done = e.target.checked; saveState(); }); li.querySelector('.x').addEventListener('click', () => { state.goals = state.goals.filter(x => x.id !== g.id); renderGoals(); saveState(); }); els.goalsList.appendChild(li); }); } function renderMemory() { els.memoryList.innerHTML = ''; state.memory.forEach((m, idx) => { const li = document.createElement('li'); li.innerHTML = ` <div> <div><strong>${escapeHtml(m.summary)}</strong> <span class="small">#${idx}</span></div> <div class="small">${escapeHtml(m.value.slice(0, 140))}${m.value.length>140?'…':''}</div> </div> <span class="x" title="remove">✕</span> `; li.querySelector('.x').addEventListener('click', () => { state.memory = state.memory.filter(x => x.id !== m.id); renderMemory(); saveState(); }); els.memoryList.appendChild(li); }); } function renderChain() { els.chainList.innerHTML = ''; state.chain.forEach((s, i) => { const li = document.createElement('li'); li.textContent = s; li.appendChild(xButton(() => { state.chain.splice(i, 1); renderChain(); saveState(); })); els.chainList.appendChild(li); }); } function renderHistory() { els.messages.innerHTML = ''; state.history.forEach(msg => renderMessageBubble(msg.role, joinParts(msg.parts))); } function renderMessageBubble(role, text, isTool) { const wrap = document.createElement('div'); wrap.className = 'msg'; const avatar = document.createElement('div'); avatar.className = 'avatar'; avatar.textContent = role === 'user' ? 'U' : (role === 'model' ? 'G' : 'T'); const bubble = document.createElement('div'); bubble.className = 'bubble ' + (role === 'user' ? 'user' : (role === 'model' ? 'assistant' : '')); bubble.innerHTML = isTool ? `<div class="meta">tool</div><pre class="toolcall">${escapeHtml(text)}</pre>` : `<div class="meta">${role}</div><div class="content">${formatMarkdown(text)}</div>`; wrap.appendChild(avatar); wrap.appendChild(bubble); els.messages.appendChild(wrap); els.messages.scrollTop = els.messages.scrollHeight; } /** ------------------------------- * Chat flow * -------------------------------- */ async function appendUserAndSend(text) { addToHistory('user', text); renderMessageBubble('user', text); try { els.sendBtn.disabled = true; const reply = await runChatLoop(text); renderMessageBubble('model', reply); addToHistory('model', reply); } catch (err) { renderMessageBubble('model', `Error: ${err.message || err}`, true); } finally { els.sendBtn.disabled = false; saveState(); } } // Iterative loop: the model can either produce a final answer or request a tool. async function runChatLoop(userText) { // Instruct model to respond with compact JSON control when using tools. const maxSteps = 4; let lastModel = ''; for (let step = 0; step < maxSteps; step++) { const { text } = await callGemini(buildPromptForModel(userText)); const parsed = parseToolOrFinal(text); if (parsed.action === 'tool') { // Execute requested tool const result = await executeTool(parsed.tool, parsed.args); await addToolResultMessage(parsed.tool, result.id, result.summary); // Feed the tool result back into the prompt for the next turn userText = parsed.next_prompt || `Tool '${parsed.tool}' result available as id=${result.id}`; lastModel = text; continue; } if (parsed.action === 'final') { return injectVariables(parsed.final_answer || text); } // Fallback: if not recognized, treat as final return injectVariables(text); } // If loop exhausted, return last model text return injectVariables(lastModel); } function buildPromptForModel(latestUserText) { // Compose stores (these are passed every iteration) const goalsSummary = state.goals.map((g,i)=>`#${i+1} ${g.title} ${g.done?'(done)':''}`).join('\n') || 'No goals set.'; const memorySummary = state.memory.map((m,i)=>`#${i}: ${m.summary}`).join('\n') || 'No memory available.'; const chainSummary = state.chain.map((s,i)=>`${i+1}. ${s}`).join('\n') || '—'; // System instruction: steer behavior + tool protocol. // IMPORTANT: ask for brief reasoning only; do not reveal chain-of-thought/scratchpads. const systemText = `You are Gemini in a hobby test app. Follow project rules: - Use **External Tools system** when useful. Tools available: 1) tool "js.run": Execute arbitrary JavaScript in the user's browser. args: { "code": string }. Returns { id } and the app will store the printed output and return summary. Use ONLY for harmless web tasks. Network access is via fetch and may be CORS-limited. 2) tool "canvas.render": Render raw HTML into a live preview iframe. args: { "html": string }. 3) tool "memory.fetch": Retrieve detailed memory value by index. args: { "index": number }. - When you want a tool, respond with JSON only: {"action":"tool","tool":"js"|"canvas"|"memory_fetch","args":{...},"next_prompt":"(optional user-facing follow up to continue reasoning)"} - Otherwise, respond with a final message: {"action":"final","final_answer":"... (you may include placeholders like {{js:RUN_ID}} which the app will replace)"} Safety and style: - Think internally; DO NOT reveal chain-of-thought or hidden scratchpads. If needed, provide a brief rationale (≤5 bullets). - Keep outputs concise and helpful.`; // Context blocks sent *every* turn: const context = `[GOALS] ${goalsSummary} [MEMORY SUMMARY] Short summaries with indices: ${memorySummary} [IMMEDIATE STEPS] (Internal use only, do not echo) ${chainSummary}`; // Build the message the model sees this turn: const composite = `${context} [USER] ${latestUserText}`; return { systemText, composite }; } async function callGemini({ systemText, composite }) { const { apiKey, model, temperature } = state.config; if (!apiKey) throw new Error('Missing API key (Settings ▸ Save).'); const url = `${API_ROOT}/models/${encodeURIComponent(model)}:generateContent?key=${encodeURIComponent(apiKey)}`; // Track in history we are sending the composite (but don't display system) const contentToSend = [ // keep full chat history lightweight: only last few messages to keep it simple ...state.history.slice(-6), { role: 'user', parts: [{ text: composite }] } ]; const body = { systemInstruction: { role: 'system', parts: [{ text: systemText }] }, contents: contentToSend, generationConfig: { temperature } // You could add safetySettings here if you want to tweak filters. }; const res = await fetch(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) }); if (!res.ok) { const t = await res.text(); throw new Error(`Gemini error ${res.status}: ${t}`); } const data = await res.json(); const text = joinParts(data?.candidates?.[0]?.content?.parts || []); return { raw: data, text }; } /** ------------------------------- * Tool protocol & execution * -------------------------------- */ function parseToolOrFinal(text) { // Try to find a top-level JSON object in the response. try { const json = findFirstJson(text); if (!json || typeof json !== 'object') return { action: 'final', final_answer: text }; if (json.action === 'tool') { const map = { js:'js', canvas:'canvas', memory_fetch:'memory_fetch' }; const tool = map[json.tool]; if (!tool) return { action: 'final', final_answer: text }; return { action: 'tool', tool, args: json.args || {}, next_prompt: json.next_prompt }; } if (json.action === 'final') return { action: 'final', final_answer: String(json.final_answer || '') }; // fallthrough } catch {} return { action: 'final', final_answer: text }; } async function executeTool(tool, args) { if (tool === 'js') { const code = String(args?.code || ''); return await runJs(code); } if (tool === 'canvas') { const html = String(args?.html || ''); return renderCanvas(html); } if (tool === 'memory_fetch') { const idx = Number(args?.index ?? -1); const mem = state.memory[idx]; const value = mem ? mem.value : '(memory index not found)'; const id = uid('memres_'); state.toolResults[id] = { type: 'memory', value, meta: { idx } }; saveState(); return { id, summary: `memory[${idx}] → ${truncate(value, 200)}` }; } const id = uid('noop_'); state.toolResults[id] = { type: 'unknown', value: null, meta: { tool } }; saveState(); return { id, summary: `Unknown tool: ${tool}` }; } async function addToolResultMessage(tool, id, summary) { const msg = `[tool:${tool}] result id=${id}\n${summary}`; addToHistory('user', msg); // feed tool result back as a user message renderMessageBubble('tool', msg, true); } /** ------------------------------- * JS Runner (unsandboxed by design) * -------------------------------- */ async function runJs(code) { const id = uid('run_'); const logs = []; const originalConsole = window.console; let result, error; // Intercept console const proxyConsole = { log: (...a)=>logs.push(['log', ...a]), info: (...a)=>logs.push(['info', ...a]), warn: (...a)=>logs.push(['warn', ...a]), error: (...a)=>logs.push(['error', ...a]), }; try { window.console = proxyConsole; // Make async, allow await fetch, etc. const fn = new Function(` "use strict"; return (async () => { ${code} })(); `); result = await fn(); } catch (e) { error = e; } finally { window.console = originalConsole; } const summary = [ error ? `❌ Error: ${String(error)}` : `✅ Result: ${safeJson(result)}`, logs.length ? `\nConsole:\n${logs.map(l=>` [${l[0]}] ${l.slice(1).map(s=>StringifyLite(s)).join(' ')}`).join('\n')}` : '' ].join(''); state.toolResults[id] = { type: 'js', value: { result, logs }, meta: { ts: Date.now() } }; saveState(); return { id, summary }; } /** ------------------------------- * Canvas renderer (iframe, no sandbox attribute) * -------------------------------- */ function renderCanvas(html) { const id = uid('canvas_'); const doc = els.canvasFrame.contentDocument; doc.open(); doc.write(html); doc.close(); state.toolResults[id] = { type: 'canvas', value: 'rendered', meta: { ts: Date.now() } }; saveState(); return { id, summary: 'Canvas rendered.' }; } /** ------------------------------- * Helpers * -------------------------------- */ function injectVariables(text) { // Replace {{js:ID}} with stored result JSON string return text.replace(/\{\{\s*js:([a-zA-Z0-9_\-]+)\s*\}\}/g, (_, rid) => { const r = state.toolResults[rid]; if (!r || r.type !== 'js') return `[[missing js:${rid}]]`; return safeJson(r.value.result ?? r.value); }); } function addToHistory(role, text) { state.history.push({ role, parts: [{ text }] }); } function joinParts(parts) { return parts.map(p => p.text || '').join(''); } function xButton(onClick) { const s = document.createElement('span'); s.textContent = '✕'; s.className = 'x'; s.addEventListener('click', onClick); return s; } function safeJson(v) { try { return JSON.stringify(v, null, 2); } catch { return String(v) } } function StringifyLite(v) { try { return typeof v === 'string' ? v : JSON.stringify(v); } catch { return String(v) } } function truncate(s, n){ s=String(s); return s.length>n ? s.slice(0,n-1)+'…' : s } function toast(msg){ console.log('[toast]', msg); } function loadState() { try { return JSON.parse(localStorage.getItem(STORAGE) || 'null'); } catch { return null; } } function saveState() { localStorage.setItem(STORAGE, JSON.stringify(state)); } function uid(p='id_'){ return p + Math.random().toString(36).slice(2, 9) } function byId(id){ return document.getElementById(id) } function escapeHtml(s){ return String(s).replace(/[&<>"']/g, c=>({ '&':' &', '<':'<', '>':'>', '"':'"', "'":''' }[c])); } // Tiny md-ish formatter (just code fences & inline code) function formatMarkdown(s) { if (!s) return ''; // code fences s = s.replace(/```([\s\S]*?)```/g, (m, p1) => `<pre>${escapeHtml(p1)}</pre>`); // inline code s = s.replace(/`([^`]+)`/g, (m, p1) => `<code>${escapeHtml(p1)}</code>`); // paragraphs s = s.split(/\n{2,}/).map(block => `<p>${block.replace(/\n/g,'<br>')}</p>`).join(''); return s; } // Find first JSON object literal in text function findFirstJson(text) { const start = text.indexOf('{'); const end = text.lastIndexOf('}'); if (start === -1 || end === -1 || end <= start) return null; try { return JSON.parse(text.slice(start, end+1)); } catch { return null; } } })();
index.html
in a modern browser (no server needed).gemini-1.5-flash
), click Save.memory_fetch
by index.{{js:RUN_ID}}
.Endpoint and payload notes: the app calls
POST https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent?key=API_KEY
withsystemInstruction
,contents
, and a smallgenerationConfig
. See Google’s API reference for details. (Google AI for Developers)
External Tools system (for the model):
{"action":"tool","tool":"js","args":{"code":"..."},"next_prompt":"...optional..."}
{"action":"tool","tool":"canvas","args":{"html":"..."},"next_prompt":"...optional..."}
{"action":"tool","tool":"memory_fetch","args":{"index":2},"next_prompt":"...optional..."}
{"action":"final","final_answer":"Text with {{js:RUN_ID}} placeholders if needed"}
Data structures (in-app):
goals[]
→ summary list (passed each turn)memory[]
→ {summary, value}
, model requests full via memory_fetch
indexchain[]
→ steps for the current reply (passed each turn, not to be echoed)Reasoning policy: The model is explicitly instructed not to reveal chain-of-thought; it may give a brief (≤5 bullets) rationale when helpful.
localStorage
).If you want me to extend this (streaming, official function calling, or a safer sandboxed JS runner toggle), I can ship an iteration.