NanoGPT: Add to image generation extension
| @@ -54,6 +54,7 @@ const sources = { | ||
| 54 | 54 | stability: 'stability', |
| 55 | 55 | blockentropy: 'blockentropy', |
| 56 | 56 | huggingface: 'huggingface', |
| 57 | + nanogpt: 'nanogpt', | |
| 57 | 58 | }; |
| 58 | 59 | |
| 59 | 60 | const initiators = { |
| @@ -1236,6 +1237,7 @@ async function onModelChange() { | ||
| 1236 | 1237 | sources.stability, |
| 1237 | 1238 | sources.blockentropy, |
| 1238 | 1239 | sources.huggingface, |
| 1240 | + sources.nanogpt, | |
| 1239 | 1241 | ]; |
| 1240 | 1242 | |
| 1241 | 1243 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1454,6 +1456,9 @@ async function loadSamplers() { | ||
| 1454 | 1456 | case sources.huggingface: |
| 1455 | 1457 | samplers = ['N/A']; |
| 1456 | 1458 | break; |
| 1459 | + case sources.nanogpt: | |
| 1460 | + samplers = ['N/A']; | |
| 1461 | + break; | |
| 1457 | 1462 | } |
| 1458 | 1463 | |
| 1459 | 1464 | for (const sampler of samplers) { |
| @@ -1646,6 +1651,9 @@ async function loadModels() { | ||
| 1646 | 1651 | case sources.huggingface: |
| 1647 | 1652 | models = [{ value: '', text: '<Enter Model ID above>' }]; |
| 1648 | 1653 | break; |
| 1654 | + case sources.nanogpt: | |
| 1655 | + models = await loadNanoGPTModels(); | |
| 1656 | + break; | |
| 1649 | 1657 | } |
| 1650 | 1658 | |
| 1651 | 1659 | for (const model of models) { |
| @@ -1725,6 +1733,25 @@ async function loadBlockEntropyModels() { | ||
| 1725 | 1733 | return []; |
| 1726 | 1734 | } |
| 1727 | 1735 | |
| 1736 | +async function loadNanoGPTModels() { | |
| 1737 | + if (!secret_state[SECRET_KEYS.NANOGPT]) { | |
| 1738 | + console.debug('NanoGPT API key is not set.'); | |
| 1739 | + return []; | |
| 1740 | + } | |
| 1741 | + | |
| 1742 | + const result = await fetch('/api/sd/nanogpt/models', { | |
| 1743 | + method: 'POST', | |
| 1744 | + headers: getRequestHeaders(), | |
| 1745 | + }); | |
| 1746 | + | |
| 1747 | + if (result.ok) { | |
| 1748 | + const data = await result.json(); | |
| 1749 | + return data; | |
| 1750 | + } | |
| 1751 | + | |
| 1752 | + return []; | |
| 1753 | +} | |
| 1754 | + | |
| 1728 | 1755 | async function loadHordeModels() { |
| 1729 | 1756 | const result = await fetch('/api/horde/sd-models', { |
| 1730 | 1757 | method: 'POST', |
| @@ -1997,6 +2024,9 @@ async function loadSchedulers() { | ||
| 1997 | 2024 | case sources.huggingface: |
| 1998 | 2025 | schedulers = ['N/A']; |
| 1999 | 2026 | break; |
| 2027 | + case sources.nanogpt: | |
| 2028 | + schedulers = ['N/A']; | |
| 2029 | + break; | |
| 2000 | 2030 | } |
| 2001 | 2031 | |
| 2002 | 2032 | for (const scheduler of schedulers) { |
| @@ -2079,6 +2109,9 @@ async function loadVaes() { | ||
| 2079 | 2109 | case sources.huggingface: |
| 2080 | 2110 | vaes = ['N/A']; |
| 2081 | 2111 | break; |
| 2112 | + case sources.nanogpt: | |
| 2113 | + vaes = ['N/A']; | |
| 2114 | + break; | |
| 2082 | 2115 | } |
| 2083 | 2116 | |
| 2084 | 2117 | for (const vae of vaes) { |
| @@ -2631,6 +2664,8 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | ||
| 2631 | 2664 | case sources.huggingface: |
| 2632 | 2665 | result = await generateHuggingFaceImage(prefixedPrompt, signal); |
| 2633 | 2666 | break; |
| 2667 | + case sources.nanogpt: | |
| 2668 | + result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal); | |
| 2634 | 2669 | } |
| 2635 | 2670 | |
| 2636 | 2671 | if (!result.data) { |
| @@ -3308,6 +3343,40 @@ async function generateHuggingFaceImage(prompt, signal) { | ||
| 3308 | 3343 | } |
| 3309 | 3344 | } |
| 3310 | 3345 | |
| 3346 | +/** | |
| 3347 | + * Generates an image using the NanoGPT API. | |
| 3348 | + * @param {string} prompt - The main instruction used to guide the image generation. | |
| 3349 | + * @param {string} negativePrompt - The instruction used to restrict the image generation. | |
| 3350 | + * @param {AbortSignal} signal - An AbortSignal object that can be used to cancel the request. | |
| 3351 | + * @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete. | |
| 3352 | + */ | |
| 3353 | +async function generateNanoGPTImage(prompt, negativePrompt, signal) { | |
| 3354 | + const result = await fetch('/api/sd/nanogpt/generate', { | |
| 3355 | + method: 'POST', | |
| 3356 | + headers: getRequestHeaders(), | |
| 3357 | + signal: signal, | |
| 3358 | + body: JSON.stringify({ | |
| 3359 | + model: extension_settings.sd.model, | |
| 3360 | + prompt: prompt, | |
| 3361 | + negative_prompt: negativePrompt, | |
| 3362 | + num_steps: parseInt(extension_settings.sd.steps), | |
| 3363 | + scale: parseFloat(extension_settings.sd.scale), | |
| 3364 | + width: parseInt(extension_settings.sd.width), | |
| 3365 | + height: parseInt(extension_settings.sd.height), | |
| 3366 | + resolution: `${extension_settings.sd.width}x${extension_settings.sd.height}`, | |
| 3367 | + showExplicitContent: true, | |
| 3368 | + nImages: 1, | |
| 3369 | + }), | |
| 3370 | + }); | |
| 3371 | + | |
| 3372 | + if (result.ok) { | |
| 3373 | + const data = await result.json(); | |
| 3374 | + return { format: 'jpg', data: data.image }; | |
| 3375 | + } else { | |
| 3376 | + const text = await result.text(); | |
| 3377 | + throw new Error(text); | |
| 3378 | + } | |
| 3379 | +} | |
| 3311 | 3380 | |
| 3312 | 3381 | async function onComfyOpenWorkflowEditorClick() { |
| 3313 | 3382 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| @@ -3591,6 +3660,8 @@ function isValidState() { | ||
| 3591 | 3660 | return secret_state[SECRET_KEYS.BLOCKENTROPY]; |
| 3592 | 3661 | case sources.huggingface: |
| 3593 | 3662 | return secret_state[SECRET_KEYS.HUGGINGFACE]; |
| 3663 | + case sources.nanogpt: | |
| 3664 | + return secret_state[SECRET_KEYS.NANOGPT]; | |
| 3594 | 3665 | } |
| 3595 | 3666 | } |
| 3596 | 3667 | |
| @@ -42,6 +42,7 @@ | ||
| 42 | 42 | <option value="drawthings">DrawThings HTTP API</option> |
| 43 | 43 | <option value="extras">Extras API (local / remote)</option> |
| 44 | 44 | <option value="huggingface">HuggingFace Inference API (serverless)</option> |
| 45 | + <option value="nanogpt">NanoGPT</option> | |
| 45 | 46 | <option value="novel">NovelAI Diffusion</option> |
| 46 | 47 | <option value="openai">OpenAI (DALL-E)</option> |
| 47 | 48 | <option value="pollinations">Pollinations</option> |
| @@ -88,6 +89,9 @@ | ||
| 88 | 89 | <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label> |
| 89 | 90 | <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 | 91 | </div> |
| 92 | + <div data-sd-source="nanogpt"> | |
| 93 | + <i>Hint: Save an API key in the NanoGPT (Chat Completion) API settings to use it here.</i> | |
| 94 | + </div> | |
| 91 | 95 | <div data-sd-source="vlad"> |
| 92 | 96 | <label for="sd_vlad_url">SD.Next API URL</label> |
| 93 | 97 | <div class="flex-container flexnowrap"> |
| @@ -1001,6 +1001,89 @@ huggingface.post('/generate', jsonParser, async (request, response) => { | ||
| 1001 | 1001 | } |
| 1002 | 1002 | }); |
| 1003 | 1003 | |
| 1004 | +const nanogpt = express.Router(); | |
| 1005 | + | |
| 1006 | +nanogpt.post('/models', jsonParser, async (request, response) => { | |
| 1007 | + try { | |
| 1008 | + const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); | |
| 1009 | + | |
| 1010 | + if (!key) { | |
| 1011 | + console.log('NanoGPT key not found.'); | |
| 1012 | + return response.sendStatus(400); | |
| 1013 | + } | |
| 1014 | + | |
| 1015 | + const modelsResponse = await fetch('https://nano-gpt.com/api/models', { | |
| 1016 | + method: 'GET', | |
| 1017 | + headers: { | |
| 1018 | + 'x-api-key': key, | |
| 1019 | + 'Content-Type': 'application/json', | |
| 1020 | + }, | |
| 1021 | + }); | |
| 1022 | + | |
| 1023 | + if (!modelsResponse.ok) { | |
| 1024 | + console.log('NanoGPT returned an error.'); | |
| 1025 | + return response.sendStatus(500); | |
| 1026 | + } | |
| 1027 | + | |
| 1028 | + /** @type {any} */ | |
| 1029 | + const data = await modelsResponse.json(); | |
| 1030 | + const imageModels = data?.models?.image; | |
| 1031 | + | |
| 1032 | + if (!imageModels || typeof imageModels !== 'object') { | |
| 1033 | + console.log('NanoGPT returned invalid data.'); | |
| 1034 | + return response.sendStatus(500); | |
| 1035 | + } | |
| 1036 | + | |
| 1037 | + const models = Object.values(imageModels).map(x => ({ value: x.model, text: x.name })); | |
| 1038 | + return response.send(models); | |
| 1039 | + } | |
| 1040 | + catch (error) { | |
| 1041 | + console.log(error); | |
| 1042 | + return response.sendStatus(500); | |
| 1043 | + } | |
| 1044 | +}); | |
| 1045 | + | |
| 1046 | +nanogpt.post('/generate', jsonParser, async (request, response) => { | |
| 1047 | + try { | |
| 1048 | + const key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT); | |
| 1049 | + | |
| 1050 | + if (!key) { | |
| 1051 | + console.log('NanoGPT key not found.'); | |
| 1052 | + return response.sendStatus(400); | |
| 1053 | + } | |
| 1054 | + | |
| 1055 | + console.log('NanoGPT request:', request.body); | |
| 1056 | + | |
| 1057 | + const result = await fetch('https://nano-gpt.com/api/generate-image', { | |
| 1058 | + method: 'POST', | |
| 1059 | + body: JSON.stringify(request.body), | |
| 1060 | + headers: { | |
| 1061 | + 'x-api-key': key, | |
| 1062 | + 'Content-Type': 'application/json', | |
| 1063 | + }, | |
| 1064 | + }); | |
| 1065 | + | |
| 1066 | + if (!result.ok) { | |
| 1067 | + console.log('NanoGPT returned an error.'); | |
| 1068 | + return response.sendStatus(500); | |
| 1069 | + } | |
| 1070 | + | |
| 1071 | + /** @type {any} */ | |
| 1072 | + const data = await result.json(); | |
| 1073 | + | |
| 1074 | + const image = data?.data?.[0]?.b64_json; | |
| 1075 | + if (!image) { | |
| 1076 | + console.log('NanoGPT returned invalid data.'); | |
| 1077 | + return response.sendStatus(500); | |
| 1078 | + } | |
| 1079 | + | |
| 1080 | + return response.send({ image }); | |
| 1081 | + } | |
| 1082 | + catch (error) { | |
| 1083 | + console.log(error); | |
| 1084 | + return response.sendStatus(500); | |
| 1085 | + } | |
| 1086 | +}); | |
| 1004 | 1087 | |
| 1005 | 1088 | router.use('/comfy', comfy); |
| 1006 | 1089 | router.use('/together', together); |
| @@ -1009,3 +1092,4 @@ router.use('/pollinations', pollinations); | ||
| 1009 | 1092 | router.use('/stability', stability); |
| 1010 | 1093 | router.use('/blockentropy', blockentropy); |
| 1011 | 1094 | router.use('/huggingface', huggingface); |
| 1095 | +router.use('/nanogpt', nanogpt); | |