Update Pollinations API (#5060) * Upgrade Pollinations API Done: text, caption To do: TTS, image Fixes #5020 * Update Pollinations TTS to new API * Update Pollinations API for images
Signed| @@ -3772,19 +3772,22 @@ | ||
| 3772 | 3772 | </select> |
| 3773 | 3773 | </div> |
| 3774 | 3774 | <div id="pollinations_form" data-source="pollinations"> |
| 3775 | + <h4> | |
| 3776 | + <a href="https://enter.pollinations.ai/" target="_blank" rel="noopener noreferrer" data-i18n="Pollinations API Key"> | |
| 3777 | + Pollinations API Key | |
| 3778 | + </a> | |
| 3779 | + </h4> | |
| 3780 | + <div class="flex-container"> | |
| 3781 | + <input id="api_key_pollinations" name="api_key_pollinations" class="text_pole flex1" value="" type="text" autocomplete="off"> | |
| 3782 | + <div title="Manage API keys" data-i18n="[title]Manage API keys" class="menu_button fa-solid fa-key fa-fw manage-api-keys" data-key="api_key_pollinations"></div> | |
| 3783 | + </div> | |
| 3784 | + <div data-for="api_key_pollinations" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you click 'Connect'."> | |
| 3785 | + For privacy reasons, your API key will be hidden after you click 'Connect'. | |
| 3786 | + </div> | |
| 3775 | 3787 | <h4 data-i18n="Pollinations Model">Pollinations Model</h4> |
| 3776 | 3788 | <select id="model_pollinations_select"> |
| 3777 | 3789 | <!-- Populated by JavaScript --> |
| 3778 | 3790 | </select> |
| 3779 | - <div class="info-block hint"> | |
| 3780 | - <a href="https://pollinations.ai/" target="_blank" rel="noopener noreferrer" data-i18n="Provided free of charge by Pollinations.AI"> | |
| 3781 | - Provided free of charge by Pollinations.AI | |
| 3782 | - </a> | |
| 3783 | - <br> | |
| 3784 | - <span data-i18n="Avoid sending sensitive information. Provider's outputs may include ads."> | |
| 3785 | - Avoid sending sensitive information. Provider's outputs may include ads. | |
| 3786 | - </span> | |
| 3787 | - </div> | |
| 3788 | 3791 | </div> |
| 3789 | 3792 | <div id="moonshot_form" data-source="moonshot"> |
| 3790 | 3793 | <h4> |
| @@ -408,7 +408,7 @@ function RA_autoconnect(PrevApi) { | ||
| 408 | 408 | || (secret_state[SECRET_KEYS.FIREWORKS] && oai_settings.chat_completion_source == chat_completion_sources.FIREWORKS) |
| 409 | 409 | || (secret_state[SECRET_KEYS.COMETAPI] && oai_settings.chat_completion_source == chat_completion_sources.COMETAPI) |
| 410 | 410 | || (secret_state[SECRET_KEYS.ZAI] && oai_settings.chat_completion_source == chat_completion_sources.ZAI) |
| 411 | 411 | || (secret_state[SECRET_KEYS.POLLINATIONS] && oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) |
| 412 | 412 | || (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM) |
| 413 | 413 | || (secret_state[SECRET_KEYS.AZURE_OPENAI] && oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI) |
| 414 | 414 | ) { |
| @@ -507,6 +507,7 @@ jQuery(async function () { | ||
| 507 | 507 | 'nanogpt': SECRET_KEYS.NANOGPT, |
| 508 | 508 | 'chutes': SECRET_KEYS.CHUTES, |
| 509 | 509 | 'electronhub': SECRET_KEYS.ELECTRONHUB, |
| 510 | + 'pollinations': SECRET_KEYS.POLLINATIONS, | |
| 510 | 511 | }; |
| 511 | 512 | |
| 512 | 513 | if (chatCompletionApis[api] && secret_state[chatCompletionApis[api]]) { |
| @@ -530,7 +531,7 @@ jQuery(async function () { | ||
| 530 | 531 | } |
| 531 | 532 | |
| 532 | 533 | // Custom API doesn't need additional checks |
| 533 | 534 | if (api === 'custom' || api === 'pollinations') { |
| 534 | 535 | return true; |
| 535 | 536 | } |
| 536 | 537 | } |
| @@ -279,6 +279,10 @@ function throwIfInvalidModel(useReverseProxy) { | ||
| 279 | 279 | if (multimodalApi === 'zai' && !secret_state[SECRET_KEYS.ZAI]) { |
| 280 | 280 | throw new Error('Z.AI API key is not set.'); |
| 281 | 281 | } |
| 282 | + | |
| 283 | + if (multimodalApi === 'pollinations' && !secret_state[SECRET_KEYS.POLLINATIONS]) { | |
| 284 | + throw new Error('Pollinations API key is not set.'); | |
| 285 | + } | |
| 282 | 286 | } |
| 283 | 287 | |
| 284 | 288 | /** |
| @@ -1955,6 +1955,8 @@ async function loadXAIModels() { | ||
| 1955 | 1955 | } |
| 1956 | 1956 | |
| 1957 | 1957 | async function loadPollinationsModels() { |
| 1958 | + $('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]); | |
| 1959 | + | |
| 1958 | 1960 | const result = await fetch('/api/sd/pollinations/models', { |
| 1959 | 1961 | method: 'POST', |
| 1960 | 1962 | headers: getRequestHeaders({ omitContentType: true }), |
| @@ -3312,7 +3314,7 @@ async function generatePollinationsImage(prompt, negativePrompt, signal) { | ||
| 3312 | 3314 | |
| 3313 | 3315 | if (result.ok) { |
| 3314 | 3316 | const data = await result.json(); |
| 3315 | 3317 | return { format: 'jpg'data?.format, data: data?.image }; |
| 3316 | 3318 | } else { |
| 3317 | 3319 | const text = await result.text(); |
| 3318 | 3320 | throw new Error(text); |
| @@ -4757,7 +4759,7 @@ function isValidState() { | ||
| 4757 | 4759 | case sources.togetherai: |
| 4758 | 4760 | return secret_state[SECRET_KEYS.TOGETHERAI]; |
| 4759 | 4761 | case sources.pollinations: |
| 4760 | 4762 | return truesecret_state[SECRET_KEYS.POLLINATIONS]; |
| 4761 | 4763 | case sources.stability: |
| 4762 | 4764 | return secret_state[SECRET_KEYS.STABILITY]; |
| 4763 | 4765 | case sources.huggingface: |
| @@ -5514,15 +5516,19 @@ jQuery(async () => { | ||
| 5514 | 5516 | |
| 5515 | 5517 | [event_types.SECRET_WRITTEN, event_types.SECRET_DELETED, event_types.SECRET_ROTATED].forEach(event => { |
| 5516 | 5518 | eventSource.on(event, async (/** @type {string} */ key) => { |
| 5517 | 5519 | switchconst (key)keySourceMap = { |
| 5518 | 5520 | case[sources.bfl]: SECRET_KEYS.BFL:, |
| 5519 | 5521 | case[sources.falai]: SECRET_KEYS.FALAI:, |
| 5520 | 5522 | case[sources.stability]: SECRET_KEYS.STABILITY:, |
| 5521 | 5523 | case[sources.aimlapi]: SECRET_KEYS.AIMLAPI:, |
| 5522 | 5524 | case[sources.comfy]: SECRET_KEYS.COMFY_RUNPOD:, |
| 5523 | - await loadSettingOptions(); | |
| 5525 | + [sources.pollinations]: SECRET_KEYS.POLLINATIONS, | |
| 5524 | - break; | |
| 5526 | + }; | |
| 5527 | + const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key); | |
| 5528 | + if (!shouldReloadOptions) { | |
| 5529 | + return; | |
| 5525 | 5530 | } |
| 5531 | + await loadSettingOptions(); | |
| 5526 | 5532 | }); |
| 5527 | 5533 | }); |
| 5528 | 5534 | |
| @@ -263,9 +263,14 @@ | ||
| 263 | 263 | </div> |
| 264 | 264 | </div> |
| 265 | 265 | <div data-sd-source="pollinations"> |
| 266 | - <p> | |
| 266 | + <a href="https://enter.pollinations.ai">Pollinations.ai</a> | |
| 267 | - <a href="https://pollinations.ai">Pollinations.ai</a> | |
| 267 | + <div class="flex-container flexnowrap alignItemsBaseline marginBot5"> | |
| 268 | - </p> | |
| 268 | + <strong class="flex1" data-i18n="API Key">API Key</strong> | |
| 269 | + <div id="sd_pollinations_key" class="menu_button menu_button_icon manage-api-keys" data-key="api_key_pollinations"> | |
| 270 | + <i class="fa-fw fa-solid fa-key"></i> | |
| 271 | + <span data-i18n="Click to set">Click to set</span> | |
| 272 | + </div> | |
| 273 | + </div> | |
| 269 | 274 | <div class="flex-container"> |
| 270 | 275 | <label class="flex1 checkbox_label" for="sd_pollinations_enhance" data-i18n="[title]Enables prompt enhancing (passes prompts through an LLM to add detail)." title="Enables prompt enhancing (passes prompts through an LLM to add detail)."> |
| 271 | 276 | <input id="sd_pollinations_enhance" type="checkbox" /> |
| @@ -2707,10 +2707,6 @@ export async function createGenerationParameters(settings, model, type, messages | ||
| 2707 | 2707 | } |
| 2708 | 2708 | } |
| 2709 | 2709 | |
| 2710 | - if (settings.chat_completion_source === chat_completion_sources.POLLINATIONS) { | |
| 2711 | - delete generate_data.max_tokens; | |
| 2712 | - } | |
| 2713 | - | |
| 2714 | 2710 | // https://docs.electronhub.ai/api-reference/chat/completions |
| 2715 | 2711 | if (settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) { |
| 2716 | 2712 | generate_data.top_k = Number(settings.top_k_openai); |
| @@ -5608,6 +5604,7 @@ async function onConnectButtonClick(e) { | ||
| 5608 | 5604 | [chat_completion_sources.AZURE_OPENAI]: { key: SECRET_KEYS.AZURE_OPENAI, selector: '#api_key_azure_openai', proxy: false }, |
| 5609 | 5605 | [chat_completion_sources.ZAI]: { key: SECRET_KEYS.ZAI, selector: '#api_key_zai', proxy: true }, |
| 5610 | 5606 | [chat_completion_sources.CHUTES]: { key: SECRET_KEYS.CHUTES, selector: '#api_key_chutes', proxy: false }, |
| 5607 | + [chat_completion_sources.POLLINATIONS]: { key: SECRET_KEYS.POLLINATIONS, selector: '#api_key_pollinations', proxy: false }, | |
| 5611 | 5608 | }; |
| 5612 | 5609 | |
| 5613 | 5610 | // Vertex AI Express version - use API key |
| @@ -5887,7 +5884,7 @@ export function isImageInliningSupported() { | ||
| 5887 | 5884 | case chat_completion_sources.ELECTRONHUB: |
| 5888 | 5885 | return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.electronhub_model)?.metadata?.vision); |
| 5889 | 5886 | case chat_completion_sources.POLLINATIONS: |
| 5890 | 5887 | return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.pollinations_model)?.visioninput_modalities?.includes('image')); |
| 5891 | 5888 | case chat_completion_sources.COMETAPI: |
| 5892 | 5889 | return true; |
| 5893 | 5890 | case chat_completion_sources.MOONSHOT: |
| @@ -73,6 +73,7 @@ export const SECRET_KEYS = { | ||
| 73 | 73 | ZAI: 'api_key_zai', |
| 74 | 74 | SILICONFLOW: 'api_key_siliconflow', |
| 75 | 75 | ELEVENLABS: 'api_key_elevenlabs', |
| 76 | + POLLINATIONS: 'api_key_pollinations', | |
| 76 | 77 | }; |
| 77 | 78 | |
| 78 | 79 | const FRIENDLY_NAMES = { |
| @@ -134,6 +135,7 @@ const FRIENDLY_NAMES = { | ||
| 134 | 135 | [SECRET_KEYS.ZAI]: 'Z.AI', |
| 135 | 136 | [SECRET_KEYS.SILICONFLOW]: 'SiliconFlow', |
| 136 | 137 | [SECRET_KEYS.ELEVENLABS]: 'ElevenLabs TTS', |
| 138 | + [SECRET_KEYS.POLLINATIONS]: 'Pollinations', | |
| 137 | 139 | }; |
| 138 | 140 | |
| 139 | 141 | const INPUT_MAP = { |
| @@ -177,6 +179,7 @@ const INPUT_MAP = { | ||
| 177 | 179 | [SECRET_KEYS.ZAI]: '#api_key_zai', |
| 178 | 180 | [SECRET_KEYS.SILICONFLOW]: '#api_key_siliconflow', |
| 179 | 181 | [SECRET_KEYS.COMFY_RUNPOD]: '#api_key_comfy_runpod', |
| 182 | + [SECRET_KEYS.POLLINATIONS]: '#api_key_pollinations', | |
| 180 | 183 | }; |
| 181 | 184 | |
| 182 | 185 | const getLabel = () => moment().format('L LT'); |
| @@ -79,7 +79,7 @@ const API_NANOGPT = 'https://nano-gpt.com/api/v1'; | ||
| 79 | 79 | const API_DEEPSEEK = 'https://api.deepseek.com/beta'; |
| 80 | 80 | const API_XAI = 'https://api.x.ai/v1'; |
| 81 | 81 | const API_AIMLAPI = 'https://api.aimlapi.com/v1'; |
| 82 | 82 | const API_POLLINATIONS = 'https://textgen.pollinations.ai/openaiv1'; |
| 83 | 83 | const API_MOONSHOT = 'https://api.moonshot.ai/v1'; |
| 84 | 84 | const API_FIREWORKS = 'https://api.fireworks.ai/inference/v1'; |
| 85 | 85 | const API_COMETAPI = 'https://api.cometapi.com/v1'; |
| @@ -1690,8 +1690,8 @@ router.post('/status', async function (request, statusResponse) { | ||
| 1690 | 1690 | apiKey = readSecret(request.user.directories, SECRET_KEYS.AIMLAPI); |
| 1691 | 1691 | headers = { ...AIMLAPI_HEADERS }; |
| 1692 | 1692 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) { |
| 1693 | 1693 | apiUrl = 'https://textgen.pollinations.ai/text'; |
| 1694 | - apiKey = 'NONE'; | |
| 1694 | + apiKey = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS); | |
| 1695 | 1695 | headers = {}; |
| 1696 | 1696 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.GROQ) { |
| 1697 | 1697 | apiUrl = API_GROQ; |
| @@ -2244,18 +2244,19 @@ router.post('/generate', async function (request, response) { | ||
| 2244 | 2244 | } |
| 2245 | 2245 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) { |
| 2246 | 2246 | apiUrl = API_POLLINATIONS; |
| 2247 | - apiKey = 'NONE'; | |
| 2247 | + apiKey = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS); | |
| 2248 | 2248 | headers = {}; |
| 2249 | - 'Authorization': '', | |
| 2250 | - }; | |
| 2251 | 2249 | bodyParams = { |
| 2252 | 2250 | reasoning_effort: request.body.reasoning_effort, |
| 2253 | - private: true, | |
| 2254 | - referrer: 'sillytavern', | |
| 2255 | 2251 | seed: request.body.seed ?? Math.floor(Math.random() * 99999999), |
| 2256 | 2252 | }; |
| 2257 | 2253 | if (request.body.json_schema) { |
| 2258 | - setJsonObjectFormat(bodyParams, request.body.messages, request.body.json_schema); | |
| 2254 | + bodyParams['response_format'] = { | |
| 2255 | + type: 'json_schema', | |
| 2256 | + json_schema: { | |
| 2257 | + schema: request.body.json_schema.value, | |
| 2258 | + }, | |
| 2259 | + }; | |
| 2259 | 2260 | } |
| 2260 | 2261 | } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MOONSHOT) { |
| 2261 | 2262 | apiUrl = new URL(request.body.reverse_proxy || API_MOONSHOT).toString(); |
| @@ -2434,7 +2435,7 @@ const multimodalModels = express.Router(); | ||
| 2434 | 2435 | |
| 2435 | 2436 | multimodalModels.post('/pollinations', async (_req, res) => { |
| 2436 | 2437 | try { |
| 2437 | 2438 | const response = await fetch('https://textgen.pollinations.ai/models'); |
| 2438 | 2439 | |
| 2439 | 2440 | if (!response.ok) { |
| 2440 | 2441 | return res.json([]); |
| @@ -2447,7 +2448,10 @@ multimodalModels.post('/pollinations', async (_req, res) => { | ||
| 2447 | 2448 | return res.json([]); |
| 2448 | 2449 | } |
| 2449 | 2450 | |
| 2450 | - const multimodalModels = data.filter(m => m?.vision).map(m => m.name); | |
| 2451 | + const multimodalModels = data | |
| 2452 | + .filter(m => Array.isArray(m?.input_modalities)) | |
| 2453 | + .filter(m => m.input_modalities.includes('image')) | |
| 2454 | + .map(m => m.name); | |
| 2451 | 2455 | return res.json(multimodalModels); |
| 2452 | 2456 | } catch (error) { |
| 2453 | 2457 | console.error(error); |
| @@ -97,7 +97,12 @@ router.post('/caption-image', async (request, response) => { | ||
| 97 | 97 | bodyParams.max_tokens = 4096; // default is 1024 |
| 98 | 98 | } |
| 99 | 99 | |
| 100 | - const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations']; | |
| 100 | + if (request.body.api === 'pollinations') { | |
| 101 | + key = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS); | |
| 102 | + bodyParams.seed = Math.floor(Math.random() * Math.pow(2, 32)); | |
| 103 | + } | |
| 104 | + | |
| 105 | + const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp']; | |
| 101 | 106 | if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) { |
| 102 | 107 | console.warn('No key found for API', request.body.api); |
| 103 | 108 | return response.sendStatus(400); |
| @@ -173,8 +178,7 @@ router.post('/caption-image', async (request, response) => { | ||
| 173 | 178 | } |
| 174 | 179 | |
| 175 | 180 | if (request.body.api === 'pollinations') { |
| 176 | - headers = { Authorization: '' }; | |
| 181 | + apiUrl = 'https://gen.pollinations.ai/v1/chat/completions'; | |
| 177 | - apiUrl = 'https://text.pollinations.ai/openai/chat/completions'; | |
| 178 | 182 | } |
| 179 | 183 | |
| 180 | 184 | if (request.body.api === 'moonshot' && !request.body.reverse_proxy) { |
| @@ -66,6 +66,7 @@ export const SECRET_KEYS = { | ||
| 66 | 66 | ZAI: 'api_key_zai', |
| 67 | 67 | SILICONFLOW: 'api_key_siliconflow', |
| 68 | 68 | ELEVENLABS: 'api_key_elevenlabs', |
| 69 | + POLLINATIONS: 'api_key_pollinations', | |
| 69 | 70 | }; |
| 70 | 71 | |
| 71 | 72 | /** |
| @@ -89,7 +89,7 @@ pollinations.post('/voices', async (req, res) => { | ||
| 89 | 89 | try { |
| 90 | 90 | const model = req.body.model || 'openai-audio'; |
| 91 | 91 | |
| 92 | 92 | const response = await fetch('https://textgen.pollinations.ai/text/models'); |
| 93 | 93 | |
| 94 | 94 | if (!response.ok) { |
| 95 | 95 | throw new Error('Failed to fetch Pollinations models'); |
| @@ -116,25 +116,56 @@ pollinations.post('/voices', async (req, res) => { | ||
| 116 | 116 | |
| 117 | 117 | pollinations.post('/generate', async (req, res) => { |
| 118 | 118 | try { |
| 119 | + const key = readSecret(req.user.directories, SECRET_KEYS.POLLINATIONS); | |
| 120 | + if (!key) { | |
| 121 | + console.warn('No API key saved for Pollinations TTS.'); | |
| 122 | + return res.sendStatus(400); | |
| 123 | + } | |
| 124 | + | |
| 119 | 125 | const text = req.body.text; |
| 120 | 126 | const model = req.body.model || 'openai-audio'; |
| 121 | 127 | const voice = req.body.voice || 'alloy'; |
| 122 | 128 | |
| 123 | - const url = new URL(`https://text.pollinations.ai/generate/${encodeURIComponent(text)}`); | |
| 129 | + console.debug('Pollinations TTS request', { text, model, voice }); | |
| 124 | - url.searchParams.append('model', model); | |
| 125 | - url.searchParams.append('voice', voice); | |
| 126 | - url.searchParams.append('referrer', 'sillytavern'); | |
| 127 | - console.info('Pollinations request URL:', url.toString()); | |
| 128 | 130 | |
| 129 | - const response = await fetch(url); | |
| 131 | + const response = await fetch('https://gen.pollinations.ai/v1/chat/completions', { | |
| 132 | + method: 'POST', | |
| 133 | + headers: { | |
| 134 | + 'Authorization': `Bearer ${key}`, | |
| 135 | + 'Content-Type': 'application/json', | |
| 136 | + }, | |
| 137 | + body: JSON.stringify({ | |
| 138 | + model: model, | |
| 139 | + stream: false, | |
| 140 | + modalities: ['text', 'audio'], | |
| 141 | + seed: Math.floor(Math.random() * Math.pow(2, 32)), | |
| 142 | + audio: { | |
| 143 | + format: 'mp3', | |
| 144 | + voice: voice, | |
| 145 | + }, | |
| 146 | + messages: [{ | |
| 147 | + role: 'user', | |
| 148 | + content: text, | |
| 149 | + }], | |
| 150 | + }), | |
| 151 | + }); | |
| 130 | 152 | |
| 131 | 153 | if (!response.ok) { |
| 132 | 154 | const text = await response.text(); |
| 133 | 155 | throw new Error(`Failed to generate audio from Pollinations: ${text}`); |
| 134 | 156 | } |
| 135 | 157 | |
| 158 | + /** @type {any} */ | |
| 159 | + const data = await response.json(); | |
| 160 | + const audioData = data?.choices?.[0]?.message?.audio?.data; | |
| 161 | + | |
| 162 | + if (!audioData) { | |
| 163 | + console.warn('Pollinations TTS audio data is missing from the response'); | |
| 164 | + return res.sendStatus(500); | |
| 165 | + } | |
| 166 | + | |
| 136 | 167 | res.set('Content-Type', 'audio/mpeg'); |
| 137 | - forwardFetchResponse(response, res); | |
| 168 | + return res.send(Buffer.from(audioData, 'base64')); | |
| 138 | 169 | } catch (error) { |
| 139 | 170 | console.error(error); |
| 140 | 171 | return res.sendStatus(500); |
| @@ -8,6 +8,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic'; | ||
| 8 | 8 | import FormData from 'form-data'; |
| 9 | 9 | import urlJoin from 'url-join'; |
| 10 | 10 | import _ from 'lodash'; |
| 11 | +import mime from 'mime-types'; | |
| 11 | 12 | |
| 12 | 13 | import { delay, getBasicAuthHeader, isValidUrl, tryParse } from '../util.js'; |
| 13 | 14 | import { readSecret, SECRET_KEYS } from './secrets.js'; |
| @@ -898,7 +899,7 @@ const pollinations = express.Router(); | ||
| 898 | 899 | |
| 899 | 900 | pollinations.post('/models', async (_request, response) => { |
| 900 | 901 | try { |
| 901 | 902 | const modelsUrl = new URL('https://imagegen.pollinations.ai/image/models'); |
| 902 | 903 | const result = await fetch(modelsUrl); |
| 903 | 904 | |
| 904 | 905 | if (!result.ok) { |
| @@ -913,7 +914,7 @@ pollinations.post('/models', async (_request, response) => { | ||
| 913 | 914 | throw new Error('Pollinations request failed.'); |
| 914 | 915 | } |
| 915 | 916 | |
| 916 | 917 | const models = data.map(x => ({ value: x.name, text: x.name })); |
| 917 | 918 | return response.send(models); |
| 918 | 919 | } catch (error) { |
| 919 | 920 | console.error(error); |
| @@ -923,17 +924,19 @@ pollinations.post('/models', async (_request, response) => { | ||
| 923 | 924 | |
| 924 | 925 | pollinations.post('/generate', async (request, response) => { |
| 925 | 926 | try { |
| 926 | - const promptUrl = new URL(`https://image.pollinations.ai/prompt/${encodeURIComponent(request.body.prompt)}`); | |
| 927 | + const key = readSecret(request.user.directories, SECRET_KEYS.POLLINATIONS); | |
| 928 | + if (!key) { | |
| 929 | + console.warn('Pollinations API key not found.'); | |
| 930 | + return response.sendStatus(400); | |
| 931 | + } | |
| 932 | + | |
| 933 | + const promptUrl = new URL(`https://gen.pollinations.ai/image/${encodeURIComponent(request.body.prompt)}`); | |
| 927 | 934 | const params = new URLSearchParams({ |
| 928 | 935 | model: String(request.body.model), |
| 929 | 936 | negative_prompt: String(request.body.negative_prompt), |
| 930 | 937 | seed: String(request.body.seed >= 0 ? request.body.seed : Math.floor(Math.random() * 10_000_000)), |
| 931 | 938 | width: String(request.body.width ?? 1024), |
| 932 | 939 | height: String(request.body.height ?? 1024), |
| 933 | - nologo: String(true), | |
| 934 | - nofeed: String(true), | |
| 935 | - private: String(true), | |
| 936 | - referrer: 'sillytavern', | |
| 937 | 940 | }); |
| 938 | 941 | if (request.body.enhance) { |
| 939 | 942 | params.set('enhance', String(true)); |
| @@ -942,7 +945,12 @@ pollinations.post('/generate', async (request, response) => { | ||
| 942 | 945 | |
| 943 | 946 | console.info('Pollinations request URL:', promptUrl.toString()); |
| 944 | 947 | |
| 945 | 948 | const result = await fetch(promptUrl);, { |
| 949 | + method: 'GET', | |
| 950 | + headers: { | |
| 951 | + 'Authorization': `Bearer ${key}`, | |
| 952 | + }, | |
| 953 | + }); | |
| 946 | 954 | |
| 947 | 955 | if (!result.ok) { |
| 948 | 956 | const text = await result.text(); |
| @@ -950,10 +958,9 @@ pollinations.post('/generate', async (request, response) => { | ||
| 950 | 958 | throw new Error('Pollinations request failed.'); |
| 951 | 959 | } |
| 952 | 960 | |
| 961 | + const format = result.headers.get('Content-Type')?.toString() || 'image/jpeg'; | |
| 953 | 962 | const buffer = await result.arrayBuffer(); |
| 954 | 963 | constreturn base64response.send({ =image: Buffer.from(buffer).toString('base64'), format: mime.extension(format) || 'jpg' }); |
| 955 | - | |
| 956 | - return response.send({ image: base64 }); | |
| 957 | 964 | } catch (error) { |
| 958 | 965 | console.error(error); |
| 959 | 966 | return response.sendStatus(500); |