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 = {
5252 pollinations: 'pollinations',
5353 stability: 'stability',
5454 blockentropy: 'blockentropy',
55+ huggingface: 'huggingface',
5556};
5657
5758const initiators = {
@@ -454,6 +455,7 @@ async function loadSettings() {
454455 $('#sd_command_visible').prop('checked', extension_settings.sd.command_visible);
455456 $('#sd_interactive_visible').prop('checked', extension_settings.sd.interactive_visible);
456457 $('#sd_stability_style_preset').val(extension_settings.sd.stability_style_preset);
458+ $('#sd_huggingface_model_id').val(extension_settings.sd.huggingface_model_id);
457459
458460 for (const style of extension_settings.sd.styles) {
459461 const option = document.createElement('option');
@@ -1091,6 +1093,11 @@ function onComfyUrlInput() {
10911093 saveSettingsDebounced();
10921094}
10931095
1096+function onHFModelInput() {
1097+ extension_settings.sd.huggingface_model_id = $('#sd_huggingface_model_id').val();
1098+ saveSettingsDebounced();
1099+}
1100+
10941101function onComfyWorkflowChange() {
10951102 extension_settings.sd.comfy_workflow = $('#sd_comfy_workflow').find(':selected').val();
10961103 saveSettingsDebounced();
@@ -2596,6 +2603,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
25962603 case sources.blockentropy:
25972604 result = await generateBlockEntropyImage(prefixedPrompt, negativePrompt, signal);
25982605 break;
2606+ case sources.huggingface:
2607+ result = await generateHuggingFaceImage(prefixedPrompt, signal);
2608+ break;
25992609 }
26002610
26012611 if (!result.data) {
@@ -3229,6 +3239,34 @@ async function generateComfyImage(prompt, negativePrompt, signal) {
32293239 return { format: 'png', data: await promptResult.text() };
32303240}
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+ */
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+
32323270async function onComfyOpenWorkflowEditorClick() {
32333271 let workflow = await (await fetch('/api/sd/comfy/workflow', {
32343272 method: 'POST',
@@ -3508,6 +3546,8 @@ function isValidState() {
35083546 return secret_state[SECRET_KEYS.STABILITY];
35093547 case sources.blockentropy:
35103548 return secret_state[SECRET_KEYS.BLOCKENTROPY];
3549+ case sources.huggingface:
3550+ return true;
35113551 }
35123552}
35133553
@@ -3848,6 +3888,7 @@ jQuery(async () => {
38483888 $('#sd_swap_dimensions').on('click', onSwapDimensionsClick);
38493889 $('#sd_stability_key').on('click', onStabilityKeyClick);
38503890 $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange);
3891+ $('#sd_huggingface_model_id').on('input', onHFModelInput);
38513892
38523893 $('.sd_settings .inline-drawer-toggle').on('click', function () {
38533894 initScrollHeight($('#sd_prompt_prefix'));
public/scripts/extensions/stable-diffusion/settings.html+6 -0
@@ -49,6 +49,7 @@
4949 <option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
5050 <option value="horde">Stable Horde</option>
5151 <option value="togetherai">TogetherAI</option>
52+ <option value="huggingface">HuggingFace (Image Inference Endpoint)</option>
5253 </select>
5354 <div data-sd-source="auto">
5455 <label for="sd_auto_url">SD Web UI URL</label>
@@ -82,6 +83,11 @@
8283 <!-- (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. -->
8384 <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>
8485 </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>
8591 <div data-sd-source="vlad">
8692 <label for="sd_vlad_url">SD.Next API URL</label>
8793 <div class="flex-container flexnowrap">
src/endpoints/stable-diffusion.js+41 -0
@@ -991,11 +991,52 @@ blockentropy.post('/generate', jsonParser, async (request, response) => {
991991});
992992
993993
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+
9941034router.use('/comfy', comfy);
9951035router.use('/together', together);
9961036router.use('/drawthings', drawthings);
9971037router.use('/pollinations', pollinations);
9981038router.use('/stability', stability);
9991039router.use('/blockentropy', blockentropy);
1040+router.use('/huggingface', huggingface);
10001041
10011042module.exports = { router };