diff --git a/PRIVACY.md b/PRIVACY.md index a4c9717..34cd2b9 100644 --- a/PRIVACY.md +++ b/PRIVACY.md @@ -74,7 +74,7 @@ All data collected by Panelize is used solely for providing extension functional ### AI Provider Websites -Panelize loads AI provider websites (ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen, and Google AI Mode) inside embedded panels. These providers operate under their own privacy policies. +Panelize loads AI provider websites (ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen, ChatGLM, and Google AI Mode) inside embedded panels. These providers operate under their own privacy policies. When you interact with these AI providers through Panelize, you are subject to their respective privacy policies. Panelize does not collect or store the content of your conversations with these services. diff --git a/README.ja.md b/README.ja.md index efc876a..f3fd44b 100644 --- a/README.ja.md +++ b/README.ja.md @@ -40,11 +40,11 @@ ### 1回の入力で同時送信 -統一入力欄から ChatGPT / Claude / Gemini / Grok / Doubao / DeepSeek / Kimi / Google へ同時に送信できます。 +統一入力欄から ChatGPT / Claude / Gemini / Grok / Doubao / DeepSeek / Kimi / Qwen / Zhipu (China) / Z.ai (Global) / Google へ同時に送信できます。 ### 柔軟なレイアウト -15種類のレイアウトを用意。素早い2モデル比較には1×2、4モデルの比較には1×4が使えます。 +22種類のレイアウトを用意。素早い2モデル比較には1×2、最大12モデルの比較には1×12または2×6が使えます。
@@ -76,6 +76,10 @@ API キー不要。通常どおり各 AI サービスにログインしていれ
- Doubao
- DeepSeek
- Kimi
+- Qwen(中国版)
+- Qwen(グローバル版)
+- Zhipu (China)
+- Z.ai (Global)
- Google
diff --git a/README.md b/README.md index 0c9aa2d..e8521fe 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,11 @@ Ever found yourself copying the same prompt across multiple AI tabs just to comp ### 🎯 Ask Once, Compare All -Type your question once and send it to ChatGPT, Claude, Gemini, Grok, Doubao, DeepSeek, Kimi, and Google simultaneously. See which AI gives you the best answer—no tab switching required. +Type your question once and send it to ChatGPT, Claude, Gemini, Grok, Doubao, DeepSeek, Kimi, Qwen, Zhipu (China), Z.ai (Global), and Google simultaneously. See which AI gives you the best answer—no tab switching required. ### 📐 Flexible Layouts -Choose from 15 different layouts to fit your workflow. Need a quick 2-way comparison? Use 1×2. Deep research across 4 models? Try 1×4. The choice is yours. +Choose from 22 different layouts to fit your workflow. Need a quick 2-way comparison? Use 1×2. Deep research across 12 models? Try 1×12 or 2×6. The choice is yours.
@@ -82,6 +82,12 @@ Save your best prompts and reuse them across all providers. Supports variables l
diff --git a/README.zh-CN.md b/README.zh-CN.md index 192a679..804de85 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -40,11 +40,11 @@ ### 一次提问,同时对比 -在统一输入框中输入一次内容,就能同时发送到 ChatGPT、Claude、Gemini、Grok、Doubao、DeepSeek、Kimi、Google。 +在统一输入框中输入一次内容,就能同时发送到 ChatGPT、Claude、Gemini、Grok、Doubao、DeepSeek、Kimi、Qwen、Zhipu (China)、Z.ai (Global)、Google。 ### 灵活布局 -支持 15 种布局。快速双模型对比可用 1×2,四模型深入分析可用 1×4。 +支持 22 种布局。快速双模型对比可用 1×2,最多可用 1×12 或 2×6 同时对比 12 个模型。
@@ -76,6 +76,10 @@
- Doubao
- DeepSeek
- Kimi
+- Qwen(中国版)
+- Qwen(海外版)
+- Zhipu (China)
+- Z.ai (Global)
- Google
diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 767af16..66fee28 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -141,7 +141,7 @@ "message": "Passen Sie an, wie die Enter-Taste und Modifikatortasten funktionieren, wenn Sie in KI-Anbieter-Eingabefeldern tippen." }, "descEnterBehavior2": { - "message": "Gilt für: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global) und Google (KI-Modus)." + "message": "Gilt für: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global) und Google (KI-Modus)." }, "labelEnableEnterCustomization": { "message": "Enter-Tasten-Anpassung aktivieren" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 0caebd3..9b58afd 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -240,7 +240,7 @@ "message": "Customize how Enter and modifier keys work when typing in AI provider input fields." }, "descEnterBehavior2": { - "message": "Applies to: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), and Google (AI Mode)." + "message": "Applies to: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global), and Google (AI Mode)." }, "labelEnableEnterCustomization": { "message": "Enable Enter Key Customization" diff --git a/_locales/es/messages.json b/_locales/es/messages.json index 829401c..6631ef0 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -141,7 +141,7 @@ "message": "Personaliza cómo funcionan la tecla Enter y las teclas modificadoras al escribir en los campos de entrada de proveedores de IA." }, "descEnterBehavior2": { - "message": "Se aplica a: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global) y Google (Modo IA)." + "message": "Se aplica a: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global) y Google (Modo IA)." }, "labelEnableEnterCustomization": { "message": "Habilitar Personalización de Tecla Enter" diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 48f9651..02d41d7 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -141,7 +141,7 @@ "message": "Personnalisez le fonctionnement de la touche Entrée et des touches modificatrices lors de la saisie dans les champs de saisie des fournisseurs IA." }, "descEnterBehavior2": { - "message": "S'applique à : ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global) et Google (Mode IA)." + "message": "S'applique à : ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global) et Google (Mode IA)." }, "labelEnableEnterCustomization": { "message": "Activer la personnalisation de la touche Entrée" diff --git a/_locales/it/messages.json b/_locales/it/messages.json index 9291be8..dce5ee4 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -141,7 +141,7 @@ "message": "Personalizza come funzionano il tasto Invio e i tasti modificatori durante la digitazione nei campi di input dei provider IA." }, "descEnterBehavior2": { - "message": "Si applica a: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global) e Google (Modalità IA)." + "message": "Si applica a: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global) e Google (Modalità IA)." }, "labelEnableEnterCustomization": { "message": "Abilita personalizzazione tasto Invio" diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 86aa104..099d056 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -141,7 +141,7 @@ "message": "AI プロバイダーの入力フィールドで Enter キーと修飾キーがどのように機能するかをカスタマイズします。" }, "descEnterBehavior2": { - "message": "適用先: ChatGPT、Claude、Gemini、Grok、DeepSeek、Kimi、Doubao、Qwen (China)、Qwen (Global)、Google(AI モード)。" + "message": "適用先: ChatGPT、Claude、Gemini、Grok、DeepSeek、Kimi、Doubao、Qwen (China)、Qwen (Global)、Zhipu (China)、Z.ai (Global)、Google(AI モード)。" }, "labelEnableEnterCustomization": { "message": "Enter キーのカスタマイズを有効にする" diff --git a/_locales/ko/messages.json b/_locales/ko/messages.json index 6242189..fc8cb3a 100644 --- a/_locales/ko/messages.json +++ b/_locales/ko/messages.json @@ -141,7 +141,7 @@ "message": "AI 제공자 입력 필드에서 Enter 키와 수정 키가 작동하는 방식을 사용자 지정합니다." }, "descEnterBehavior2": { - "message": "적용 대상: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global) 및 Google (AI 모드)." + "message": "적용 대상: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global) 및 Google (AI 모드)." }, "labelEnableEnterCustomization": { "message": "Enter 키 사용자 지정 활성화" diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 639d6a6..cecca22 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -141,7 +141,7 @@ "message": "Настройте, как работают клавиша Enter и модификаторы при вводе в полях ввода провайдеров ИИ." }, "descEnterBehavior2": { - "message": "Применяется к: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global) и Google (AI Mode)." + "message": "Применяется к: ChatGPT, Claude, Gemini, Grok, DeepSeek, Kimi, Doubao, Qwen (China), Qwen (Global), Zhipu (China), Z.ai (Global) и Google (AI Mode)." }, "labelEnableEnterCustomization": { "message": "Включить настройку клавиши Enter" diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index c6bd9e2..397b743 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -141,7 +141,7 @@ "message": "自定义在 AI 提供商输入字段中输入时回车键和修饰键的工作方式。" }, "descEnterBehavior2": { - "message": "适用于: ChatGPT、Claude、Gemini、Grok、DeepSeek、Kimi、Doubao、Qwen (China)、Qwen (Global) 和 Google(AI 模式)。" + "message": "适用于: ChatGPT、Claude、Gemini、Grok、DeepSeek、Kimi、Doubao、Qwen (China)、Qwen (Global)、Zhipu (China)、Z.ai (Global) 和 Google(AI 模式)。" }, "labelEnableEnterCustomization": { "message": "启用回车键自定义" diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index e0e8dd0..5f96691 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -141,7 +141,7 @@ "message": "自訂在 AI 提供者輸入欄位中輸入時 Enter 鍵和修飾鍵的工作方式。" }, "descEnterBehavior2": { - "message": "適用於: ChatGPT、Claude、Gemini、Grok、DeepSeek、Kimi、Doubao、Qwen (China)、Qwen (Global) 和 Google(AI 模式)。" + "message": "適用於: ChatGPT、Claude、Gemini、Grok、DeepSeek、Kimi、Doubao、Qwen (China)、Qwen (Global)、Zhipu (China)、Z.ai (Global) 和 Google(AI 模式)。" }, "labelEnableEnterCustomization": { "message": "啟用 Enter 鍵自訂" diff --git a/content-scripts/enter-behavior-chatglm.js b/content-scripts/enter-behavior-chatglm.js new file mode 100644 index 0000000..6022f4d --- /dev/null +++ b/content-scripts/enter-behavior-chatglm.js @@ -0,0 +1,84 @@ +// ChatGLM Enter/Shift+Enter behavior swap +// Depends on enter-behavior-utils.js and button-finder-utils.js. + +const CHATGLM_SEND_BUTTON_SELECTORS = [ + { + type: 'css', + value: '#search-input-box .enter.is-main-chat .enter-icon-container' + } +]; + +function isChatGLMInput(element) { + return Boolean( + element && + element.tagName === 'TEXTAREA' && + element.offsetParent !== null && + element.classList.contains('scroll-display-none') && + element.closest('#search-input-box') + ); +} + +function insertChatGLMNewline(textarea) { + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + textarea.setRangeText('\n', start, end, 'end'); + textarea.dispatchEvent(new Event('input', { bubbles: true })); + textarea.dispatchEvent(new Event('change', { bubbles: true })); +} + +function dispatchChatGLMEnter(textarea) { + textarea.dispatchEvent(new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + keyCode: 13, + which: 13, + bubbles: true, + cancelable: true + })); +} + +function dispatchChatGLMSend(sendButton) { + // ChatGLM submits from its mousedown handler; HTMLElement.click() skips it. + sendButton.dispatchEvent(new MouseEvent('mousedown', { + bubbles: true, + cancelable: true, + view: window, + button: 0, + buttons: 1 + })); +} + +function handleEnterSwap(event) { + if (!event.isTrusted || event.code !== 'Enter' || event.isComposing || !enterKeyConfig?.enabled) { + return; + } + + const input = document.activeElement; + if (!isChatGLMInput(input)) { + return; + } + + if (matchesModifiers(event, enterKeyConfig.newlineModifiers)) { + event.preventDefault(); + event.stopImmediatePropagation(); + insertChatGLMNewline(input); + return; + } + + if (!matchesModifiers(event, enterKeyConfig.sendModifiers)) { + return; + } + + event.preventDefault(); + event.stopImmediatePropagation(); + + const sendButton = window.ButtonFinderUtils?.findButton(CHATGLM_SEND_BUTTON_SELECTORS); + if (sendButton && !sendButton.disabled && sendButton.getAttribute('aria-disabled') !== 'true') { + dispatchChatGLMSend(sendButton); + return; + } + + dispatchChatGLMEnter(input); +} + +applyEnterSwapSetting(); diff --git a/content-scripts/enter-behavior-zai-global.js b/content-scripts/enter-behavior-zai-global.js new file mode 100644 index 0000000..27e2b90 --- /dev/null +++ b/content-scripts/enter-behavior-zai-global.js @@ -0,0 +1,75 @@ +// Z.ai Global Enter/Shift+Enter behavior swap +// Depends on enter-behavior-utils.js and button-finder-utils.js. + +const ZAI_GLOBAL_SEND_BUTTON_SELECTORS = [ + { + type: 'css', + value: 'button.sendMessageButton' + } +]; + +function isZaiGlobalInput(element) { + return Boolean( + element && + element.tagName === 'TEXTAREA' && + element.offsetParent !== null && + ( + element.placeholder === 'How can I help you today?' || + element.classList.contains('input-scroll') + ) + ); +} + +function insertZaiGlobalNewline(textarea) { + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + textarea.setRangeText('\n', start, end, 'end'); + textarea.dispatchEvent(new Event('input', { bubbles: true })); + textarea.dispatchEvent(new Event('change', { bubbles: true })); +} + +function dispatchZaiGlobalEnter(textarea) { + textarea.dispatchEvent(new KeyboardEvent('keydown', { + key: 'Enter', + code: 'Enter', + keyCode: 13, + which: 13, + bubbles: true, + cancelable: true + })); +} + +function handleEnterSwap(event) { + if (!event.isTrusted || event.code !== 'Enter' || event.isComposing || !enterKeyConfig?.enabled) { + return; + } + + const input = document.activeElement; + if (!isZaiGlobalInput(input)) { + return; + } + + if (matchesModifiers(event, enterKeyConfig.newlineModifiers)) { + event.preventDefault(); + event.stopImmediatePropagation(); + insertZaiGlobalNewline(input); + return; + } + + if (!matchesModifiers(event, enterKeyConfig.sendModifiers)) { + return; + } + + event.preventDefault(); + event.stopImmediatePropagation(); + + const sendButton = window.ButtonFinderUtils?.findButton(ZAI_GLOBAL_SEND_BUTTON_SELECTORS); + if (sendButton && !sendButton.disabled && sendButton.getAttribute('aria-disabled') !== 'true') { + sendButton.click(); + return; + } + + dispatchZaiGlobalEnter(input); +} + +applyEnterSwapSetting(); diff --git a/content-scripts/focus-toggle.js b/content-scripts/focus-toggle.js index b17e8e3..4d7622c 100644 --- a/content-scripts/focus-toggle.js +++ b/content-scripts/focus-toggle.js @@ -47,6 +47,17 @@ function findProviderInput() { document.querySelector('textarea.ds-scroll-area'); } + // ChatGLM + if (host === 'chatglm.cn') { + return document.querySelector('#search-input-box textarea.scroll-display-none'); + } + + // Z.ai Global + if (host === 'chat.z.ai') { + return document.querySelector('textarea[placeholder="How can I help you today?"]') || + document.querySelector('textarea.input-scroll'); + } + // Generic fallback: find any visible textarea or contenteditable const textarea = document.querySelector('textarea:not([hidden])'); if (textarea && textarea.offsetParent !== null) return textarea; diff --git a/content-scripts/text-injection-all-providers.js b/content-scripts/text-injection-all-providers.js index 5500b60..5152706 100644 --- a/content-scripts/text-injection-all-providers.js +++ b/content-scripts/text-injection-all-providers.js @@ -66,6 +66,14 @@ '#message-input-container textarea.message-input-textarea', '.message-input-container textarea.message-input-textarea' ], + chatglm: [ + '#search-input-box textarea.scroll-display-none', + 'textarea.scroll-display-none' + ], + 'zai-global': [ + 'textarea[placeholder="How can I help you today?"]', + 'textarea.input-scroll' + ], google: [ 'textarea.ITIRGe', 'textarea[aria-label="Ask anything"]', @@ -97,6 +105,8 @@ doubao: true, 'qwen-cn': true, 'qwen-global': true, + chatglm: true, + 'zai-global': true, google: true // Google AI Mode supports images }; @@ -114,6 +124,8 @@ '.message-input-container input#filesUpload', '#dropzone-container input#filesUpload' ], + chatglm: ['input.el-upload__input[type="file"]'], + 'zai-global': ['input[type="file"][multiple][accept*=".png"]'], google: ['input[type="file"]'] }; @@ -203,6 +215,14 @@ '#message-input-container .chat-prompt-send-button button.send-button', '.message-input-container .chat-prompt-send-button button.send-button' ], + chatglm: [ + '#search-input-box .enter.is-main-chat .enter-icon-container', + '#search-input-box .enter.is-main-chat', + '.enter.is-main-chat' + ], + 'zai-global': [ + 'button.sendMessageButton' + ], google: [ 'button[data-xid="input-plate-send-button"]', 'button[aria-label="Send"]', @@ -266,6 +286,11 @@ 'button.new-chat', '[data-testid="sidebar-new-chat-button"]' ], + chatglm: ['.aside-subjects .new-session'], + 'zai-global': [ + 'button[aria-label="New Chat"]', + 'button.navNewChat' + ], google: [ 'button[aria-label="New search"]', 'a[aria-label="Google"]', @@ -284,6 +309,8 @@ doubao: 'https://www.doubao.com/chat/', 'qwen-cn': 'https://www.qianwen.com/', 'qwen-global': 'https://chat.qwen.ai/c/new-chat', + chatglm: 'https://chatglm.cn/', + 'zai-global': 'https://chat.z.ai/', google: 'https://www.google.com/search?udm=50' }; @@ -321,6 +348,10 @@ return 'qwen-cn'; } else if (hostname === 'chat.qwen.ai') { return 'qwen-global'; + } else if (hostname === 'chatglm.cn') { + return 'chatglm'; + } else if (hostname === 'chat.z.ai') { + return 'zai-global'; } else if (hostname.includes('google.com') || hostname.includes('google.') || hostname === 'www.google.com') { // Google Search / AI Mode // Always return 'google' for any google.com page @@ -1051,7 +1082,18 @@ if (!isDisabled) { console.log('[Text Injection] Clicking send button:', selector, targetElement); - targetElement.click(); + if (provider === 'chatglm') { + // ChatGLM submits from its mousedown handler; HTMLElement.click() skips it. + targetElement.dispatchEvent(new MouseEvent('mousedown', { + bubbles: true, + cancelable: true, + view: window, + button: 0, + buttons: 1 + })); + } else { + targetElement.click(); + } return true; } else { console.log('[Text Injection] Button found but disabled:', selector); @@ -1088,7 +1130,14 @@ } // Special handling for providers that can fall back to Enter on the editor - if (provider === 'kimi' || provider === 'doubao' || provider === 'qwen-cn' || provider === 'qwen-global') { + if ( + provider === 'kimi' || + provider === 'doubao' || + provider === 'qwen-cn' || + provider === 'qwen-global' || + provider === 'chatglm' || + provider === 'zai-global' + ) { console.log('[Text Injection] Provider send button not found, trying Enter key on input:', provider); try { const inputSelectors = PROVIDER_SELECTORS[provider]; @@ -1416,7 +1465,13 @@ console.log('[Text Injection] Text injected via injectText helper for', provider); if (autoSubmit) { // Use longer delay for providers whose composer state updates asynchronously - const delay = (provider === 'deepseek' || provider === 'kimi' || provider === 'doubao') ? 800 : 500; + const delay = ( + provider === 'deepseek' || + provider === 'kimi' || + provider === 'doubao' || + provider === 'chatglm' || + provider === 'zai-global' + ) ? 800 : 500; setTimeout(() => clickSendButton(provider, providerMode), delay); } return true; @@ -1532,6 +1587,10 @@ case 'qwen-cn': case 'qwen-global': return await injectImageToQwen(provider, imageData); + case 'chatglm': + return await injectImageToChatGLM(imageData); + case 'zai-global': + return await injectImageToZaiGlobal(imageData); case 'google': return await injectImageToGoogle(imageData); default: @@ -1886,6 +1945,134 @@ return false; } + function countChatGLMImagePreviews() { + const previewList = document.querySelector('.file-list-box-content-list'); + return previewList ? previewList.children.length : 0; + } + + async function waitForChatGLMImagePreview(previousCount, timeoutMs) { + const start = Date.now(); + + while (Date.now() - start < timeoutMs) { + if (countChatGLMImagePreviews() > previousCount) { + console.log('[Image Injection] ChatGLM image preview detected'); + return true; + } + + await sleep(100); + } + + return false; + } + + function findChatGLMFileInput() { + return document.querySelector('.upload-popover input.el-upload__input[type="file"]') || + document.querySelector('input.el-upload__input[type="file"]'); + } + + async function waitForChatGLMFileInput(timeoutMs) { + const start = Date.now(); + + while (Date.now() - start < timeoutMs) { + const fileInput = findChatGLMFileInput(); + if (fileInput) { + return fileInput; + } + + await sleep(100); + } + + return null; + } + + async function injectImageToChatGLM(imageData) { + try { + let fileInput = findChatGLMFileInput(); + + if (!fileInput) { + const uploadButton = document.querySelector('.upload-image-btn-container'); + if (uploadButton) { + uploadButton.click(); + fileInput = await waitForChatGLMFileInput(1000); + } + } + + if (!fileInput) { + console.warn('[Image Injection] ChatGLM image input not found'); + return false; + } + + const previousCount = countChatGLMImagePreviews(); + const blob = await dataUrlToBlob(imageData.dataUrl); + const file = new File([blob], imageData.name, { type: imageData.type }); + + if (!assignFilesToInput(fileInput, [file])) { + return false; + } + + fileInput.dispatchEvent(new Event('input', { bubbles: true })); + fileInput.dispatchEvent(new Event('change', { bubbles: true })); + + const accepted = await waitForChatGLMImagePreview(previousCount, 6000); + if (!accepted) { + console.warn('[Image Injection] ChatGLM upload did not produce an image preview'); + } + return accepted; + } catch (error) { + console.error('[Image Injection] ChatGLM error:', error); + return false; + } + } + + function countZaiGlobalImagePreviews() { + return document.querySelectorAll('.chip-scroll img[data-cy="image"]').length; + } + + async function waitForZaiGlobalImagePreview(previousCount, timeoutMs) { + const start = Date.now(); + + while (Date.now() - start < timeoutMs) { + if (countZaiGlobalImagePreviews() > previousCount) { + console.log('[Image Injection] Z.ai Global image preview detected'); + return true; + } + + await sleep(100); + } + + return false; + } + + async function injectImageToZaiGlobal(imageData) { + try { + const fileInput = document.querySelector('input[type="file"][multiple][accept*=".png"]'); + if (!fileInput) { + console.warn('[Image Injection] Z.ai Global image input not found'); + return false; + } + + const previousCount = countZaiGlobalImagePreviews(); + const blob = await dataUrlToBlob(imageData.dataUrl); + const file = new File([blob], imageData.name, { type: imageData.type }); + + if (!assignFilesToInput(fileInput, [file])) { + return false; + } + + fileInput.dispatchEvent(new Event('input', { bubbles: true })); + fileInput.dispatchEvent(new Event('change', { bubbles: true })); + + const accepted = await waitForZaiGlobalImagePreview(previousCount, 6000); + if (!accepted) { + console.warn('[Image Injection] Z.ai Global upload did not produce an image preview'); + } + return accepted; + } catch (error) { + console.error('[Image Injection] Z.ai Global error:', error); + return false; + } + } + async function waitForDoubaoFileInput(timeoutMs = 800) { const start = Date.now(); diff --git a/icons/providers/dark/zhipu.svg b/icons/providers/dark/zhipu.svg new file mode 100644 index 0000000..1b96f4e --- /dev/null +++ b/icons/providers/dark/zhipu.svg @@ -0,0 +1,6 @@ + diff --git a/icons/providers/zhipu.svg b/icons/providers/zhipu.svg new file mode 100644 index 0000000..1b96f4e --- /dev/null +++ b/icons/providers/zhipu.svg @@ -0,0 +1,6 @@ + diff --git a/manifest.json b/manifest.json index cc4cc2f..c29a106 100644 --- a/manifest.json +++ b/manifest.json @@ -30,7 +30,9 @@ ], "optional_host_permissions": [ "https://www.qianwen.com/*", - "https://chat.qwen.ai/*" + "https://chat.qwen.ai/*", + "https://chatglm.cn/*", + "https://chat.z.ai/*" ], "background": { "service_worker": "background/service-worker.js", diff --git a/modules/optional-provider-access.js b/modules/optional-provider-access.js index 3c58e0a..526a32c 100644 --- a/modules/optional-provider-access.js +++ b/modules/optional-provider-access.js @@ -52,6 +52,24 @@ export const OPTIONAL_PROVIDER_CONFIGS = Object.freeze({ 'content-scripts/enter-behavior-qwen-global.js' )), frameRule: Object.freeze(createFrameRule(1002, 'https://chat.qwen.ai/*')) + }), + chatglm: Object.freeze({ + origins: Object.freeze(['https://chatglm.cn/*']), + contentScript: Object.freeze(createContentScript( + 'chatglm-scripts', + ['https://chatglm.cn/*'], + 'content-scripts/enter-behavior-chatglm.js' + )), + frameRule: Object.freeze(createFrameRule(1003, 'https://chatglm.cn/*')) + }), + 'zai-global': Object.freeze({ + origins: Object.freeze(['https://chat.z.ai/*']), + contentScript: Object.freeze(createContentScript( + 'zai-global-scripts', + ['https://chat.z.ai/*'], + 'content-scripts/enter-behavior-zai-global.js' + )), + frameRule: Object.freeze(createFrameRule(1004, 'https://chat.z.ai/*')) }) }); diff --git a/modules/provider-open-url.js b/modules/provider-open-url.js index d17db2a..c58168b 100644 --- a/modules/provider-open-url.js +++ b/modules/provider-open-url.js @@ -10,7 +10,9 @@ const PROVIDER_ALLOWED_HOSTS = { google: new Set(['www.google.com', 'google.com']), doubao: new Set(['www.doubao.com', 'doubao.com']), 'qwen-cn': new Set(['www.qianwen.com']), - 'qwen-global': new Set(['chat.qwen.ai']) + 'qwen-global': new Set(['chat.qwen.ai']), + chatglm: new Set(['chatglm.cn']), + 'zai-global': new Set(['chat.z.ai']) }; const QWEN_GLOBAL_NON_CONVERSATION_IDS = new Set(['new-chat', 'new-branch', 'guest']); @@ -87,6 +89,10 @@ export function isProviderCurrentUrl(providerId, rawUrl) { !QWEN_GLOBAL_NON_CONVERSATION_IDS.has(conversationMatch[1]) ); } + case 'chatglm': + return url.pathname === '/main/alltoolsdetail' && Boolean(url.searchParams.get('cid')); + case 'zai-global': + return /^\/c\/[^/]+\/?$/.test(url.pathname); default: return false; } diff --git a/modules/providers.js b/modules/providers.js index 8a1b624..8c5c7a0 100644 --- a/modules/providers.js +++ b/modules/providers.js @@ -98,6 +98,26 @@ export const PROVIDERS = [ iconDark: '/icons/providers/dark/qwen.png', enabled: true, optionalOrigins: OPTIONAL_PROVIDER_CONFIGS['qwen-global'].origins + }, + { + id: 'chatglm', + name: 'Zhipu (China)', + url: 'https://chatglm.cn/', + topLevelUrl: 'https://chatglm.cn/', + icon: '/icons/providers/zhipu.svg', + iconDark: '/icons/providers/dark/zhipu.svg', + enabled: true, + optionalOrigins: OPTIONAL_PROVIDER_CONFIGS.chatglm.origins + }, + { + id: 'zai-global', + name: 'Z.ai (Global)', + url: 'https://chat.z.ai/', + topLevelUrl: 'https://chat.z.ai/', + icon: '/icons/providers/zhipu.svg', + iconDark: '/icons/providers/dark/zhipu.svg', + enabled: true, + optionalOrigins: OPTIONAL_PROVIDER_CONFIGS['zai-global'].origins } ]; diff --git a/multi-panel/multi-panel.css b/multi-panel/multi-panel.css index 62e0fef..92d6bd4 100644 --- a/multi-panel/multi-panel.css +++ b/multi-panel/multi-panel.css @@ -349,6 +349,16 @@ body { grid-template-rows: 1fr; } +.layout-1x11 { + grid-template-columns: repeat(11, minmax(320px, 1fr)); + grid-template-rows: 1fr; +} + +.layout-1x12 { + grid-template-columns: repeat(12, minmax(320px, 1fr)); + grid-template-rows: 1fr; +} + .layout-2x1 { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; @@ -379,6 +389,11 @@ body { grid-template-rows: 1fr 1fr; } +.layout-2x6 { + grid-template-columns: repeat(6, 1fr); + grid-template-rows: 1fr 1fr; +} + .layout-3x2 { grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; @@ -404,7 +419,9 @@ body { .layout-1x7 .panel-item, .layout-1x8 .panel-item, .layout-1x9 .panel-item, -.layout-1x10 .panel-item { +.layout-1x10 .panel-item, +.layout-1x11 .panel-item, +.layout-1x12 .panel-item { scroll-snap-align: start; } @@ -1210,6 +1227,16 @@ body { grid-template-rows: 1fr; } +.layout-1x11-preview { + grid-template-columns: repeat(11, 1fr); + grid-template-rows: 1fr; +} + +.layout-1x12-preview { + grid-template-columns: repeat(12, 1fr); + grid-template-rows: 1fr; +} + .layout-2x3-preview { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr 1fr; @@ -1225,6 +1252,11 @@ body { grid-template-rows: 1fr 1fr; } +.layout-2x6-preview { + grid-template-columns: repeat(6, 1fr); + grid-template-rows: 1fr 1fr; +} + .layout-3x2-preview { grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr; diff --git a/multi-panel/multi-panel.html b/multi-panel/multi-panel.html index 3efac86..4ec2796 100644 --- a/multi-panel/multi-panel.html +++ b/multi-panel/multi-panel.html @@ -190,6 +190,18 @@