Add xAI for image generation extension
| @@ -81,6 +81,7 @@ const sources = { | ||
| 81 | 81 | nanogpt: 'nanogpt', |
| 82 | 82 | bfl: 'bfl', |
| 83 | 83 | falai: 'falai', |
| 84 | + xai: 'xai', | |
| 84 | 85 | }; |
| 85 | 86 | |
| 86 | 87 | const initiators = { |
| @@ -1303,6 +1304,7 @@ async function onModelChange() { | ||
| 1303 | 1304 | sources.nanogpt, |
| 1304 | 1305 | sources.bfl, |
| 1305 | 1306 | sources.falai, |
| 1307 | + sources.xai, | |
| 1306 | 1308 | ]; |
| 1307 | 1309 | |
| 1308 | 1310 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1518,6 +1520,9 @@ async function loadSamplers() { | ||
| 1518 | 1520 | case sources.bfl: |
| 1519 | 1521 | samplers = ['N/A']; |
| 1520 | 1522 | break; |
| 1523 | + case sources.xai: | |
| 1524 | + samplers = ['N/A']; | |
| 1525 | + break; | |
| 1521 | 1526 | } |
| 1522 | 1527 | |
| 1523 | 1528 | for (const sampler of samplers) { |
| @@ -1708,6 +1713,9 @@ async function loadModels() { | ||
| 1708 | 1713 | case sources.falai: |
| 1709 | 1714 | models = await loadFalaiModels(); |
| 1710 | 1715 | break; |
| 1716 | + case sources.xai: | |
| 1717 | + models = await loadXAIModels(); | |
| 1718 | + break; | |
| 1711 | 1719 | } |
| 1712 | 1720 | |
| 1713 | 1721 | for (const model of models) { |
| @@ -1760,6 +1768,12 @@ async function loadFalaiModels() { | ||
| 1760 | 1768 | return []; |
| 1761 | 1769 | } |
| 1762 | 1770 | |
| 1771 | +async function loadXAIModels() { | |
| 1772 | + return [ | |
| 1773 | + { value: 'grok-2-image-1212', text: 'grok-2-image-1212' }, | |
| 1774 | + ]; | |
| 1775 | +} | |
| 1776 | + | |
| 1763 | 1777 | async function loadPollinationsModels() { |
| 1764 | 1778 | const result = await fetch('/api/sd/pollinations/models', { |
| 1765 | 1779 | method: 'POST', |
| @@ -2081,6 +2095,9 @@ async function loadSchedulers() { | ||
| 2081 | 2095 | case sources.falai: |
| 2082 | 2096 | schedulers = ['N/A']; |
| 2083 | 2097 | break; |
| 2098 | + case sources.xai: | |
| 2099 | + schedulers = ['N/A']; | |
| 2100 | + break; | |
| 2084 | 2101 | } |
| 2085 | 2102 | |
| 2086 | 2103 | for (const scheduler of schedulers) { |
| @@ -2166,6 +2183,12 @@ async function loadVaes() { | ||
| 2166 | 2183 | case sources.bfl: |
| 2167 | 2184 | vaes = ['N/A']; |
| 2168 | 2185 | break; |
| 2186 | + case sources.falai: | |
| 2187 | + vaes = ['N/A']; | |
| 2188 | + break; | |
| 2189 | + case sources.xai: | |
| 2190 | + vaes = ['N/A']; | |
| 2191 | + break; | |
| 2169 | 2192 | } |
| 2170 | 2193 | |
| 2171 | 2194 | for (const vae of vaes) { |
| @@ -2735,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2735 | 2758 | case sources.falai: |
| 2736 | 2759 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); |
| 2737 | 2760 | break; |
| 2761 | + case sources.xai: | |
| 2762 | + result = await generateXAIImage(prefixedPrompt, negativePrompt, signal); | |
| 2763 | + break; | |
| 2738 | 2764 | } |
| 2739 | 2765 | |
| 2740 | 2766 | if (!result.data) { |
| @@ -3464,6 +3490,33 @@ async function generateBflImage(prompt, signal) { | ||
| 3464 | 3490 | } |
| 3465 | 3491 | |
| 3466 | 3492 | /** |
| 3493 | + * Generates an image using the xAI API. | |
| 3494 | + * @param {string} prompt The main instruction used to guide the image generation. | |
| 3495 | + * @param {string} _negativePrompt Negative prompt is not used in this API | |
| 3496 | + * @param {AbortSignal} signal An AbortSignal object that can be used to cancel the request. | |
| 3497 | + * @returns {Promise<{format: string, data: string}>} A promise that resolves when the image generation and processing are complete. | |
| 3498 | + */ | |
| 3499 | +async function generateXAIImage(prompt, _negativePrompt, signal) { | |
| 3500 | + const result = await fetch('/api/sd/xai/generate', { | |
| 3501 | + method: 'POST', | |
| 3502 | + headers: getRequestHeaders(), | |
| 3503 | + signal: signal, | |
| 3504 | + body: JSON.stringify({ | |
| 3505 | + prompt: prompt, | |
| 3506 | + model: extension_settings.sd.model, | |
| 3507 | + }), | |
| 3508 | + }); | |
| 3509 | + | |
| 3510 | + if (result.ok) { | |
| 3511 | + const data = await result.json(); | |
| 3512 | + return { format: 'jpg', data: data.image }; | |
| 3513 | + } else { | |
| 3514 | + const text = await result.text(); | |
| 3515 | + throw new Error(text); | |
| 3516 | + } | |
| 3517 | +} | |
| 3518 | + | |
| 3519 | +/** | |
| 3467 | 3520 | * Generates an image using the FAL.AI API. |
| 3468 | 3521 | * @param {string} prompt - The main instruction used to guide the image generation. |
| 3469 | 3522 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. |
| @@ -3782,6 +3835,8 @@ function isValidState() { | ||
| 3782 | 3835 | return secret_state[SECRET_KEYS.BFL]; |
| 3783 | 3836 | case sources.falai: |
| 3784 | 3837 | return secret_state[SECRET_KEYS.FALAI]; |
| 3838 | + case sources.xai: | |
| 3839 | + return secret_state[SECRET_KEYS.XAI]; | |
| 3785 | 3840 | } |
| 3786 | 3841 | } |
| 3787 | 3842 | |
| @@ -52,6 +52,7 @@ | ||
| 52 | 52 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> |
| 53 | 53 | <option value="horde">Stable Horde</option> |
| 54 | 54 | <option value="togetherai">TogetherAI</option> |
| 55 | + <option value="xai">xAI (Grok)</option> | |
| 55 | 56 | </select> |
| 56 | 57 | <div data-sd-source="auto"> |
| 57 | 58 | <label for="sd_auto_url">SD Web UI URL</label> |
| @@ -1245,6 +1245,7 @@ falai.post('/generate', async (request, response) => { | ||
| 1245 | 1245 | 'Authorization': `Key ${key}`, |
| 1246 | 1246 | }, |
| 1247 | 1247 | }); |
| 1248 | + /** @type {any} */ | |
| 1248 | 1249 | const resultData = await resultFetch.json(); |
| 1249 | 1250 | |
| 1250 | 1251 | if (resultData.detail !== null && resultData.detail !== undefined) { |
| @@ -1270,6 +1271,56 @@ falai.post('/generate', async (request, response) => { | ||
| 1270 | 1271 | } |
| 1271 | 1272 | }); |
| 1272 | 1273 | |
| 1274 | +const xai = express.Router(); | |
| 1275 | + | |
| 1276 | +xai.post('/generate', async (request, response) => { | |
| 1277 | + try { | |
| 1278 | + const key = readSecret(request.user.directories, SECRET_KEYS.XAI); | |
| 1279 | + | |
| 1280 | + if (!key) { | |
| 1281 | + console.warn('xAI key not found.'); | |
| 1282 | + return response.sendStatus(400); | |
| 1283 | + } | |
| 1284 | + | |
| 1285 | + const requestBody = { | |
| 1286 | + prompt: request.body.prompt, | |
| 1287 | + model: request.body.model, | |
| 1288 | + response_format: 'b64_json', | |
| 1289 | + }; | |
| 1290 | + | |
| 1291 | + console.debug('xAI request:', requestBody); | |
| 1292 | + | |
| 1293 | + const result = await fetch('https://api.x.ai/v1/images/generations', { | |
| 1294 | + method: 'POST', | |
| 1295 | + body: JSON.stringify(requestBody), | |
| 1296 | + headers: { | |
| 1297 | + 'Content-Type': 'application/json', | |
| 1298 | + 'Authorization': `Bearer ${key}`, | |
| 1299 | + }, | |
| 1300 | + }); | |
| 1301 | + | |
| 1302 | + if (!result.ok) { | |
| 1303 | + const text = await result.text(); | |
| 1304 | + console.warn('xAI returned an error.', text); | |
| 1305 | + return response.sendStatus(500); | |
| 1306 | + } | |
| 1307 | + | |
| 1308 | + /** @type {any} */ | |
| 1309 | + const data = await result.json(); | |
| 1310 | + | |
| 1311 | + const image = data?.data?.[0]?.b64_json; | |
| 1312 | + if (!image) { | |
| 1313 | + console.warn('xAI returned invalid data.'); | |
| 1314 | + return response.sendStatus(500); | |
| 1315 | + } | |
| 1316 | + | |
| 1317 | + return response.send({ image }); | |
| 1318 | + } catch (error) { | |
| 1319 | + console.error('Error communicating with xAI', error); | |
| 1320 | + return response.sendStatus(500); | |
| 1321 | + } | |
| 1322 | +}); | |
| 1323 | + | |
| 1273 | 1324 | router.use('/comfy', comfy); |
| 1274 | 1325 | router.use('/together', together); |
| 1275 | 1326 | router.use('/drawthings', drawthings); |
| @@ -1279,3 +1330,4 @@ router.use('/huggingface', huggingface); | ||
| 1279 | 1330 | router.use('/nanogpt', nanogpt); |
| 1280 | 1331 | router.use('/bfl', bfl); |
| 1281 | 1332 | router.use('/falai', falai); |
| 1333 | +router.use('/xai', xai); | |