NanoGPT: Add as captioning source

285b09ceb75620a24500949e294f1641add2c77e

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

5 files changed, +45 -11Ignore whitespace
public/scripts/extensions/caption/index.js+4 -2
@@ -439,6 +439,7 @@ jQuery(async function () {
439439 'cohere': SECRET_KEYS.COHERE,
440440 'aimlapi': SECRET_KEYS.AIMLAPI,
441441 'moonshot': SECRET_KEYS.MOONSHOT,
442+ 'nanogpt': SECRET_KEYS.NANOGPT,
442443 };
443444
444445 if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) {
@@ -543,8 +544,9 @@ jQuery(async function () {
543544 }
544545
545546 await processEndpoint('openrouter', '/api/openrouter/models/multimodal');
546547 await processEndpoint('aimlapi', '/api/backends/chat-completions/aimlapi/multimodal-models/multimodalaimlapi');
547548 await processEndpoint('pollinations', '/api/backends/chat-completions/pollinations/multimodal-models/multimodalpollinations');
549+ await processEndpoint('nanogpt', '/api/backends/chat-completions/multimodal-models/nanogpt');
548550 }
549551
550552 await addSettings();
public/scripts/extensions/caption/settings.html+2 -1
@@ -28,6 +28,7 @@
2828 <option value="llamacpp">llama.cpp</option>
2929 <option value="mistral">MistralAI</option>
3030 <option value="moonshot">Moonshot AI</option>
31+ <option value="nanogpt">NanoGPT</option>
3132 <option value="ollama">Ollama</option>
3233 <option value="openai">OpenAI</option>
3334 <option value="openrouter">OpenRouter</option>
@@ -40,7 +41,7 @@
4041 <div class="flex1 flex-container flexFlowColumn flexNoGap">
4142 <label for="caption_multimodal_model" data-i18n="Model">Model</label>
4243 <select id="caption_multimodal_model" class="flex1 text_pole">
4344 <!-- AI/ML API, OpenRouter, Pollinations, NanoGPT are added externally by JavaScript -->
4445 <option data-type="cohere" value="c4ai-aya-vision-8b">c4ai-aya-vision-8b</option>
4546 <option data-type="cohere" value="c4ai-aya-vision-32b">c4ai-aya-vision-32b</option>
4647 <option data-type="cohere" value="command-a-vision-07-2025">command-a-vision-07-2025</option>
public/scripts/extensions/shared.js+4 -0
@@ -247,6 +247,10 @@ function throwIfInvalidModel(useReverseProxy) {
247247 if (multimodalApi === 'moonshot' && !secret_state[SECRET_KEYS.MOONSHOT]) {
248248 throw new Error('Moonshot AI API key is not set.');
249249 }
250+
251+ if (multimodalApi === 'nanogpt' && !secret_state[SECRET_KEYS.NANOGPT]) {
252+ throw new Error('NanoGPT API key is not set.');
253+ }
250254}
251255
252256/**
src/endpoints/backends/chat-completions.js+27 -8
@@ -1865,9 +1865,9 @@ router.post('/generate', function (request, response) {
18651865 }
18661866});
18671867
18681868const pollinationsmultimodalModels = express.Router();
18691869
18701870pollinationsmultimodalModels.post('/models/multimodalpollinations', async (_req, res) => {
18711871 try {
18721872 const response = await fetch('https://text.pollinations.ai/models');
18731873
@@ -1890,11 +1890,7 @@ pollinations.post('/models/multimodal', async (_req, res) => {
18901890 }
18911891});
18921892
1893-router.use('/pollinations', pollinations);
1893+multimodalModels.post('/aimlapi', async (_req, res) => {
1894-
1895-const aimlapi = express.Router();
1896-
1897-aimlapi.post('/models/multimodal', async (_req, res) => {
18981894 try {
18991895 const response = await fetch('https://api.aimlapi.com/v1/models');
19001896
@@ -1917,4 +1913,27 @@ aimlapi.post('/models/multimodal', async (_req, res) => {
19171913 }
19181914});
19191915
1920-router.use('/aimlapi', aimlapi);
1916+multimodalModels.post('/nanogpt', async (_req, res) => {
1917+ try {
1918+ const response = await fetch('https://nano-gpt.com/api/v1/models?detailed=true');
1919+
1920+ if (!response.ok) {
1921+ return res.json([]);
1922+ }
1923+
1924+ /** @type {any} */
1925+ const data = await response.json();
1926+
1927+ if (!Array.isArray(data?.data)) {
1928+ return res.json([]);
1929+ }
1930+
1931+ const multimodalModels = data.data.filter(m => m?.capabilities?.vision).map(m => m.id);
1932+ return res.json(multimodalModels);
1933+ } catch (error) {
1934+ console.error(error);
1935+ return res.sendStatus(500);
1936+ }
1937+});
1938+
1939+router.use('/multimodal-models', multimodalModels);
src/endpoints/openai.js+8 -0
@@ -77,6 +77,10 @@ router.post('/caption-image', async (request, response) => {
7777 key = readSecret(request.user.directories, SECRET_KEYS.MOONSHOT);
7878 }
7979
80+ if (request.body.api === 'nanogpt') {
81+ key = readSecret(request.user.directories, SECRET_KEYS.NANOGPT);
82+ }
83+
8084 const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];
8185 if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {
8286 console.warn('No key found for API', request.body.api);
@@ -161,6 +165,10 @@ router.post('/caption-image', async (request, response) => {
161165 apiUrl = 'https://api.moonshot.ai/v1/chat/completions';
162166 }
163167
168+ if (request.body.api === 'nanogpt') {
169+ apiUrl = 'https://nano-gpt.com/api/v1/chat/completions';
170+ }
171+
164172 if (['koboldcpp', 'vllm', 'llamacpp', 'ooba'].includes(request.body.api)) {
165173 apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
166174 }