NanoGPT: Add to image generation extension

77be125a9949dcd98d1d78ba1f79972c295f45d4

Cohee <18619528+Cohee1207@users.noreply.github.com>

3 files changed, +159 -0Ignore whitespace
public/scripts/extensions/stable-diffusion/index.js+71 -0
@@ -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};
5859
59const initiators = {60const 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 ];
12401242
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 }
14581463
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 }
16501658
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}
17271735
1736async 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
1728async function loadHordeModels() {1755async 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 }
20012031
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 }
20832116
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 }
26352670
2636 if (!result.data) {2671 if (!result.data) {
@@ -3308,6 +3343,40 @@ async function generateHuggingFaceImage(prompt, signal) {
3308 }3343 }
3309}3344}
33103345
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 */
3353async 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}
33113380
3312async function onComfyOpenWorkflowEditorClick() {3381async 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}
35963667
public/scripts/extensions/stable-diffusion/settings.html+4 -0
@@ -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">
src/endpoints/stable-diffusion.js+84 -0
@@ -1001,6 +1001,89 @@ huggingface.post('/generate', jsonParser, async (request, response) => {
1001 }1001 }
1002});1002});
10031003
1004const nanogpt = express.Router();
1005
1006nanogpt.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
1046nanogpt.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});
10041087
1005router.use('/comfy', comfy);1088router.use('/comfy', comfy);
1006router.use('/together', together);1089router.use('/together', together);
@@ -1009,3 +1092,4 @@ router.use('/pollinations', pollinations);
1009router.use('/stability', stability);1092router.use('/stability', stability);
1010router.use('/blockentropy', blockentropy);1093router.use('/blockentropy', blockentropy);
1011router.use('/huggingface', huggingface);1094router.use('/huggingface', huggingface);
1095router.use('/nanogpt', nanogpt);