NanoGPT: Add to image generation extension
| @@ -54,6 +54,7 @@ const sources = { | |||
| 54 | stability: 'stability', | 54 | stability: 'stability', |
| 55 | blockentropy: 'blockentropy', | 55 | blockentropy: 'blockentropy', |
| 56 | huggingface: 'huggingface', | 56 | huggingface: 'huggingface', |
| 57 | nanogpt: 'nanogpt', | ||
| 57 | }; | 58 | }; |
| 58 | 59 | ||
| 59 | const initiators = { | 60 | const initiators = { |
| @@ -1236,6 +1237,7 @@ async function onModelChange() { | |||
| 1236 | sources.stability, | 1237 | sources.stability, |
| 1237 | sources.blockentropy, | 1238 | sources.blockentropy, |
| 1238 | sources.huggingface, | 1239 | sources.huggingface, |
| 1240 | sources.nanogpt, | ||
| 1239 | ]; | 1241 | ]; |
| 1240 | 1242 | ||
| 1241 | if (cloudSources.includes(extension_settings.sd.source)) { | 1243 | if (cloudSources.includes(extension_settings.sd.source)) { |
| @@ -1454,6 +1456,9 @@ async function loadSamplers() { | |||
| 1454 | case sources.huggingface: | 1456 | case sources.huggingface: |
| 1455 | samplers = ['N/A']; | 1457 | samplers = ['N/A']; |
| 1456 | break; | 1458 | break; |
| 1459 | case sources.nanogpt: | ||
| 1460 | samplers = ['N/A']; | ||
| 1461 | break; | ||
| 1457 | } | 1462 | } |
| 1458 | 1463 | ||
| 1459 | for (const sampler of samplers) { | 1464 | for (const sampler of samplers) { |
| @@ -1646,6 +1651,9 @@ async function loadModels() { | |||
| 1646 | case sources.huggingface: | 1651 | case sources.huggingface: |
| 1647 | models = [{ value: '', text: '<Enter Model ID above>' }]; | 1652 | models = [{ value: '', text: '<Enter Model ID above>' }]; |
| 1648 | break; | 1653 | break; |
| 1654 | case sources.nanogpt: | ||
| 1655 | models = await loadNanoGPTModels(); | ||
| 1656 | break; | ||
| 1649 | } | 1657 | } |
| 1650 | 1658 | ||
| 1651 | for (const model of models) { | 1659 | for (const model of models) { |
| @@ -1725,6 +1733,25 @@ async function loadBlockEntropyModels() { | |||
| 1725 | return []; | 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 | async function loadHordeModels() { | 1755 | async function loadHordeModels() { |
| 1729 | const result = await fetch('/api/horde/sd-models', { | 1756 | const result = await fetch('/api/horde/sd-models', { |
| 1730 | method: 'POST', | 1757 | method: 'POST', |
| @@ -1997,6 +2024,9 @@ async function loadSchedulers() { | |||
| 1997 | case sources.huggingface: | 2024 | case sources.huggingface: |
| 1998 | schedulers = ['N/A']; | 2025 | schedulers = ['N/A']; |
| 1999 | break; | 2026 | break; |
| 2027 | case sources.nanogpt: | ||
| 2028 | schedulers = ['N/A']; | ||
| 2029 | break; | ||
| 2000 | } | 2030 | } |
| 2001 | 2031 | ||
| 2002 | for (const scheduler of schedulers) { | 2032 | for (const scheduler of schedulers) { |
| @@ -2079,6 +2109,9 @@ async function loadVaes() { | |||
| 2079 | case sources.huggingface: | 2109 | case sources.huggingface: |
| 2080 | vaes = ['N/A']; | 2110 | vaes = ['N/A']; |
| 2081 | break; | 2111 | break; |
| 2112 | case sources.nanogpt: | ||
| 2113 | vaes = ['N/A']; | ||
| 2114 | break; | ||
| 2082 | } | 2115 | } |
| 2083 | 2116 | ||
| 2084 | for (const vae of vaes) { | 2117 | for (const vae of vaes) { |
| @@ -2631,6 +2664,8 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP | |||
| 2631 | case sources.huggingface: | 2664 | case sources.huggingface: |
| 2632 | result = await generateHuggingFaceImage(prefixedPrompt, signal); | 2665 | result = await generateHuggingFaceImage(prefixedPrompt, signal); |
| 2633 | break; | 2666 | break; |
| 2667 | case sources.nanogpt: | ||
| 2668 | result = await generateNanoGPTImage(prefixedPrompt, negativePrompt, signal); | ||
| 2634 | } | 2669 | } |
| 2635 | 2670 | ||
| 2636 | if (!result.data) { | 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 | async function onComfyOpenWorkflowEditorClick() { | 3381 | async function onComfyOpenWorkflowEditorClick() { |
| 3313 | let workflow = await (await fetch('/api/sd/comfy/workflow', { | 3382 | let workflow = await (await fetch('/api/sd/comfy/workflow', { |
| @@ -3591,6 +3660,8 @@ function isValidState() { | |||
| 3591 | return secret_state[SECRET_KEYS.BLOCKENTROPY]; | 3660 | return secret_state[SECRET_KEYS.BLOCKENTROPY]; |
| 3592 | case sources.huggingface: | 3661 | case sources.huggingface: |
| 3593 | return secret_state[SECRET_KEYS.HUGGINGFACE]; | 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 | <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="huggingface">HuggingFace Inference API (serverless)</option> |
| 45 | <option value="nanogpt">NanoGPT</option> | ||
| 45 | <option value="novel">NovelAI Diffusion</option> | 46 | <option value="novel">NovelAI Diffusion</option> |
| 46 | <option value="openai">OpenAI (DALL-E)</option> | 47 | <option value="openai">OpenAI (DALL-E)</option> |
| 47 | <option value="pollinations">Pollinations</option> | 48 | <option value="pollinations">Pollinations</option> |
| @@ -88,6 +89,9 @@ | |||
| 88 | <label for="sd_huggingface_model_id" data-i18n="Model ID">Model ID</label> | 89 | <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 | <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> | 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 | <div data-sd-source="vlad"> | 95 | <div data-sd-source="vlad"> |
| 92 | <label for="sd_vlad_url">SD.Next API URL</label> | 96 | <label for="sd_vlad_url">SD.Next API URL</label> |
| 93 | <div class="flex-container flexnowrap"> | 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 | router.use('/comfy', comfy); | 1088 | router.use('/comfy', comfy); |
| 1006 | router.use('/together', together); | 1089 | router.use('/together', together); |
| @@ -1009,3 +1092,4 @@ router.use('/pollinations', pollinations); | |||
| 1009 | router.use('/stability', stability); | 1092 | router.use('/stability', stability); |
| 1010 | router.use('/blockentropy', blockentropy); | 1093 | router.use('/blockentropy', blockentropy); |
| 1011 | router.use('/huggingface', huggingface); | 1094 | router.use('/huggingface', huggingface); |
| 1095 | router.use('/nanogpt', nanogpt); | ||