Z.ai Enhancer
+26
-10
@@ -18,2 +18,4 @@ /* background.js | ||
| stopped_msg: "Você parou a geração manualmente.", | ||
| error_title: "Z.ai — erro no stream", | ||
| error_msg: "A resposta falhou — verifique o console.", | ||
| done_default: "O agente terminou de responder.", | ||
@@ -28,2 +30,4 @@ sound_toggle: "Som ao concluir: {state}", | ||
| stopped_msg: "You stopped generation manually.", | ||
| error_title: "Z.ai — stream error", | ||
| error_msg: "The response failed — check the console.", | ||
| done_default: "The agent finished responding.", | ||
@@ -38,2 +42,4 @@ sound_toggle: "Completion sound: {state}", | ||
| stopped_msg: "您手动停止了生成。", | ||
| error_title: "Z.ai — 流式传输错误", | ||
| error_msg: "响应失败 — 请查看控制台。", | ||
| done_default: "代理已完成回复。", | ||
@@ -48,2 +54,4 @@ sound_toggle: "完成声音:{state}", | ||
| stopped_msg: "Detuviste la generación manualmente.", | ||
| error_title: "Z.ai — error en el stream", | ||
| error_msg: "La respuesta falló — revisa la consola.", | ||
| done_default: "El agente terminó de responder.", | ||
@@ -103,3 +111,3 @@ sound_toggle: "Sonido de completado: {state}", | ||
| async function notifyDone({ userStopped, durationMs, textLen }) { | ||
| async function notifyDone({ userStopped, error, durationMs, textLen }) { | ||
| const prefs = await getPrefs(); | ||
@@ -113,12 +121,19 @@ if (!prefs.nativeEnabled) return; | ||
| const msgs = getI18nMessages(); | ||
| const title = userStopped ? msgs.stopped_title : msgs.done_title; | ||
| const parts = []; | ||
| if (textLen) parts.push(`${textLen.toLocaleString()} chars`); | ||
| if (durationMs) { | ||
| const s = Math.round(durationMs / 1000); | ||
| parts.push(s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`); | ||
| let title, message; | ||
| if (userStopped) { | ||
| title = msgs.stopped_title; | ||
| message = msgs.stopped_msg; | ||
| } else if (error) { | ||
| title = msgs.error_title; | ||
| message = msgs.error_msg; | ||
| } else { | ||
| title = msgs.done_title; | ||
| const parts = []; | ||
| if (textLen) parts.push(`${textLen.toLocaleString()} chars`); | ||
| if (durationMs) { | ||
| const s = Math.round(durationMs / 1000); | ||
| parts.push(s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`); | ||
| } | ||
| message = parts.length ? parts.join(" · ") : msgs.done_default; | ||
| } | ||
| const message = userStopped | ||
| ? msgs.stopped_msg | ||
| : (parts.length ? parts.join(" · ") : msgs.done_default); | ||
@@ -142,2 +157,3 @@ try { | ||
| userStopped: msg.userStopped, | ||
| error: msg.error, | ||
| durationMs: msg.durationMs, | ||
@@ -144,0 +160,0 @@ textLen: msg.textLen |
+13
-0
| # Changelog | ||
| ## v0.12.3 — 2026-07-08 (fix Android + fix detecção) | ||
| - **Fix (crítico):** Detecção de fim de mensagem quebrada desde v0.11.0 | ||
| - `page-hook.js` linha 52 referenciava `cloneForObservation` que nunca era definido — `ReferenceError` síncrono fazia `stream-end` disparar com erro imediatamente após `stream-start` | ||
| - Sintoma: som/toast/fila só funcionavam quando o stream falhava dentro da janela de confirmação de 2.5s (respostas curtas ou com erro) | ||
| - Corrigido: agora `response.clone()` é chamado e o stream-end dispara quando o body do clone é totalmente drenado | ||
| - `detectors.js` agora propaga `error` do `stream-end` até o evento `agent:done` (o handler de erro em `autosend.js` estava morto) | ||
| - `toast.js`, `sound.js`, `background.js` distinguem erro de sucesso (toast âmbar, sem chime, notificação nativa diferente) | ||
| - **Fix (Android):** FAB (botão flutuante) no Android agora fica no canto superior direito em vez de inferior direito | ||
| - No Android o botão de envio do chat fica no canto inferior direito, sobrepondo o FAB | ||
| - Detectado via `navigator.userAgent` (regex `/Android/i`), adiciona classe `zai-mobile` ao `<html>` | ||
| - CSS move o FAB para `top: 12px; right: 12px` e o esconde (`top: -50px`) quando o painel está aberto | ||
| - **UI:** Removido o label "LANGUAGE" / "Language" antes do seletor de idioma (estava quebrando em larguras apertadas, exibindo "langua [select] ge") | ||
| ## v0.10.2 — 2026-07-04 (remoção do toggle de previews) | ||
@@ -4,0 +17,0 @@ - **Removed:** Toggle "Ocultar previews de arquivos" da guia Configurações |
@@ -36,2 +36,3 @@ /* content/detectors.js | ||
| streamEndedAt: null, | ||
| streamError: null, // error string from the last stream-end (null on success) | ||
| stopBtnVisible: false, | ||
@@ -63,2 +64,3 @@ lastStreamUrl: null, | ||
| state.streamEndedAt = null; | ||
| state.streamError = null; | ||
| state.lastStreamUrl = data.url; | ||
@@ -72,2 +74,3 @@ state.lastStartTs = data.ts; | ||
| state.streamEndedAt = data.ts; | ||
| state.streamError = data.error || null; | ||
| state.lastEndTs = data.ts; | ||
@@ -171,2 +174,4 @@ bus.emit("agent:maybe-done", { | ||
| state.stopBtnVisible = false; | ||
| const error = state.streamError; | ||
| state.streamError = null; | ||
| setAgentRunning(false); | ||
@@ -176,2 +181,3 @@ const userStoppedRecently = state.stopClickedTs && (Date.now() - state.stopClickedTs < 2000); | ||
| url, messageId, textLen, reason, | ||
| error, | ||
| userStopped: userStoppedRecently, | ||
@@ -178,0 +184,0 @@ startedAt: state.streamStartedAt, |
+8
-0
@@ -124,2 +124,4 @@ /* content/i18n.js | ||
| "toast.stopped_msg": "Você parou a geração manualmente.", | ||
| "toast.error_title": "Erro no stream", | ||
| "toast.error_msg": "A resposta falhou — verifique o console.", | ||
| "toast.test_msg": "1234 chars · 8s (simulado)", | ||
@@ -292,2 +294,4 @@ // Background notifications | ||
| "toast.stopped_msg": "You stopped generation manually.", | ||
| "toast.error_title": "Stream error", | ||
| "toast.error_msg": "The response failed — check the console.", | ||
| "toast.test_msg": "1234 chars · 8s (simulated)", | ||
@@ -453,2 +457,4 @@ "notif.bg.done_title": "Z.ai — response complete", | ||
| "toast.stopped_msg": "您手动停止了生成。", | ||
| "toast.error_title": "流式传输错误", | ||
| "toast.error_msg": "响应失败 — 请查看控制台。", | ||
| "toast.test_msg": "1234 字符 · 8秒(模拟)", | ||
@@ -614,2 +620,4 @@ "notif.bg.done_title": "Z.ai — 回复完成", | ||
| "toast.stopped_msg": "Detuviste la generación manualmente.", | ||
| "toast.error_title": "Error en el stream", | ||
| "toast.error_msg": "La respuesta falló — revisa la consola.", | ||
| "toast.test_msg": "1234 chars · 8s (simulado)", | ||
@@ -616,0 +624,0 @@ "notif.bg.done_title": "Z.ai — respuesta completada", |
+1
-0
@@ -53,2 +53,3 @@ /* content/main.js | ||
| userStopped: !!evt.userStopped, | ||
| error: evt.error || null, | ||
| durationMs: evt.durationMs || 0, | ||
@@ -55,0 +56,0 @@ textLen: evt.textLen || 0 |
+15
-4
@@ -46,8 +46,19 @@ /* content/page-hook.js | ||
| // Clone so the page still gets the body uninterrupted. | ||
| // v0.11.0: we no longer consume stream chunks (no listener for stream-chunk), | ||
| // so we don't need to read the body at all. Just detect stream-end via | ||
| // the response promise settling. | ||
| // Clone the Response so the page still gets the body uninterrupted, while | ||
| // we independently drain our clone to detect when the stream actually ends. | ||
| // | ||
| // IMPORTANT: the `response` promise resolves as soon as the HEADERS arrive, | ||
| // NOT when the body is fully received. So we cannot rely on promise | ||
| // settlement to detect stream-end — we must drain the body reader until | ||
| // `done` is true. | ||
| // | ||
| // v0.11.0 regression: a previous refactor dropped the | ||
| // `const cloneForObservation = response.clone()` line but kept the | ||
| // reference, causing a synchronous ReferenceError inside the IIFE. The | ||
| // catch block then posted `stream-end` with that error IMMEDIATELY (at | ||
| // stream-start time), which made detectors.js start its 2.5s confirmation | ||
| // window far too early — any response longer than 2.5s was never detected. | ||
| (async () => { | ||
| try { | ||
| const cloneForObservation = response.clone(); | ||
| const reader = cloneForObservation.body?.getReader(); | ||
@@ -54,0 +65,0 @@ if (!reader) { |
+21
-0
@@ -237,2 +237,23 @@ /* content/panel.css | ||
| /* ---------- Mobile (Android Firefox) ---------- | ||
| On Android, chat.z.ai's send button sits at the bottom-right corner, which | ||
| is exactly where the FAB lives by default. Moving the FAB to the top-right | ||
| avoids the overlap. The class is added to <html> by panel.js based on | ||
| navigator.userAgent. iOS Safari is intentionally not matched because z.ai | ||
| doesn't officially support iOS via this extension. */ | ||
| html.zai-mobile #zai-fab { | ||
| top: 12px; | ||
| right: 12px; | ||
| bottom: auto; | ||
| /* Slightly smaller on mobile to stay out of the way of the header */ | ||
| width: 36px; | ||
| height: 36px; | ||
| } | ||
| /* When the sidebar is open on mobile, the FAB slides off the top edge so it | ||
| doesn't sit on top of the sidebar's close button. */ | ||
| html.zai-mobile #zai-fab.zai-open { | ||
| top: -50px; | ||
| } | ||
| /* ---------- Sidebar header ---------- */ | ||
@@ -239,0 +260,0 @@ #zai-sidebar .zai-header { |
+8
-6
@@ -76,2 +76,9 @@ /* content/panel.js | ||
| // Detect Android Firefox so we can move the FAB out of the way of the | ||
| // chat's send button (which sits at the bottom-right on mobile). | ||
| // On desktop the FAB stays bottom-right (its default position). | ||
| if (/Android/i.test(navigator.userAgent)) { | ||
| document.documentElement.classList.add("zai-mobile"); | ||
| } | ||
| buildFAB(); | ||
@@ -244,3 +251,2 @@ buildSidebar(); | ||
| const section = el("div", { class: "zai-section" }); | ||
| section.appendChild(el("div", { class: "zai-section-title", text: t("lang.label") })); | ||
@@ -260,7 +266,3 @@ const locales = window.__zaiI18n?.getAvailableLocales?.() || []; | ||
| const row = el("div", { class: "zai-row" }, [ | ||
| el("div", { class: "zai-row-label", text: t("lang.label") }), | ||
| select | ||
| ]); | ||
| section.appendChild(row); | ||
| section.appendChild(select); | ||
| return section; | ||
@@ -267,0 +269,0 @@ } |
+2
-1
@@ -102,4 +102,5 @@ /* content/sound.js | ||
| bus.on("agent:done", (evt) => { | ||
| // Skip the chime if the user manually stopped the agent | ||
| // Skip the chime if the user manually stopped the agent or if the stream errored | ||
| if (evt.userStopped) return; | ||
| if (evt.error) return; | ||
| playChime(); | ||
@@ -106,0 +107,0 @@ }); |
+10
-0
@@ -105,2 +105,12 @@ /* content/toast.js | ||
| } | ||
| if (evt.error) { | ||
| show({ | ||
| title: t("toast.error_title"), | ||
| message: t("toast.error_msg"), | ||
| icon: "!", | ||
| accent: "#f59e0b", | ||
| durationMs: 5000 | ||
| }); | ||
| return; | ||
| } | ||
| const dur = fmtDuration(evt.durationMs); | ||
@@ -107,0 +117,0 @@ const parts = []; |
+1
-1
| { | ||
| "manifest_version": 3, | ||
| "name": "Z.ai Enhancer", | ||
| "version": "0.12.2", | ||
| "version": "0.12.3", | ||
| "description": "Floating panel for chat.z.ai: sound + visual notifications when the agent finishes responding, auto-send queue, and a reusable prompt library with {{variables}}.", | ||
@@ -6,0 +6,0 @@ "browser_specific_settings": { |
| --- | ||
| name: Bug report | ||
| about: Something isn't working as expected | ||
| title: "[BUG] " | ||
| labels: bug | ||
| assignees: '' | ||
| --- | ||
| ## Describe the bug | ||
| A clear description of what the bug is. | ||
| ## To reproduce | ||
| Steps to reproduce the behavior: | ||
| 1. Go to '...' | ||
| 2. Click on '...' | ||
| 3. See error | ||
| ## Expected behavior | ||
| What you expected to happen. | ||
| ## Actual behavior | ||
| What actually happened. | ||
| ## Environment | ||
| - Firefox version: [e.g. 141.0] | ||
| - Z.ai Enhancer version: [e.g. 0.5.0 — see about:addons] | ||
| - OS: [e.g. Ubuntu 24.04] | ||
| - Did you load it as a temporary add-on or install from AMO? | ||
| ## Console output | ||
| If there are errors in the extension's console, paste them here. | ||
| Open via: `about:debugging → Z.ai Enhancer → Inspect`. | ||
| ## Screenshots | ||
| If applicable, add screenshots. |
| --- | ||
| name: Feature request | ||
| about: Suggest an idea for this extension | ||
| title: "[FEAT] " | ||
| labels: enhancement | ||
| assignees: '' | ||
| --- | ||
| ## Is your feature request related to a problem? | ||
| A clear description of what the problem is. Ex: "I'm always frustrated when [...]" | ||
| ## Proposed solution | ||
| A clear description of what you want to happen. | ||
| ## Alternatives considered | ||
| Any alternative solutions or features you've considered. | ||
| ## Additional context | ||
| Screenshots, mockups, examples from other extensions, etc. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet