Pollinations - Text (#3985)

* [wip] Pollinations for text

* Implement generate API request

* Determine Pollinations model tools via models list

* Add Pollinations option to /model command

* Add Pollinations support to caption

* Update link to pollinations site

* Fix type errors in openai.js

* Fix API connection test to use AbortController for request cancellation

* Remove hard coded list of pollinations vision models

* Remove openai-audio from captioning models
This commit is contained in:
Cohee
2025-05-11 20:14:11 +03:00
committed by GitHub
parent 99e3c22311
commit 420d568cd3
13 changed files with 174 additions and 51 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -974,7 +974,7 @@
</div>
</div>
</div>
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai">
<div class="range-block" data-source="openai,openrouter,mistralai,custom,cohere,groq,nanogpt,xai,pollinations">
<div class="range-block-title justifyLeft" data-i18n="Seed">
Seed
</div>
@ -1977,7 +1977,7 @@
</b>
</div>
</div>
<div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai">
<div class="range-block" data-source="openai,cohere,mistralai,custom,claude,openrouter,groq,deepseek,makersuite,ai21,xai,pollinations">
<label for="openai_function_calling" class="checkbox_label flexWrap widthFreeExpand">
<input id="openai_function_calling" type="checkbox" />
<span data-i18n="Enable function calling">Enable function calling</span>
@ -1987,7 +1987,7 @@
<span data-i18n="enable_functions_desc_3">Can be utilized by various extensions to provide additional functionality.</span>
</div>
</div>
<div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai">
<div class="range-block" data-source="openai,openrouter,mistralai,makersuite,claude,custom,01ai,xai,pollinations">
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
<input id="openai_image_inlining" type="checkbox" />
<span data-i18n="Send inline images">Send inline images</span>
@ -1999,7 +1999,7 @@
<code><i class="fa-solid fa-wand-magic-sparkles"></i></code>
<span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
</div>
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai">
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,xai,pollinations">
<div class="flex-container oneline-dropdown">
<label for="openai_inline_image_quality" data-i18n="Inline Image Quality">
Inline Image Quality
@ -2057,7 +2057,7 @@
</span>
</div>
</div>
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,openrouter">
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom,claude,xai,makersuite,openrouter,pollinations">
<div class="flex-container oneline-dropdown" title="Constrains effort on reasoning for reasoning models.&#10;Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response." data-i18n="[title]Constrains effort on reasoning for reasoning models.">
<label for="openai_reasoning_effort">
<span data-i18n="Reasoning Effort">Reasoning Effort</span>
@ -2770,6 +2770,7 @@
<option value="nanogpt">NanoGPT</option>
<option value="openrouter">OpenRouter</option>
<option value="perplexity">Perplexity</option>
<option value="pollinations">Pollinations</option>
<option value="scale">Scale</option>
<option value="windowai">Window AI</option>
<option value="xai">xAI (Grok)</option>
@ -3480,6 +3481,21 @@
<option value="grok-beta">grok-beta</option>
</select>
</div>
<div id="pollinations_form" data-source="pollinations">
<h4 data-i18n="Pollinations Model">Pollinations Model</h4>
<select id="model_pollinations_select">
<!-- Populated by JavaScript -->
</select>
<div class="info-block hint">
<a href="https://pollinations.ai/" target="_blank" rel="noopener noreferrer" data-i18n="Provided free of charge by Pollinations.AI">
Provided free of charge by Pollinations.AI
</a>
<br>
<span data-i18n="Avoid sending sensitive information. Provider's outputs may include ads.">
Avoid sending sensitive information. Provider's outputs may include ads.
</span>
</div>
</div>
<div id="prompt_post_porcessing_form" data-source="custom,openrouter">
<h4 data-i18n="Prompt Post-Processing">Prompt Post-Processing</h4>
<select id="custom_prompt_post_processing" class="text_pole" title="Applies additional processing to the prompt before sending it to the API." data-i18n="[title]Applies additional processing to the prompt before sending it to the API.">

View File

@ -4811,7 +4811,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
name2: name2,
charDescription: description,
charPersonality: personality,
Scenario: scenario,
scenario: scenario,
worldInfoBefore: worldInfoBefore,
worldInfoAfter: worldInfoAfter,
extensionPrompts: extension_prompts,

View File

@ -86,15 +86,15 @@ class Prompt {
* @param {string} param0.identifier - The unique identifier of the prompt.
* @param {string} param0.role - The role associated with the prompt.
* @param {string} param0.content - The content of the prompt.
* @param {string} param0.name - The name of the prompt.
* @param {boolean} param0.system_prompt - Indicates if the prompt is a system prompt.
* @param {string} param0.position - The position of the prompt in the prompt list.
* @param {number} param0.injection_position - The insert position of the prompt.
* @param {number} param0.injection_depth - The depth of the prompt in the chat.
* @param {boolean} param0.forbid_overrides - Indicates if the prompt should not be overridden.
* @param {boolean} param0.extension - Prompt is added by an extension.
* @param {string} [param0.name] - The name of the prompt.
* @param {boolean} [param0.system_prompt] - Indicates if the prompt is a system prompt.
* @param {string} [param0.position] - The position of the prompt in the prompt list.
* @param {number} [param0.injection_position] - The insert position of the prompt.
* @param {number} [param0.injection_depth] - The depth of the prompt in the chat.
* @param {boolean} [param0.forbid_overrides] - Indicates if the prompt should not be overridden.
* @param {boolean} [param0.extension] - Prompt is added by an extension.
*/
constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides, extension } = {}) {
constructor({ identifier, role, content, name, system_prompt, position, injection_depth, injection_position, forbid_overrides, extension }) {
this.identifier = identifier;
this.role = role;
this.content = content;

View File

@ -410,6 +410,7 @@ function RA_autoconnect(PrevApi) {
|| (secret_state[SECRET_KEYS.NANOGPT] && oai_settings.chat_completion_source == chat_completion_sources.NANOGPT)
|| (secret_state[SECRET_KEYS.DEEPSEEK] && oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK)
|| (secret_state[SECRET_KEYS.XAI] && oai_settings.chat_completion_source == chat_completion_sources.XAI)
|| (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS)
|| (isValidUrl(oai_settings.custom_url) && oai_settings.chat_completion_source == chat_completion_sources.CUSTOM)
) {
$('#api_button_openai').trigger('click');

View File

@ -448,7 +448,7 @@ jQuery(async function () {
}
// Custom API doesn't need additional checks
if (api === 'custom') {
if (api === 'custom' || api === 'pollinations') {
return true;
}
}

View File

@ -30,6 +30,7 @@
<option value="openai">OpenAI</option>
<option value="openrouter">OpenRouter</option>
<option value="ooba" data-i18n="Text Generation WebUI (oobabooga)">Text Generation WebUI (oobabooga)</option>
<option value="pollinations">Pollinations</option>
<option value="vllm">vLLM</option>
<option value="xai">xAI (Grok)</option>
</select>
@ -151,6 +152,18 @@
<option data-type="custom" value="custom_current" data-i18n="currently_selected">[Currently selected]</option>
<option data-type="xai" value="grok-2-vision-1212">grok-2-vision-1212</option>
<option data-type="xai" value="grok-vision-beta">grok-vision-beta</option>
<option data-type="pollinations" value="openai">openai</option>
<option data-type="pollinations" value="openai-fast">openai-fast</option>
<option data-type="pollinations" value="openai-large">openai-large</option>
<option data-type="pollinations" value="openai-roblox">openai-roblox</option>
<option data-type="pollinations" value="mistral">mistral</option>
<option data-type="pollinations" value="unity">unity</option>
<option data-type="pollinations" value="mirexa">mirexa</option>
<option data-type="pollinations" value="searchgpt">searchgpt</option>
<option data-type="pollinations" value="evil">evil</option>
<option data-type="pollinations" value="phi">phi</option>
<option data-type="pollinations" value="sur">sur</option>
<option data-type="pollinations" value="bidara">bidara</option>
</select>
</div>
<div data-type="ollama">

View File

@ -185,6 +185,7 @@ export const chat_completion_sources = {
NANOGPT: 'nanogpt',
DEEPSEEK: 'deepseek',
XAI: 'xai',
POLLINATIONS: 'pollinations',
};
const character_names_behavior = {
@ -268,6 +269,7 @@ export const settingsToUpdate = {
deepseek_model: ['#model_deepseek_select', 'deepseek_model', false],
zerooneai_model: ['#model_01ai_select', 'zerooneai_model', false],
xai_model: ['#model_xai_select', 'xai_model', false],
pollinations_model: ['#model_pollinations_select', 'pollinations_model', false],
custom_model: ['#custom_model_id', 'custom_model', false],
custom_url: ['#custom_api_url_text', 'custom_url', false],
custom_include_body: ['#custom_include_body', 'custom_include_body', false],
@ -357,6 +359,7 @@ const default_settings = {
zerooneai_model: 'yi-large',
deepseek_model: 'deepseek-chat',
xai_model: 'grok-3-beta',
pollinations_model: 'openai',
custom_model: '',
custom_url: '',
custom_include_body: '',
@ -438,6 +441,7 @@ const oai_settings = {
zerooneai_model: 'yi-large',
deepseek_model: 'deepseek-chat',
xai_model: 'grok-3-beta',
pollinations_model: 'openai',
custom_model: '',
custom_url: '',
custom_include_body: '',
@ -1181,7 +1185,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
* Combines system prompts with prompt manager prompts
*
* @param {Object} options - An object with optional settings.
* @param {string} options.Scenario - The scenario or context of the dialogue.
* @param {string} options.scenario - The scenario or context of the dialogue.
* @param {string} options.charPersonality - Description of the character's personality.
* @param {string} options.name2 - The second name to be used in the messages.
* @param {string} options.worldInfoBefore - The world info to be added before the main conversation.
@ -1195,8 +1199,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
* @param {string} options.personaDescription
* @returns {Promise<Object>} prompts - The prepared and merged system and user-defined prompts.
*/
async function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
const scenarioText = Scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : '';
async function preparePromptsForChatCompletion({ scenario, charPersonality, name2, worldInfoBefore, worldInfoAfter, charDescription, quietPrompt, bias, extensionPrompts, systemPromptOverride, jailbreakPromptOverride, personaDescription }) {
const scenarioText = scenario && oai_settings.scenario_format ? substituteParams(oai_settings.scenario_format) : '';
const charPersonalityText = charPersonality && oai_settings.personality_format ? substituteParams(oai_settings.personality_format) : '';
const groupNudge = substituteParams(oai_settings.group_nudge_prompt);
const impersonationPrompt = oai_settings.impersonation_prompt ? substituteParams(oai_settings.impersonation_prompt) : '';
@ -1352,7 +1356,7 @@ async function preparePromptsForChatCompletion({ Scenario, charPersonality, name
* @param {string} content.name2 - The second name to be used in the messages.
* @param {string} content.charDescription - Description of the character.
* @param {string} content.charPersonality - Description of the character's personality.
* @param {string} content.Scenario - The scenario or context of the dialogue.
* @param {string} content.scenario - The scenario or context of the dialogue.
* @param {string} content.worldInfoBefore - The world info to be added before the main conversation.
* @param {string} content.worldInfoAfter - The world info to be added after the main conversation.
* @param {string} content.bias - The bias to be added in the conversation.
@ -1373,7 +1377,7 @@ export async function prepareOpenAIMessages({
name2,
charDescription,
charPersonality,
Scenario,
scenario,
worldInfoBefore,
worldInfoAfter,
bias,
@ -1400,21 +1404,18 @@ export async function prepareOpenAIMessages({
try {
// Merge markers and ordered user prompts with system prompts
const prompts = await preparePromptsForChatCompletion({
Scenario,
scenario,
charPersonality,
name2,
worldInfoBefore,
worldInfoAfter,
charDescription,
quietPrompt,
quietImage,
bias,
extensionPrompts,
systemPromptOverride,
jailbreakPromptOverride,
personaDescription,
messages,
messageExamples,
});
// Fill the chat completion with as much context as the budget allows
@ -1660,6 +1661,8 @@ export function getChatCompletionModel(source = null) {
return oai_settings.deepseek_model;
case chat_completion_sources.XAI:
return oai_settings.xai_model;
case chat_completion_sources.POLLINATIONS:
return oai_settings.pollinations_model;
default:
console.error(`Unknown chat completion source: ${activeSource}`);
return '';
@ -1842,6 +1845,24 @@ function saveModelList(data) {
$('#model_deepseek_select').val(oai_settings.deepseek_model).trigger('change');
}
if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
$('#model_pollinations_select').empty();
model_list.forEach((model) => {
$('#model_pollinations_select').append(
$('<option>', {
value: model.id,
text: model.id,
}));
});
const selectedModel = model_list.find(model => model.id === oai_settings.pollinations_model);
if (model_list.length > 0 && (!selectedModel || !oai_settings.pollinations_model)) {
oai_settings.pollinations_model = model_list[0].id;
}
$('#model_pollinations_select').val(oai_settings.pollinations_model).trigger('change');
}
}
function appendOpenRouterOptions(model_list, groupModels = false, sort = false) {
@ -1954,6 +1975,7 @@ function getReasoningEffort() {
chat_completion_sources.CUSTOM,
chat_completion_sources.XAI,
chat_completion_sources.OPENROUTER,
chat_completion_sources.POLLINATIONS,
];
if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {
@ -2009,6 +2031,7 @@ async function sendOpenAIRequest(type, messages, signal) {
const isNano = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;
const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;
const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;
const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;
const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);
const isQuiet = type === 'quiet';
const isImpersonate = type === 'impersonate';
@ -2211,7 +2234,15 @@ async function sendOpenAIRequest(type, messages, signal) {
}
}
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano || isXAI) && oai_settings.seed >= 0) {
if (isPollinations) {
delete generate_data.temperature;
delete generate_data.top_p;
delete generate_data.frequency_penalty;
delete generate_data.presence_penalty;
delete generate_data.max_tokens;
}
if ((isOAI || isOpenRouter || isMistral || isCustom || isCohere || isNano || isXAI || isPollinations) && oai_settings.seed >= 0) {
generate_data['seed'] = oai_settings.seed;
}
@ -2407,13 +2438,14 @@ function parseOpenAIChatLogprobs(logprobs) {
return null;
}
/** @type {({ token: string, logprob: number }) => [string, number]} */
/** @type {(x: { token: string, logprob: number }) => [string, number]} */
const toTuple = (x) => [x.token, x.logprob];
return content.map(({ token, logprob, top_logprobs }) => {
// Add the chosen token to top_logprobs if it's not already there, then
// convert to a list of [token, logprob] pairs
const chosenTopToken = top_logprobs.some((top) => token === top.token);
/** @type {import('./logprobs.js').Candidate[]} */
const topLogprobs = chosenTopToken
? top_logprobs.map(toTuple)
: [...top_logprobs.map(toTuple), [token, logprob]];
@ -2438,6 +2470,7 @@ function parseOpenAITextLogprobs(logprobs) {
return tokens.map((token, i) => {
// Add the chosen token to top_logprobs if it's not already there, then
// convert to a list of [token, logprob] pairs
/** @type {any[]} */
const topLogprobs = top_logprobs[i] ? Object.entries(top_logprobs[i]) : [];
const chosenTopToken = topLogprobs.some(([topToken]) => token === topToken);
if (!chosenTopToken) {
@ -3262,7 +3295,7 @@ function loadOpenAISettings(data, settings) {
openai_setting_names = arr_holder;
oai_settings.preset_settings_openai = settings.preset_settings_openai;
$(`#settings_preset_openai option[value=${openai_setting_names[oai_settings.preset_settings_openai]}]`).attr('selected', true);
$(`#settings_preset_openai option[value=${openai_setting_names[oai_settings.preset_settings_openai]}]`).prop('selected', true);
oai_settings.temp_openai = settings.temp_openai ?? default_settings.temp_openai;
oai_settings.freq_pen_openai = settings.freq_pen_openai ?? default_settings.freq_pen_openai;
@ -3300,6 +3333,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.deepseek_model = settings.deepseek_model ?? default_settings.deepseek_model;
oai_settings.zerooneai_model = settings.zerooneai_model ?? default_settings.zerooneai_model;
oai_settings.xai_model = settings.xai_model ?? default_settings.xai_model;
oai_settings.pollinations_model = settings.pollinations_model ?? default_settings.pollinations_model;
oai_settings.custom_model = settings.custom_model ?? default_settings.custom_model;
oai_settings.custom_url = settings.custom_url ?? default_settings.custom_url;
oai_settings.custom_include_body = settings.custom_include_body ?? default_settings.custom_include_body;
@ -3363,30 +3397,32 @@ function loadOpenAISettings(data, settings) {
$(`#openai_inline_image_quality option[value="${oai_settings.inline_image_quality}"]`).prop('selected', true);
$('#model_openai_select').val(oai_settings.openai_model);
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).prop('selected', true);
$('#model_claude_select').val(oai_settings.claude_model);
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).attr('selected', true);
$(`#model_claude_select option[value="${oai_settings.claude_model}"`).prop('selected', true);
$('#model_windowai_select').val(oai_settings.windowai_model);
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).attr('selected', true);
$(`#model_windowai_select option[value="${oai_settings.windowai_model}"`).prop('selected', true);
$('#model_google_select').val(oai_settings.google_model);
$(`#model_google_select option[value="${oai_settings.google_model}"`).attr('selected', true);
$(`#model_google_select option[value="${oai_settings.google_model}"`).prop('selected', true);
$('#model_ai21_select').val(oai_settings.ai21_model);
$(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).attr('selected', true);
$(`#model_ai21_select option[value="${oai_settings.ai21_model}"`).prop('selected', true);
$('#model_mistralai_select').val(oai_settings.mistralai_model);
$(`#model_mistralai_select option[value="${oai_settings.mistralai_model}"`).attr('selected', true);
$(`#model_mistralai_select option[value="${oai_settings.mistralai_model}"`).prop('selected', true);
$('#model_cohere_select').val(oai_settings.cohere_model);
$(`#model_cohere_select option[value="${oai_settings.cohere_model}"`).attr('selected', true);
$(`#model_cohere_select option[value="${oai_settings.cohere_model}"`).prop('selected', true);
$('#model_perplexity_select').val(oai_settings.perplexity_model);
$(`#model_perplexity_select option[value="${oai_settings.perplexity_model}"`).attr('selected', true);
$(`#model_perplexity_select option[value="${oai_settings.perplexity_model}"`).prop('selected', true);
$('#model_groq_select').val(oai_settings.groq_model);
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).attr('selected', true);
$(`#model_groq_select option[value="${oai_settings.groq_model}"`).prop('selected', true);
$('#model_nanogpt_select').val(oai_settings.nanogpt_model);
$(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).attr('selected', true);
$(`#model_nanogpt_select option[value="${oai_settings.nanogpt_model}"`).prop('selected', true);
$('#model_deepseek_select').val(oai_settings.deepseek_model);
$(`#model_deepseek_select option[value="${oai_settings.deepseek_model}"`).prop('selected', true);
$('#model_01ai_select').val(oai_settings.zerooneai_model);
$('#model_xai_select').val(oai_settings.xai_model);
$(`#model_xai_select option[value="${oai_settings.xai_model}"`).attr('selected', true);
$(`#model_xai_select option[value="${oai_settings.xai_model}"`).prop('selected', true);
$('#model_pollinations_select').val(oai_settings.pollinations_model);
$(`#model_pollinations_select option[value="${oai_settings.pollinations_model}"`).prop('selected', true);
$('#custom_model_id').val(oai_settings.custom_model);
$('#custom_api_url_text').val(oai_settings.custom_url);
$('#openai_max_context').val(oai_settings.openai_max_context);
@ -3492,7 +3528,7 @@ function loadOpenAISettings(data, settings) {
$('#chat_completion_source').val(oai_settings.chat_completion_source).trigger('change');
$('#oai_max_context_unlocked').prop('checked', oai_settings.max_context_unlocked);
$('#custom_prompt_post_processing').val(oai_settings.custom_prompt_post_processing);
$(`#custom_prompt_post_processing option[value="${oai_settings.custom_prompt_post_processing}"]`).attr('selected', true);
$(`#custom_prompt_post_processing option[value="${oai_settings.custom_prompt_post_processing}"]`).prop('selected', true);
}
function setNamesBehaviorControls() {
@ -3667,6 +3703,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
groq_model: settings.groq_model,
zerooneai_model: settings.zerooneai_model,
xai_model: settings.xai_model,
pollinations_model: settings.pollinations_model,
custom_model: settings.custom_model,
custom_url: settings.custom_url,
custom_include_body: settings.custom_include_body,
@ -3739,7 +3776,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
oai_settings.preset_settings_openai = data.name;
const value = openai_setting_names[data.name];
Object.assign(openai_settings[value], presetBody);
$(`#settings_preset_openai option[value="${value}"]`).attr('selected', true);
$(`#settings_preset_openai option[value="${value}"]`).prop('selected', true);
if (triggerUi) $('#settings_preset_openai').trigger('change');
}
else {
@ -3747,7 +3784,7 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
openai_setting_names[data.name] = openai_settings.length - 1;
const option = document.createElement('option');
option.selected = true;
option.value = openai_settings.length - 1;
option.value = String(openai_settings.length - 1);
option.innerText = data.name;
if (triggerUi) $('#settings_preset_openai').append(option).trigger('change');
}
@ -3955,14 +3992,14 @@ async function onPresetImportFileChange(e) {
oai_settings.preset_settings_openai = data.name;
const value = openai_setting_names[data.name];
Object.assign(openai_settings[value], presetBody);
$(`#settings_preset_openai option[value="${value}"]`).attr('selected', true);
$(`#settings_preset_openai option[value="${value}"]`).prop('selected', true);
$('#settings_preset_openai').trigger('change');
} else {
openai_settings.push(presetBody);
openai_setting_names[data.name] = openai_settings.length - 1;
const option = document.createElement('option');
option.selected = true;
option.value = openai_settings.length - 1;
option.value = String(openai_settings.length - 1);
option.innerText = data.name;
$('#settings_preset_openai').append(option).trigger('change');
}
@ -4067,7 +4104,7 @@ async function onDeletePresetClick() {
if (Object.keys(openai_setting_names).length) {
oai_settings.preset_settings_openai = Object.keys(openai_setting_names)[0];
const newValue = openai_setting_names[oai_settings.preset_settings_openai];
$(`#settings_preset_openai option[value="${newValue}"]`).attr('selected', true);
$(`#settings_preset_openai option[value="${newValue}"]`).prop('selected', true);
$('#settings_preset_openai').trigger('change');
}
@ -4099,7 +4136,7 @@ async function onLogitBiasPresetDeleteClick() {
if (Object.keys(oai_settings.bias_presets).length) {
oai_settings.bias_preset_selected = Object.keys(oai_settings.bias_presets)[0];
$(`#openai_logit_bias_preset option[value="${oai_settings.bias_preset_selected}"]`).attr('selected', true);
$(`#openai_logit_bias_preset option[value="${oai_settings.bias_preset_selected}"]`).prop('selected', true);
$('#openai_logit_bias_preset').trigger('change');
}
@ -4465,6 +4502,11 @@ async function onModelChange() {
$('#custom_model_id').val(value).trigger('input');
}
if (value && $(this).is('#model_pollinations_select')) {
console.log('Pollinations model changed to', value);
oai_settings.pollinations_model = value;
}
if ($(this).is('#model_xai_select')) {
console.log('XAI model changed to', value);
oai_settings.xai_model = value;
@ -4705,6 +4747,18 @@ async function onModelChange() {
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
}
if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
} else {
$('#openai_max_context').attr('max', max_128k);
}
oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
$('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
$('#temp_openai').attr('max', oai_max_temp).val(oai_settings.temp_openai).trigger('input');
}
if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
if (oai_settings.max_context_unlocked) {
$('#openai_max_context').attr('max', unlocked_max);
@ -5047,6 +5101,9 @@ function toggleChatCompletionForms() {
else if (oai_settings.chat_completion_source == chat_completion_sources.XAI) {
$('#model_xai_select').trigger('change');
}
else if (oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS) {
$('#model_pollinations_select').trigger('change');
}
$('[data-source]').each(function () {
const validSources = $(this).data('source').split(',');
$(this).toggle(validSources.includes(oai_settings.chat_completion_source));
@ -5061,7 +5118,7 @@ async function testApiConnection() {
}
try {
const reply = await sendOpenAIRequest('quiet', [{ 'role': 'user', 'content': 'Hi' }]);
const reply = await sendOpenAIRequest('quiet', [{ 'role': 'user', 'content': 'Hi' }], new AbortController().signal);
console.log(reply);
toastr.success(t`API connection successful!`);
}
@ -5186,6 +5243,8 @@ export function isImageInliningSupported() {
return visionSupportedModels.some(model => oai_settings.cohere_model.includes(model));
case chat_completion_sources.XAI:
return visionSupportedModels.some(model => oai_settings.xai_model.includes(model));
case chat_completion_sources.POLLINATIONS:
return (Array.isArray(model_list) && model_list.find(m => m.id === oai_settings.pollinations_model)?.vision);
default:
return false;
}
@ -5258,8 +5317,8 @@ $('#save_proxy').on('click', async function () {
toastr.success(t`Proxy Saved`);
if ($('#openai_proxy_preset').val() !== presetName) {
const option = document.createElement('option');
option.text = presetName;
option.value = presetName;
option.text = String(presetName);
option.value = String(presetName);
$('#openai_proxy_preset').append(option);
}
@ -5784,6 +5843,7 @@ export function initOpenAI() {
$('#model_01ai_select').on('change', onModelChange);
$('#model_custom_select').on('change', onModelChange);
$('#model_xai_select').on('change', onModelChange);
$('#model_pollinations_select').on('change', onModelChange);
$('#settings_preset_openai').on('change', onSettingsPresetChange);
$('#new_oai_preset').on('click', onNewPresetClick);
$('#delete_oai_preset').on('click', onDeletePresetClick);

View File

@ -4136,6 +4136,7 @@ function getModelOptions(quiet) {
{ id: 'model_01ai_select', api: 'openai', type: chat_completion_sources.ZEROONEAI },
{ id: 'model_deepseek_select', api: 'openai', type: chat_completion_sources.DEEPSEEK },
{ id: 'model_xai_select', api: 'openai', type: chat_completion_sources.XAI },
{ id: 'model_pollinations_select', api: 'openai', type: chat_completion_sources.POLLINATIONS },
{ id: 'model_novel_select', api: 'novel', type: null },
{ id: 'horde_model', api: 'koboldhorde', type: null },
];

View File

@ -1,7 +1,7 @@
import { DOMPurify } from '../lib.js';
import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js';
import { chat_completion_sources, oai_settings } from './openai.js';
import { chat_completion_sources, model_list, oai_settings } from './openai.js';
import { Popup } from './popup.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
@ -575,6 +575,13 @@ export class ToolManager {
return false;
}
if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS && Array.isArray(model_list)) {
const currentModel = model_list.find(model => model.id === oai_settings.pollinations_model);
if (currentModel) {
return currentModel.tools;
}
}
const supportedSources = [
chat_completion_sources.OPENAI,
chat_completion_sources.CUSTOM,
@ -587,6 +594,7 @@ export class ToolManager {
chat_completion_sources.MAKERSUITE,
chat_completion_sources.AI21,
chat_completion_sources.XAI,
chat_completion_sources.POLLINATIONS,
];
return supportedSources.includes(oai_settings.chat_completion_source);
}

View File

@ -177,6 +177,7 @@ export const CHAT_COMPLETION_SOURCES = {
NANOGPT: 'nanogpt',
DEEPSEEK: 'deepseek',
XAI: 'xai',
POLLINATIONS: 'pollinations',
};
/**

View File

@ -57,6 +57,7 @@ const API_AI21 = 'https://api.ai21.com/studio/v1';
const API_NANOGPT = 'https://nano-gpt.com/api/v1';
const API_DEEPSEEK = 'https://api.deepseek.com/beta';
const API_XAI = 'https://api.x.ai/v1';
const API_POLLINATIONS = 'https://text.pollinations.ai/openai';
/**
* Applies a post-processing step to the generated messages.
@ -1010,6 +1011,10 @@ router.post('/status', async function (request, response_getstatus_openai) {
api_url = new URL(request.body.reverse_proxy || API_XAI);
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI);
headers = {};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) {
api_url = 'https://text.pollinations.ai';
api_key_openai = 'NONE';
headers = {};
} else {
console.warn('This chat completion source is not supported yet.');
return response_getstatus_openai.status(400).send({ error: true });
@ -1031,7 +1036,12 @@ router.post('/status', async function (request, response_getstatus_openai) {
if (response.ok) {
/** @type {any} */
const data = await response.json();
let data = await response.json();
if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS && Array.isArray(data)) {
data = { data: data.map(model => ({ id: model.name, ...model })) };
}
response_getstatus_openai.send(data);
if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.COHERE && Array.isArray(data?.models)) {
@ -1293,6 +1303,14 @@ router.post('/generate', function (request, response) {
apiKey = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
headers = {};
bodyParams = {};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.POLLINATIONS) {
apiUrl = API_POLLINATIONS;
apiKey = 'NONE';
headers = {};
bodyParams = {
reasoning_effort: request.body.reasoning_effort,
private: true,
};
} else {
console.warn('This chat completion source is not supported yet.');
return response.status(400).send({ error: true });

View File

@ -73,7 +73,7 @@ router.post('/caption-image', async (request, response) => {
key = readSecret(request.user.directories, SECRET_KEYS.XAI);
}
const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp'];
const noKeyTypes = ['custom', 'ooba', 'koboldcpp', 'vllm', 'llamacpp', 'pollinations'];
if (!key && !request.body.reverse_proxy && !noKeyTypes.includes(request.body.api)) {
console.warn('No key found for API', request.body.api);
return response.sendStatus(400);
@ -147,6 +147,10 @@ router.post('/caption-image', async (request, response) => {
apiUrl = 'https://api.x.ai/v1/chat/completions';
}
if (request.body.api === 'pollinations') {
apiUrl = 'https://text.pollinations.ai/openai/chat/completions';
}
if (request.body.api === 'ooba') {
apiUrl = `${trimV1(request.body.server_url)}/v1/chat/completions`;
const imgMessage = body.messages.pop();