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>

bce1372ed4551391d7b5c0e0f418149acc413246

Mooki <m@schienbein.dev>

Signed
3 files changed, +35 -14Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+31 -11
@@ -979,6 +979,13 @@ const resolutionOptions = {
979 sd_res_1024x1536: { width: 1024, height: 1536, name: '1024x1536 (2:3, ChatGPT)' },979 sd_res_1024x1536: { width: 1024, height: 1536, name: '1024x1536 (2:3, ChatGPT)' },
980 sd_res_1024x1792: { width: 1024, height: 1792, name: '1024x1792 (4:7, DALL-E)' },980 sd_res_1024x1792: { width: 1024, height: 1792, name: '1024x1792 (4:7, DALL-E)' },
981 sd_res_1792x1024: { width: 1792, height: 1024, name: '1792x1024 (7:4, DALL-E)' },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};
983990
984function onResolutionChange() {991function onResolutionChange() {
@@ -2297,7 +2304,12 @@ async function loadGoogleModels() {
2297}2304}
22982305
2299async function loadZaiModels() {2306async 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}
23022314
2303async function loadOpenRouterModels() {2315async function loadOpenRouterModels() {
@@ -4268,6 +4280,7 @@ async function generateGoogleImage(prompt, negativePrompt, signal) {
4268 * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.4280 * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete.
4269 */4281 */
4270async function generateZaiImage(prompt, signal) {4282async function generateZaiImage(prompt, signal) {
4283 // Video generation models (CogVideoX, Viduq1)
4271 if (/(cogvideox|vidu)/.test(extension_settings.sd.model)) {4284 if (/(cogvideox|vidu)/.test(extension_settings.sd.model)) {
4272 const videoParams = {};4285 const videoParams = {};
4273 if (/cogvideox/.test(extension_settings.sd.model)) {4286 if (/cogvideox/.test(extension_settings.sd.model)) {
@@ -4298,16 +4311,23 @@ async function generateZaiImage(prompt, signal) {
4298 const text = await videoResult.text();4311 const text = await videoResult.text();
4299 throw new Error(text);4312 throw new Error(text);
4300 } else {4313 } else {
4301 // Round width and height to nearest multiple of 16, and clamp to 512-2048 range4314 // 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);
43044317 const multiple = isGlmImage ? 32 : 16;
4305 // Make sure the pixel count does not exceed 2^21px4318
4306 while ((width * height) > Math.pow(2, 21)) {4319 // Round width and height to nearest multiple and clamp to 512-2048 range
4307 if (width >= height) {4320 let width = clamp(Math.round(extension_settings.sd.width / multiple) * multiple, 512, 2048);
4308 width -= 16;4321 let height = clamp(Math.round(extension_settings.sd.height / multiple) * multiple, 512, 2048);
4309 } else {4322
4310 height -= 16;4323 // CogView has a 2^21px pixel count limit, GLM-Image does not
4324 if (!isGlmImage) {
4325 while ((width * height) > Math.pow(2, 21)) {
4326 if (width >= height) {
4327 width -= multiple;
4328 } else {
4329 height -= multiple;
4330 }
4311 }4331 }
4312 }4332 }
43134333
public/scripts/extensions/stable-diffusion/settings.html+2 -2
@@ -58,7 +58,7 @@
58 <option value="horde">Stable Horde</option>58 <option value="horde">Stable Horde</option>
59 <option value="togetherai">TogetherAI</option>59 <option value="togetherai">TogetherAI</option>
60 <option value="xai">xAI (Grok)</option>60 <option value="xai">xAI (Grok)</option>
61 <option value="zai">Z.AI (CogView)</option>61 <option value="zai">Z.AI</option>
62 </select>62 </select>
63 <div data-sd-source="auto">63 <div data-sd-source="auto">
64 <label for="sd_auto_url">SD Web UI URL</label>64 <label for="sd_auto_url">SD Web UI URL</label>
@@ -187,7 +187,7 @@
187 <option value="high" data-i18n="High">High</option>187 <option value="high" data-i18n="High">High</option>
188 </select>188 </select>
189 </div>189 </div>
190 <div data-sd-model="dall-e-3,cogview-4,cogvideox" class="flex1">190 <div data-sd-model="dall-e-3,cogview-4,glm-image,cogvideox" class="flex1">
191 <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>191 <label for="sd_openai_quality" data-i18n="Image Quality">Image Quality</label>
192 <select id="sd_openai_quality">192 <select id="sd_openai_quality">
193 <option value="standard" data-i18n="Standard">Standard</option>193 <option value="standard" data-i18n="Standard">Standard</option>
src/endpoints/stable-diffusion.js+2 -1
@@ -1758,6 +1758,7 @@ zai.post('/generate', async (request, response) => {
17581758
1759 console.debug('Z.AI image request:', request.body);1759 console.debug('Z.AI image request:', request.body);
17601760
1761 // Always use Common API for image generation (Coding API has stricter rate limits)
1761 const generateResponse = await fetch('https://api.z.ai/api/paas/v4/images/generations', {1762 const generateResponse = await fetch('https://api.z.ai/api/paas/v4/images/generations', {
1762 method: 'POST',1763 method: 'POST',
1763 headers: {1764 headers: {
@@ -1790,7 +1791,7 @@ zai.post('/generate', async (request, response) => {
17901791
1791 const imageResponse = await fetch(url);1792 const imageResponse = await fetch(url);
1792 if (!imageResponse.ok) {1793 if (!imageResponse.ok) {
1793 console.warn('Z.AI image fetch returned an error.');1794 console.warn('Z.AI image fetch returned an error. Status:', imageResponse.status, imageResponse.statusText);
1794 return response.sendStatus(500);1795 return response.sendStatus(500);
1795 }1796 }
17961797