gork-imagine Closes #5216
| @@ -2091,6 +2091,8 @@ async function loadFalaiModels() { | ||
| 2091 | 2091 | |
| 2092 | 2092 | async function loadXAIModels() { |
| 2093 | 2093 | return [ |
| 2094 | + { value: 'grok-imagine-image', text: 'grok-imagine-image' }, | |
| 2095 | + { value: 'grok-imagine-image-pro', text: 'grok-imagine-image-pro' }, | |
| 2094 | 2096 | { value: 'grok-2-image-1212', text: 'grok-2-image-1212' }, |
| 2095 | 2097 | ]; |
| 2096 | 2098 | } |
| @@ -3547,7 +3549,7 @@ async function generateExtrasImage(prompt, negativePrompt, signal) { | ||
| 3547 | 3549 | * Gets an aspect ratio for Stability that is the closest to the given width and height. |
| 3548 | 3550 | * @param {number} width Target width |
| 3549 | 3551 | * @param {number} height Target height |
| 3550 | 3552 | * @param {'google'|'stability'|'zai'|'xai'} source Source of the request, used to determine aspect ratio |
| 3551 | 3553 | * @returns {string} Closest aspect ratio as a string |
| 3552 | 3554 | */ |
| 3553 | 3555 | function getClosestAspectRatio(width, height, source) { |
| @@ -3579,6 +3581,22 @@ function getClosestAspectRatio(width, height, source) { | ||
| 3579 | 3581 | '16:9': 16 / 9, |
| 3580 | 3582 | '9:16': 9 / 16, |
| 3581 | 3583 | }; |
| 3584 | + case 'xai': | |
| 3585 | + return { | |
| 3586 | + '1:1': 1, | |
| 3587 | + '3:4': 3 / 4, | |
| 3588 | + '4:3': 4 / 3, | |
| 3589 | + '9:16': 9 / 16, | |
| 3590 | + '16:9': 16 / 9, | |
| 3591 | + '2:3': 2 / 3, | |
| 3592 | + '3:2': 3 / 2, | |
| 3593 | + '9:19.5': 9 / 19.5, | |
| 3594 | + '19.5:9': 19.5 / 9, | |
| 3595 | + '9:20': 9 / 20, | |
| 3596 | + '20:9': 20 / 9, | |
| 3597 | + '1:2': 1 / 2, | |
| 3598 | + '2:1': 2 / 1, | |
| 3599 | + }; | |
| 3582 | 3600 | default: |
| 3583 | 3601 | console.warn(`Unknown source "${source}" for aspect ratio calculation.`); |
| 3584 | 3602 | return null; |
| @@ -4464,6 +4482,16 @@ async function generateBflImage(prompt, signal) { | ||
| 4464 | 4482 | * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete. |
| 4465 | 4483 | */ |
| 4466 | 4484 | async function generateXAIImage(prompt, _negativePrompt, signal) { |
| 4485 | + let aspectRatio; | |
| 4486 | + let resolution; | |
| 4487 | + | |
| 4488 | + if (/grok-imagine/.test(extension_settings.sd.model)) { | |
| 4489 | + const resolutionThreshold = 1296 * 864; | |
| 4490 | + const use2kResolution = (extension_settings.sd.width * extension_settings.sd.height) > resolutionThreshold; | |
| 4491 | + aspectRatio = getClosestAspectRatio(extension_settings.sd.width, extension_settings.sd.height, 'xai'); | |
| 4492 | + resolution = use2kResolution ? '2k' : '1k'; | |
| 4493 | + } | |
| 4494 | + | |
| 4467 | 4495 | const result = await fetch('/api/sd/xai/generate', { |
| 4468 | 4496 | method: 'POST', |
| 4469 | 4497 | headers: getRequestHeaders(), |
| @@ -4471,12 +4499,14 @@ async function generateXAIImage(prompt, _negativePrompt, signal) { | ||
| 4471 | 4499 | body: JSON.stringify({ |
| 4472 | 4500 | prompt: prompt, |
| 4473 | 4501 | model: extension_settings.sd.model, |
| 4502 | + aspect_ratio: aspectRatio, | |
| 4503 | + resolution: resolution, | |
| 4474 | 4504 | }), |
| 4475 | 4505 | }); |
| 4476 | 4506 | |
| 4477 | 4507 | if (result.ok) { |
| 4478 | 4508 | const data = await result.json(); |
| 4479 | 4509 | return { format: 'jpg'data.format, data: data.image }; |
| 4480 | 4510 | } else { |
| 4481 | 4511 | const text = await result.text(); |
| 4482 | 4512 | throw new Error(text); |
| @@ -1723,6 +1723,8 @@ xai.post('/generate', async (request, response) => { | ||
| 1723 | 1723 | const requestBody = { |
| 1724 | 1724 | prompt: request.body.prompt, |
| 1725 | 1725 | model: request.body.model, |
| 1726 | + aspect_ratio: request.body.aspect_ratio, | |
| 1727 | + resolution: request.body.resolution, | |
| 1726 | 1728 | response_format: 'b64_json', |
| 1727 | 1729 | }; |
| 1728 | 1730 | |
| @@ -1746,13 +1748,19 @@ xai.post('/generate', async (request, response) => { | ||
| 1746 | 1748 | /** @type {any} */ |
| 1747 | 1749 | const data = await result.json(); |
| 1748 | 1750 | |
| 1749 | - const image = data?.data?.[0]?.b64_json; | |
| 1751 | + // Can either be a base64 buffer (always JPEG) or a data URL (with MIME type) | |
| 1750 | - if (!image) { | |
| 1752 | + const encodedImage = String(data?.data?.[0]?.b64_json || ''); | |
| 1753 | + if (!encodedImage) { | |
| 1751 | 1754 | console.warn('xAI returned invalid data.'); |
| 1752 | 1755 | return response.sendStatus(500); |
| 1753 | 1756 | } |
| 1754 | 1757 | |
| 1755 | - return response.send({ image }); | |
| 1758 | + const dataUrlMatch = encodedImage.match(/^data:(.+);base64,(.+)$/); | |
| 1759 | + const mimeType = dataUrlMatch?.[1] || 'image/jpeg'; | |
| 1760 | + const format = mime.extension(mimeType) || 'jpg'; | |
| 1761 | + const image = dataUrlMatch?.[2] || encodedImage; | |
| 1762 | + | |
| 1763 | + return response.send({ image, format }); | |
| 1756 | 1764 | } catch (error) { |
| 1757 | 1765 | console.error('Error communicating with xAI', error); |
| 1758 | 1766 | return response.sendStatus(500); |