Hugging Face inference API for image generation
| @@ -52,6 +52,7 @@ const sources = { | ||
| 52 | 52 | pollinations: 'pollinations', |
| 53 | 53 | stability: 'stability', |
| 54 | 54 | blockentropy: 'blockentropy', |
| 55 | + huggingface: 'huggingface', | |
| 55 | 56 | }; |
| 56 | 57 | |
| 57 | 58 | const initiators = { |
| @@ -454,6 +455,7 @@ async function loadSettings() { | ||
| 454 | 455 | $('#sd_command_visible').prop('checked', extension_settings.sd.command_visible); |
| 455 | 456 | $('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible); |
| 456 | 457 | $('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset); |
| 458 | + $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id); | |
| 457 | 459 | |
| 458 | 460 | for (const style of extension_settings.sd.styles) { |
| 459 | 461 | const option = document.createElement('option'); |
| @@ -1091,6 +1093,11 @@ function onComfyUrlInput() { | ||
| 1091 | 1093 | saveSettingsDebounced(); |
| 1092 | 1094 | } |
| 1093 | 1095 | |
| 1096 | +function onHFModelInput() { | |
| 1097 | + extension_settings.sd.huggingface_model_id = $('#sd_huggingface_model_id').val(); | |
| 1098 | + saveSettingsDebounced(); | |
| 1099 | +} | |
| 1100 | + | |
| 1094 | 1101 | function onComfyWorkflowChange() { |
| 1095 | 1102 | extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val(); |
| 1096 | 1103 | saveSettingsDebounced(); |
| @@ -2596,6 +2603,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2596 | 2603 | case sources.blockentropy: |
| 2597 | 2604 | result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal); |
| 2598 | 2605 | break; |
| 2606 | + case sources.huggingface: | |
| 2607 | + result = await generateHuggingFaceImage(prefixedPrompt, signal); | |
| 2608 | + break; | |
| 2599 | 2609 | } |
| 2600 | 2610 | |
| 2601 | 2611 | if (!result.data) { |
| @@ -3229,6 +3239,34 @@ async function generateComfyImage(prompt, negativePrompt, signal) { | ||
| 3229 | 3239 | return { format: 'png', data: await promptResult.text() }; |
| 3230 | 3240 | } |
| 3231 | 3241 | |
| 3242 | + | |
| 3243 | +/** | |
| 3244 | + * Generates an image in Hugging Face Inference API using the provided prompt and configuration settings (model selected). | |
| 3245 | + * @param {string} prompt - The main instruction used to guide the image generation. | |
| 3246 | + * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request. | |
| 3247 | + * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete. | |
| 3248 | + */ | |
| 3249 | +async function generateHuggingFaceImage(prompt, signal) { | |
| 3250 | + const result = await fetch('/api/sd/huggingface/generate', { | |
| 3251 | + method: 'POST', | |
| 3252 | + headers: getRequestHeaders(), | |
| 3253 | + signal: signal, | |
| 3254 | + body: JSON.stringify({ | |
| 3255 | + model: extension_settings.sd.huggingface_model_id, | |
| 3256 | + prompt: prompt, | |
| 3257 | + }), | |
| 3258 | + }); | |
| 3259 | + | |
| 3260 | + if (result.ok) { | |
| 3261 | + const data = await result.json(); | |
| 3262 | + return { format: 'jpg', data: data.image }; | |
| 3263 | + } else { | |
| 3264 | + const text = await result.text(); | |
| 3265 | + throw new Error(text); | |
| 3266 | + } | |
| 3267 | +} | |
| 3268 | + | |
| 3269 | + | |
| 3232 | 3270 | async function onComfyOpenWorkflowEditorClick() { |
| 3233 | 3271 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| 3234 | 3272 | method: 'POST', |
| @@ -3508,6 +3546,8 @@ function isValidState() { | ||
| 3508 | 3546 | return secret_state[SECRET_KEYS.STABILITY]; |
| 3509 | 3547 | case sources.blockentropy: |
| 3510 | 3548 | return secret_state[SECRET_KEYS.BLOCKENTROPY]; |
| 3549 | + case sources.huggingface: | |
| 3550 | + return true; | |
| 3511 | 3551 | } |
| 3512 | 3552 | } |
| 3513 | 3553 | |
| @@ -3848,6 +3888,7 @@ jQuery(async () => { | ||
| 3848 | 3888 | $('#sd_swap_dimensions').on('click', onSwapDimensionsClick); |
| 3849 | 3889 | $('#sd_stability_key').on('click', onStabilityKeyClick); |
| 3850 | 3890 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); |
| 3891 | + $('#sd_huggingface_model_id').on('input', onHFModelInput); | |
| 3851 | 3892 | |
| 3852 | 3893 | $('.sd_settings .inline-drawer-toggle').on('click', function () { |
| 3853 | 3894 | initScrollHeight($('#sd_prompt_prefix')); |
| @@ -49,6 +49,7 @@ | ||
| 49 | 49 | <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option> |
| 50 | 50 | <option value="horde">Stable Horde</option> |
| 51 | 51 | <option value="togetherai">TogetherAI</option> |
| 52 | + <option value="huggingface">HuggingFace (Image Inference Endpoint)</option> | |
| 52 | 53 | </select> |
| 53 | 54 | <div data-sd-source="auto"> |
| 54 | 55 | <label for="sd_auto_url">SD Web UI URL</label> |
| @@ -82,6 +83,11 @@ | ||
| 82 | 83 | <!-- (Original Text)<b>Important:</b> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine. --> |
| 83 | 84 | <i><b data-i18n="Important:">Important:</b></i><i data-i18n="sd_drawthings_auth_txt"> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine.</i> |
| 84 | 85 | </div> |
| 86 | + <div data-sd-source="huggingface"> | |
| 87 | + <i>Hint: Save an API key in the Hugging Face API settings to use it here.</i> | |
| 88 | + <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label> | |
| 89 | + <input id="sd_huggingface_model_id" type="text" class="text_pole" data-i18n="[placeholder]black-forest-labs/FLUX.1-dev" placeholder="black-forest-labs/FLUX.1-dev" value="" /> | |
| 90 | + </div> | |
| 85 | 91 | <div data-sd-source="vlad"> |
| 86 | 92 | <label for="sd_vlad_url">SD.Next API URL</label> |
| 87 | 93 | <div class="flex-container flexnowrap"> |
| @@ -991,11 +991,52 @@ blockentropy.post('/generate', jsonParser, async (request, response) => { | ||
| 991 | 991 | }); |
| 992 | 992 | |
| 993 | 993 | |
| 994 | +const huggingface = express.Router(); | |
| 995 | + | |
| 996 | +huggingface.post('/generate', jsonParser, async (request, response) => { | |
| 997 | + try { | |
| 998 | + const key = readSecret(request.user.directories, SECRET_KEYS.HUGGINGFACE); | |
| 999 | + | |
| 1000 | + if (!key) { | |
| 1001 | + console.log('Hugging Face key not found.'); | |
| 1002 | + return response.sendStatus(400); | |
| 1003 | + } | |
| 1004 | + | |
| 1005 | + console.log('Hugging Face request:', request.body); | |
| 1006 | + | |
| 1007 | + const result = await fetch(`https://api-inference.huggingface.co/models/${request.body.model}`, { | |
| 1008 | + method: 'POST', | |
| 1009 | + body: JSON.stringify({ | |
| 1010 | + inputs: request.body.prompt, | |
| 1011 | + }), | |
| 1012 | + headers: { | |
| 1013 | + 'Content-Type': 'application/json', | |
| 1014 | + 'Authorization': `Bearer ${key}`, | |
| 1015 | + }, | |
| 1016 | + }); | |
| 1017 | + | |
| 1018 | + if (!result.ok) { | |
| 1019 | + console.log('Hugging Face returned an error.'); | |
| 1020 | + return response.sendStatus(500); | |
| 1021 | + } | |
| 1022 | + | |
| 1023 | + const buffer = await result.buffer(); | |
| 1024 | + return response.send({ | |
| 1025 | + image: buffer.toString('base64'), | |
| 1026 | + }); | |
| 1027 | + } catch (error) { | |
| 1028 | + console.log(error); | |
| 1029 | + return response.sendStatus(500); | |
| 1030 | + } | |
| 1031 | +}); | |
| 1032 | + | |
| 1033 | + | |
| 994 | 1034 | router.use('/comfy', comfy); |
| 995 | 1035 | router.use('/together', together); |
| 996 | 1036 | router.use('/drawthings', drawthings); |
| 997 | 1037 | router.use('/pollinations', pollinations); |
| 998 | 1038 | router.use('/stability', stability); |
| 999 | 1039 | router.use('/blockentropy', blockentropy); |
| 1040 | +router.use('/huggingface', huggingface); | |
| 1000 | 1041 | |
| 1001 | 1042 | module.exports = { router }; |