huggingworld's picture
Upload 16 files
7577b79 verified
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gemma4 E2B Coding Agent WebGPU</title>
<link href="/assets/fonts.css" rel="stylesheet">
<script src="/assets/marked.umd.min.js"></script>
<script src="/assets/purify.min.js"></script>
<script src="/assets/highlight.min.js"></script>
<link rel="stylesheet" href="/assets/atom-one-dark.min.css">
<style>
:root {
color-scheme: light;
/* Gemma Light Theme */
--bg: #f8f9fa;
--panel: #ffffff;
--line: #e8eaed;
--text: #202124;
--muted: #5f6368;
--accent: #a142f4; /* Google AI / Gemma Purple */
--accent-soft: rgba(161, 66, 244, 0.1);
--user: #f3e8fd;
--assistant: #ffffff;
--error: #d93025;
--stop: #d93025;
--shadow: 0 16px 48px rgba(32, 33, 36, 0.08);
--code-bg: rgba(32, 33, 36, 0.05);
--radius: 22px;
--radius-sm: 16px;
--radius-pill: 999px;
--sans: "Avenir Next", "Segoe UI", sans-serif;
--mono: "SFMono-Regular", Consolas, monospace;
}
@media (prefers-color-scheme: dark) {
:root {
color-scheme: dark;
/* Gemma Dark Theme */
--bg: #131314;
--panel: #1e1f20;
--line: #444746;
--text: #e3e3e3;
--muted: #9aa0a6;
--accent: #c58af9; /* Lighter Purple for dark mode */
--accent-soft: rgba(197, 138, 249, 0.15);
--user: #3b2a50;
--assistant: #1e1f20;
--error: #f28b82;
--stop: #f28b82;
--shadow: 0 16px 48px rgba(0, 0, 0, 0.3);
--code-bg: rgba(255, 255, 255, 0.1);
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
font-family: var(--sans);
background: var(--bg);
color: var(--text);
display: grid;
place-items: center;
padding: 20px;
transition: background 0.3s ease, color 0.3s ease;
}
.app {
width: min(1260px, 98vw);
height: min(92vh, 1000px);
background: var(--panel);
border: 1px solid var(--line);
border-radius: var(--radius);
box-shadow: var(--shadow);
display: grid;
grid-template-rows: auto 1fr auto;
overflow: hidden;
}
.header {
padding: 18px 24px;
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
background: var(--panel);
}
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.logo-link {
display: flex;
align-items: center;
text-decoration: none;
transition: opacity 0.2s;
}
.logo-link:hover {
opacity: 0.8;
}
.logo-img {
height: 36px;
width: auto;
object-fit: contain;
}
.title {
margin: 0;
/* Use Google Sans if available, fallback to the closely matching 'Outfit' font */
font-family: "Google Sans", "Product Sans", "Outfit", system-ui, sans-serif;
font-size: 1.6rem;
font-weight: 500;
letter-spacing: -0.02em;
color: #4285f4; /* Matched to the specific blue from your image */
}
.subtitle {
margin: 0;
color: var(--muted);
font-size: 0.95rem;
text-align: right;
}
.spinner {
width: 18px;
height: 18px;
border: 2px solid var(--accent-soft);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
flex: 0 0 auto;
}
.spinner.hidden {
display: none;
}
.chat {
padding: 24px;
overflow: auto;
background: var(--bg);
display: grid;
align-content: start;
gap: 16px;
position: relative;
}
.message {
max-width: 88%;
padding: 16px 20px;
border-radius: 18px;
line-height: 1.6;
border: 1px solid var(--line);
font-size: 1rem;
}
.message.user {
margin-left: auto;
background: var(--user);
border-color: transparent;
}
.message.assistant {
background: var(--assistant);
}
.message.placeholder {
max-width: 100%;
color: var(--muted);
background: var(--panel);
text-align: left;
padding: 20px;
}
.message.placeholder strong {
display: block;
margin-bottom: 14px;
color: var(--text);
}
.examples {
display: grid;
gap: 10px;
}
.example {
width: 100%;
text-align: left;
min-width: 0;
padding: 14px 16px;
border-radius: 14px;
border: 1px solid var(--line);
background: var(--bg);
color: var(--text);
font-weight: 500;
transition: background 0.2s, border-color 0.2s;
}
.example:hover:not(:disabled) {
background: var(--accent-soft);
border-color: var(--accent);
cursor: pointer;
}
.center-state {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 24px;
text-align: center;
color: var(--muted);
}
.center-card {
display: grid;
justify-items: center;
gap: 14px;
}
.center-card .spinner {
width: 28px;
height: 28px;
border-width: 3px;
}
.composer {
padding: 20px;
border-top: 1px solid var(--line);
background: var(--panel);
display: grid;
gap: 14px;
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.metrics {
display: flex;
justify-content: flex-end;
color: var(--muted);
font-size: 0.85rem;
font-family: var(--mono);
}
.reset {
border: 1px solid var(--line);
background: var(--bg);
color: var(--text);
padding: 10px 16px;
min-width: 0;
transition: opacity 0.2s;
}
.reset:hover:not(:disabled) {
background: var(--accent-soft);
}
.input-row {
position: relative;
}
textarea {
width: 100%;
min-height: 68px;
max-height: 250px;
resize: vertical;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
padding: 16px 64px 16px 16px;
font: inherit;
color: var(--text);
background: var(--bg);
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 4px var(--accent-soft);
}
button {
appearance: none;
border: 0;
border-radius: var(--radius-pill);
padding: 12px 16px;
background: var(--accent);
color: #fff;
font: inherit;
font-weight: 500;
cursor: pointer;
}
.send {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
z-index: 1;
width: 44px;
height: 44px;
min-width: 44px;
padding: 0;
display: grid;
place-items: center;
border-radius: 14px;
color: #fff;
background: var(--accent);
}
.send svg {
width: 20px;
height: 20px;
fill: currentColor;
}
.send.stop {
background: var(--stop);
}
button:disabled {
cursor: not-allowed;
opacity: 0.5;
}
.error {
color: var(--error);
}
.cursor {
display: inline-block;
width: 0.75ch;
color: var(--accent);
animation: blink 1s step-end infinite;
vertical-align: text-bottom;
}
/* Markdown specific styles */
.message p { margin: 0 0 12px 0; }
.message p:last-child { margin-bottom: 0; }
.message a { color: var(--accent); text-decoration: none; }
.message a:hover { text-decoration: underline; }
.message pre {
background: #282c34;
color: #abb2bf;
padding: 16px;
border-radius: 12px;
overflow-x: auto;
margin: 14px 0;
font-size: 0.9em;
font-family: var(--mono);
}
.message code {
font-family: var(--mono);
background: var(--code-bg);
padding: 3px 6px;
border-radius: 6px;
font-size: 0.9em;
}
.message pre code {
background: transparent;
padding: 0;
border-radius: 0;
font-size: inherit;
}
.message ul, .message ol { margin: 12px 0; padding-left: 24px; }
.message li { margin-bottom: 6px; }
/* Think block formatting */
.think-block {
margin: 14px 0;
padding: 14px;
background: var(--code-bg);
border-left: 4px solid var(--accent);
border-radius: 0 8px 8px 0;
color: var(--muted);
font-size: 0.9em;
}
.think-block summary {
cursor: pointer;
font-weight: 600;
color: var(--accent);
margin-bottom: 10px;
outline: none;
user-select: none;
}
.think-block summary:hover {
opacity: 0.8;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@keyframes blink {
50% {
opacity: 0;
}
}
@media (max-width: 720px) {
.app {
height: calc(100vh - 20px);
}
.header,
.toolbar {
align-items: start;
flex-direction: column;
}
.subtitle {
text-align: left;
}
.message {
max-width: 95%;
}
}
</style>
</head>
<body>
<main class="app">
<header class="header">
<div class="header-left">
<a href="#" class="logo-link" title="Gemma 4">
<img src="logo.svg" alt="Logo" class="logo-img" />
</a>
<h1 class="title"><a href="https://huggingface.co/google/gemma-4-E2B-it" target="_blank">Gemma 4 E2B</a> coding agent WebGPU</h1>
</div>
<p class="subtitle">Powered by 🤗 <a href="https://huggingface.co/docs/transformers.js/index" target="_blank">transformers.js</a> v4.0.1</p>
</header>
<section id="chat" class="chat">
<div id="loadingState" class="center-state">
<div class="center-card">
<span class="spinner"></span>
<div id="statusText">Loading model...</div>
</div>
</div>
</section>
<section class="composer">
<div class="toolbar">
<button id="resetButton" class="reset" type="button" disabled>
Reset Chat
</button>
<div class="metrics" id="metrics">tokens/sec: -</div>
</div>
<div class="input-row">
<textarea
id="prompt"
placeholder="Ask Gemma to write or execute code..."
disabled
></textarea>
<button
id="sendButton"
class="send"
type="button"
disabled
aria-label="Send message"
title="Send message"
></button>
</div>
</section>
</main>
<script type="module">
import {
pipeline,
TextStreamer,
InterruptableStoppingCriteria,
} from "/assets/transformers.min.js";
marked.use({
breaks: true,
gfm: true,
renderer: {
code(token) {
const code = token.text;
const lang = (token.lang || '').match(/\S*/)[0];
if (lang && hljs.getLanguage(lang)) {
try {
const highlighted = hljs.highlight(code, { language: lang }).value;
return `<pre><code class="hljs language-${lang}">${highlighted}</code></pre>\n`;
} catch (__) {}
}
const escaped = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
return `<pre><code class="hljs">${escaped}</code></pre>\n`;
}
}
});
function renderMarkdown(text, isStreaming = false) {
let t = text;
t = t.replace(/<\|?think\|?>/g, '\n\n<details class="think-block" open><summary>Thinking Process</summary>\n\n');
t = t.replace(/<\/?(?:\|?)think(?:\|?)>/g, '\n\n</details>\n\n');
const openCount = (t.match(/<details/g) || []).length;
const closeCount = (t.match(/<\/details>/g) || []).length;
if (openCount > closeCount) {
t += '\n\n</details>';
}
if (isStreaming) {
t += ' <span class="cursor">▋</span>';
}
const rawHtml = marked.parse(t);
return window.DOMPurify.sanitize(rawHtml, {
ADD_TAGS: ['details', 'summary'],
ADD_ATTR: ['class', 'open']
});
}
const MODEL_ID = "huggingworld/gemma-4-E2B-it-ONNX";
const DTYPE = "q4f16";
const REVISION = "main";
const MAX_NEW_TOKENS = 32768;
const SYSTEM_PROMPT =
"You are an expert autonomous software engineer and coding agent specializing in Python, JavaScript, and web technologies. Your purpose is to write, debug, and help the user execute code within their terminal environment. **Capabilities:** 1. **Code Execution:** The user can execute Python/JS code to verify its functionality instantly. 2. **File Management:** The user can create, read, update, and delete files in their sandbox. 3. **Terminal Access:** The user can run bash commands (e.g., pip install, npm install, git) inside their sandbox. **Operational Guidelines:** - **Think First:** Before writing code, use the thinking process to analyze the requirement, outline the architectural approach, and plan the file structure. - **Sandboxed Execution:** Treat the user's sandbox as your primary workspace. If code can be executed, ask the user to execute it. - **Safety First:** Do not run destructive commands. - **Debugging Loop:** If code execution fails, ask the user to read the error message, and enter it for you to analyze, and fix the code. - **Output:** For final answers, provide the code within markdown blocks and summarize the actions to be taken. **User Integration:** - User will/can execute python or terminal commands, ask user what their terminal environment is, and if they know how to interact with the environment. **Persona:** - Efficient, precise, safety-conscious, and very patient if teaching is needed for the user. - Focus on producing functional, production-ready code. **Current Task:** [Insert User Request Here]";
const chatEl = document.getElementById("chat");
const promptEl = document.getElementById("prompt");
const sendButton = document.getElementById("sendButton");
const resetButton = document.getElementById("resetButton");
const statusTextEl = document.getElementById("statusText");
const metricsEl = document.getElementById("metrics");
const loadingStateEl = document.getElementById("loadingState");
const SEND_ICON = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M3.4 20.6 21 13 3.4 5.4l.1 5.8 11 1.8-11 1.8z"></path>
</svg>`;
const STOP_ICON = `
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M7 7h10v10H7z"></path>
</svg>`;
let generator = null;
let isGenerating = false;
let loadFailed = false;
const stoppingCriteria = new InterruptableStoppingCriteria();
const conversation = [{ role: "system", content: SYSTEM_PROMPT }];
const examplePrompts = [
"Implement a Thread-Safe Skip List data structure in C++17. The implementation must support concurrent insert, search, and delete operations, utilizing std::atomic for managing node pointers rather than just a global mutex. Optimize for lookup speed and provide a brief justification of your chosen MAX_LEVEL based on anticipated data volume (N=10^6).",
"Create a Node.js-based state machine that manages a mock asynchronous workflow (e.g., handling payment, user creation, and email verification) where each state (e.g., PAYMENT_PENDING, PAYMENT_SUCCESS) is an async function. The machine must handle network timeouts, retries with exponential backoff, and allow for state rollback if a subsequent state fails. Implement it using a strict class-based architecture with TypeScript",
"Using FastAPI, create an endpoint that accepts a large (5,000+ line) JSON structure of nested employee data. Write a function using Pandas to reframe this data into a relational structure, handling missing values, transforming date formats, and computing a new seniority_score based on years_employed and department_rank. Ensure the code includes unit tests using pytest for edge cases in the data transformation",
];
function setStatus(message, { error = false } = {}) {
statusTextEl.textContent = message;
statusTextEl.classList.toggle("error", error);
}
function setMetrics(text) {
metricsEl.textContent = text;
}
function scrollChatToBottom() {
chatEl.scrollTop = chatEl.scrollHeight;
}
function removePlaceholder() {
const placeholder = chatEl.querySelector(".placeholder");
if (placeholder) {
placeholder.remove();
}
}
function clearCenterState() {
if (loadingStateEl) {
loadingStateEl.remove();
}
}
function showExamples() {
clearCenterState();
removePlaceholder();
if (chatEl.querySelector(".examples")) {
return;
}
const card = document.createElement("div");
card.className = "message assistant placeholder";
card.innerHTML = "<strong>Try one of these examples or enter your own below:</strong>";
const list = document.createElement("div");
list.className = "examples";
for (const prompt of examplePrompts) {
const button = document.createElement("button");
button.type = "button";
button.className = "example";
button.textContent = prompt;
button.addEventListener("click", () => {
promptEl.value = prompt;
sendMessage(prompt);
});
list.appendChild(button);
}
card.appendChild(list);
chatEl.appendChild(card);
}
function addMessage(role, text = "") {
removePlaceholder();
const node = document.createElement("div");
node.className = `message ${role}`;
if (text) {
node.innerHTML = renderMarkdown(text, false);
}
chatEl.appendChild(node);
scrollChatToBottom();
return node;
}
function setStreamingMessage(node, text) {
node.innerHTML = renderMarkdown(text, true);
scrollChatToBottom();
}
function updateComposer() {
const ready = Boolean(generator) && !loadFailed;
promptEl.disabled = !ready || isGenerating;
sendButton.disabled = !ready;
resetButton.disabled = !ready || isGenerating;
sendButton.classList.toggle("stop", isGenerating);
sendButton.innerHTML = isGenerating ? STOP_ICON : SEND_ICON;
sendButton.setAttribute(
"aria-label",
isGenerating ? "Stop generation" : "Send message",
);
sendButton.setAttribute(
"title",
isGenerating ? "Stop generation" : "Send message",
);
}
function resetChat() {
if (isGenerating) {
return;
}
conversation.length = 1;
chatEl.innerHTML = "";
showExamples();
setStatus("Ready");
setMetrics("tokens/sec: -");
promptEl.value = "";
promptEl.focus();
}
async function loadModel() {
if (!window.isSecureContext) {
throw new Error(
"WebGPU requires a secure context like https:// or localhost.",
);
}
if (!("gpu" in navigator)) {
throw new Error("This browser does not support WebGPU.");
}
setStatus("Loading model...");
setMetrics("tokens/sec: -");
const startedAt = performance.now();
generator = await pipeline("text-generation", MODEL_ID, {
dtype: DTYPE,
revision: REVISION,
device: "webgpu",
progress_callback: (p) => {
if (p.status !== "progress_total") return;
const percent = Math.round(p.progress);
setStatus(`Downloading model... ${percent}%`);
},
});
const seconds = ((performance.now() - startedAt) / 1000).toFixed(1);
setStatus(`Ready in ${seconds}s`);
setMetrics("tokens/sec: -");
showExamples();
updateComposer();
}
async function sendMessage(overrideText) {
if (isGenerating) {
stoppingCriteria.interrupt();
setStatus("Stopping...");
return;
}
const text = (overrideText ?? promptEl.value).trim();
if (!text || !generator) {
return;
}
isGenerating = true;
updateComposer();
stoppingCriteria.reset();
addMessage("user", text);
const assistantNode = addMessage("assistant", "");
promptEl.value = "";
conversation.push({ role: "user", content: text });
let streamedText = "";
let tokenCount = 0;
let decodeStartedAt = null;
clearCenterState();
setStatus("Generating...");
setMetrics("tokens/sec: -");
try {
const output = await generator(conversation, {
max_new_tokens: MAX_NEW_TOKENS,
do_sample: false,
stopping_criteria: stoppingCriteria,
streamer: new TextStreamer(generator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function: (chunk) => {
streamedText += chunk;
setStreamingMessage(assistantNode, streamedText);
},
token_callback_function: () => {
const now = performance.now();
if (decodeStartedAt === null) {
decodeStartedAt = now;
}
tokenCount += 1;
if (tokenCount < 2) {
setMetrics("tokens/sec: -");
return;
}
const elapsedSeconds = Math.max(
(now - decodeStartedAt) / 1000,
0.001,
);
const tokensPerSecond = (
(tokenCount - 1) /
elapsedSeconds
).toFixed(1);
setMetrics(`tokens/sec: ${tokensPerSecond}`);
},
}),
});
const assistantText =
output?.[0]?.generated_text?.at(-1)?.content?.trim() ||
streamedText.trim() ||
"No response generated.";
assistantNode.innerHTML = renderMarkdown(assistantText, false);
conversation.push({ role: "assistant", content: assistantText });
if (tokenCount >= 2 && decodeStartedAt !== null) {
const elapsedSeconds = Math.max(
(performance.now() - decodeStartedAt) / 1000,
0.001,
);
const tokensPerSecond = ((tokenCount - 1) / elapsedSeconds).toFixed(
1,
);
setMetrics(`tokens/sec: ${tokensPerSecond}`);
} else {
setMetrics("tokens/sec: -");
}
setStatus("Ready");
} catch (error) {
console.error(error);
assistantNode.innerHTML = renderMarkdown("Something went wrong while generating a response.");
assistantNode.classList.add("error");
setStatus(error.message, { error: true });
setMetrics("tokens/sec: -");
} finally {
isGenerating = false;
updateComposer();
if (generator && !loadFailed) {
promptEl.focus();
}
}
}
sendButton.addEventListener("click", () => sendMessage());
resetButton.addEventListener("click", resetChat);
promptEl.addEventListener("keydown", (event) => {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
sendMessage();
}
});
updateComposer();
loadModel().catch((error) => {
console.error(error);
loadFailed = true;
setStatus(error.message, { error: true });
setMetrics("tokens/sec: -");
updateComposer();
});
</script>
</body>
</html>