Merge pull request #2688 from ayancey/hugging-face-imagegen Hugging Face Inference API Image Generation
Signed| @@ -52,6 +52,7 @@ const sources = { | |||
| 52 | pollinations: 'pollinations', | 52 | pollinations: 'pollinations', |
| 53 | stability: 'stability', | 53 | stability: 'stability', |
| 54 | blockentropy: 'blockentropy', | 54 | blockentropy: 'blockentropy', |
| 55 | huggingface: 'huggingface', | ||
| 55 | }; | 56 | }; |
| 56 | 57 | ||
| 57 | const initiators = { | 58 | const initiators = { |
| @@ -454,6 +455,7 @@ async function loadSettings() { | |||
| 454 | $('#sd_command_visible').prop('checked', extension_settings.sd.command_visible); | 455 | $('#sd_command_visible').prop('checked', extension_settings.sd.command_visible); |
| 455 | $('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible); | 456 | $('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible); |
| 456 | $('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset); | 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 | for (const style of extension_settings.sd.styles) { | 460 | for (const style of extension_settings.sd.styles) { |
| 459 | const option = document.createElement('option'); | 461 | const option = document.createElement('option'); |
| @@ -1091,6 +1093,11 @@ function onComfyUrlInput() { | |||
| 1091 | saveSettingsDebounced(); | 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 | function onComfyWorkflowChange() { | 1101 | function onComfyWorkflowChange() { |
| 1095 | extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val(); | 1102 | extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val(); |
| 1096 | saveSettingsDebounced(); | 1103 | saveSettingsDebounced(); |
| @@ -1235,7 +1242,16 @@ async function onModelChange() { | |||
| 1235 | extension_settings.sd.model = $('#sd_model').find(':selected').val(); | 1242 | extension_settings.sd.model = $('#sd_model').find(':selected').val(); |
| 1236 | saveSettingsDebounced(); | 1243 | saveSettingsDebounced(); |
| 1237 | 1244 | ||
| 1238 | const cloudSources = [sources.horde, sources.novel, sources.openai, sources.togetherai, sources.pollinations, sources.stability, sources.blockentropy]; | 1245 | const cloudSources = [ |
| 1246 | sources.horde, | ||
| 1247 | sources.novel, | ||
| 1248 | sources.openai, | ||
| 1249 | sources.togetherai, | ||
| 1250 | sources.pollinations, | ||
| 1251 | sources.stability, | ||
| 1252 | sources.blockentropy, | ||
| 1253 | sources.huggingface, | ||
| 1254 | ]; | ||
| 1239 | 1255 | ||
| 1240 | if (cloudSources.includes(extension_settings.sd.source)) { | 1256 | if (cloudSources.includes(extension_settings.sd.source)) { |
| 1241 | return; | 1257 | return; |
| @@ -1450,6 +1466,9 @@ async function loadSamplers() { | |||
| 1450 | case sources.blockentropy: | 1466 | case sources.blockentropy: |
| 1451 | samplers = ['N/A']; | 1467 | samplers = ['N/A']; |
| 1452 | break; | 1468 | break; |
| 1469 | case sources.huggingface: | ||
| 1470 | samplers = ['N/A']; | ||
| 1471 | break; | ||
| 1453 | } | 1472 | } |
| 1454 | 1473 | ||
| 1455 | for (const sampler of samplers) { | 1474 | for (const sampler of samplers) { |
| @@ -1639,6 +1658,9 @@ async function loadModels() { | |||
| 1639 | case sources.blockentropy: | 1658 | case sources.blockentropy: |
| 1640 | models = await loadBlockEntropyModels(); | 1659 | models = await loadBlockEntropyModels(); |
| 1641 | break; | 1660 | break; |
| 1661 | case sources.huggingface: | ||
| 1662 | models = [{ value: '', text: '<Enter Model ID above>' }]; | ||
| 1663 | break; | ||
| 1642 | } | 1664 | } |
| 1643 | 1665 | ||
| 1644 | for (const model of models) { | 1666 | for (const model of models) { |
| @@ -1986,6 +2008,9 @@ async function loadSchedulers() { | |||
| 1986 | case sources.blockentropy: | 2008 | case sources.blockentropy: |
| 1987 | schedulers = ['N/A']; | 2009 | schedulers = ['N/A']; |
| 1988 | break; | 2010 | break; |
| 2011 | case sources.huggingface: | ||
| 2012 | schedulers = ['N/A']; | ||
| 2013 | break; | ||
| 1989 | } | 2014 | } |
| 1990 | 2015 | ||
| 1991 | for (const scheduler of schedulers) { | 2016 | for (const scheduler of schedulers) { |
| @@ -2065,6 +2090,9 @@ async function loadVaes() { | |||
| 2065 | case sources.blockentropy: | 2090 | case sources.blockentropy: |
| 2066 | vaes = ['N/A']; | 2091 | vaes = ['N/A']; |
| 2067 | break; | 2092 | break; |
| 2093 | case sources.huggingface: | ||
| 2094 | vaes = ['N/A']; | ||
| 2095 | break; | ||
| 2068 | } | 2096 | } |
| 2069 | 2097 | ||
| 2070 | for (const vae of vaes) { | 2098 | for (const vae of vaes) { |
| @@ -2596,6 +2624,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2596 | case sources.blockentropy: | 2624 | case sources.blockentropy: |
| 2597 | result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal); | 2625 | result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal); |
| 2598 | break; | 2626 | break; |
| 2627 | case sources.huggingface: | ||
| 2628 | result = await generateHuggingFaceImage(prefixedPrompt, signal); | ||
| 2629 | break; | ||
| 2599 | } | 2630 | } |
| 2600 | 2631 | ||
| 2601 | if (!result.data) { | 2632 | if (!result.data) { |
| @@ -3229,6 +3260,34 @@ async function generateComfyImage(prompt, negativePrompt, signal) { | |||
| 3229 | return { format: 'png', data: await promptResult.text() }; | 3260 | return { format: 'png', data: await promptResult.text() }; |
| 3230 | } | 3261 | } |
| 3231 | 3262 | ||
| 3263 | |||
| 3264 | /** | ||
| 3265 | * Generates an image in Hugging Face Inference API using the provided prompt and configuration settings (model selected). | ||
| 3266 | * @param {string} prompt - The main instruction used to guide the image generation. | ||
| 3267 | * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request. | ||
| 3268 | * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete. | ||
| 3269 | */ | ||
| 3270 | async function generateHuggingFaceImage(prompt, signal) { | ||
| 3271 | const result = await fetch('/api/sd/huggingface/generate', { | ||
| 3272 | method: 'POST', | ||
| 3273 | headers: getRequestHeaders(), | ||
| 3274 | signal: signal, | ||
| 3275 | body: JSON.stringify({ | ||
| 3276 | model: extension_settings.sd.huggingface_model_id, | ||
| 3277 | prompt: prompt, | ||
| 3278 | }), | ||
| 3279 | }); | ||
| 3280 | |||
| 3281 | if (result.ok) { | ||
| 3282 | const data = await result.json(); | ||
| 3283 | return { format: 'jpg', data: data.image }; | ||
| 3284 | } else { | ||
| 3285 | const text = await result.text(); | ||
| 3286 | throw new Error(text); | ||
| 3287 | } | ||
| 3288 | } | ||
| 3289 | |||
| 3290 | |||
| 3232 | async function onComfyOpenWorkflowEditorClick() { | 3291 | async function onComfyOpenWorkflowEditorClick() { |
| 3233 | let workflow = await (await fetch('/api/sd/comfy/workflow', { | 3292 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| 3234 | method: 'POST', | 3293 | method: 'POST', |
| @@ -3508,6 +3567,8 @@ function isValidState() { | |||
| 3508 | return secret_state[SECRET_KEYS.STABILITY]; | 3567 | return secret_state[SECRET_KEYS.STABILITY]; |
| 3509 | case sources.blockentropy: | 3568 | case sources.blockentropy: |
| 3510 | return secret_state[SECRET_KEYS.BLOCKENTROPY]; | 3569 | return secret_state[SECRET_KEYS.BLOCKENTROPY]; |
| 3570 | case sources.huggingface: | ||
| 3571 | return secret_state[SECRET_KEYS.HUGGINGFACE]; | ||
| 3511 | } | 3572 | } |
| 3512 | } | 3573 | } |
| 3513 | 3574 | ||
| @@ -3848,6 +3909,7 @@ jQuery(async () => { | |||
| 3848 | $('#sd_swap_dimensions').on('click', onSwapDimensionsClick); | 3909 | $('#sd_swap_dimensions').on('click', onSwapDimensionsClick); |
| 3849 | $('#sd_stability_key').on('click', onStabilityKeyClick); | 3910 | $('#sd_stability_key').on('click', onStabilityKeyClick); |
| 3850 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); | 3911 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); |
| 3912 | $('#sd_huggingface_model_id').on('input', onHFModelInput); | ||
| 3851 | 3913 | ||
| 3852 | $('.sd_settings .inline-drawer-toggle').on('click', function () { | 3914 | $('.sd_settings .inline-drawer-toggle').on('click', function () { |
| 3853 | initScrollHeight($('#sd_prompt_prefix')); | 3915 | initScrollHeight($('#sd_prompt_prefix')); |
| @@ -41,6 +41,7 @@ | |||
| 41 | <option value="comfy">ComfyUI</option> | 41 | <option value="comfy">ComfyUI</option> |
| 42 | <option value="drawthings">DrawThings HTTP API</option> | 42 | <option value="drawthings">DrawThings HTTP API</option> |
| 43 | <option value="extras">Extras API (local / remote)</option> | 43 | <option value="extras">Extras API (local / remote)</option> |
| 44 | <option value="huggingface">HuggingFace Inference API (serverless)</option> | ||
| 44 | <option value="novel">NovelAI Diffusion</option> | 45 | <option value="novel">NovelAI Diffusion</option> |
| 45 | <option value="openai">OpenAI (DALL-E)</option> | 46 | <option value="openai">OpenAI (DALL-E)</option> |
| 46 | <option value="pollinations">Pollinations</option> | 47 | <option value="pollinations">Pollinations</option> |
| @@ -82,6 +83,11 @@ | |||
| 82 | <!-- (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 | <!-- (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 | <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 | <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 | </div> | 85 | </div> |
| 86 | <div data-sd-source="huggingface"> | ||
| 87 | <i>Hint: Save an API key in the Hugging Face (Text Completion) 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]e.g. black-forest-labs/FLUX.1-dev" placeholder="e.g. black-forest-labs/FLUX.1-dev" value="" /> | ||
| 90 | </div> | ||
| 85 | <div data-sd-source="vlad"> | 91 | <div data-sd-source="vlad"> |
| 86 | <label for="sd_vlad_url">SD.Next API URL</label> | 92 | <label for="sd_vlad_url">SD.Next API URL</label> |
| 87 | <div class="flex-container flexnowrap"> | 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 | router.use('/comfy', comfy); | 1034 | router.use('/comfy', comfy); |
| 995 | router.use('/together', together); | 1035 | router.use('/together', together); |
| 996 | router.use('/drawthings', drawthings); | 1036 | router.use('/drawthings', drawthings); |
| 997 | router.use('/pollinations', pollinations); | 1037 | router.use('/pollinations', pollinations); |
| 998 | router.use('/stability', stability); | 1038 | router.use('/stability', stability); |
| 999 | router.use('/blockentropy', blockentropy); | 1039 | router.use('/blockentropy', blockentropy); |
| 1040 | router.use('/huggingface', huggingface); | ||
| 1000 | 1041 | ||
| 1001 | module.exports = { router }; | 1042 | module.exports = { router }; |