Add xAI for image generation extension
| @@ -81,6 +81,7 @@ const sources = { | |||
| 81 | nanogpt: 'nanogpt', | 81 | nanogpt: 'nanogpt', |
| 82 | bfl: 'bfl', | 82 | bfl: 'bfl', |
| 83 | falai: 'falai', | 83 | falai: 'falai', |
| 84 | xai: 'xai', | ||
| 84 | }; | 85 | }; |
| 85 | 86 | ||
| 86 | const initiators = { | 87 | const initiators = { |
| @@ -1303,6 +1304,7 @@ async function onModelChange() { | |||
| 1303 | sources.nanogpt, | 1304 | sources.nanogpt, |
| 1304 | sources.bfl, | 1305 | sources.bfl, |
| 1305 | sources.falai, | 1306 | sources.falai, |
| 1307 | sources.xai, | ||
| 1306 | ]; | 1308 | ]; |
| 1307 | 1309 | ||
| 1308 | if (cloudSources.includes(extension_settings.sd.source)) { | 1310 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1518,6 +1520,9 @@ async function loadSamplers() { | |||
| 1518 | case sources.bfl: | 1520 | case sources.bfl: |
| 1519 | samplers = ['N/A']; | 1521 | samplers = ['N/A']; |
| 1520 | break; | 1522 | break; |
| 1523 | case sources.xai: | ||
| 1524 | samplers = ['N/A']; | ||
| 1525 | break; | ||
| 1521 | } | 1526 | } |
| 1522 | 1527 | ||
| 1523 | for (const sampler of samplers) { | 1528 | for (const sampler of samplers) { |
| @@ -1708,6 +1713,9 @@ async function loadModels() { | |||
| 1708 | case sources.falai: | 1713 | case sources.falai: |
| 1709 | models = await loadFalaiModels(); | 1714 | models = await loadFalaiModels(); |
| 1710 | break; | 1715 | break; |
| 1716 | case sources.xai: | ||
| 1717 | models = await loadXAIModels(); | ||
| 1718 | break; | ||
| 1711 | } | 1719 | } |
| 1712 | 1720 | ||
| 1713 | for (const model of models) { | 1721 | for (const model of models) { |
| @@ -1760,6 +1768,12 @@ async function loadFalaiModels() { | |||
| 1760 | return []; | 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 | async function loadPollinationsModels() { | 1777 | async function loadPollinationsModels() { |
| 1764 | const result = await fetch('/api/sd/pollinations/models', { | 1778 | const result = await fetch('/api/sd/pollinations/models', { |
| 1765 | method: 'POST', | 1779 | method: 'POST', |
| @@ -2081,6 +2095,9 @@ async function loadSchedulers() { | |||
| 2081 | case sources.falai: | 2095 | case sources.falai: |
| 2082 | schedulers = ['N/A']; | 2096 | schedulers = ['N/A']; |
| 2083 | break; | 2097 | break; |
| 2098 | case sources.xai: | ||
| 2099 | schedulers = ['N/A']; | ||
| 2100 | break; | ||
| 2084 | } | 2101 | } |
| 2085 | 2102 | ||
| 2086 | for (const scheduler of schedulers) { | 2103 | for (const scheduler of schedulers) { |
| @@ -2166,6 +2183,12 @@ async function loadVaes() { | |||
| 2166 | case sources.bfl: | 2183 | case sources.bfl: |
| 2167 | vaes = ['N/A']; | 2184 | vaes = ['N/A']; |
| 2168 | break; | 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 | for (const vae of vaes) { | 2194 | for (const vae of vaes) { |
| @@ -2735,6 +2758,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2735 | case sources.falai: | 2758 | case sources.falai: |
| 2736 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); | 2759 | result = await generateFalaiImage(prefixedPrompt, negativePrompt, signal); |
| 2737 | break; | 2760 | break; |
| 2761 | case sources.xai: | ||
| 2762 | result = await generateXAIImage(prefixedPrompt, negativePrompt, signal); | ||
| 2763 | break; | ||
| 2738 | } | 2764 | } |
| 2739 | 2765 | ||
| 2740 | if (!result.data) { | 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 | * Generates an image using the FAL.AI API. | 3520 | * Generates an image using the FAL.AI API. |
| 3468 | * @param {string} prompt - The main instruction used to guide the image generation. | 3521 | * @param {string} prompt - The main instruction used to guide the image generation. |
| 3469 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. | 3522 | * @param {string} negativePrompt - The negative prompt used to guide the image generation. |
| @@ -3782,6 +3835,8 @@ function isValidState() { | |||
| 3782 | return secret_state[SECRET_KEYS.BFL]; | 3835 | return secret_state[SECRET_KEYS.BFL]; |
| 3783 | case sources.falai: | 3836 | case sources.falai: |
| 3784 | return secret_state[SECRET_KEYS.FALAI]; | 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 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> | 52 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> |
| 53 | <option value="horde">Stable Horde</option> | 53 | <option value="horde">Stable Horde</option> |
| 54 | <option value="togetherai">TogetherAI</option> | 54 | <option value="togetherai">TogetherAI</option> |
| 55 | <option value="xai">xAI (Grok)</option> | ||
| 55 | </select> | 56 | </select> |
| 56 | <div data-sd-source="auto"> | 57 | <div data-sd-source="auto"> |
| 57 | <label for="sd_auto_url">SD Web UI URL</label> | 58 | <label for="sd_auto_url">SD Web UI URL</label> |
| @@ -1245,6 +1245,7 @@ falai.post('/generate', async (request, response) => { | |||
| 1245 | 'Authorization': `Key ${key}`, | 1245 | 'Authorization': `Key ${key}`, |
| 1246 | }, | 1246 | }, |
| 1247 | }); | 1247 | }); |
| 1248 | /** @type {any} */ | ||
| 1248 | const resultData = await resultFetch.json(); | 1249 | const resultData = await resultFetch.json(); |
| 1249 | 1250 | ||
| 1250 | if (resultData.detail !== null && resultData.detail !== undefined) { | 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 | router.use('/comfy', comfy); | 1324 | router.use('/comfy', comfy); |
| 1274 | router.use('/together', together); | 1325 | router.use('/together', together); |
| 1275 | router.use('/drawthings', drawthings); | 1326 | router.use('/drawthings', drawthings); |
| @@ -1279,3 +1330,4 @@ router.use('/huggingface', huggingface); | |||
| 1279 | router.use('/nanogpt', nanogpt); | 1330 | router.use('/nanogpt', nanogpt); |
| 1280 | router.use('/bfl', bfl); | 1331 | router.use('/bfl', bfl); |
| 1281 | router.use('/falai', falai); | 1332 | router.use('/falai', falai); |
| 1333 | router.use('/xai', xai); | ||