gork-imagine Closes #5216
| @@ -2091,6 +2091,8 @@ async function loadFalaiModels() { | |||
| 2091 | 2091 | ||
| 2092 | async function loadXAIModels() { | 2092 | async function loadXAIModels() { |
| 2093 | return [ | 2093 | return [ |
| 2094 | { value: 'grok-imagine-image', text: 'grok-imagine-image' }, | ||
| 2095 | { value: 'grok-imagine-image-pro', text: 'grok-imagine-image-pro' }, | ||
| 2094 | { value: 'grok-2-image-1212', text: 'grok-2-image-1212' }, | 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 | * Gets an aspect ratio for Stability that is the closest to the given width and height. | 3549 | * Gets an aspect ratio for Stability that is the closest to the given width and height. |
| 3548 | * @param {number} width Target width | 3550 | * @param {number} width Target width |
| 3549 | * @param {number} height Target height | 3551 | * @param {number} height Target height |
| 3550 | * @param {'google'|'stability'|'zai'} source Source of the request, used to determine aspect ratio | 3552 | * @param {'google'|'stability'|'zai'|'xai'} source Source of the request, used to determine aspect ratio |
| 3551 | * @returns {string} Closest aspect ratio as a string | 3553 | * @returns {string} Closest aspect ratio as a string |
| 3552 | */ | 3554 | */ |
| 3553 | function getClosestAspectRatio(width, height, source) { | 3555 | function getClosestAspectRatio(width, height, source) { |
| @@ -3579,6 +3581,22 @@ function getClosestAspectRatio(width, height, source) { | |||
| 3579 | '16:9': 16 / 9, | 3581 | '16:9': 16 / 9, |
| 3580 | '9:16': 9 / 16, | 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 | default: | 3600 | default: |
| 3583 | console.warn(`Unknown source "${source}" for aspect ratio calculation.`); | 3601 | console.warn(`Unknown source "${source}" for aspect ratio calculation.`); |
| 3584 | return null; | 3602 | return null; |
| @@ -4464,6 +4482,16 @@ async function generateBflImage(prompt, signal) { | |||
| 4464 | * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete. | 4482 | * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete. |
| 4465 | */ | 4483 | */ |
| 4466 | async function generateXAIImage(prompt, _negativePrompt, signal) { | 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 | const result = await fetch('/api/sd/xai/generate', { | 4495 | const result = await fetch('/api/sd/xai/generate', { |
| 4468 | method: 'POST', | 4496 | method: 'POST', |
| 4469 | headers: getRequestHeaders(), | 4497 | headers: getRequestHeaders(), |
| @@ -4471,12 +4499,14 @@ async function generateXAIImage(prompt, _negativePrompt, signal) { | |||
| 4471 | body: JSON.stringify({ | 4499 | body: JSON.stringify({ |
| 4472 | prompt: prompt, | 4500 | prompt: prompt, |
| 4473 | model: extension_settings.sd.model, | 4501 | model: extension_settings.sd.model, |
| 4502 | aspect_ratio: aspectRatio, | ||
| 4503 | resolution: resolution, | ||
| 4474 | }), | 4504 | }), |
| 4475 | }); | 4505 | }); |
| 4476 | 4506 | ||
| 4477 | if (result.ok) { | 4507 | if (result.ok) { |
| 4478 | const data = await result.json(); | 4508 | const data = await result.json(); |
| 4479 | return { format: 'jpg', data: data.image }; | 4509 | return { format: data.format, data: data.image }; |
| 4480 | } else { | 4510 | } else { |
| 4481 | const text = await result.text(); | 4511 | const text = await result.text(); |
| 4482 | throw new Error(text); | 4512 | throw new Error(text); |
| @@ -1723,6 +1723,8 @@ xai.post('/generate', async (request, response) => { | |||
| 1723 | const requestBody = { | 1723 | const requestBody = { |
| 1724 | prompt: request.body.prompt, | 1724 | prompt: request.body.prompt, |
| 1725 | model: request.body.model, | 1725 | model: request.body.model, |
| 1726 | aspect_ratio: request.body.aspect_ratio, | ||
| 1727 | resolution: request.body.resolution, | ||
| 1726 | response_format: 'b64_json', | 1728 | response_format: 'b64_json', |
| 1727 | }; | 1729 | }; |
| 1728 | 1730 | ||
| @@ -1746,13 +1748,19 @@ xai.post('/generate', async (request, response) => { | |||
| 1746 | /** @type {any} */ | 1748 | /** @type {any} */ |
| 1747 | const data = await result.json(); | 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 | console.warn('xAI returned invalid data.'); | 1754 | console.warn('xAI returned invalid data.'); |
| 1752 | return response.sendStatus(500); | 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 | } catch (error) { | 1764 | } catch (error) { |
| 1757 | console.error('Error communicating with xAI', error); | 1765 | console.error('Error communicating with xAI', error); |
| 1758 | return response.sendStatus(500); | 1766 | return response.sendStatus(500); |