ImageGen: add BFL API for image generation
| @@ -39,6 +39,7 @@ const UPDATE_INTERVAL = 1000; | |||
| 39 | // This is a 1x1 transparent PNG | 39 | // This is a 1x1 transparent PNG |
| 40 | const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; | 40 | const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII='; |
| 41 | const CUSTOM_STOP_EVENT = 'sd_stop_generation'; | 41 | const CUSTOM_STOP_EVENT = 'sd_stop_generation'; |
| 42 | const clamp = (value, min, max) => Math.min(Math.max(value, min), max); | ||
| 42 | 43 | ||
| 43 | const sources = { | 44 | const sources = { |
| 44 | extras: 'extras', | 45 | extras: 'extras', |
| @@ -55,6 +56,7 @@ const sources = { | |||
| 55 | blockentropy: 'blockentropy', | 56 | blockentropy: 'blockentropy', |
| 56 | huggingface: 'huggingface', | 57 | huggingface: 'huggingface', |
| 57 | nanogpt: 'nanogpt', | 58 | nanogpt: 'nanogpt', |
| 59 | bfl: 'bfl', | ||
| 58 | }; | 60 | }; |
| 59 | 61 | ||
| 60 | const initiators = { | 62 | const initiators = { |
| @@ -296,6 +298,9 @@ const defaultSettings = { | |||
| 296 | 298 | ||
| 297 | // Stability AI settings | 299 | // Stability AI settings |
| 298 | stability_style_preset: 'anime', | 300 | stability_style_preset: 'anime', |
| 301 | |||
| 302 | // BFL API settings | ||
| 303 | bfl_upsampling: false, | ||
| 299 | }; | 304 | }; |
| 300 | 305 | ||
| 301 | const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed); | 306 | const writePromptFieldsDebounced = debounce(writePromptFields, debounce_timeout.relaxed); |
| @@ -463,6 +468,7 @@ async function loadSettings() { | |||
| 463 | $('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset); | 468 | $('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset); |
| 464 | $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id); | 469 | $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id); |
| 465 | $('#sd_function_tool').prop('checked', extension_settings.sd.function_tool); | 470 | $('#sd_function_tool').prop('checked', extension_settings.sd.function_tool); |
| 471 | $('#sd_bfl_upsampling').prop('checked', extension_settings.sd.bfl_upsampling); | ||
| 466 | 472 | ||
| 467 | for (const style of extension_settings.sd.styles) { | 473 | for (const style of extension_settings.sd.styles) { |
| 468 | const option = document.createElement('option'); | 474 | const option = document.createElement('option'); |
| @@ -1089,15 +1095,14 @@ function onComfyWorkflowChange() { | |||
| 1089 | saveSettingsDebounced(); | 1095 | saveSettingsDebounced(); |
| 1090 | } | 1096 | } |
| 1091 | 1097 | ||
| 1092 | async function onStabilityKeyClick() { | 1098 | async function onApiKeyClick(popupText, secretKey) { |
| 1093 | const popupText = 'Stability AI API Key:'; | ||
| 1094 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', { | 1099 | const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', { |
| 1095 | customButtons: [{ | 1100 | customButtons: [{ |
| 1096 | text: 'Remove Key', | 1101 | text: 'Remove Key', |
| 1097 | appendAtEnd: true, | 1102 | appendAtEnd: true, |
| 1098 | result: POPUP_RESULT.NEGATIVE, | 1103 | result: POPUP_RESULT.NEGATIVE, |
| 1099 | action: async () => { | 1104 | action: async () => { |
| 1100 | await writeSecret(SECRET_KEYS.STABILITY, ''); | 1105 | await writeSecret(secretKey, ''); |
| 1101 | toastr.success('API Key removed'); | 1106 | toastr.success('API Key removed'); |
| 1102 | await loadSettingOptions(); | 1107 | await loadSettingOptions(); |
| 1103 | }, | 1108 | }, |
| @@ -1108,12 +1113,25 @@ async function onStabilityKeyClick() { | |||
| 1108 | return; | 1113 | return; |
| 1109 | } | 1114 | } |
| 1110 | 1115 | ||
| 1111 | await writeSecret(SECRET_KEYS.STABILITY, String(key)); | 1116 | await writeSecret(secretKey, String(key)); |
| 1112 | 1117 | ||
| 1113 | toastr.success('API Key saved'); | 1118 | toastr.success('API Key saved'); |
| 1114 | await loadSettingOptions(); | 1119 | await loadSettingOptions(); |
| 1115 | } | 1120 | } |
| 1116 | 1121 | ||
| 1122 | async function onStabilityKeyClick() { | ||
| 1123 | return onApiKeyClick('Stability AI API Key:', SECRET_KEYS.STABILITY); | ||
| 1124 | } | ||
| 1125 | |||
| 1126 | async function onBflKeyClick() { | ||
| 1127 | return onApiKeyClick('BFL API Key:', SECRET_KEYS.BFL); | ||
| 1128 | } | ||
| 1129 | |||
| 1130 | function onBflUpsamplingInput() { | ||
| 1131 | extension_settings.sd.bfl_upsampling = !!$('#sd_bfl_upsampling').prop('checked'); | ||
| 1132 | saveSettingsDebounced(); | ||
| 1133 | } | ||
| 1134 | |||
| 1117 | function onStabilityStylePresetChange() { | 1135 | function onStabilityStylePresetChange() { |
| 1118 | extension_settings.sd.stability_style_preset = String($('#sd_stability_style_preset').val()); | 1136 | extension_settings.sd.stability_style_preset = String($('#sd_stability_style_preset').val()); |
| 1119 | saveSettingsDebounced(); | 1137 | saveSettingsDebounced(); |
| @@ -1238,6 +1256,7 @@ async function onModelChange() { | |||
| 1238 | sources.blockentropy, | 1256 | sources.blockentropy, |
| 1239 | sources.huggingface, | 1257 | sources.huggingface, |
| 1240 | sources.nanogpt, | 1258 | sources.nanogpt, |
| 1259 | sources.bfl, | ||
| 1241 | ]; | 1260 | ]; |
| 1242 | 1261 | ||
| 1243 | if (cloudSources.includes(extension_settings.sd.source)) { | 1262 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1459,6 +1478,9 @@ async function loadSamplers() { | |||
| 1459 | case sources.nanogpt: | 1478 | case sources.nanogpt: |
| 1460 | samplers = ['N/A']; | 1479 | samplers = ['N/A']; |
| 1461 | break; | 1480 | break; |
| 1481 | case sources.bfl: | ||
| 1482 | samplers = ['N/A']; | ||
| 1483 | break; | ||
| 1462 | } | 1484 | } |
| 1463 | 1485 | ||
| 1464 | for (const sampler of samplers) { | 1486 | for (const sampler of samplers) { |
| @@ -1654,6 +1676,9 @@ async function loadModels() { | |||
| 1654 | case sources.nanogpt: | 1676 | case sources.nanogpt: |
| 1655 | models = await loadNanoGPTModels(); | 1677 | models = await loadNanoGPTModels(); |
| 1656 | break; | 1678 | break; |
| 1679 | case sources.bfl: | ||
| 1680 | models = await loadBflModels(); | ||
| 1681 | break; | ||
| 1657 | } | 1682 | } |
| 1658 | 1683 | ||
| 1659 | for (const model of models) { | 1684 | for (const model of models) { |
| @@ -1680,6 +1705,17 @@ async function loadStabilityModels() { | |||
| 1680 | ]; | 1705 | ]; |
| 1681 | } | 1706 | } |
| 1682 | 1707 | ||
| 1708 | async function loadBflModels() { | ||
| 1709 | $('#sd_bfl_key').toggleClass('success', !!secret_state[SECRET_KEYS.BFL]); | ||
| 1710 | |||
| 1711 | return [ | ||
| 1712 | { value: 'flux-pro-1.1-ultra', text: 'flux-pro-1.1-ultra' }, | ||
| 1713 | { value: 'flux-pro-1.1', text: 'flux-pro-1.1' }, | ||
| 1714 | { value: 'flux-pro', text: 'flux-pro' }, | ||
| 1715 | { value: 'flux-dev', text: 'flux-dev' }, | ||
| 1716 | ]; | ||
| 1717 | } | ||
| 1718 | |||
| 1683 | async function loadPollinationsModels() { | 1719 | async function loadPollinationsModels() { |
| 1684 | const result = await fetch('/api/sd/pollinations/models', { | 1720 | const result = await fetch('/api/sd/pollinations/models', { |
| 1685 | method: 'POST', | 1721 | method: 'POST', |
| @@ -2027,6 +2063,9 @@ async function loadSchedulers() { | |||
| 2027 | case sources.nanogpt: | 2063 | case sources.nanogpt: |
| 2028 | schedulers = ['N/A']; | 2064 | schedulers = ['N/A']; |
| 2029 | break; | 2065 | break; |
| 2066 | case sources.bfl: | ||
| 2067 | schedulers = ['N/A']; | ||
| 2068 | break; | ||
| 2030 | } | 2069 | } |
| 2031 | 2070 | ||
| 2032 | for (const scheduler of schedulers) { | 2071 | for (const scheduler of schedulers) { |
| @@ -2112,6 +2151,9 @@ async function loadVaes() { | |||
| 2112 | case sources.nanogpt: | 2151 | case sources.nanogpt: |
| 2113 | vaes = ['N/A']; | 2152 | vaes = ['N/A']; |
| 2114 | break; | 2153 | break; |
| 2154 | case sources.bfl: | ||
| 2155 | vaes = ['N/A']; | ||
| 2156 | break; | ||
| 2115 | } | 2157 | } |
| 2116 | 2158 | ||
| 2117 | for (const vae of vaes) { | 2159 | for (const vae of vaes) { |
| @@ -2666,6 +2708,10 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2666 | break; | 2708 | break; |
| 2667 | case sources.nanogpt: | 2709 | case sources.nanogpt: |
| 2668 | result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal); | 2710 | result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal); |
| 2711 | break; | ||
| 2712 | case sources.bfl: | ||
| 2713 | result = await generateBflImage(prefixedPrompt, signal); | ||
| 2714 | break; | ||
| 2669 | } | 2715 | } |
| 2670 | 2716 | ||
| 2671 | if (!result.data) { | 2717 | if (!result.data) { |
| @@ -3384,6 +3430,38 @@ async function generateNanoGPTImage(prompt, negativePrompt, signal) { | |||
| 3384 | } | 3430 | } |
| 3385 | } | 3431 | } |
| 3386 | 3432 | ||
| 3433 | /** | ||
| 3434 | * Generates an image using the BFL API. | ||
| 3435 | * @param {string} prompt - The main instruction used to guide the image generation. | ||
| 3436 | * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request. | ||
| 3437 | * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete. | ||
| 3438 | */ | ||
| 3439 | async function generateBflImage(prompt, signal) { | ||
| 3440 | const result = await fetch('/api/sd/bfl/generate', { | ||
| 3441 | method: 'POST', | ||
| 3442 | headers: getRequestHeaders(), | ||
| 3443 | signal: signal, | ||
| 3444 | body: JSON.stringify({ | ||
| 3445 | prompt: prompt, | ||
| 3446 | model: extension_settings.sd.model, | ||
| 3447 | steps: clamp(extension_settings.sd.steps, 1, 50), | ||
| 3448 | guidance: clamp(extension_settings.sd.scale, 1.5, 5), | ||
| 3449 | width: clamp(extension_settings.sd.width, 256, 1440), | ||
| 3450 | height: clamp(extension_settings.sd.height, 256, 1440), | ||
| 3451 | prompt_upsampling: !!extension_settings.sd.bfl_upsampling, | ||
| 3452 | seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined, | ||
| 3453 | }), | ||
| 3454 | }); | ||
| 3455 | |||
| 3456 | if (result.ok) { | ||
| 3457 | const data = await result.json(); | ||
| 3458 | return { format: 'jpg', data: data.image }; | ||
| 3459 | } else { | ||
| 3460 | const text = await result.text(); | ||
| 3461 | throw new Error(text); | ||
| 3462 | } | ||
| 3463 | } | ||
| 3464 | |||
| 3387 | async function onComfyOpenWorkflowEditorClick() { | 3465 | async function onComfyOpenWorkflowEditorClick() { |
| 3388 | let workflow = await (await fetch('/api/sd/comfy/workflow', { | 3466 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| 3389 | method: 'POST', | 3467 | method: 'POST', |
| @@ -3668,6 +3746,8 @@ function isValidState() { | |||
| 3668 | return secret_state[SECRET_KEYS.HUGGINGFACE]; | 3746 | return secret_state[SECRET_KEYS.HUGGINGFACE]; |
| 3669 | case sources.nanogpt: | 3747 | case sources.nanogpt: |
| 3670 | return secret_state[SECRET_KEYS.NANOGPT]; | 3748 | return secret_state[SECRET_KEYS.NANOGPT]; |
| 3749 | case sources.bfl: | ||
| 3750 | return secret_state[SECRET_KEYS.BFL]; | ||
| 3671 | } | 3751 | } |
| 3672 | } | 3752 | } |
| 3673 | 3753 | ||
| @@ -4338,6 +4418,8 @@ jQuery(async () => { | |||
| 4338 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); | 4418 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); |
| 4339 | $('#sd_huggingface_model_id').on('input', onHFModelInput); | 4419 | $('#sd_huggingface_model_id').on('input', onHFModelInput); |
| 4340 | $('#sd_function_tool').on('input', onFunctionToolInput); | 4420 | $('#sd_function_tool').on('input', onFunctionToolInput); |
| 4421 | $('#sd_bfl_key').on('click', onBflKeyClick); | ||
| 4422 | $('#sd_bfl_upsampling').on('input', onBflUpsamplingInput); | ||
| 4341 | 4423 | ||
| 4342 | if (!CSS.supports('field-sizing', 'content')) { | 4424 | if (!CSS.supports('field-sizing', 'content')) { |
| 4343 | $('.sd_settings .inline-drawer-toggle').on('click', function () { | 4425 | $('.sd_settings .inline-drawer-toggle').on('click', function () { |
| @@ -37,6 +37,7 @@ | |||
| 37 | </label> | 37 | </label> |
| 38 | <label for="sd_source" data-i18n="Source">Source</label> | 38 | <label for="sd_source" data-i18n="Source">Source</label> |
| 39 | <select id="sd_source"> | 39 | <select id="sd_source"> |
| 40 | <option value="bfl">BFL (Black Forest Labs)</option> | ||
| 40 | <option value="blockentropy">Block Entropy</option> | 41 | <option value="blockentropy">Block Entropy</option> |
| 41 | <option value="comfy">ComfyUI</option> | 42 | <option value="comfy">ComfyUI</option> |
| 42 | <option value="drawthings">DrawThings HTTP API</option> | 43 | <option value="drawthings">DrawThings HTTP API</option> |
| @@ -234,6 +235,27 @@ | |||
| 234 | </div> | 235 | </div> |
| 235 | </div> | 236 | </div> |
| 236 | </div> | 237 | </div> |
| 238 | |||
| 239 | <div data-sd-source="bfl"> | ||
| 240 | <div class="flex-container flexnowrap alignItemsBaseline marginBot5"> | ||
| 241 | <a href="https://api.bfl.ml/" target="_blank" rel="noopener noreferrer"> | ||
| 242 | <strong data-i18n="API Key">API Key</strong> | ||
| 243 | <i class="fa-solid fa-share-from-square"></i> | ||
| 244 | </a> | ||
| 245 | <span class="expander"></span> | ||
| 246 | <div id="sd_bfl_key" class="menu_button menu_button_icon"> | ||
| 247 | <i class="fa-fw fa-solid fa-key"></i> | ||
| 248 | <span data-i18n="Click to set">Click to set</span> | ||
| 249 | </div> | ||
| 250 | </div> | ||
| 251 | <label class="checkbox_label marginBot5" for="sd_bfl_upsampling" title="Whether to perform upsampling on the prompt. If active, automatically modifies the prompt for more creative generation."> | ||
| 252 | <input id="sd_bfl_upsampling" type="checkbox" /> | ||
| 253 | <span data-i18n="Prompt Upsampling"> | ||
| 254 | Prompt Upsampling | ||
| 255 | </span> | ||
| 256 | </label> | ||
| 257 | </div> | ||
| 258 | |||
| 237 | <div class="flex-container"> | 259 | <div class="flex-container"> |
| 238 | <div class="flex1"> | 260 | <div class="flex1"> |
| 239 | <label for="sd_model" data-i18n="Model">Model</label> | 261 | <label for="sd_model" data-i18n="Model">Model</label> |
| @@ -385,7 +407,7 @@ | |||
| 385 | </label> | 407 | </label> |
| 386 | </div> | 408 | </div> |
| 387 | 409 | ||
| 388 | <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,blockentropy" class="marginTop5"> | 410 | <div data-sd-source="novel,togetherai,pollinations,comfy,drawthings,vlad,auto,horde,extras,stability,blockentropy,bfl" class="marginTop5"> |
| 389 | <label for="sd_seed"> | 411 | <label for="sd_seed"> |
| 390 | <span data-i18n="Seed">Seed</span> | 412 | <span data-i18n="Seed">Seed</span> |
| 391 | <small data-i18n="(-1 for random)">(-1 for random)</small> | 413 | <small data-i18n="(-1 for random)">(-1 for random)</small> |
| @@ -37,6 +37,7 @@ export const SECRET_KEYS = { | |||
| 37 | CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts', | 37 | CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts', |
| 38 | NANOGPT: 'api_key_nanogpt', | 38 | NANOGPT: 'api_key_nanogpt', |
| 39 | TAVILY: 'api_key_tavily', | 39 | TAVILY: 'api_key_tavily', |
| 40 | BFL: 'api_key_bfl', | ||
| 40 | }; | 41 | }; |
| 41 | 42 | ||
| 42 | const INPUT_MAP = { | 43 | const INPUT_MAP = { |
| @@ -49,6 +49,7 @@ export const SECRET_KEYS = { | |||
| 49 | CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts', | 49 | CUSTOM_OPENAI_TTS: 'api_key_custom_openai_tts', |
| 50 | TAVILY: 'api_key_tavily', | 50 | TAVILY: 'api_key_tavily', |
| 51 | NANOGPT: 'api_key_nanogpt', | 51 | NANOGPT: 'api_key_nanogpt', |
| 52 | BFL: 'api_key_bfl', | ||
| 52 | }; | 53 | }; |
| 53 | 54 | ||
| 54 | // These are the keys that are safe to expose, even if allowKeysExposure is false | 55 | // These are the keys that are safe to expose, even if allowKeysExposure is false |
| @@ -1101,6 +1101,120 @@ nanogpt.post('/generate', jsonParser, async (request, response) => { | |||
| 1101 | } | 1101 | } |
| 1102 | }); | 1102 | }); |
| 1103 | 1103 | ||
| 1104 | const bfl = express.Router(); | ||
| 1105 | |||
| 1106 | bfl.post('/generate', jsonParser, async (request, response) => { | ||
| 1107 | try { | ||
| 1108 | const key = readSecret(request.user.directories, SECRET_KEYS.BFL); | ||
| 1109 | |||
| 1110 | if (!key) { | ||
| 1111 | console.log('BFL key not found.'); | ||
| 1112 | return response.sendStatus(400); | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | const requestBody = { | ||
| 1116 | prompt: request.body.prompt, | ||
| 1117 | steps: request.body.steps, | ||
| 1118 | guidance: request.body.guidance, | ||
| 1119 | width: request.body.width, | ||
| 1120 | height: request.body.height, | ||
| 1121 | prompt_upsampling: request.body.prompt_upsampling, | ||
| 1122 | seed: request.body.seed ?? null, | ||
| 1123 | safety_tolerance: 6, // being least strict | ||
| 1124 | output_format: 'jpeg', | ||
| 1125 | }; | ||
| 1126 | |||
| 1127 | function getClosestAspectRatio(width, height) { | ||
| 1128 | const minAspect = 9 / 21; | ||
| 1129 | const maxAspect = 21 / 9; | ||
| 1130 | const currentAspect = width / height; | ||
| 1131 | |||
| 1132 | const gcd = (a, b) => b === 0 ? a : gcd(b, a % b); | ||
| 1133 | const simplifyRatio = (w, h) => { | ||
| 1134 | const divisor = gcd(w, h); | ||
| 1135 | return `${w / divisor}:${h / divisor}`; | ||
| 1136 | }; | ||
| 1137 | |||
| 1138 | if (currentAspect < minAspect) { | ||
| 1139 | const adjustedHeight = Math.round(width / minAspect); | ||
| 1140 | return simplifyRatio(width, adjustedHeight); | ||
| 1141 | } else if (currentAspect > maxAspect) { | ||
| 1142 | const adjustedWidth = Math.round(height * maxAspect); | ||
| 1143 | return simplifyRatio(adjustedWidth, height); | ||
| 1144 | } else { | ||
| 1145 | return simplifyRatio(width, height); | ||
| 1146 | } | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | if (String(request.body.model).endsWith('-ultra')) { | ||
| 1150 | requestBody.aspect_ratio = getClosestAspectRatio(request.body.width, request.body.height); | ||
| 1151 | delete requestBody.steps; | ||
| 1152 | delete requestBody.guidance; | ||
| 1153 | delete requestBody.width; | ||
| 1154 | delete requestBody.height; | ||
| 1155 | delete requestBody.prompt_upsampling; | ||
| 1156 | } | ||
| 1157 | |||
| 1158 | if (String(request.body.model).endsWith('-pro-1.1')) { | ||
| 1159 | delete requestBody.steps; | ||
| 1160 | delete requestBody.guidance; | ||
| 1161 | } | ||
| 1162 | |||
| 1163 | console.log('BFL request:', requestBody); | ||
| 1164 | |||
| 1165 | const result = await fetch(`https://api.bfl.ml/v1/${request.body.model}`, { | ||
| 1166 | method: 'POST', | ||
| 1167 | body: JSON.stringify(requestBody), | ||
| 1168 | headers: { | ||
| 1169 | 'Content-Type': 'application/json', | ||
| 1170 | 'x-key': key, | ||
| 1171 | }, | ||
| 1172 | }); | ||
| 1173 | |||
| 1174 | if (!result.ok) { | ||
| 1175 | console.log('BFL returned an error.'); | ||
| 1176 | return response.sendStatus(500); | ||
| 1177 | } | ||
| 1178 | |||
| 1179 | /** @type {any} */ | ||
| 1180 | const taskData = await result.json(); | ||
| 1181 | const { id } = taskData; | ||
| 1182 | |||
| 1183 | const MAX_ATTEMPTS = 100; | ||
| 1184 | for (let i = 0; i < MAX_ATTEMPTS; i++) { | ||
| 1185 | await delay(2500); | ||
| 1186 | |||
| 1187 | const statusResult = await fetch(`https://api.bfl.ml/v1/get_result?id=${id}`); | ||
| 1188 | |||
| 1189 | if (!statusResult.ok) { | ||
| 1190 | const text = await statusResult.text(); | ||
| 1191 | console.log('BFL returned an error.', text); | ||
| 1192 | return response.sendStatus(500); | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | /** @type {any} */ | ||
| 1196 | const statusData = await statusResult.json(); | ||
| 1197 | |||
| 1198 | if (statusData?.status === 'Pending') { | ||
| 1199 | continue; | ||
| 1200 | } | ||
| 1201 | |||
| 1202 | if (statusData?.status === 'Ready') { | ||
| 1203 | const { sample } = statusData.result; | ||
| 1204 | const fetchResult = await fetch(sample); | ||
| 1205 | const fetchData = await fetchResult.arrayBuffer(); | ||
| 1206 | const image = Buffer.from(fetchData).toString('base64'); | ||
| 1207 | return response.send({ image: image }); | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | throw new Error('BFL failed to generate image.', { cause: statusData }); | ||
| 1211 | } | ||
| 1212 | } catch (error) { | ||
| 1213 | console.log(error); | ||
| 1214 | return response.sendStatus(500); | ||
| 1215 | } | ||
| 1216 | }); | ||
| 1217 | |||
| 1104 | router.use('/comfy', comfy); | 1218 | router.use('/comfy', comfy); |
| 1105 | router.use('/together', together); | 1219 | router.use('/together', together); |
| 1106 | router.use('/drawthings', drawthings); | 1220 | router.use('/drawthings', drawthings); |
| @@ -1109,3 +1223,4 @@ router.use('/stability', stability); | |||
| 1109 | router.use('/blockentropy', blockentropy); | 1223 | router.use('/blockentropy', blockentropy); |
| 1110 | router.use('/huggingface', huggingface); | 1224 | router.use('/huggingface', huggingface); |
| 1111 | router.use('/nanogpt', nanogpt); | 1225 | router.use('/nanogpt', nanogpt); |
| 1226 | router.use('/bfl', bfl); | ||