feat(sd): Add Z.AI GLM-Image model support (#5012) * feat(sd): Add Z.AI GLM-Image model support Add the new GLM-Image model to the Z.AI image generation source: - Add 'glm-image' to the model dropdown with friendly display name - Handle GLM-Image's requirement for dimensions in multiples of 32 (vs CogView's multiples of 16) - Show quality dropdown for GLM-Image (supports standard/hd) The GLM-Image model uses the same API endpoint as CogView but has different dimension constraints. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(sd): Enhance Z.AI GLM-Image support - Use regex for GLM-Image model detection (futureproofing) - Skip 2^21 pixel limit for GLM-Image (CogView-specific) - Add Z.AI recommended resolutions (1280x1280, 1568x1056, etc.) - Add "Use Coding API" toggle for GLM Coding Plan users - Add better error logging for image fetch debugging Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(sd): Address PR review feedback for Z.AI GLM-Image - Remove custom zai_coding_api setting, use existing oai_settings.zai_endpoint - Always use Common API for image generation (avoids rate limits) - Keep ZAI_ENDPOINT import for consistency with other extensions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: always use Common API for Z.AI image generation Removes conditional endpoint selection since we decided to always use Common API for image generation (Coding API has stricter rate limits). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * It's not only cogview anymore * Remove unused param from request payload * Remove redundant debug logs * Loosen the check on image quality data attribute * Bring back coding API notice --------- Co-authored-by: mschienbein <mschienbein@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -979,6 +979,13 @@ const resolutionOptions = { | ||
| 979 | 979 | sd_res_1024x1536: { width: 1024, height: 1536, name: '1024x1536 (2:3, ChatGPT)' }, |
| 980 | 980 | sd_res_1024x1792: { width: 1024, height: 1792, name: '1024x1792 (4:7, DALL-E)' }, |
| 981 | 981 | sd_res_1792x1024: { width: 1792, height: 1024, name: '1792x1024 (7:4, DALL-E)' }, |
| 982 | + sd_res_1280x1280: { width: 1280, height: 1280, name: '1280x1280 (1:1, Z.AI)' }, | |
| 983 | + sd_res_1568x1056: { width: 1568, height: 1056, name: '1568x1056 (3:2, Z.AI)' }, | |
| 984 | + sd_res_1056x1568: { width: 1056, height: 1568, name: '1056x1568 (2:3, Z.AI)' }, | |
| 985 | + sd_res_1472x1088: { width: 1472, height: 1088, name: '1472x1088 (4:3, Z.AI)' }, | |
| 986 | + sd_res_1088x1472: { width: 1088, height: 1472, name: '1088x1472 (3:4, Z.AI)' }, | |
| 987 | + sd_res_1728x960: { width: 1728, height: 960, name: '1728x960 (16:9, Z.AI)' }, | |
| 988 | + sd_res_960x1728: { width: 960, height: 1728, name: '960x1728 (9:16, Z.AI)' }, | |
| 982 | 989 | }; |
| 983 | 990 | |
| 984 | 991 | function onResolutionChange() { |
| @@ -2297,7 +2304,12 @@ async function loadGoogleModels() { | ||
| 2297 | 2304 | } |
| 2298 | 2305 | |
| 2299 | 2306 | async function loadZaiModels() { |
| 2300 | - return ['cogview-4-250304', 'cogvideox-3', 'viduq1-text'].map(name => ({ value: name, text: name })); | |
| 2307 | + return [ | |
| 2308 | + { value: 'glm-image', text: 'GLM-Image' }, | |
| 2309 | + { value: 'cogview-4-250304', text: 'CogView-4' }, | |
| 2310 | + { value: 'cogvideox-3', text: 'CogVideoX-3' }, | |
| 2311 | + { value: 'viduq1-text', text: 'Viduq1-Text' }, | |
| 2312 | + ]; | |
| 2301 | 2313 | } |
| 2302 | 2314 | |
| 2303 | 2315 | async function loadOpenRouterModels() { |
| @@ -4268,6 +4280,7 @@ async function generateGoogleImage(prompt, negativePrompt, signal) { | ||
| 4268 | 4280 | * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete. |
| 4269 | 4281 | */ |
| 4270 | 4282 | async function generateZaiImage(prompt, signal) { |
| 4283 | + // Video generation models (CogVideoX, Viduq1) | |
| 4271 | 4284 | if (/(cogvideox|vidu)/.test(extension_settings.sd.model)) { |
| 4272 | 4285 | const videoParams = {}; |
| 4273 | 4286 | if (/cogvideox/.test(extension_settings.sd.model)) { |
| @@ -4298,16 +4311,23 @@ async function generateZaiImage(prompt, signal) { | ||
| 4298 | 4311 | const text = await videoResult.text(); |
| 4299 | 4312 | throw new Error(text); |
| 4300 | 4313 | } else { |
| 4301 | - // Round width and height to nearest multiple of 16, and clamp to 512-2048 range | |
| 4314 | + // Image generation models (GLM-Image, CogView) | |
| 4302 | - let width = clamp(Math.round(extension_settings.sd.width / 16) * 16, 512, 2048); | |
| 4315 | + // GLM-Image requires multiples of 32, CogView requires multiples of 16 | |
| 4303 | - let height = clamp(Math.round(extension_settings.sd.height / 16) * 16, 512, 2048); | |
| 4316 | + const isGlmImage = /glm-image/.test(extension_settings.sd.model); | |
| 4317 | + const multiple = isGlmImage ? 32 : 16; | |
| 4304 | 4318 | |
| 4305 | - // Make sure the pixel count does not exceed 2^21px | |
| 4319 | + // Round width and height to nearest multiple and clamp to 512-2048 range | |
| 4320 | + let width = clamp(Math.round(extension_settings.sd.width / multiple) * multiple, 512, 2048); | |
| 4321 | + let height = clamp(Math.round(extension_settings.sd.height / multiple) * multiple, 512, 2048); | |
| 4322 | + | |
| 4323 | + // CogView has a 2^21px pixel count limit, GLM-Image does not | |
| 4324 | + if (!isGlmImage) { | |
| 4306 | 4325 | while ((width * height) > Math.pow(2, 21)) { |
| 4307 | 4326 | if (width >= height) { |
| 4308 | 4327 | width -= 16multiple; |
| 4309 | 4328 | } else { |
| 4310 | 4329 | height -= 16multiple; |
| 4330 | + } | |
| 4311 | 4331 | } |
| 4312 | 4332 | } |
| 4313 | 4333 | |
| @@ -58,7 +58,7 @@ | ||
| 58 | 58 | <option value="horde">Stable Horde</option> |
| 59 | 59 | <option value="togetherai">TogetherAI</option> |
| 60 | 60 | <option value="xai">xAI (Grok)</option> |
| 61 | 61 | <option value="zai">Z.AI (CogView)</option> |
| 62 | 62 | </select> |
| 63 | 63 | <div data-sd-source="auto"> |
| 64 | 64 | <label for="sd_auto_url">SD Web UI URL</label> |
| @@ -187,7 +187,7 @@ | ||
| 187 | 187 | <option value="high" data-i18n="High">High</option> |
| 188 | 188 | </select> |
| 189 | 189 | </div> |
| 190 | 190 | <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1"> |
| 191 | 191 | <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label> |
| 192 | 192 | <select id="sd_openai_quality"> |
| 193 | 193 | <option value="standard" data-i18n="Standard">Standard</option> |
| @@ -1758,6 +1758,7 @@ zai.post('/generate', async (request, response) => { | ||
| 1758 | 1758 | |
| 1759 | 1759 | console.debug('Z.AI image request:', request.body); |
| 1760 | 1760 | |
| 1761 | + // Always use Common API for image generation (Coding API has stricter rate limits) | |
| 1761 | 1762 | const generateResponse = await fetch('https://api.z.ai/api/paas/v4/images/generations', { |
| 1762 | 1763 | method: 'POST', |
| 1763 | 1764 | headers: { |
| @@ -1790,7 +1791,7 @@ zai.post('/generate', async (request, response) => { | ||
| 1790 | 1791 | |
| 1791 | 1792 | const imageResponse = await fetch(url); |
| 1792 | 1793 | if (!imageResponse.ok) { |
| 1793 | 1794 | console.warn('Z.AI image fetch returned an error. Status:', imageResponse.status, imageResponse.statusText); |
| 1794 | 1795 | return response.sendStatus(500); |
| 1795 | 1796 | } |
| 1796 | 1797 | |