Spaces:
Sleeping
Sleeping
Add Gemma thinking toggle
Browse files- dist/assets/index-Bqz15WNm.js +0 -0
- dist/assets/index-COohfmVW.css +1 -0
- dist/assets/wllama-ByHMPmd7.wasm +3 -0
- dist/index.html +14 -0
- src/main.ts +30 -14
- src/style.css +34 -11
dist/assets/index-Bqz15WNm.js
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
dist/assets/index-COohfmVW.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
:root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;color:#f4f0e8;font-synthesis:none;text-rendering:optimizelegibility;-webkit-font-smoothing:antialiased;background:#10100f;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;line-height:1.4}*{box-sizing:border-box}body{background:linear-gradient(#39453a38,#0000 360px),#10100f;min-width:320px;min-height:100vh;margin:0}button,input,textarea,select{font:inherit}button{border:0}.app{width:min(1180px,100vw - 32px);margin:0 auto;padding:24px 0 36px}.topbar{justify-content:space-between;align-items:center;gap:16px;margin-bottom:20px;display:flex}.brand{align-items:center;gap:12px;display:flex}.mark{color:#15170f;background:#d7ff7a;border:1px solid #f4f0e829;border-radius:8px;place-items:center;width:38px;height:38px;font-weight:800;display:grid}h1,h2,h3,p{margin:0}h1{letter-spacing:0;font-size:19px;font-weight:720}.subtle{color:#a7a093;font-size:13px}.actions{align-items:center;gap:8px;display:flex;position:relative}.icon-button,.button{color:#f4f0e8;cursor:pointer;background:#24231f;border:1px solid #f4f0e81f;border-radius:8px;justify-content:center;align-items:center;gap:8px;min-height:38px;padding:0 14px;display:inline-flex}.icon-button{width:38px;padding:0}.icon-button svg{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1.8px;width:18px;height:18px}.settings-popover{z-index:10;width:min(430px,100vw - 32px);padding:16px;position:absolute;top:46px;right:0;box-shadow:0 18px 42px #00000061}.settings-popover h2{margin-bottom:8px;font-size:15px;font-weight:720}.button.primary{color:#15170f;background:#d7ff7a;border-color:#d7ff7a;font-weight:740}.button.danger{color:#ffd9d9;background:#3a2525;border-color:#6e3737}.button:disabled,.icon-button:disabled{cursor:not-allowed;opacity:.55}.shell{grid-template-columns:minmax(0,1fr) 340px;gap:18px;display:grid}.panel{background:#1a1916f0;border:1px solid #f4f0e81f;border-radius:8px}.chat-panel{grid-template-rows:auto minmax(0,1fr) auto;min-height:690px;display:grid}.status-strip{background:#f4f0e814;border-bottom:1px solid #f4f0e81a;grid-template-columns:repeat(4,minmax(0,1fr));gap:1px;display:grid}.metric{background:#1a1916;min-width:0;padding:12px 14px}.metric span{color:#a7a093;text-transform:uppercase;font-size:11px;display:block}.metric strong{text-overflow:ellipsis;white-space:nowrap;margin-top:2px;font-size:14px;font-weight:680;display:block;overflow:hidden}.messages{padding:18px;overflow:auto}.message-list{flex-direction:column;gap:14px;display:flex}.message{white-space:pre-wrap;overflow-wrap:anywhere;border:1px solid #f4f0e81a;border-radius:8px;max-width:86%;padding:13px 14px}.message.user{background:#243124;align-self:flex-end}.message.assistant{background:#22211d;align-self:flex-start}.message.thinking-message{padding:8px}.answer-text{white-space:pre-wrap}.thinking-block{flex-direction:column;align-items:flex-start;gap:6px;margin:0 0 8px;display:flex}.thinking-message .thinking-block{margin-bottom:0}.thinking-toggle{color:#c9c1b2;cursor:pointer;background:#171714;border-radius:6px;align-items:center;gap:7px;min-height:26px;padding:0 7px;font-size:12px;display:inline-flex}.thinking-arrow{color:#d7ff7a;justify-content:center;align-items:center;width:14px;height:14px;display:inline-flex}.thinking-arrow svg{fill:none;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:2.2px;width:14px;height:14px}.thinking-content{color:#a7a093;white-space:pre-wrap;background:#141411;border-radius:6px;max-height:240px;padding:8px;font-size:12px;overflow:auto}.empty{color:#a7a093;text-align:center;place-items:center;min-height:340px;display:grid}.composer{border-top:1px solid #f4f0e81a;gap:10px;padding:14px;display:grid}textarea,input,select{color:#f4f0e8;background:#121210;border:1px solid #f4f0e81f;border-radius:8px;outline:none;width:100%}textarea:focus,input:focus,select:focus{border-color:#d7ff7acc;box-shadow:0 0 0 3px #d7ff7a1f}textarea{resize:vertical;min-height:96px;padding:12px}input,select{min-height:38px;padding:0 10px}.composer-row{justify-content:space-between;align-items:center;gap:10px;display:flex}.file-chip{color:#a7a093;flex:1;align-items:center;gap:8px;min-width:0;font-size:13px;display:flex}.file-chip input{display:none}.chip-button{color:#f4f0e8;cursor:pointer;background:#24231f;border-radius:8px;align-items:center;gap:7px;min-height:34px;padding:0 10px;display:inline-flex}.file-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.side{flex-direction:column;gap:14px;display:flex}.card{padding:16px}.card h2{margin-bottom:10px;font-size:15px;font-weight:720}.field{gap:6px;margin-top:12px;display:grid}.field label{color:#c9c1b2;font-size:12px;font-weight:650}.field-grid{grid-template-columns:1fr 1fr;gap:10px;display:grid}.progress{background:#11110f;border-radius:999px;height:10px;overflow:hidden}.bar{border-radius:inherit;background:#d7ff7a;width:0%;height:100%;transition:width .18s}.indeterminate .bar{background:linear-gradient(90deg,#d7ff7a59,#d7ff7a,#d7ff7a59) 0 0/200% 100%;animation:1.1s linear infinite load-sweep}@keyframes load-sweep{0%{background-position:200% 0}to{background-position:0 0}}.log{color:#c9c1b2;white-space:pre-wrap;background:#11110f;border-radius:8px;min-height:106px;max-height:190px;padding:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;overflow:auto}.pill-row{flex-wrap:wrap;gap:8px;margin-top:12px;display:flex}.pill{color:#c9c1b2;border:1px solid #f4f0e81f;border-radius:999px;padding:5px 9px;font-size:12px}.source{overflow-wrap:anywhere}@media (max-width:900px){.app{width:min(100vw - 20px,720px);padding-top:14px}.shell{grid-template-columns:1fr}.status-strip{grid-template-columns:1fr 1fr}.message{max-width:94%}.topbar{flex-direction:column;align-items:flex-start}.actions{justify-content:flex-end;align-self:stretch}}
|
dist/assets/wllama-ByHMPmd7.wasm
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a3e827b9fc3536fd1b061eee8b130e46f6366d25b062438a791d8b17b2a72a30
|
| 3 |
+
size 7308965
|
dist/index.html
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>Gemma 4 WebGPU</title>
|
| 7 |
+
<link rel="preconnect" href="https://huggingface.co" />
|
| 8 |
+
<script type="module" crossorigin src="./assets/index-Bqz15WNm.js"></script>
|
| 9 |
+
<link rel="stylesheet" crossorigin href="./assets/index-COohfmVW.css">
|
| 10 |
+
</head>
|
| 11 |
+
<body>
|
| 12 |
+
<div id="app"></div>
|
| 13 |
+
</body>
|
| 14 |
+
</html>
|
src/main.ts
CHANGED
|
@@ -75,7 +75,7 @@ const state = {
|
|
| 75 |
maxTokens: Number(params.get("maxTokens") || 384),
|
| 76 |
temperature: Number(params.get("temperature") || 0.7),
|
| 77 |
topP: Number(params.get("topP") || 0.95),
|
| 78 |
-
thinking: params.get("thinking") === "
|
| 79 |
seed: Number(params.get("seed") || -1),
|
| 80 |
prompt:
|
| 81 |
params.get("prompt") ||
|
|
@@ -159,7 +159,7 @@ function webgpuStatus(): string {
|
|
| 159 |
return modelLoaded && wllama?.isSupportWebGPU() ? "ready" : "available";
|
| 160 |
}
|
| 161 |
|
| 162 |
-
function iconSvg(name: "settings" | "newChat"): string {
|
| 163 |
if (name === "settings") {
|
| 164 |
return `
|
| 165 |
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
@@ -168,6 +168,20 @@ function iconSvg(name: "settings" | "newChat"): string {
|
|
| 168 |
</svg>
|
| 169 |
`;
|
| 170 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
return `
|
| 172 |
<svg viewBox="0 0 24 24" aria-hidden="true">
|
| 173 |
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
|
@@ -196,8 +210,8 @@ function renderMessage(item: ChatEntry, index: number): string {
|
|
| 196 |
}
|
| 197 |
|
| 198 |
const thinking = item.thinking?.trim() || "";
|
| 199 |
-
const shouldShowThinking = state.thinking === "
|
| 200 |
-
const arrow = item.thinkingOpen ? "
|
| 201 |
const thinkingBlock = shouldShowThinking
|
| 202 |
? `<div class="thinking-block">
|
| 203 |
<button class="thinking-toggle" type="button" data-thinking-index="${index}">
|
|
@@ -211,11 +225,10 @@ function renderMessage(item: ChatEntry, index: number): string {
|
|
| 211 |
}
|
| 212 |
</div>`
|
| 213 |
: "";
|
| 214 |
-
const answer = item.content.trim()
|
| 215 |
-
|
| 216 |
-
: `<span class="subtle">Waiting for response...</span>`;
|
| 217 |
|
| 218 |
-
return `<div class="message assistant
|
| 219 |
}
|
| 220 |
|
| 221 |
function render(): void {
|
|
@@ -329,10 +342,10 @@ function render(): void {
|
|
| 329 |
</div>
|
| 330 |
</div>
|
| 331 |
<div class="field">
|
| 332 |
-
<label for="thinking">
|
| 333 |
<select id="thinking">
|
| 334 |
-
<option value="
|
| 335 |
-
<option value="
|
| 336 |
</select>
|
| 337 |
</div>
|
| 338 |
</section>
|
|
@@ -403,7 +416,7 @@ function syncInputs(): void {
|
|
| 403 |
state.maxTokens = getNumberValue("maxTokens", state.maxTokens);
|
| 404 |
state.temperature = getNumberValue("temperature", state.temperature);
|
| 405 |
state.topP = getNumberValue("topP", state.topP);
|
| 406 |
-
state.thinking = getInputValue("thinking", state.thinking) === "
|
| 407 |
state.seed = getNumberValue("seed", state.seed);
|
| 408 |
}
|
| 409 |
|
|
@@ -624,7 +637,7 @@ async function sendMessage(): Promise<void> {
|
|
| 624 |
content,
|
| 625 |
} as ChatCompletionMessage;
|
| 626 |
const thinkingInstruction =
|
| 627 |
-
state.thinking === "
|
| 628 |
? ([
|
| 629 |
{
|
| 630 |
role: "system",
|
|
@@ -644,6 +657,9 @@ async function sendMessage(): Promise<void> {
|
|
| 644 |
seed: state.seed >= 0 ? state.seed : undefined,
|
| 645 |
cache_prompt: true,
|
| 646 |
timings_per_token: true,
|
|
|
|
|
|
|
|
|
|
| 647 |
};
|
| 648 |
|
| 649 |
await wllama.createChatCompletion({
|
|
@@ -666,7 +682,7 @@ async function sendMessage(): Promise<void> {
|
|
| 666 |
currentLog = [
|
| 667 |
`Generating ${formatSeconds(elapsed)}`,
|
| 668 |
assistant?.content ? `Answer ${assistant.content.length} chars` : "Waiting for answer text...",
|
| 669 |
-
assistant?.thinking ? `Thinking ${assistant.thinking.length} chars` : "",
|
| 670 |
lastTimings ? `Prompt ${lastTimings.prompt_per_second.toFixed(1)} tok/s` : "",
|
| 671 |
lastTimings ? `Output ${lastTimings.predicted_per_second.toFixed(1)} tok/s` : "",
|
| 672 |
]
|
|
|
|
| 75 |
maxTokens: Number(params.get("maxTokens") || 384),
|
| 76 |
temperature: Number(params.get("temperature") || 0.7),
|
| 77 |
topP: Number(params.get("topP") || 0.95),
|
| 78 |
+
thinking: params.get("thinking") === "enabled" ? "enabled" : "disabled",
|
| 79 |
seed: Number(params.get("seed") || -1),
|
| 80 |
prompt:
|
| 81 |
params.get("prompt") ||
|
|
|
|
| 159 |
return modelLoaded && wllama?.isSupportWebGPU() ? "ready" : "available";
|
| 160 |
}
|
| 161 |
|
| 162 |
+
function iconSvg(name: "settings" | "newChat" | "chevronDown" | "chevronUp"): string {
|
| 163 |
if (name === "settings") {
|
| 164 |
return `
|
| 165 |
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
|
|
| 168 |
</svg>
|
| 169 |
`;
|
| 170 |
}
|
| 171 |
+
if (name === "chevronUp") {
|
| 172 |
+
return `
|
| 173 |
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
| 174 |
+
<path d="m6 15 6-6 6 6"></path>
|
| 175 |
+
</svg>
|
| 176 |
+
`;
|
| 177 |
+
}
|
| 178 |
+
if (name === "chevronDown") {
|
| 179 |
+
return `
|
| 180 |
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
| 181 |
+
<path d="m6 9 6 6 6-6"></path>
|
| 182 |
+
</svg>
|
| 183 |
+
`;
|
| 184 |
+
}
|
| 185 |
return `
|
| 186 |
<svg viewBox="0 0 24 24" aria-hidden="true">
|
| 187 |
<path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
|
|
|
|
| 210 |
}
|
| 211 |
|
| 212 |
const thinking = item.thinking?.trim() || "";
|
| 213 |
+
const shouldShowThinking = state.thinking === "enabled" && thinking.length > 0;
|
| 214 |
+
const arrow = item.thinkingOpen ? iconSvg("chevronUp") : iconSvg("chevronDown");
|
| 215 |
const thinkingBlock = shouldShowThinking
|
| 216 |
? `<div class="thinking-block">
|
| 217 |
<button class="thinking-toggle" type="button" data-thinking-index="${index}">
|
|
|
|
| 225 |
}
|
| 226 |
</div>`
|
| 227 |
: "";
|
| 228 |
+
const answer = item.content.trim() ? `<div class="answer-text">${escapeHtml(item.content)}</div>` : "";
|
| 229 |
+
const waiting = !answer && !thinkingBlock ? `<div class="answer-text"><span class="subtle">Waiting for response...</span></div>` : "";
|
|
|
|
| 230 |
|
| 231 |
+
return `<div class="message assistant${thinkingBlock && !answer ? " thinking-message" : ""}">${thinkingBlock}${answer}${waiting}</div>`;
|
| 232 |
}
|
| 233 |
|
| 234 |
function render(): void {
|
|
|
|
| 342 |
</div>
|
| 343 |
</div>
|
| 344 |
<div class="field">
|
| 345 |
+
<label for="thinking">Model thinking</label>
|
| 346 |
<select id="thinking">
|
| 347 |
+
<option value="disabled" ${state.thinking === "disabled" ? "selected" : ""}>Disabled</option>
|
| 348 |
+
<option value="enabled" ${state.thinking === "enabled" ? "selected" : ""}>Enabled</option>
|
| 349 |
</select>
|
| 350 |
</div>
|
| 351 |
</section>
|
|
|
|
| 416 |
state.maxTokens = getNumberValue("maxTokens", state.maxTokens);
|
| 417 |
state.temperature = getNumberValue("temperature", state.temperature);
|
| 418 |
state.topP = getNumberValue("topP", state.topP);
|
| 419 |
+
state.thinking = getInputValue("thinking", state.thinking) === "enabled" ? "enabled" : "disabled";
|
| 420 |
state.seed = getNumberValue("seed", state.seed);
|
| 421 |
}
|
| 422 |
|
|
|
|
| 637 |
content,
|
| 638 |
} as ChatCompletionMessage;
|
| 639 |
const thinkingInstruction =
|
| 640 |
+
state.thinking === "disabled"
|
| 641 |
? ([
|
| 642 |
{
|
| 643 |
role: "system",
|
|
|
|
| 657 |
seed: state.seed >= 0 ? state.seed : undefined,
|
| 658 |
cache_prompt: true,
|
| 659 |
timings_per_token: true,
|
| 660 |
+
chat_template_kwargs: {
|
| 661 |
+
enable_thinking: state.thinking === "enabled",
|
| 662 |
+
},
|
| 663 |
};
|
| 664 |
|
| 665 |
await wllama.createChatCompletion({
|
|
|
|
| 682 |
currentLog = [
|
| 683 |
`Generating ${formatSeconds(elapsed)}`,
|
| 684 |
assistant?.content ? `Answer ${assistant.content.length} chars` : "Waiting for answer text...",
|
| 685 |
+
state.thinking === "enabled" && assistant?.thinking ? `Thinking ${assistant.thinking.length} chars` : "",
|
| 686 |
lastTimings ? `Prompt ${lastTimings.prompt_per_second.toFixed(1)} tok/s` : "",
|
| 687 |
lastTimings ? `Output ${lastTimings.predicted_per_second.toFixed(1)} tok/s` : "",
|
| 688 |
]
|
src/style.css
CHANGED
|
@@ -236,40 +236,63 @@ h1 {
|
|
| 236 |
background: #22211d;
|
| 237 |
}
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
.answer-text {
|
| 240 |
white-space: pre-wrap;
|
| 241 |
}
|
| 242 |
|
| 243 |
.thinking-block {
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 247 |
}
|
| 248 |
|
| 249 |
.thinking-toggle {
|
| 250 |
display: inline-flex;
|
| 251 |
-
min-height:
|
| 252 |
align-items: center;
|
| 253 |
gap: 7px;
|
| 254 |
-
border-radius:
|
| 255 |
-
padding: 0
|
| 256 |
-
background: #
|
| 257 |
color: #c9c1b2;
|
| 258 |
cursor: pointer;
|
| 259 |
font-size: 12px;
|
| 260 |
}
|
| 261 |
|
| 262 |
.thinking-arrow {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
color: #d7ff7a;
|
| 264 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
}
|
| 266 |
|
| 267 |
.thinking-content {
|
| 268 |
-
margin-top: 8px;
|
| 269 |
max-height: 240px;
|
| 270 |
overflow: auto;
|
| 271 |
-
border-radius:
|
| 272 |
-
padding:
|
| 273 |
background: #141411;
|
| 274 |
color: #a7a093;
|
| 275 |
font-size: 12px;
|
|
|
|
| 236 |
background: #22211d;
|
| 237 |
}
|
| 238 |
|
| 239 |
+
.message.thinking-message {
|
| 240 |
+
padding: 8px;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
.answer-text {
|
| 244 |
white-space: pre-wrap;
|
| 245 |
}
|
| 246 |
|
| 247 |
.thinking-block {
|
| 248 |
+
display: flex;
|
| 249 |
+
align-items: flex-start;
|
| 250 |
+
flex-direction: column;
|
| 251 |
+
gap: 6px;
|
| 252 |
+
margin: 0 0 8px;
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
.thinking-message .thinking-block {
|
| 256 |
+
margin-bottom: 0;
|
| 257 |
}
|
| 258 |
|
| 259 |
.thinking-toggle {
|
| 260 |
display: inline-flex;
|
| 261 |
+
min-height: 26px;
|
| 262 |
align-items: center;
|
| 263 |
gap: 7px;
|
| 264 |
+
border-radius: 6px;
|
| 265 |
+
padding: 0 7px;
|
| 266 |
+
background: #171714;
|
| 267 |
color: #c9c1b2;
|
| 268 |
cursor: pointer;
|
| 269 |
font-size: 12px;
|
| 270 |
}
|
| 271 |
|
| 272 |
.thinking-arrow {
|
| 273 |
+
display: inline-flex;
|
| 274 |
+
width: 14px;
|
| 275 |
+
height: 14px;
|
| 276 |
+
align-items: center;
|
| 277 |
+
justify-content: center;
|
| 278 |
color: #d7ff7a;
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
.thinking-arrow svg {
|
| 282 |
+
width: 14px;
|
| 283 |
+
height: 14px;
|
| 284 |
+
fill: none;
|
| 285 |
+
stroke: currentColor;
|
| 286 |
+
stroke-linecap: round;
|
| 287 |
+
stroke-linejoin: round;
|
| 288 |
+
stroke-width: 2.2;
|
| 289 |
}
|
| 290 |
|
| 291 |
.thinking-content {
|
|
|
|
| 292 |
max-height: 240px;
|
| 293 |
overflow: auto;
|
| 294 |
+
border-radius: 6px;
|
| 295 |
+
padding: 8px;
|
| 296 |
background: #141411;
|
| 297 |
color: #a7a093;
|
| 298 |
font-size: 12px;
|