Hugging Face inference API for image generation

135ba2336e78ad3464decdbbbc87b22aad51ac5e

Alex Yancey <me@alexyancey.com>

3 files changed, +88 -0Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+41 -0
@@ -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};
5657
57const initiators = {58const 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);
457459
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}
10931095
1096function onHFModelInput() {
1097 extension_settings.sd.huggingface_model_id = $('#sd_huggingface_model_id').val();
1098 saveSettingsDebounced();
1099}
1100
1094function onComfyWorkflowChange() {1101function 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();
@@ -2596,6 +2603,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
2596 case sources.blockentropy:2603 case sources.blockentropy:
2597 result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);2604 result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);
2598 break;2605 break;
2606 case sources.huggingface:
2607 result = await generateHuggingFaceImage(prefixedPrompt, signal);
2608 break;
2599 }2609 }
26002610
2601 if (!result.data) {2611 if (!result.data) {
@@ -3229,6 +3239,34 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
3229 return { format: 'png', data: await promptResult.text() };3239 return { format: 'png', data: await promptResult.text() };
3230}3240}
32313241
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 */
3249async 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
3232async function onComfyOpenWorkflowEditorClick() {3270async function onComfyOpenWorkflowEditorClick() {
3233 let workflow = await (await fetch('/api/sd/comfy/workflow', {3271 let workflow = await (await fetch('/api/sd/comfy/workflow', {
3234 method: 'POST',3272 method: 'POST',
@@ -3508,6 +3546,8 @@ function isValidState() {
3508 return secret_state[SECRET_KEYS.STABILITY];3546 return secret_state[SECRET_KEYS.STABILITY];
3509 case sources.blockentropy:3547 case sources.blockentropy:
3510 return secret_state[SECRET_KEYS.BLOCKENTROPY];3548 return secret_state[SECRET_KEYS.BLOCKENTROPY];
3549 case sources.huggingface:
3550 return true;
3511 }3551 }
3512}3552}
35133553
@@ -3848,6 +3888,7 @@ jQuery(async () => {
3848 $('#sd_swap_dimensions').on('click', onSwapDimensionsClick);3888 $('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
3849 $('#sd_stability_key').on('click', onStabilityKeyClick);3889 $('#sd_stability_key').on('click', onStabilityKeyClick);
3850 $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);3890 $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
3891 $('#sd_huggingface_model_id').on('input', onHFModelInput);
38513892
3852 $('.sd_settings .inline-drawer-toggle').on('click', function () {3893 $('.sd_settings .inline-drawer-toggle').on('click', function () {
3853 initScrollHeight($('#sd_prompt_prefix'));3894 initScrollHeight($('#sd_prompt_prefix'));
public/scripts/extensions/stable-diffusion/settings.html+6 -0
@@ -49,6 +49,7 @@
49 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>49 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
50 <option value="horde">Stable Horde</option>50 <option value="horde">Stable Horde</option>
51 <option value="togetherai">TogetherAI</option>51 <option value="togetherai">TogetherAI</option>
52 <option value="huggingface">HuggingFace (Image Inference Endpoint)</option>
52 </select>53 </select>
53 <div data-sd-source="auto">54 <div data-sd-source="auto">
54 <label for="sd_auto_url">SD Web UI URL</label>55 <label for="sd_auto_url">SD Web UI URL</label>
@@ -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 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 <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">
src/endpoints/stable-diffusion.js+41 -0
@@ -991,11 +991,52 @@ blockentropy.post('/generate', jsonParser, async (request, response) => {
991});991});
992992
993993
994const huggingface = express.Router();
995
996huggingface.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
994router.use('/comfy', comfy);1034router.use('/comfy', comfy);
995router.use('/together', together);1035router.use('/together', together);
996router.use('/drawthings', drawthings);1036router.use('/drawthings', drawthings);
997router.use('/pollinations', pollinations);1037router.use('/pollinations', pollinations);
998router.use('/stability', stability);1038router.use('/stability', stability);
999router.use('/blockentropy', blockentropy);1039router.use('/blockentropy', blockentropy);
1040router.use('/huggingface', huggingface);
10001041
1001module.exports = { router };1042module.exports = { router };