Facillitate extension use of ConnectionManagerRequestService (#4841) * Separate prompt-building functionality from request-sending functionality * removing logs and clarifying comments * separating parameter construction functionality to allow ConnectionManagerRequestService to use all other preset parameters * fixing chat completion issues, adding documentation to new functions. * Improving ConnectionManagerRequestService errors. Adding parseReasoningFromString option to override reasoning template. * Adjusting TextCompletionService prompt formatting * linting * Use settingsToUpdate to convert from OAI preset to OAI settings. * lint * throw errors when profile ID not found * Fix missed instances of global completion settings being used (CC and TC), replaced with optional argument. Specified typing for ChatCompletionSettings and TextCompletionSettings. * Adjusting parameters of parseReasoningFromString and adding getReasoningTemplateByName * using messages.role as a fallback for custom requests, fixing newline removal. * parameters => settings I like how it sounds better * ditto * You know I had to do it to 'em * Update getCustomTokenBans * Fix calculateLogitBias * Fix param attributes * Fix type checks * Less strict role type on ChatCompletionMessage * Add missing space * fixing getChatCompletionModel to use an arbitrary chat completion settings object * Fixing issues with preset overriding custom data passed. * Pass model to createGenerationParameters externally * Unify seed param handling for CHUTES * Fix non-existing CC source * Use strict comparison * Use global settings as a base for generation parameters creation * removing unnecessary handling of preset fields * don't pass preset prompts, use the passed payload override messages * refactoring text generation prompt building of last line * Pass model to getReasoningEffort * Pass model name to canPerformToolCalls * Pass model to createTextGenGenerationData --------- Co-authored-by: qvink <qvink@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

a4cc9b3989dd1a96b1dc48e21c3b4ca951577dd0

qvink <191186569+qvink@users.noreply.github.com>

Signed
8 files changed, +663 -519Ignore whitespace
public/global.d.ts+6 -0
@@ -146,6 +146,12 @@ declare global {
146 paused: boolean;146 paused: boolean;
147 }147 }
148148
149 interface ChatCompletionMessage {
150 name?: string;
151 role: string;
152 content: string;
153 }
154
149 // Global namespace modules155 // Global namespace modules
150 interface Window {156 interface Window {
151 ai: any;157 ai: any;
public/scripts/custom-request.js+160 -149
@@ -1,9 +1,9 @@
1import { getPresetManager } from './preset-manager.js';1import { getPresetManager } from './preset-manager.js';
2import { extractJsonFromData, extractMessageFromData, getGenerateUrl, getRequestHeaders } from '../script.js';2import { extractJsonFromData, extractMessageFromData, getGenerateUrl, getRequestHeaders, name1, name2 } from '../script.js';
3import { getTextGenServer } from './textgen-settings.js';3import { getTextGenServer, createTextGenGenerationData, setting_names, textgenerationwebui_settings } from './textgen-settings.js';
4import { extractReasoningFromData } from './reasoning.js';4import { extractReasoningFromData } from './reasoning.js';
5import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences, names_behavior_types } from './instruct-mode.js';5import { formatInstructModeChat, formatInstructModePrompt, getInstructStoppingSequences } from './instruct-mode.js';
6import { getStreamingReply, tryParseStreamingError } from './openai.js';6import { getStreamingReply, tryParseStreamingError, createGenerationParameters, settingsToUpdate, oai_settings } from './openai.js';
7import EventSourceStream from './sse-stream.js';7import EventSourceStream from './sse-stream.js';
88
9// #region Type Definitions9// #region Type Definitions
@@ -34,6 +34,7 @@ import EventSourceStream from './sse-stream.js';
3434
35/**35/**
36 * @typedef {Object} ChatCompletionMessage36 * @typedef {Object} ChatCompletionMessage
37 * @property {string} [name] - The name of the message author (optional)
37 * @property {string} role - The role of the message author (e.g., "user", "assistant", "system")38 * @property {string} role - The role of the message author (e.g., "user", "assistant", "system")
38 * @property {string} content - The content of the message39 * @property {string} content - The content of the message
39 */40 */
@@ -125,7 +126,7 @@ export class TextCompletionService {
125126
126 const json = await response.json();127 const json = await response.json();
127 if (!response.ok || json.error) {128 if (!response.ok || json.error) {
128 throw json;129 throw new Error(String(json.error?.message || 'Response not OK'));
129 }130 }
130131
131 if (!extractData) {132 if (!extractData) {
@@ -188,8 +189,87 @@ export class TextCompletionService {
188 }189 }
189190
190 /**191 /**
192 * Return a formatted prompt string given an array of messages, a chosen instruct preset, and instruct settings.
193 * @param {(ChatCompletionMessage & {ignoreInstruct?: boolean})[]} prompt An array of messages
194 * @param {InstructSettings|string} instructPreset Either the name of an instruct preset or the instruct preset object itself.
195 * @param {Partial<InstructSettings>} instructSettings Optional instruct settings
196 */
197 static constructPrompt(prompt, instructPreset, instructSettings) {
198 // InstructPreset may either be a name or itself a preset
199 if (typeof instructPreset === 'string') {
200 const instructPresetManager = getPresetManager('instruct');
201 instructPreset = instructPresetManager?.getCompletionPresetByName(instructPreset);
202 }
203
204 // Clone the preset to avoid modifying the original
205 instructPreset = structuredClone(instructPreset);
206 if (instructSettings) { // apply any additional settings
207 Object.assign(instructPreset, instructSettings);
208 }
209
210 // Make the type check shut up. We 100% don't have a string here.
211 if (typeof instructPreset === 'string') {
212 return;
213 }
214
215 // Format messages using instruct formatting
216 const formattedMessages = [];
217 const prefillActive = prompt.length > 0 ? prompt[prompt.length - 1].role === 'assistant' : false;
218 for (const message of prompt) {
219 let messageContent = message.content;
220 if (!message.ignoreInstruct) {
221 const isLastMessage = message === prompt[prompt.length - 1];
222
223 // This complicated logic means:
224 // 1. If prefill is not active, format all messages
225 // 2. If prefill is active, format all messages except the last one
226 if (!isLastMessage || !prefillActive) {
227 messageContent = formatInstructModeChat(
228 message.name ?? message.role,
229 message.content,
230 message.role === 'user',
231 message.role === 'system',
232 undefined,
233 name1, // for macros
234 name2, // for macros
235 undefined,
236 instructPreset,
237 );
238 }
239
240 // Add prompt formatting for the last message.
241 // e.g. "<|im_start|>assistant"
242 if (isLastMessage) {
243 let last_line = formatInstructModePrompt(
244 'assistant', // for sequences using {{name}}
245 false, // not an impersonation
246 prefillActive ? message.content : undefined, // if using prefill, last message is the prefill
247 name1, // for macros
248 name2, // for macros
249 true, // quiet
250 false,
251 instructPreset,
252 );
253
254 if (prefillActive) { // content is the prefilled message
255 if (last_line.endsWith('\n') && !message.content.endsWith('\n')) {
256 last_line = last_line.slice(0, -1); // remove newline after prefill if it's not in the prefill itself
257 }
258 messageContent = last_line;
259 } else { // append last line to content (e.g. "<|im_start|>assistant:")
260 messageContent += last_line;
261 }
262 }
263 }
264 formattedMessages.push(messageContent);
265 }
266 return formattedMessages.join('');
267 }
268
269
270 /**
191 * Process and send a text completion request with optional preset & instruct271 * Process and send a text completion request with optional preset & instruct
192 * @param {Record<string, any> & TextCompletionRequestBase & {prompt: (ChatCompletionMessage & {ignoreInstruct?: boolean})[] |string}} custom272 * @param {TextCompletionPayload} requestData
193 * @param {Object} options - Configuration options273 * @param {Object} options - Configuration options
194 * @param {string?} [options.presetName] - Name of the preset to use for generation settings274 * @param {string?} [options.presetName] - Name of the preset to use for generation settings
195 * @param {string?} [options.instructName] - Name of instruct preset for message formatting275 * @param {string?} [options.instructName] - Name of instruct preset for message formatting
@@ -199,15 +279,35 @@ export class TextCompletionService {
199 * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator279 * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
200 * @throws {Error}280 * @throws {Error}
201 */281 */
202 static async processRequest(282 static async processRequest(requestData, options = {}, extractData = true, signal = null) {
203 custom,
204 options = {},
205 extractData = true,
206 signal = null,
207 ) {
208 const { presetName, instructName } = options;283 const { presetName, instructName } = options;
209 let requestData = { ...custom };284
210 const prompt = custom.prompt;285 // remove any undefined params in given request data
286 requestData = this.createRequestData(requestData);
287
288 /** @type {InstructSettings | undefined} */
289 let instructPreset;
290 const prompt = requestData.prompt;
291 // Handle instruct formatting if requested
292 if (Array.isArray(prompt)) {
293 if (instructName) {
294 const instructPresetManager = getPresetManager('instruct');
295 instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
296 if (instructPreset) {
297 requestData.prompt = this.constructPrompt(prompt, instructPreset, options.instructSettings);
298 const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false });
299 requestData.stop = stoppingStrings;
300 requestData.stopping_strings = stoppingStrings;
301 } else {
302 console.warn(`Instruct preset "${instructName}" not found, using basic formatting`);
303 requestData.prompt = prompt.map(x => x.content).join('\n\n');
304 }
305 } else {
306 requestData.prompt = prompt.map(x => x.content).join('\n\n');
307 }
308 } else if (typeof prompt === 'string') {
309 requestData.prompt = prompt;
310 }
211311
212 // Apply generation preset if specified312 // Apply generation preset if specified
213 if (presetName) {313 if (presetName) {
@@ -215,9 +315,8 @@ export class TextCompletionService {
215 if (presetManager) {315 if (presetManager) {
216 const preset = presetManager.getCompletionPresetByName(presetName);316 const preset = presetManager.getCompletionPresetByName(presetName);
217 if (preset) {317 if (preset) {
218 // Convert preset to payload and merge with custom parameters318 // Convert preset to payload and merge with custom data
219 const presetPayload = this.presetToGeneratePayload(preset, {});319 requestData = this.presetToGeneratePayload(preset, {}, requestData);
220 requestData = { ...presetPayload, ...requestData };
221 } else {320 } else {
222 console.warn(`Preset "${presetName}" not found, continuing with default settings`);321 console.warn(`Preset "${presetName}" not found, continuing with default settings`);
223 }322 }
@@ -226,99 +325,10 @@ export class TextCompletionService {
226 }325 }
227 }326 }
228327
328 const response = await this.sendRequest(requestData, extractData, signal);
229329
230 /** @type {InstructSettings | undefined} */
231 let instructPreset;
232 // Handle instruct formatting if requested
233 if (Array.isArray(prompt) && instructName) {
234 const instructPresetManager = getPresetManager('instruct');
235 instructPreset = instructPresetManager?.getCompletionPresetByName(instructName);
236 if (instructPreset) {
237 // Clone the preset to avoid modifying the original
238 instructPreset = structuredClone(instructPreset);
239 instructPreset.names_behavior = names_behavior_types.NONE;
240 if (options.instructSettings) {
241 Object.assign(instructPreset, options.instructSettings);
242 }
243
244 // Format messages using instruct formatting
245 const formattedMessages = [];
246 const prefillActive = prompt.length > 0 ? prompt[prompt.length - 1].role === 'assistant' : false;
247 for (const message of prompt) {
248 let messageContent = message.content;
249 if (!message.ignoreInstruct) {
250 const isLastMessage = message === prompt[prompt.length - 1];
251
252 // This complicated logic means:
253 // 1. If prefill is not active, format all messages
254 // 2. If prefill is active, format all messages except the last one
255 if (!isLastMessage || !prefillActive) {
256 messageContent = formatInstructModeChat(
257 message.role,
258 message.content,
259 message.role === 'user',
260 message.role === 'system',
261 undefined,
262 undefined,
263 undefined,
264 undefined,
265 instructPreset,
266 );
267 }
268
269 // Add prompt formatting for the last message.
270 if (isLastMessage) {
271 if (!prefillActive) { // e.g. "<|im_start|>user:"
272 messageContent += formatInstructModePrompt(
273 undefined,
274 false,
275 undefined,
276 undefined,
277 undefined,
278 false,
279 false,
280 instructPreset,
281 );
282 } else { // e.g. "<|im_start|>assistant: Hello, my name is"
283 const overriddenInstructPreset = structuredClone(instructPreset);
284 overriddenInstructPreset.output_suffix = '';
285 overriddenInstructPreset.wrap = false;
286 messageContent = formatInstructModeChat(
287 message.role,
288 message.content,
289 false, // since it is assistant
290 false,
291 undefined,
292 undefined,
293 undefined,
294 undefined,
295 overriddenInstructPreset,
296 );
297 }
298 }
299 }
300 formattedMessages.push(messageContent);
301 }
302 requestData.prompt = formattedMessages.join('');
303 const stoppingStrings = getInstructStoppingSequences({ customInstruct: instructPreset, useStopStrings: false });
304 requestData.stop = stoppingStrings;
305 requestData.stopping_strings = stoppingStrings;
306 } else {
307 console.warn(`Instruct preset "${instructName}" not found, using basic formatting`);
308 requestData.prompt = prompt.map(x => x.content).join('\n\n');
309 }
310 } else if (typeof prompt === 'string') {
311 requestData.prompt = prompt;
312 } else {
313 requestData.prompt = prompt.map(x => x.content).join('\n\n');
314 }
315
316 // @ts-ignore
317 const data = this.createRequestData(requestData);
318
319 const response = await this.sendRequest(data, extractData, signal);
320 // Remove stopping strings from the end330 // Remove stopping strings from the end
321 if (!data.stream && extractData) {331 if (!requestData.stream && extractData) {
322 /** @type {ExtractedData} */332 /** @type {ExtractedData} */
323 // @ts-ignore333 // @ts-ignore
324 const extractedData = response;334 const extractedData = response;
@@ -377,31 +387,30 @@ export class TextCompletionService {
377 * Converts a preset to a valid text completion payload.387 * Converts a preset to a valid text completion payload.
378 * Only supports temperature.388 * Only supports temperature.
379 * @param {Object} preset - The preset configuration389 * @param {Object} preset - The preset configuration
380 * @param {Object} customPreset - Additional parameters to override preset values390 * @param {Object} overridePreset - Additional parameters to override preset values
391 * @param {Object} overridePayload - Additional parameters to override payload values
381 * @returns {Object} - Formatted payload for text completion API392 * @returns {Object} - Formatted payload for text completion API
382 */393 */
383 static presetToGeneratePayload(preset, customPreset = {}) {394 static presetToGeneratePayload(preset, overridePreset = {}, overridePayload = {}) {
384 if (!preset || typeof preset !== 'object') {395 if (!preset || typeof preset !== 'object') {
385 throw new Error('Invalid preset: must be an object');396 throw new Error('Invalid preset: must be an object');
386 }397 }
387398
388 // Merge preset with custom parameters399 // apply preset overrides
389 const settings = { ...preset, ...customPreset };400 preset = { ...preset, ...overridePreset };
390401
391 // Initialize base payload with common parameters402 // Only take fields from the preset specified in setting_names to use as TextCompletionSettings
392 let payload = {403 const settings = structuredClone(textgenerationwebui_settings);
393 'temperature': settings.temp >= 0 ? Number(settings.temp) : undefined,404 for (const [key, value] of Object.entries(preset)) {
394 'min_p': settings.min_p >= 0 ? Number(settings.min_p) : undefined,405 if (!setting_names.includes(key)) continue;
395 };406 settings[key] = value;
407 }
396408
397 // Remove undefined values to avoid API errors409 // convert to a generation payload
398 Object.keys(payload).forEach(key => {410 const payload = createTextGenGenerationData(settings, overridePayload.model, overridePayload.prompt, preset.genamt);
399 if (payload[key] === undefined) {
400 delete payload[key];
401 }
402 });
403411
404 return payload;412 // apply overrides
413 return this.createRequestData({ ...payload, ...overridePayload });
405 }414 }
406}415}
407416
@@ -461,7 +470,7 @@ export class ChatCompletionService {
461 if (!data.stream) {470 if (!data.stream) {
462 const json = await response.json();471 const json = await response.json();
463 if (!response.ok || json.error) {472 if (!response.ok || json.error) {
464 throw json;473 throw new Error(String(json.error?.message || 'Response not OK'));
465 }474 }
466475
467 if (!extractData) {476 if (!extractData) {
@@ -523,7 +532,7 @@ export class ChatCompletionService {
523532
524 /**533 /**
525 * Process and send a chat completion request with optional preset534 * Process and send a chat completion request with optional preset
526 * @param {ChatCompletionPayload} custom535 * @param {ChatCompletionPayload} requestData - payload data, overriding preset if given
527 * @param {Object} options - Configuration options536 * @param {Object} options - Configuration options
528 * @param {string?} [options.presetName] - Name of the preset to use for generation settings537 * @param {string?} [options.presetName] - Name of the preset to use for generation settings
529 * @param {boolean} [extractData=true] - Whether to extract structured data from response538 * @param {boolean} [extractData=true] - Whether to extract structured data from response
@@ -531,9 +540,9 @@ export class ChatCompletionService {
531 * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator540 * @returns {Promise<ExtractedData | (() => AsyncGenerator<StreamResponse>)>} If not streaming, returns extracted data; if streaming, returns a function that creates an AsyncGenerator
532 * @throws {Error}541 * @throws {Error}
533 */542 */
534 static async processRequest(custom, options, extractData = true, signal = null) {543 static async processRequest(requestData, options, extractData = true, signal = null) {
535 const { presetName } = options;544 const { presetName } = options;
536 let requestData = { ...custom };545 requestData = this.createRequestData(requestData);
537546
538 // Apply generation preset if specified547 // Apply generation preset if specified
539 if (presetName) {548 if (presetName) {
@@ -542,8 +551,7 @@ export class ChatCompletionService {
542 const preset = presetManager.getCompletionPresetByName(presetName);551 const preset = presetManager.getCompletionPresetByName(presetName);
543 if (preset) {552 if (preset) {
544 // Convert preset to payload and merge with custom parameters553 // Convert preset to payload and merge with custom parameters
545 const presetPayload = this.presetToGeneratePayload(preset, {});554 requestData = await this.presetToGeneratePayload(preset, {}, requestData);
546 requestData = { ...presetPayload, ...requestData };
547 } else {555 } else {
548 console.warn(`Preset "${presetName}" not found, continuing with default settings`);556 console.warn(`Preset "${presetName}" not found, continuing with default settings`);
549 }557 }
@@ -552,38 +560,41 @@ export class ChatCompletionService {
552 }560 }
553 }561 }
554562
555 const data = this.createRequestData(requestData);563 return await this.sendRequest(requestData, extractData, signal);
556
557 return await this.sendRequest(data, extractData, signal);
558 }564 }
559565
560 /**566 /**
561 * Converts a preset to a valid chat completion payload567 * Converts a preset to a valid chat completion payload
562 * Only supports temperature.568 * Only supports temperature.
563 * @param {Object} preset - The preset configuration569 * @param {Object} preset - The preset configuration
564 * @param {Object} customParams - Additional parameters to override preset values570 * @param {Object} overridePreset - Additional parameters to override preset values
565 * @returns {Object} - Formatted payload for chat completion API571 * @param {Object} overridePayload - Additional parameters to override payload values
572 * @returns {Promise<any>} - Formatted payload for chat completion API
566 */573 */
567 static presetToGeneratePayload(preset, customParams = {}) {574 static async presetToGeneratePayload(preset, overridePreset = {}, overridePayload = {}) {
568 if (!preset || typeof preset !== 'object') {575 if (!preset || typeof preset !== 'object') {
569 throw new Error('Invalid preset: must be an object');576 throw new Error('Invalid preset: must be an object');
570 }577 }
571578
572 // Merge preset with custom parameters579 // apply preset overrides
573 const settings = { ...preset, ...customParams };580 preset = { ...preset, ...overridePreset };
574581
575 // Initialize base payload with common parameters582 // Fix any fields before converting to settings
576 const payload = {583 preset.bias_preset_selected = preset.bias_presets !== undefined ? preset.bias_preset_selected : undefined; // presets might have bias_preset_selected but not bias_presets, but settings need both or neither.
577 temperature: settings.temperature >= 0 ? Number(settings.temperature) : undefined,
578 };
579584
580 // Remove undefined values to avoid API errors585 // Convert from preset to ChatCompletionSettings
581 Object.keys(payload).forEach(key => {586 const settings = structuredClone(oai_settings);
582 if (payload[key] === undefined) {587 for (const [key, value] of Object.entries(preset)) {
583 delete payload[key];588 const settingToUpdate = settingsToUpdate[key];
584 }589 if (!settingToUpdate) continue;
585 });590 settings[settingToUpdate[1]] = value;
591 }
586592
587 return payload;593 // Convert from settings to generation payload
594 const data = await createGenerationParameters(settings, overridePayload.model, 'quiet', overridePayload.messages);
595 const payload = data.generate_data;
596
597 // apply overrides
598 return this.createRequestData({ ...payload, ...overridePayload });
588 }599 }
589}600}
public/scripts/extensions/shared.js+45 -1
@@ -397,7 +397,7 @@ export class ConnectionManagerRequestService {
397 throw new Error('Connection Manager is not available');397 throw new Error('Connection Manager is not available');
398 }398 }
399399
400 const profile = context.extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);400 const profile = this.getProfile(profileId);
401 const selectedApiMap = this.validateProfile(profile);401 const selectedApiMap = this.validateProfile(profile);
402402
403 try {403 try {
@@ -454,6 +454,38 @@ export class ConnectionManagerRequestService {
454 }454 }
455455
456 /**456 /**
457 * If using text completion, return a formatted prompt string given an array of messages, a given profile ID, and optional instruct settings.
458 * If using chat completion, simply return the given prompt as-is.
459 * @param {ChatCompletionMessage[]} prompt An array of prompt messages.
460 * @param {string} profileId ID of a given connection profile (from which to infer a completion preset).
461 * @param {InstructSettings} instructSettings optional instruct settings
462 */
463 static constructPrompt(prompt, profileId, instructSettings = null) {
464 const context = SillyTavern.getContext();
465 const profile = this.getProfile(profileId);
466 const selectedApiMap = this.validateProfile(profile);
467 const instructName = profile.instruct;
468
469 switch (selectedApiMap.selected) {
470 case 'openai': {
471 if (!selectedApiMap.source) {
472 throw new Error(`API type ${selectedApiMap.selected} does not support chat completions`);
473 }
474 return prompt;
475 }
476 case 'textgenerationwebui': {
477 if (!selectedApiMap.type) {
478 throw new Error(`API type ${selectedApiMap.selected} does not support text completions`);
479 }
480 return context.TextCompletionService.constructPrompt(prompt, instructName, instructSettings);
481 }
482 default: {
483 throw new Error(`Unknown API type ${selectedApiMap.selected}`);
484 }
485 }
486 }
487
488 /**
457 * Respects allowed types.489 * Respects allowed types.
458 * @returns {import('./connection-manager/index.js').ConnectionProfile[]}490 * @returns {import('./connection-manager/index.js').ConnectionProfile[]}
459 */491 */
@@ -468,6 +500,18 @@ export class ConnectionManagerRequestService {
468 }500 }
469501
470 /**502 /**
503 * Return profile data given the profile ID
504 * @param {string} profileId
505 * @returns {import('./connection-manager/index.js').ConnectionProfile?} [profile]
506 * @throws {Error}
507 */
508 static getProfile(profileId) {
509 const profile = SillyTavern.getContext().extensionSettings.connectionManager.profiles.find((p) => p.id === profileId);
510 if (!profile) throw new Error(`Profile not found (ID: ${profileId})`);
511 return profile;
512 }
513
514 /**
471 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]515 * @param {import('./connection-manager/index.js').ConnectionProfile?} [profile]
472 * @returns {boolean}516 * @returns {boolean}
473 */517 */
public/scripts/openai.js+251 -200
@@ -18,7 +18,6 @@ import {
18 getMediaDisplay,18 getMediaDisplay,
19 getMediaIndex,19 getMediaIndex,
20 getRequestHeaders,20 getRequestHeaders,
21 getStoppingStrings,
22 is_send_press,21 is_send_press,
23 main_api,22 main_api,
24 name1,23 name1,
@@ -1565,62 +1564,63 @@ function checkModerationError(data, { quiet = false } = {}) {
15651564
1566/**1565/**
1567 * Gets the API model for the selected chat completion source.1566 * Gets the API model for the selected chat completion source.
1568 * @param {string} source If it's set, ignores active source1567 * @param {ChatCompletionSettings} settings Chat completion settings
1569 * @returns {string} API model1568 * @returns {string} API model
1570 */1569 */
1571export function getChatCompletionModel(source = null) {1570export function getChatCompletionModel(settings = null) {
1572 const activeSource = source ?? oai_settings.chat_completion_source;1571 settings = settings ?? oai_settings;
1573 switch (activeSource) {1572 const source = settings.chat_completion_source;
1573 switch (source) {
1574 case chat_completion_sources.CLAUDE:1574 case chat_completion_sources.CLAUDE:
1575 return oai_settings.claude_model;1575 return settings.claude_model;
1576 case chat_completion_sources.OPENAI:1576 case chat_completion_sources.OPENAI:
1577 return oai_settings.openai_model;1577 return settings.openai_model;
1578 case chat_completion_sources.MAKERSUITE:1578 case chat_completion_sources.MAKERSUITE:
1579 return oai_settings.google_model;1579 return settings.google_model;
1580 case chat_completion_sources.VERTEXAI:1580 case chat_completion_sources.VERTEXAI:
1581 return oai_settings.vertexai_model;1581 return settings.vertexai_model;
1582 case chat_completion_sources.OPENROUTER:1582 case chat_completion_sources.OPENROUTER:
1583 return oai_settings.openrouter_model !== openrouter_website_model ? oai_settings.openrouter_model : null;1583 return settings.openrouter_model !== openrouter_website_model ? settings.openrouter_model : null;
1584 case chat_completion_sources.AI21:1584 case chat_completion_sources.AI21:
1585 return oai_settings.ai21_model;1585 return settings.ai21_model;
1586 case chat_completion_sources.MISTRALAI:1586 case chat_completion_sources.MISTRALAI:
1587 return oai_settings.mistralai_model;1587 return settings.mistralai_model;
1588 case chat_completion_sources.CUSTOM:1588 case chat_completion_sources.CUSTOM:
1589 return oai_settings.custom_model;1589 return settings.custom_model;
1590 case chat_completion_sources.COHERE:1590 case chat_completion_sources.COHERE:
1591 return oai_settings.cohere_model;1591 return settings.cohere_model;
1592 case chat_completion_sources.PERPLEXITY:1592 case chat_completion_sources.PERPLEXITY:
1593 return oai_settings.perplexity_model;1593 return settings.perplexity_model;
1594 case chat_completion_sources.GROQ:1594 case chat_completion_sources.GROQ:
1595 return oai_settings.groq_model;1595 return settings.groq_model;
1596 case chat_completion_sources.SILICONFLOW:1596 case chat_completion_sources.SILICONFLOW:
1597 return oai_settings.siliconflow_model;1597 return settings.siliconflow_model;
1598 case chat_completion_sources.ELECTRONHUB:1598 case chat_completion_sources.ELECTRONHUB:
1599 return oai_settings.electronhub_model;1599 return settings.electronhub_model;
1600 case chat_completion_sources.CHUTES:1600 case chat_completion_sources.CHUTES:
1601 return oai_settings.chutes_model;1601 return settings.chutes_model;
1602 case chat_completion_sources.NANOGPT:1602 case chat_completion_sources.NANOGPT:
1603 return oai_settings.nanogpt_model;1603 return settings.nanogpt_model;
1604 case chat_completion_sources.DEEPSEEK:1604 case chat_completion_sources.DEEPSEEK:
1605 return oai_settings.deepseek_model;1605 return settings.deepseek_model;
1606 case chat_completion_sources.AIMLAPI:1606 case chat_completion_sources.AIMLAPI:
1607 return oai_settings.aimlapi_model;1607 return settings.aimlapi_model;
1608 case chat_completion_sources.XAI:1608 case chat_completion_sources.XAI:
1609 return oai_settings.xai_model;1609 return settings.xai_model;
1610 case chat_completion_sources.POLLINATIONS:1610 case chat_completion_sources.POLLINATIONS:
1611 return oai_settings.pollinations_model;1611 return settings.pollinations_model;
1612 case chat_completion_sources.COMETAPI:1612 case chat_completion_sources.COMETAPI:
1613 return oai_settings.cometapi_model;1613 return settings.cometapi_model;
1614 case chat_completion_sources.MOONSHOT:1614 case chat_completion_sources.MOONSHOT:
1615 return oai_settings.moonshot_model;1615 return settings.moonshot_model;
1616 case chat_completion_sources.FIREWORKS:1616 case chat_completion_sources.FIREWORKS:
1617 return oai_settings.fireworks_model;1617 return settings.fireworks_model;
1618 case chat_completion_sources.AZURE_OPENAI:1618 case chat_completion_sources.AZURE_OPENAI:
1619 return oai_settings.azure_openai_model;1619 return settings.azure_openai_model;
1620 case chat_completion_sources.ZAI:1620 case chat_completion_sources.ZAI:
1621 return oai_settings.zai_model;1621 return settings.zai_model;
1622 default:1622 default:
1623 console.error(`Unknown chat completion source: ${activeSource}`);1623 console.error(`Unknown chat completion source: ${source}`);
1624 return '';1624 return '';
1625 }1625 }
1626}1626}
@@ -2300,7 +2300,16 @@ function getAimlapiModelTemplate(option) {
2300 `));2300 `));
2301}2301}
23022302
2303function getReasoningEffort() {2303/**
2304 * Get the reasoning effort from chat completion settings
2305 * @param {ChatCompletionSettings} settings Chat completion settings
2306 * @param {string} model Model name (optional, used for ElectronHub)
2307 * @returns {string} Reasoning effort, if present
2308 */
2309function getReasoningEffort(settings = null, model = null) {
2310 settings = settings ?? oai_settings;
2311 model = model ?? getChatCompletionModel(settings);
2312
2304 // These sources expect the effort as string.2313 // These sources expect the effort as string.
2305 const reasoningEffortSources = [2314 const reasoningEffortSources = [
2306 chat_completion_sources.OPENAI,2315 chat_completion_sources.OPENAI,
@@ -2316,31 +2325,31 @@ function getReasoningEffort() {
2316 chat_completion_sources.CHUTES,2325 chat_completion_sources.CHUTES,
2317 ];2326 ];
23182327
2319 if (!reasoningEffortSources.includes(oai_settings.chat_completion_source)) {2328 if (!reasoningEffortSources.includes(settings.chat_completion_source)) {
2320 return oai_settings.reasoning_effort;2329 return settings.reasoning_effort;
2321 }2330 }
23222331
2323 function resolveReasoningEffort() {2332 function resolveReasoningEffort() {
2324 switch (oai_settings.reasoning_effort) {2333 switch (settings.reasoning_effort) {
2325 case reasoning_effort_types.auto:2334 case reasoning_effort_types.auto:
2326 return undefined;2335 return undefined;
2327 case reasoning_effort_types.min:2336 case reasoning_effort_types.min:
2328 return [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(oai_settings.chat_completion_source) && /^gpt-5/.test(getChatCompletionModel())2337 return [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source) && /^gpt-5/.test(model)
2329 ? reasoning_effort_types.min2338 ? reasoning_effort_types.min
2330 : reasoning_effort_types.low;2339 : reasoning_effort_types.low;
2331 case reasoning_effort_types.max:2340 case reasoning_effort_types.max:
2332 return reasoning_effort_types.high;2341 return reasoning_effort_types.high;
2333 default:2342 default:
2334 return oai_settings.reasoning_effort;2343 return settings.reasoning_effort;
2335 }2344 }
2336 }2345 }
23372346
2338 const reasoningEffort = resolveReasoningEffort();2347 const reasoningEffort = resolveReasoningEffort();
23392348
2340 // Check if the resolved effort supported by the model2349 // Check if the resolved effort supported by the model
2341 if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {2350 if (settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
2342 if (Array.isArray(model_list) && reasoningEffort) {2351 if (Array.isArray(model_list) && reasoningEffort) {
2343 const currentModel = model_list.find(m => m.id === oai_settings.electronhub_model);2352 const currentModel = model_list.find(m => m.id === model);
2344 const supportedEfforts = currentModel?.metadata?.supported_reasoning_efforts;2353 const supportedEfforts = currentModel?.metadata?.supported_reasoning_efforts;
2345 if (Array.isArray(supportedEfforts) && supportedEfforts.includes(reasoningEffort)) {2354 if (Array.isArray(supportedEfforts) && supportedEfforts.includes(reasoningEffort)) {
2346 return reasoningEffort;2355 return reasoningEffort;
@@ -2352,72 +2361,117 @@ function getReasoningEffort() {
2352 return reasoningEffort;2361 return reasoningEffort;
2353}2362}
23542363
2355function getVerbosity() {2364/**
2356 if (oai_settings.verbosity === verbosity_levels.auto) {2365 * Get the verbosity from chat completion settings
2366 * @param {ChatCompletionSettings} settings Chat completion settings
2367 * @returns {string} Verbosity level, if present
2368 */
2369function getVerbosity(settings = null) {
2370 settings = settings ?? oai_settings;
2371
2372 if (settings.verbosity === verbosity_levels.auto) {
2357 return undefined;2373 return undefined;
2358 }2374 }
23592375
2360 // TODO: Adjust verbosity based on model capabilities2376 // TODO: Adjust verbosity based on model capabilities
2361 return oai_settings.verbosity;2377 return settings.verbosity;
2362}2378}
23632379
2364/**2380/**
2365 * Send a chat completion request to backend2381 * Build the generation parameter object for an OAI request.
2366 * @param {string} type (impersonate, quiet, continue, etc)2382 * @param {ChatCompletionSettings} settings Initial chat completion settings
2367 * @param {Array} messages2383 * @param {string} model Model name
2368 * @param {AbortSignal?} signal2384 * @param {string} type Request type (impersonate, quiet, continue, etc)
2369 * @param {import('../script.js').AdditionalRequestOptions} options2385 * @param {ChatCompletionMessage[]} messages Array of chat completion messages
2370 * @returns {Promise<unknown>}2386 * @param {import('../script.js').AdditionalRequestOptions} options Additional request options
2371 * @throws {Error}2387 * @returns {Promise<object>} Final generation parameters object appropriate for the chat completion source
2372 */2388 */
23732389export async function createGenerationParameters(settings, model, type, messages, { jsonSchema = null } = {}) {
2374async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = {}) {
2375 // Provide default abort signal
2376 if (!signal) {
2377 signal = new AbortController().signal;
2378 }
2379
2380 // HACK: Filter out null and non-object messages2390 // HACK: Filter out null and non-object messages
2381 if (!Array.isArray(messages)) {2391 if (!Array.isArray(messages)) {
2382 throw new Error('messages must be an array');2392 throw new Error('messages must be an array');
2383 }2393 }
2384
2385 messages = messages.filter(msg => msg && typeof msg === 'object');2394 messages = messages.filter(msg => msg && typeof msg === 'object');
23862395
2387 let logit_bias = {};2396 // "OpenAI-like" sources
2388 const isClaude = oai_settings.chat_completion_source == chat_completion_sources.CLAUDE;2397 const gptSources = [
2389 const isOpenRouter = oai_settings.chat_completion_source == chat_completion_sources.OPENROUTER;2398 chat_completion_sources.OPENAI,
2390 const isGoogle = oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE;2399 chat_completion_sources.AZURE_OPENAI,
2391 const isVertexAI = oai_settings.chat_completion_source == chat_completion_sources.VERTEXAI;2400 chat_completion_sources.OPENROUTER,
2392 const isOAI = oai_settings.chat_completion_source == chat_completion_sources.OPENAI;2401 ];
2393 const isMistral = oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI;2402
2394 const isCustom = oai_settings.chat_completion_source == chat_completion_sources.CUSTOM;2403 // Sources that support the "seed" parameter
2395 const isCohere = oai_settings.chat_completion_source == chat_completion_sources.COHERE;2404 const seedSupportedSources = [
2396 const isPerplexity = oai_settings.chat_completion_source == chat_completion_sources.PERPLEXITY;2405 chat_completion_sources.OPENAI,
2397 const isGroq = oai_settings.chat_completion_source == chat_completion_sources.GROQ;2406 chat_completion_sources.AZURE_OPENAI,
2398 const isDeepSeek = oai_settings.chat_completion_source == chat_completion_sources.DEEPSEEK;2407 chat_completion_sources.OPENROUTER,
2399 const isAimlapi = oai_settings.chat_completion_source == chat_completion_sources.AIMLAPI;2408 chat_completion_sources.MISTRALAI,
2400 const isElectronHub = oai_settings.chat_completion_source == chat_completion_sources.ELECTRONHUB;2409 chat_completion_sources.CUSTOM,
2401 const isChutes = oai_settings.chat_completion_source == chat_completion_sources.CHUTES;2410 chat_completion_sources.COHERE,
2402 const isXAI = oai_settings.chat_completion_source == chat_completion_sources.XAI;2411 chat_completion_sources.GROQ,
2403 const isPollinations = oai_settings.chat_completion_source == chat_completion_sources.POLLINATIONS;2412 chat_completion_sources.ELECTRONHUB,
2404 const isMoonshot = oai_settings.chat_completion_source == chat_completion_sources.MOONSHOT;2413 chat_completion_sources.NANOGPT,
2405 const isAzureOpenAI = oai_settings.chat_completion_source == chat_completion_sources.AZURE_OPENAI;2414 chat_completion_sources.XAI,
2406 const isZai = oai_settings.chat_completion_source == chat_completion_sources.ZAI;2415 chat_completion_sources.POLLINATIONS,
2407 const isNanoGPT = oai_settings.chat_completion_source == chat_completion_sources.NANOGPT;2416 chat_completion_sources.AIMLAPI,
2408 const isTextCompletion = isOAI && textCompletionModels.includes(oai_settings.openai_model);2417 chat_completion_sources.VERTEXAI,
2409 const isQuiet = type === 'quiet';2418 chat_completion_sources.MAKERSUITE,
2410 const isImpersonate = type === 'impersonate';2419 chat_completion_sources.CHUTES,
2411 const isContinue = type === 'continue';2420 ];
2412 const stream = oai_settings.stream_openai && !isQuiet && !((isOAI || isAzureOpenAI) && ['o1-2024-12-17', 'o1'].includes(getChatCompletionModel()));2421
2413 const useLogprobs = !!power_user.request_token_probabilities;2422 // Sources that support proxying
2414 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isAzureOpenAI || isCustom || isXAI || isAimlapi || isMoonshot);2423 const proxySupportedSources = [
2424 chat_completion_sources.CLAUDE,
2425 chat_completion_sources.OPENAI,
2426 chat_completion_sources.MISTRALAI,
2427 chat_completion_sources.MAKERSUITE,
2428 chat_completion_sources.VERTEXAI,
2429 chat_completion_sources.DEEPSEEK,
2430 chat_completion_sources.XAI,
2431 ];
2432
2433 // Sources that support logprobs
2434 const logprobsSupportedSources = [
2435 chat_completion_sources.OPENAI,
2436 chat_completion_sources.AZURE_OPENAI,
2437 chat_completion_sources.CUSTOM,
2438 chat_completion_sources.DEEPSEEK,
2439 chat_completion_sources.XAI,
2440 chat_completion_sources.AIMLAPI,
2441 chat_completion_sources.CHUTES,
2442 ];
2443
2444 // Sources that support logit bias
2445 const logitBiasSources = [
2446 chat_completion_sources.OPENAI,
2447 chat_completion_sources.AZURE_OPENAI,
2448 chat_completion_sources.OPENROUTER,
2449 chat_completion_sources.ELECTRONHUB,
2450 chat_completion_sources.CHUTES,
2451 chat_completion_sources.CUSTOM,
2452 ];
2453
2454 // Sources that support "n" parameter for multi-swipe
2455 const multiswipeSources = [
2456 chat_completion_sources.OPENAI,
2457 chat_completion_sources.AZURE_OPENAI,
2458 chat_completion_sources.CUSTOM,
2459 chat_completion_sources.XAI,
2460 chat_completion_sources.AIMLAPI,
2461 chat_completion_sources.MOONSHOT,
2462 ];
2463
2464 const isO1 = gptSources.includes(settings.chat_completion_source) && ['o1-2024-12-17', 'o1'].includes(model);
2465 const stream = settings.stream_openai && type !== 'quiet' && !isO1;
2466
2467 const noMultiSwipeTypes = ['quiet', 'impersonate', 'continue'];
2468 const canMultiSwipe = settings.n > 1 && !noMultiSwipeTypes.includes(type) && multiswipeSources.includes(settings.chat_completion_source);
24152469
2416 const logitBiasSources = [chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI, chat_completion_sources.OPENROUTER, chat_completion_sources.ELECTRONHUB, chat_completion_sources.CHUTES, chat_completion_sources.CUSTOM];2470 let logit_bias = {};
2417 if (oai_settings.bias_preset_selected2471 if (settings.bias_preset_selected
2418 && logitBiasSources.includes(oai_settings.chat_completion_source)2472 && logitBiasSources.includes(settings.chat_completion_source)
2419 && Array.isArray(oai_settings.bias_presets[oai_settings.bias_preset_selected])2473 && Array.isArray(settings.bias_presets[settings.bias_preset_selected])
2420 && oai_settings.bias_presets[oai_settings.bias_preset_selected].length) {2474 && settings.bias_presets[settings.bias_preset_selected].length) {
2421 logit_bias = biasCache || await calculateLogitBias();2475 logit_bias = biasCache || await calculateLogitBias();
2422 biasCache = logit_bias;2476 biasCache = logit_bias;
2423 }2477 }
@@ -2426,45 +2480,44 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2426 logit_bias = undefined;2480 logit_bias = undefined;
2427 }2481 }
24282482
2429 const model = getChatCompletionModel();
2430 const generate_data = {2483 const generate_data = {
2431 'type': type,2484 'type': type,
2432 'messages': messages,2485 'messages': messages,
2433 'model': model,2486 'model': model,
2434 'temperature': Number(oai_settings.temp_openai),2487 'temperature': Number(settings.temp_openai),
2435 'frequency_penalty': Number(oai_settings.freq_pen_openai),2488 'frequency_penalty': Number(settings.freq_pen_openai),
2436 'presence_penalty': Number(oai_settings.pres_pen_openai),2489 'presence_penalty': Number(settings.pres_pen_openai),
2437 'top_p': Number(oai_settings.top_p_openai),2490 'top_p': Number(settings.top_p_openai),
2438 'max_tokens': oai_settings.openai_max_tokens,2491 'max_tokens': settings.openai_max_tokens,
2439 'stream': stream,2492 'stream': stream,
2440 'logit_bias': logit_bias,2493 'logit_bias': logit_bias,
2441 'stop': getCustomStoppingStrings(openai_max_stop_strings),2494 'stop': getCustomStoppingStrings(openai_max_stop_strings),
2442 'chat_completion_source': oai_settings.chat_completion_source,2495 'chat_completion_source': settings.chat_completion_source,
2443 'n': canMultiSwipe ? oai_settings.n : undefined,2496 'n': canMultiSwipe ? settings.n : undefined,
2444 'user_name': name1,2497 'user_name': name1,
2445 'char_name': name2,2498 'char_name': name2,
2446 'group_names': getGroupNames(),2499 'group_names': getGroupNames(),
2447 'include_reasoning': Boolean(oai_settings.show_thoughts),2500 'include_reasoning': Boolean(settings.show_thoughts),
2448 'reasoning_effort': getReasoningEffort(),2501 'reasoning_effort': getReasoningEffort(settings, model),
2449 'enable_web_search': Boolean(oai_settings.enable_web_search),2502 'enable_web_search': Boolean(settings.enable_web_search),
2450 'request_images': Boolean(oai_settings.request_images),2503 'request_images': Boolean(settings.request_images),
2451 'request_image_resolution': String(oai_settings.request_image_resolution),2504 'request_image_resolution': String(settings.request_image_resolution),
2452 'request_image_aspect_ratio': String(oai_settings.request_image_aspect_ratio),2505 'request_image_aspect_ratio': String(settings.request_image_aspect_ratio),
2453 'custom_prompt_post_processing': oai_settings.custom_prompt_post_processing,2506 'custom_prompt_post_processing': settings.custom_prompt_post_processing,
2454 'verbosity': getVerbosity(),2507 'verbosity': getVerbosity(settings),
2455 };2508 };
24562509
2457 if (isAzureOpenAI) {2510 if (settings.chat_completion_source === chat_completion_sources.AZURE_OPENAI) {
2458 generate_data.azure_base_url = oai_settings.azure_base_url;2511 generate_data.azure_base_url = settings.azure_base_url;
2459 generate_data.azure_deployment_name = oai_settings.azure_deployment_name;2512 generate_data.azure_deployment_name = settings.azure_deployment_name;
2460 generate_data.azure_api_version = oai_settings.azure_api_version;2513 generate_data.azure_api_version = settings.azure_api_version;
2461 // Reasoning effort is not supported on some Azure models (e.g. GPT-3.x, GPT-4.x)2514 // Reasoning effort is not supported on some Azure models (e.g. GPT-3.x, GPT-4.x)
2462 if (/^gpt-[34]/.test(oai_settings.azure_openai_model)) {2515 if (/^gpt-[34]/.test(model)) {
2463 delete generate_data.reasoning_effort;2516 delete generate_data.reasoning_effort;
2464 }2517 }
2465 }2518 }
24662519
2467 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type)) {2520 if (!canMultiSwipe && ToolManager.canPerformToolCalls(type, settings, model)) {
2468 await ToolManager.registerFunctionToolsOpenAI(generate_data);2521 await ToolManager.registerFunctionToolsOpenAI(generate_data);
2469 }2522 }
24702523
@@ -2473,99 +2526,95 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2473 delete generate_data.stop;2526 delete generate_data.stop;
2474 }2527 }
24752528
2476 // Proxy is only supported for Claude, OpenAI, Mistral, Google MakerSuite, and Vertex AI2529 if (settings.reverse_proxy && proxySupportedSources.includes(settings.chat_completion_source)) {
2477 if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
2478 await validateReverseProxy();2530 await validateReverseProxy();
2479 generate_data['reverse_proxy'] = oai_settings.reverse_proxy;2531 generate_data['reverse_proxy'] = settings.reverse_proxy;
2480 generate_data['proxy_password'] = oai_settings.proxy_password;2532 generate_data['proxy_password'] = settings.proxy_password;
2481 }2533 }
24822534
2483 // Add logprobs request (currently OpenAI only, max 5 on their side)2535 // Add logprobs request (max 5 per OpenAI docs)
2484 if (useLogprobs && (isOAI || isAzureOpenAI || isCustom || isDeepSeek || isXAI || isAimlapi || isChutes)) {2536 const useLogprobs = !!power_user.request_token_probabilities;
2537 if (useLogprobs && logprobsSupportedSources.includes(settings.chat_completion_source)) {
2485 generate_data['logprobs'] = 5;2538 generate_data['logprobs'] = 5;
2486 }2539 }
24872540
2488 // Remove logit bias/logprobs/stop-strings if not supported by the model2541 // Remove logit bias/logprobs/stop-strings if not supported by the model
2489 const isVision = (m) => ['gpt', 'vision'].every(x => m.includes(x));2542 const isVision = (m) => ['gpt', 'vision'].every(x => m.includes(x));
2490 if ((isOAI && isVision(oai_settings.openai_model)) || (isAzureOpenAI && isVision(oai_settings.azure_openai_model)) || (isOpenRouter && isVision(oai_settings.openrouter_model))) {2543 if (gptSources.includes(settings.chat_completion_source) && isVision(model)) {
2491 delete generate_data.logit_bias;2544 delete generate_data.logit_bias;
2492 delete generate_data.stop;2545 delete generate_data.stop;
2493 delete generate_data.logprobs;2546 delete generate_data.logprobs;
2494 }2547 }
2495 if ((isOAI && oai_settings.openai_model.includes('gpt-4.5')) || (isAzureOpenAI && oai_settings.azure_openai_model.includes('gpt-4.5')) || (isOpenRouter && oai_settings.openrouter_model.includes('gpt-4.5'))) {2548 if (gptSources.includes(settings.chat_completion_source) && model.includes('gpt-4.5')) {
2496 delete generate_data.logprobs;2549 delete generate_data.logprobs;
2497 }2550 }
24982551
2499 if (isClaude) {2552 if (settings.chat_completion_source === chat_completion_sources.CLAUDE) {
2500 generate_data['top_k'] = Number(oai_settings.top_k_openai);2553 generate_data['top_k'] = Number(settings.top_k_openai);
2501 generate_data['use_sysprompt'] = oai_settings.use_sysprompt;2554 generate_data['use_sysprompt'] = settings.use_sysprompt;
2502 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.2555 generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
2503 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.2556 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
2504 if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {2557 if (type !== 'quiet' && !(type === 'continue' && settings.continue_prefill)) {
2505 generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);2558 generate_data['assistant_prefill'] = type === 'impersonate'
2559 ? substituteParams(settings.assistant_impersonation)
2560 : substituteParams(settings.assistant_prefill);
2506 }2561 }
2507 }2562 }
25082563
2509 if (isOpenRouter) {2564 if (settings.chat_completion_source === chat_completion_sources.OPENROUTER) {
2510 generate_data['top_k'] = Number(oai_settings.top_k_openai);2565 generate_data['top_k'] = Number(settings.top_k_openai);
2511 generate_data['min_p'] = Number(oai_settings.min_p_openai);2566 generate_data['min_p'] = Number(settings.min_p_openai);
2512 generate_data['repetition_penalty'] = Number(oai_settings.repetition_penalty_openai);2567 generate_data['repetition_penalty'] = Number(settings.repetition_penalty_openai);
2513 generate_data['top_a'] = Number(oai_settings.top_a_openai);2568 generate_data['top_a'] = Number(settings.top_a_openai);
2514 generate_data['use_fallback'] = oai_settings.openrouter_use_fallback;2569 generate_data['use_fallback'] = settings.openrouter_use_fallback;
2515 generate_data['provider'] = oai_settings.openrouter_providers;2570 generate_data['provider'] = settings.openrouter_providers;
2516 generate_data['allow_fallbacks'] = oai_settings.openrouter_allow_fallbacks;2571 generate_data['allow_fallbacks'] = settings.openrouter_allow_fallbacks;
2517 generate_data['middleout'] = oai_settings.openrouter_middleout;2572 generate_data['middleout'] = settings.openrouter_middleout;
2518
2519 if (isTextCompletion) {
2520 generate_data['stop'] = getStoppingStrings(isImpersonate, isContinue);
2521 }
2522 }2573 }
25232574
2524 if (isGoogle || isVertexAI) {2575 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {
2525 const stopStringsLimit = 5;2576 const stopStringsLimit = 5;
2526 generate_data['top_k'] = Number(oai_settings.top_k_openai);2577 generate_data['top_k'] = Number(settings.top_k_openai);
2527 generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);2578 generate_data['stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);
2528 generate_data['use_sysprompt'] = oai_settings.use_sysprompt;2579 generate_data['use_sysprompt'] = settings.use_sysprompt;
2529 if (isVertexAI) {2580 if (settings.chat_completion_source === chat_completion_sources.VERTEXAI) {
2530 generate_data['vertexai_auth_mode'] = oai_settings.vertexai_auth_mode;2581 generate_data['vertexai_auth_mode'] = settings.vertexai_auth_mode;
2531 generate_data['vertexai_region'] = oai_settings.vertexai_region;2582 generate_data['vertexai_region'] = settings.vertexai_region;
2532 generate_data['vertexai_express_project_id'] = oai_settings.vertexai_express_project_id;2583 generate_data['vertexai_express_project_id'] = settings.vertexai_express_project_id;
2533 }2584 }
2534 }2585 }
25352586
2536 if (isMistral) {2587 if (settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
2537 generate_data['safe_prompt'] = false; // already defaults to false, but just incase they change that in the future.2588 generate_data['safe_prompt'] = false; // already defaults to false, but just incase they change that in the future.
2538 generate_data['stop'] = getCustomStoppingStrings(); // Mistral shouldn't have limits on stop strings.2589 generate_data['stop'] = getCustomStoppingStrings(); // Mistral shouldn't have limits on stop strings.
2539 }2590 }
25402591
2541 if (isCustom) {2592 if (settings.chat_completion_source === chat_completion_sources.CUSTOM) {
2542 generate_data['custom_url'] = oai_settings.custom_url;2593 generate_data['custom_url'] = settings.custom_url;
2543 generate_data['custom_include_body'] = oai_settings.custom_include_body;2594 generate_data['custom_include_body'] = settings.custom_include_body;
2544 generate_data['custom_exclude_body'] = oai_settings.custom_exclude_body;2595 generate_data['custom_exclude_body'] = settings.custom_exclude_body;
2545 generate_data['custom_include_headers'] = oai_settings.custom_include_headers;2596 generate_data['custom_include_headers'] = settings.custom_include_headers;
2546 }2597 }
25472598
2548 if (isCohere) {2599 if (settings.chat_completion_source === chat_completion_sources.COHERE) {
2549 // Clamp to 0.01 -> 0.992600 // Clamp to 0.01 -> 0.99
2550 generate_data['top_p'] = Math.min(Math.max(Number(oai_settings.top_p_openai), 0.01), 0.99);2601 generate_data['top_p'] = Math.min(Math.max(Number(settings.top_p_openai), 0.01), 0.99);
2551 generate_data['top_k'] = Number(oai_settings.top_k_openai);2602 generate_data['top_k'] = Number(settings.top_k_openai);
2552 // Clamp to 0 -> 12603 // Clamp to 0 -> 1
2553 generate_data['frequency_penalty'] = Math.min(Math.max(Number(oai_settings.freq_pen_openai), 0), 1);2604 generate_data['frequency_penalty'] = Math.min(Math.max(Number(settings.freq_pen_openai), 0), 1);
2554 generate_data['presence_penalty'] = Math.min(Math.max(Number(oai_settings.pres_pen_openai), 0), 1);2605 generate_data['presence_penalty'] = Math.min(Math.max(Number(settings.pres_pen_openai), 0), 1);
2555 generate_data['stop'] = getCustomStoppingStrings(5);2606 generate_data['stop'] = getCustomStoppingStrings(5);
2556 }2607 }
25572608
2558 if (isPerplexity) {2609 if (settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {
2559 generate_data['top_k'] = Number(oai_settings.top_k_openai);2610 generate_data['top_k'] = Number(settings.top_k_openai);
2560 generate_data['frequency_penalty'] = Number(oai_settings.freq_pen_openai);2611 generate_data['frequency_penalty'] = Number(settings.freq_pen_openai);
2561 generate_data['presence_penalty'] = Number(oai_settings.pres_pen_openai);2612 generate_data['presence_penalty'] = Number(settings.pres_pen_openai);
2562
2563 // YEAH BRO JUST USE OPENAI CLIENT BRO
2564 delete generate_data['stop'];2613 delete generate_data['stop'];
2565 }2614 }
25662615
2567 // https://console.groq.com/docs/openai2616 // https://console.groq.com/docs/openai
2568 if (isGroq) {2617 if (settings.chat_completion_source === chat_completion_sources.GROQ) {
2569 delete generate_data.logprobs;2618 delete generate_data.logprobs;
2570 delete generate_data.logit_bias;2619 delete generate_data.logit_bias;
2571 delete generate_data.top_logprobs;2620 delete generate_data.top_logprobs;
@@ -2573,12 +2622,11 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2573 }2622 }
25742623
2575 // https://api-docs.deepseek.com/api/create-chat-completion2624 // https://api-docs.deepseek.com/api/create-chat-completion
2576 if (isDeepSeek) {2625 if (settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
2577 generate_data.top_p = generate_data.top_p || Number.EPSILON;2626 generate_data.top_p = generate_data.top_p || Number.EPSILON;
2578 }2627 }
25792628
2580 if (isXAI) {2629 if (settings.chat_completion_source === chat_completion_sources.XAI) {
2581 const model = generate_data.model;
2582 if (model.includes('grok-3-mini')) {2630 if (model.includes('grok-3-mini')) {
2583 delete generate_data.presence_penalty;2631 delete generate_data.presence_penalty;
2584 delete generate_data.frequency_penalty;2632 delete generate_data.frequency_penalty;
@@ -2599,61 +2647,44 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2599 }2647 }
2600 }2648 }
26012649
2602 if (isPollinations) {2650 if (settings.chat_completion_source === chat_completion_sources.POLLINATIONS) {
2603 delete generate_data.max_tokens;2651 delete generate_data.max_tokens;
2604 }2652 }
26052653
2606 // https://docs.electronhub.ai/api-reference/chat/completions2654 // https://docs.electronhub.ai/api-reference/chat/completions
2607 if (isElectronHub) {2655 if (settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
2608 generate_data['top_k'] = Number(oai_settings.top_k_openai);2656 generate_data['top_k'] = Number(settings.top_k_openai);
2609 }2657 }
26102658
2611 if (isChutes) {2659 if (settings.chat_completion_source === chat_completion_sources.CHUTES) {
2612 generate_data['min_p'] = Number(oai_settings.min_p_openai);2660 generate_data['min_p'] = Number(settings.min_p_openai);
2613 generate_data['top_k'] = oai_settings.top_k_openai > 0 ? Number(oai_settings.top_k_openai) : undefined;2661 generate_data['top_k'] = settings.top_k_openai > 0 ? Number(settings.top_k_openai) : undefined;
2614 generate_data['repetition_penalty'] = Number(oai_settings.repetition_penalty_openai);2662 generate_data['repetition_penalty'] = Number(settings.repetition_penalty_openai);
2615 generate_data['seed'] = oai_settings.seed >= 0 ? oai_settings.seed : undefined;
2616 generate_data['stop'] = getCustomStoppingStrings();2663 generate_data['stop'] = getCustomStoppingStrings();
2617 }2664 }
26182665
2619 // https://docs.z.ai/api-reference/llm/chat-completion2666 // https://docs.z.ai/api-reference/llm/chat-completion
2620 if (isZai) {2667 if (settings.chat_completion_source === chat_completion_sources.ZAI) {
2621 generate_data['top_p'] = generate_data.top_p || 0.01;2668 generate_data['top_p'] = generate_data.top_p || 0.01;
2622 generate_data['stop'] = getCustomStoppingStrings(1);2669 generate_data['stop'] = getCustomStoppingStrings(1);
2623 generate_data['zai_endpoint'] = oai_settings.zai_endpoint || ZAI_ENDPOINT.COMMON;2670 generate_data['zai_endpoint'] = settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
2624 delete generate_data.presence_penalty;2671 delete generate_data.presence_penalty;
2625 delete generate_data.frequency_penalty;2672 delete generate_data.frequency_penalty;
2626 }2673 }
26272674
2628 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus2675 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus
2629 if (isNanoGPT) {2676 if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
2630 generate_data['top_k'] = Number(oai_settings.top_k_openai);2677 generate_data['top_k'] = Number(settings.top_k_openai);
2631 generate_data['min_p'] = Number(oai_settings.min_p_openai);2678 generate_data['min_p'] = Number(settings.min_p_openai);
2632 generate_data['repetition_penalty'] = Number(oai_settings.repetition_penalty_openai);2679 generate_data['repetition_penalty'] = Number(settings.repetition_penalty_openai);
2633 generate_data['top_a'] = Number(oai_settings.top_a_openai);2680 generate_data['top_a'] = Number(settings.top_a_openai);
2634 }2681 }
26352682
2636 const seedSupportedSources = [2683 if (seedSupportedSources.includes(settings.chat_completion_source) && settings.seed >= 0) {
2637 chat_completion_sources.OPENAI,2684 generate_data['seed'] = settings.seed;
2638 chat_completion_sources.AZURE_OPENAI,
2639 chat_completion_sources.OPENROUTER,
2640 chat_completion_sources.MISTRALAI,
2641 chat_completion_sources.CUSTOM,
2642 chat_completion_sources.COHERE,
2643 chat_completion_sources.GROQ,
2644 chat_completion_sources.ELECTRONHUB,
2645 chat_completion_sources.NANOGPT,
2646 chat_completion_sources.XAI,
2647 chat_completion_sources.POLLINATIONS,
2648 chat_completion_sources.AIMLAPI,
2649 chat_completion_sources.VERTEXAI,
2650 chat_completion_sources.MAKERSUITE,
2651 ];
2652 if (seedSupportedSources.includes(oai_settings.chat_completion_source) && oai_settings.seed >= 0) {
2653 generate_data['seed'] = oai_settings.seed;
2654 }2685 }
26552686
2656 if ((isOAI && /^(o1|o3|o4)/.test(model)) || (isAzureOpenAI && /^(o1|o3|o4)/.test(model))) {2687 if (gptSources.includes(settings.chat_completion_source) && /^(o1|o3|o4)/.test(model)) {
2657 generate_data.max_completion_tokens = generate_data.max_tokens;2688 generate_data.max_completion_tokens = generate_data.max_tokens;
2658 delete generate_data.max_tokens;2689 delete generate_data.max_tokens;
2659 delete generate_data.logprobs;2690 delete generate_data.logprobs;
@@ -2676,7 +2707,7 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2676 }2707 }
2677 }2708 }
26782709
2679 if ((isOAI && /^gpt-5/.test(model)) || (isAzureOpenAI && /^gpt-5/.test(model))) {2710 if (gptSources.includes(settings.chat_completion_source) && /^gpt-5/.test(model)) {
2680 generate_data.max_completion_tokens = generate_data.max_tokens;2711 generate_data.max_completion_tokens = generate_data.max_tokens;
2681 delete generate_data.max_tokens;2712 delete generate_data.max_tokens;
2682 delete generate_data.logprobs;2713 delete generate_data.logprobs;
@@ -2703,6 +2734,26 @@ async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } =
2703 generate_data.json_schema = jsonSchema;2734 generate_data.json_schema = jsonSchema;
2704 }2735 }
27052736
2737 return { generate_data, stream, canMultiSwipe };
2738}
2739
2740/**
2741 * Send a chat completion request to backend
2742 * @param {string} type Request type (impersonate, quiet, continue, etc)
2743 * @param {ChatCompletionMessage[]} messages Array of chat completion messages
2744 * @param {AbortSignal?} signal Abort signal for request cancellation
2745 * @param {import('../script.js').AdditionalRequestOptions} options Additional request options
2746 * @returns {Promise<unknown>}
2747 * @throws {Error}
2748 */
2749async function sendOpenAIRequest(type, messages, signal, { jsonSchema = null } = {}) {
2750 // Provide default abort signal
2751 if (!signal) {
2752 signal = new AbortController().signal;
2753 }
2754
2755 const model = getChatCompletionModel(oai_settings);
2756 const { generate_data, stream, canMultiSwipe } = await createGenerationParameters(oai_settings, model, type, messages, { jsonSchema });
2706 await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);2757 await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
27072758
2708 const generate_url = '/api/backends/chat-completions/generate';2759 const generate_url = '/api/backends/chat-completions/generate';
public/scripts/reasoning.js+19 -4
@@ -1220,23 +1220,38 @@ export function removeReasoningFromString(str) {
1220}1220}
12211221
1222/**1222/**
1223 * Parses reasoning from a string using the power user reasoning settings.1223 * Returns the reasoning template object from its name
1224 * @param {string} name of the template
1225 * @returns {ReasoningTemplate} the reasoning template object
1226 * @throws {Error}
1227 */
1228export function getReasoningTemplateByName(name) {
1229 const template = reasoning_templates.find(p => p.name === name);
1230 if (!template) throw new Error(`Unknown reasoning template name: "${name}"`);
1231 return template;
1232}
1233
1234/**
1235 * Parses reasoning from a string using the power user reasoning settings or optional template.
1224 * @typedef {Object} ParsedReasoning1236 * @typedef {Object} ParsedReasoning
1225 * @property {string} reasoning Reasoning block1237 * @property {string} reasoning Reasoning block
1226 * @property {string} content Message content1238 * @property {string} content Message content
1227 * @param {string} str Content of the message1239 * @param {string} str Content of the message
1228 * @param {Object} options Optional arguments1240 * @param {Object} options Optional arguments
1229 * @param {boolean} [options.strict=true] Whether the reasoning block **has** to be at the beginning of the provided string (excluding whitespaces), or can be anywhere in it1241 * @param {boolean} [options.strict=true] Whether the reasoning block **has** to be at the beginning of the provided string (excluding whitespaces), or can be anywhere in it
1242 * @param {ReasoningTemplate} template Optional reasoning template to use instead of power_user.reasoning
1230 * @returns {ParsedReasoning|null} Parsed reasoning block and message content1243 * @returns {ParsedReasoning|null} Parsed reasoning block and message content
1231 */1244 */
1232export function parseReasoningFromString(str, { strict = true } = {}) {1245export function parseReasoningFromString(str, { strict = true } = {}, template = null) {
1246 template = template ?? power_user.reasoning; // if no template given, use the currently selected template
1247
1233 // Both prefix and suffix must be defined1248 // Both prefix and suffix must be defined
1234 if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {1249 if (!template.prefix || !template.suffix) {
1235 return null;1250 return null;
1236 }1251 }
12371252
1238 try {1253 try {
1239 const regex = new RegExp(`${(strict ? '^\\s*?' : '')}${escapeRegex(power_user.reasoning.prefix)}(.*?)${escapeRegex(power_user.reasoning.suffix)}`, 's');1254 const regex = new RegExp(`${(strict ? '^\\s*?' : '')}${escapeRegex(template.prefix)}(.*?)${escapeRegex(template.suffix)}`, 's');
12401255
1241 let didReplace = false;1256 let didReplace = false;
1242 let reasoning = '';1257 let reasoning = '';
public/scripts/st-context.js+2 -1
@@ -99,7 +99,7 @@ import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariabl
99import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, reloadEditor, saveWorldInfo, updateWorldInfoList } from './world-info.js';99import { convertCharacterBook, getWorldInfoPrompt, loadWorldInfo, reloadEditor, saveWorldInfo, updateWorldInfoList } from './world-info.js';
100import { ChatCompletionService, TextCompletionService } from './custom-request.js';100import { ChatCompletionService, TextCompletionService } from './custom-request.js';
101import { ConnectionManagerRequestService } from './extensions/shared.js';101import { ConnectionManagerRequestService } from './extensions/shared.js';
102import { updateReasoningUI, parseReasoningFromString } from './reasoning.js';102import { updateReasoningUI, parseReasoningFromString, getReasoningTemplateByName } from './reasoning.js';
103import { IGNORE_SYMBOL } from './constants.js';103import { IGNORE_SYMBOL } from './constants.js';
104104
105export function getContext() {105export function getContext() {
@@ -259,6 +259,7 @@ export function getContext() {
259 ConnectionManagerRequestService,259 ConnectionManagerRequestService,
260 updateReasoningUI,260 updateReasoningUI,
261 parseReasoningFromString,261 parseReasoningFromString,
262 getReasoningTemplateByName,
262 unshallowCharacter,263 unshallowCharacter,
263 unshallowGroupMembers,264 unshallowGroupMembers,
264 openThirdPartyExtensionMenu,265 openThirdPartyExtensionMenu,
public/scripts/textgen-settings.js+147 -111
@@ -139,7 +139,7 @@ export const SERVER_INPUTS = {
139};139};
140140
141const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];141const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5];
142const settings = {142export const textgenerationwebui_settings = {
143 temp: 0.7,143 temp: 0.7,
144 temperature_last: true,144 temperature_last: true,
145 top_p: 0.5,145 top_p: 0.5,
@@ -231,7 +231,6 @@ const settings = {
231};231};
232232
233export {233export {
234 settings as textgenerationwebui_settings,
235 showSamplerControls as showTGSamplerControls,234 showSamplerControls as showTGSamplerControls,
236};235};
237236
@@ -318,7 +317,7 @@ export const setting_names = [
318const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');317const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
319318
320export function validateTextGenUrl() {319export function validateTextGenUrl() {
321 const selector = SERVER_INPUTS[settings.type];320 const selector = SERVER_INPUTS[textgenerationwebui_settings.type];
322321
323 if (!selector) {322 if (!selector) {
324 return;323 return;
@@ -342,7 +341,7 @@ export function validateTextGenUrl() {
342 * @returns {string} API URL341 * @returns {string} API URL
343 */342 */
344export function getTextGenServer(type = null) {343export function getTextGenServer(type = null) {
345 const selectedType = type ?? settings.type;344 const selectedType = type ?? textgenerationwebui_settings.type;
346 switch (selectedType) {345 switch (selectedType) {
347 case FEATHERLESS:346 case FEATHERLESS:
348 return FEATHERLESS_SERVER;347 return FEATHERLESS_SERVER;
@@ -357,7 +356,7 @@ export function getTextGenServer(type = null) {
357 case OPENROUTER:356 case OPENROUTER:
358 return OPENROUTER_SERVER;357 return OPENROUTER_SERVER;
359 default:358 default:
360 return settings.server_urls[selectedType] ?? '';359 return textgenerationwebui_settings.server_urls[selectedType] ?? '';
361 }360 }
362}361}
363362
@@ -368,7 +367,7 @@ async function selectPreset(name) {
368 return;367 return;
369 }368 }
370369
371 settings.preset = name;370 textgenerationwebui_settings.preset = name;
372 for (const name of setting_names) {371 for (const name of setting_names) {
373 const value = preset[name];372 const value = preset[name];
374 setSettingByName(name, value, true);373 setSettingByName(name, value, true);
@@ -382,7 +381,7 @@ async function selectPreset(name) {
382export function formatTextGenURL(value) {381export function formatTextGenURL(value) {
383 try {382 try {
384 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];383 const noFormatTypes = [MANCER, TOGETHERAI, INFERMATICAI, DREAMGEN, OPENROUTER];
385 if (noFormatTypes.includes(settings.type)) {384 if (noFormatTypes.includes(textgenerationwebui_settings.type)) {
386 return value;385 return value;
387 }386 }
388387
@@ -399,7 +398,7 @@ function convertPresets(presets) {
399}398}
400399
401function getTokenizerForTokenIds() {400function getTokenizerForTokenIds() {
402 if (power_user.tokenizer === tokenizers.API_CURRENT && TEXTGEN_TOKENIZERS.includes(settings.type)) {401 if (power_user.tokenizer === tokenizers.API_CURRENT && TEXTGEN_TOKENIZERS.includes(textgenerationwebui_settings.type)) {
403 return tokenizers.API_CURRENT;402 return tokenizers.API_CURRENT;
404 }403 }
405404
@@ -407,11 +406,11 @@ function getTokenizerForTokenIds() {
407 return power_user.tokenizer;406 return power_user.tokenizer;
408 }407 }
409408
410 if (settings.type === OPENROUTER) {409 if (textgenerationwebui_settings.type === OPENROUTER) {
411 return getCurrentOpenRouterModelTokenizer();410 return getCurrentOpenRouterModelTokenizer();
412 }411 }
413412
414 if (settings.type === DREAMGEN) {413 if (textgenerationwebui_settings.type === DREAMGEN) {
415 return getCurrentDreamGenModelTokenizer();414 return getCurrentDreamGenModelTokenizer();
416 }415 }
417416
@@ -419,10 +418,13 @@ function getTokenizerForTokenIds() {
419}418}
420419
421/**420/**
421 * Gets the custom token bans from settings and macros.
422 * @param {TextCompletionSettings} settings Text completion settings to use
422 * @typedef {{banned_tokens: string, banned_strings: string[]}} TokenBanResult423 * @typedef {{banned_tokens: string, banned_strings: string[]}} TokenBanResult
423 * @returns {TokenBanResult} String with comma-separated banned token IDs424 * @returns {TokenBanResult} String with comma-separated banned token IDs
424 */425 */
425function getCustomTokenBans() {426function getCustomTokenBans(settings = null) {
427 settings = settings ?? textgenerationwebui_settings;
426 if (!settings.send_banned_tokens || (!settings.banned_tokens && !settings.global_banned_tokens && !textgenerationwebui_banned_in_macros.length)) {428 if (!settings.send_banned_tokens || (!settings.banned_tokens && !settings.global_banned_tokens && !textgenerationwebui_banned_in_macros.length)) {
427 return {429 return {
428 banned_tokens: '',430 banned_tokens: '',
@@ -491,15 +493,18 @@ function getCustomTokenBans() {
491function toggleBannedStringsKillSwitch(isEnabled, title) {493function toggleBannedStringsKillSwitch(isEnabled, title) {
492 $('#send_banned_tokens_textgenerationwebui').prop('checked', isEnabled);494 $('#send_banned_tokens_textgenerationwebui').prop('checked', isEnabled);
493 $('#send_banned_tokens_label').find('.menu_button').toggleClass('toggleEnabled', isEnabled).prop('title', title);495 $('#send_banned_tokens_label').find('.menu_button').toggleClass('toggleEnabled', isEnabled).prop('title', title);
494 settings.send_banned_tokens = isEnabled;496 textgenerationwebui_settings.send_banned_tokens = isEnabled;
495 saveSettingsDebounced();497 saveSettingsDebounced();
496}498}
497499
498/**500/**
499 * Calculates logit bias object from the logit bias list.501 * Calculates logit bias object from the logit bias list.
502 * @param {TextCompletionSettings} settings Text completion settings
500 * @returns {object} Logit bias object503 * @returns {object} Logit bias object
501 */504 */
502function calculateLogitBias() {505function calculateLogitBias(settings = null) {
506 settings = settings ?? textgenerationwebui_settings;
507
503 if (!Array.isArray(settings.logit_bias) || settings.logit_bias.length === 0) {508 if (!Array.isArray(settings.logit_bias) || settings.logit_bias.length === 0) {
504 return {};509 return {};
505 }510 }
@@ -535,25 +540,25 @@ export async function loadTextGenSettings(data, loadedSettings) {
535 await loadApiSelectedSamplers();540 await loadApiSelectedSamplers();
536 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);541 textgenerationwebui_presets = convertPresets(data.textgenerationwebui_presets);
537 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];542 textgenerationwebui_preset_names = data.textgenerationwebui_preset_names ?? [];
538 Object.assign(settings, loadedSettings.textgenerationwebui_settings ?? {});543 Object.assign(textgenerationwebui_settings, loadedSettings.textgenerationwebui_settings ?? {});
539544
540 if (loadedSettings.api_server_textgenerationwebui) {545 if (loadedSettings.api_server_textgenerationwebui) {
541 for (const type of Object.keys(SERVER_INPUTS)) {546 for (const type of Object.keys(SERVER_INPUTS)) {
542 settings.server_urls[type] = loadedSettings.api_server_textgenerationwebui;547 textgenerationwebui_settings.server_urls[type] = loadedSettings.api_server_textgenerationwebui;
543 }548 }
544 delete loadedSettings.api_server_textgenerationwebui;549 delete loadedSettings.api_server_textgenerationwebui;
545 }550 }
546551
547 for (const [type, selector] of Object.entries(SERVER_INPUTS)) {552 for (const [type, selector] of Object.entries(SERVER_INPUTS)) {
548 const control = $(selector);553 const control = $(selector);
549 control.val(settings.server_urls[type] ?? '').on('input', function () {554 control.val(textgenerationwebui_settings.server_urls[type] ?? '').on('input', function () {
550 settings.server_urls[type] = String($(this).val()).trim();555 textgenerationwebui_settings.server_urls[type] = String($(this).val()).trim();
551 saveSettingsDebounced();556 saveSettingsDebounced();
552 });557 });
553 }558 }
554559
555 if (loadedSettings.api_use_mancer_webui) {560 if (loadedSettings.api_use_mancer_webui) {
556 settings.type = MANCER;561 textgenerationwebui_settings.type = MANCER;
557 }562 }
558563
559 for (const name of textgenerationwebui_preset_names) {564 for (const name of textgenerationwebui_preset_names) {
@@ -563,20 +568,20 @@ export async function loadTextGenSettings(data, loadedSettings) {
563 $('#settings_preset_textgenerationwebui').append(option);568 $('#settings_preset_textgenerationwebui').append(option);
564 }569 }
565570
566 if (settings.preset) {571 if (textgenerationwebui_settings.preset) {
567 $('#settings_preset_textgenerationwebui').val(settings.preset);572 $('#settings_preset_textgenerationwebui').val(textgenerationwebui_settings.preset);
568 }573 }
569574
570 for (const i of setting_names) {575 for (const i of setting_names) {
571 const value = settings[i];576 const value = textgenerationwebui_settings[i];
572 setSettingByName(i, value);577 setSettingByName(i, value);
573 }578 }
574579
575 $('#textgen_type').val(settings.type);580 $('#textgen_type').val(textgenerationwebui_settings.type);
576 $('#openrouter_providers_text').val(settings.openrouter_providers).trigger('change');581 $('#openrouter_providers_text').val(textgenerationwebui_settings.openrouter_providers).trigger('change');
577 showSamplerControls(settings.type);582 showSamplerControls(textgenerationwebui_settings.type);
578 BIAS_CACHE.delete(BIAS_KEY);583 BIAS_CACHE.delete(BIAS_KEY);
579 displayLogitBias(settings.logit_bias, BIAS_KEY);584 displayLogitBias(textgenerationwebui_settings.logit_bias, BIAS_KEY);
580585
581 registerDebugFunction('change-mancer-url', 'Change Mancer base URL', 'Change Mancer API server base URL', () => {586 registerDebugFunction('change-mancer-url', 'Change Mancer base URL', 'Change Mancer API server base URL', () => {
582 const result = prompt(`Enter Mancer base URL\nDefault: ${MANCER_SERVER_DEFAULT}`, MANCER_SERVER);587 const result = prompt(`Enter Mancer base URL\nDefault: ${MANCER_SERVER_DEFAULT}`, MANCER_SERVER);
@@ -648,7 +653,7 @@ async function getStatusTextgen() {
648 return resultCheckStatus();653 return resultCheckStatus();
649 }654 }
650655
651 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(settings.type) && settings.bypass_status_check) {656 if ([textgen_types.GENERIC, textgen_types.OOBA].includes(textgenerationwebui_settings.type) && textgenerationwebui_settings.bypass_status_check) {
652 setOnlineStatus(t`Status check bypassed`);657 setOnlineStatus(t`Status check bypassed`);
653 return resultCheckStatus();658 return resultCheckStatus();
654 }659 }
@@ -659,46 +664,46 @@ async function getStatusTextgen() {
659 headers: getRequestHeaders(),664 headers: getRequestHeaders(),
660 body: JSON.stringify({665 body: JSON.stringify({
661 api_server: endpoint,666 api_server: endpoint,
662 api_type: settings.type,667 api_type: textgenerationwebui_settings.type,
663 }),668 }),
664 signal: abortStatusCheck.signal,669 signal: abortStatusCheck.signal,
665 });670 });
666671
667 const data = await response.json();672 const data = await response.json();
668673
669 if (settings.type === textgen_types.MANCER) {674 if (textgenerationwebui_settings.type === textgen_types.MANCER) {
670 loadMancerModels(data?.data);675 loadMancerModels(data?.data);
671 setOnlineStatus(settings.mancer_model);676 setOnlineStatus(textgenerationwebui_settings.mancer_model);
672 } else if (settings.type === textgen_types.TOGETHERAI) {677 } else if (textgenerationwebui_settings.type === textgen_types.TOGETHERAI) {
673 loadTogetherAIModels(data?.data);678 loadTogetherAIModels(data?.data);
674 setOnlineStatus(settings.togetherai_model);679 setOnlineStatus(textgenerationwebui_settings.togetherai_model);
675 } else if (settings.type === textgen_types.OLLAMA) {680 } else if (textgenerationwebui_settings.type === textgen_types.OLLAMA) {
676 loadOllamaModels(data?.data);681 loadOllamaModels(data?.data);
677 setOnlineStatus(settings.ollama_model || t`Connected`);682 setOnlineStatus(textgenerationwebui_settings.ollama_model || t`Connected`);
678 } else if (settings.type === textgen_types.INFERMATICAI) {683 } else if (textgenerationwebui_settings.type === textgen_types.INFERMATICAI) {
679 loadInfermaticAIModels(data?.data);684 loadInfermaticAIModels(data?.data);
680 setOnlineStatus(settings.infermaticai_model);685 setOnlineStatus(textgenerationwebui_settings.infermaticai_model);
681 } else if (settings.type === textgen_types.DREAMGEN) {686 } else if (textgenerationwebui_settings.type === textgen_types.DREAMGEN) {
682 loadDreamGenModels(data?.data);687 loadDreamGenModels(data?.data);
683 setOnlineStatus(settings.dreamgen_model);688 setOnlineStatus(textgenerationwebui_settings.dreamgen_model);
684 } else if (settings.type === textgen_types.OPENROUTER) {689 } else if (textgenerationwebui_settings.type === textgen_types.OPENROUTER) {
685 loadOpenRouterModels(data?.data);690 loadOpenRouterModels(data?.data);
686 setOnlineStatus(settings.openrouter_model);691 setOnlineStatus(textgenerationwebui_settings.openrouter_model);
687 } else if (settings.type === textgen_types.VLLM) {692 } else if (textgenerationwebui_settings.type === textgen_types.VLLM) {
688 loadVllmModels(data?.data);693 loadVllmModels(data?.data);
689 setOnlineStatus(settings.vllm_model);694 setOnlineStatus(textgenerationwebui_settings.vllm_model);
690 } else if (settings.type === textgen_types.APHRODITE) {695 } else if (textgenerationwebui_settings.type === textgen_types.APHRODITE) {
691 loadAphroditeModels(data?.data);696 loadAphroditeModels(data?.data);
692 setOnlineStatus(settings.aphrodite_model);697 setOnlineStatus(textgenerationwebui_settings.aphrodite_model);
693 } else if (settings.type === textgen_types.FEATHERLESS) {698 } else if (textgenerationwebui_settings.type === textgen_types.FEATHERLESS) {
694 loadFeatherlessModels(data?.data);699 loadFeatherlessModels(data?.data);
695 setOnlineStatus(settings.featherless_model);700 setOnlineStatus(textgenerationwebui_settings.featherless_model);
696 } else if (settings.type === textgen_types.TABBY) {701 } else if (textgenerationwebui_settings.type === textgen_types.TABBY) {
697 loadTabbyModels(data?.data);702 loadTabbyModels(data?.data);
698 setOnlineStatus(settings.tabby_model || data?.result);703 setOnlineStatus(textgenerationwebui_settings.tabby_model || data?.result);
699 } else if (settings.type === textgen_types.GENERIC) {704 } else if (textgenerationwebui_settings.type === textgen_types.GENERIC) {
700 loadGenericModels(data?.data);705 loadGenericModels(data?.data);
701 setOnlineStatus(settings.generic_model || data?.result || t`Connected`);706 setOnlineStatus(textgenerationwebui_settings.generic_model || data?.result || t`Connected`);
702 } else {707 } else {
703 setOnlineStatus(data?.result);708 setOnlineStatus(data?.result);
704 }709 }
@@ -718,7 +723,7 @@ async function getStatusTextgen() {
718 const wantsInstructDerivation = !autoSelected && (power_user.instruct.enabled && power_user.instruct_derived);723 const wantsInstructDerivation = !autoSelected && (power_user.instruct.enabled && power_user.instruct_derived);
719 const wantsContextDerivation = !autoSelected && power_user.context_derived;724 const wantsContextDerivation = !autoSelected && power_user.context_derived;
720 const wantsContextSize = power_user.context_size_derived;725 const wantsContextSize = power_user.context_size_derived;
721 const supportsChatTemplate = [textgen_types.KOBOLDCPP, textgen_types.LLAMACPP].includes(settings.type);726 const supportsChatTemplate = [textgen_types.KOBOLDCPP, textgen_types.LLAMACPP].includes(textgenerationwebui_settings.type);
722727
723 if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation || wantsContextSize)) {728 if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation || wantsContextSize)) {
724 const response = await fetch('/api/backends/text-completions/props', {729 const response = await fetch('/api/backends/text-completions/props', {
@@ -726,7 +731,7 @@ async function getStatusTextgen() {
726 headers: getRequestHeaders(),731 headers: getRequestHeaders(),
727 body: JSON.stringify({732 body: JSON.stringify({
728 api_server: endpoint,733 api_server: endpoint,
729 api_type: settings.type,734 api_type: textgenerationwebui_settings.type,
730 }),735 }),
731 });736 });
732737
@@ -795,15 +800,15 @@ export function initTextGenSettings() {
795 $('#koboldcpp_order').children().each(function () {800 $('#koboldcpp_order').children().each(function () {
796 order.push($(this).data('id'));801 order.push($(this).data('id'));
797 });802 });
798 settings.sampler_order = order;803 textgenerationwebui_settings.sampler_order = order;
799 console.log('Samplers reordered:', settings.sampler_order);804 console.log('Samplers reordered:', textgenerationwebui_settings.sampler_order);
800 saveSettingsDebounced();805 saveSettingsDebounced();
801 },806 },
802 });807 });
803808
804 $('#koboldcpp_default_order').on('click', function () {809 $('#koboldcpp_default_order').on('click', function () {
805 settings.sampler_order = KOBOLDCPP_ORDER;810 textgenerationwebui_settings.sampler_order = KOBOLDCPP_ORDER;
806 sortKoboldItemsByOrder(settings.sampler_order);811 sortKoboldItemsByOrder(textgenerationwebui_settings.sampler_order);
807 saveSettingsDebounced();812 saveSettingsDebounced();
808 });813 });
809814
@@ -814,16 +819,16 @@ export function initTextGenSettings() {
814 $('#llamacpp_samplers_sortable').children().each(function () {819 $('#llamacpp_samplers_sortable').children().each(function () {
815 order.push($(this).data('name'));820 order.push($(this).data('name'));
816 });821 });
817 settings.samplers = order;822 textgenerationwebui_settings.samplers = order;
818 console.log('Samplers reordered:', settings.samplers);823 console.log('Samplers reordered:', textgenerationwebui_settings.samplers);
819 saveSettingsDebounced();824 saveSettingsDebounced();
820 },825 },
821 });826 });
822827
823 $('#llamacpp_samplers_default_order').on('click', function () {828 $('#llamacpp_samplers_default_order').on('click', function () {
824 sortLlamacppItemsByOrder(LLAMACPP_DEFAULT_ORDER);829 sortLlamacppItemsByOrder(LLAMACPP_DEFAULT_ORDER);
825 settings.samplers = LLAMACPP_DEFAULT_ORDER;830 textgenerationwebui_settings.samplers = LLAMACPP_DEFAULT_ORDER;
826 console.log('Default samplers order loaded:', settings.samplers);831 console.log('Default samplers order loaded:', textgenerationwebui_settings.samplers);
827 saveSettingsDebounced();832 saveSettingsDebounced();
828 });833 });
829834
@@ -834,8 +839,8 @@ export function initTextGenSettings() {
834 $('#sampler_priority_container').children().each(function () {839 $('#sampler_priority_container').children().each(function () {
835 order.push($(this).data('name'));840 order.push($(this).data('name'));
836 });841 });
837 settings.sampler_priority = order;842 textgenerationwebui_settings.sampler_priority = order;
838 console.log('Samplers reordered:', settings.sampler_priority);843 console.log('Samplers reordered:', textgenerationwebui_settings.sampler_priority);
839 saveSettingsDebounced();844 saveSettingsDebounced();
840 },845 },
841 });846 });
@@ -847,8 +852,8 @@ export function initTextGenSettings() {
847 $('#sampler_priority_container_aphrodite').children().each(function () {852 $('#sampler_priority_container_aphrodite').children().each(function () {
848 order.push($(this).data('name'));853 order.push($(this).data('name'));
849 });854 });
850 settings.samplers_priorities = order;855 textgenerationwebui_settings.samplers_priorities = order;
851 console.log('Samplers reordered:', settings.samplers_priorities);856 console.log('Samplers reordered:', textgenerationwebui_settings.samplers_priorities);
852 saveSettingsDebounced();857 saveSettingsDebounced();
853 },858 },
854 });859 });
@@ -858,12 +863,12 @@ export function initTextGenSettings() {
858863
859 if (json_schema_string) {864 if (json_schema_string) {
860 try {865 try {
861 settings.json_schema = JSON.parse(json_schema_string);866 textgenerationwebui_settings.json_schema = JSON.parse(json_schema_string);
862 } catch {867 } catch {
863 settings.json_schema = null;868 textgenerationwebui_settings.json_schema = null;
864 }869 }
865 } else {870 } else {
866 settings.json_schema = null;871 textgenerationwebui_settings.json_schema = null;
867 }872 }
868873
869 saveSettingsDebounced();874 saveSettingsDebounced();
@@ -871,38 +876,38 @@ export function initTextGenSettings() {
871876
872 $('#textgenerationwebui_default_order').on('click', function () {877 $('#textgenerationwebui_default_order').on('click', function () {
873 sortOobaItemsByOrder(OOBA_DEFAULT_ORDER);878 sortOobaItemsByOrder(OOBA_DEFAULT_ORDER);
874 settings.sampler_priority = OOBA_DEFAULT_ORDER;879 textgenerationwebui_settings.sampler_priority = OOBA_DEFAULT_ORDER;
875 console.log('Default samplers order loaded:', settings.sampler_priority);880 console.log('Default samplers order loaded:', textgenerationwebui_settings.sampler_priority);
876 saveSettingsDebounced();881 saveSettingsDebounced();
877 });882 });
878883
879 $('#aphrodite_default_order').on('click', function () {884 $('#aphrodite_default_order').on('click', function () {
880 sortAphroditeItemsByOrder(APHRODITE_DEFAULT_ORDER);885 sortAphroditeItemsByOrder(APHRODITE_DEFAULT_ORDER);
881 settings.samplers_priorities = APHRODITE_DEFAULT_ORDER;886 textgenerationwebui_settings.samplers_priorities = APHRODITE_DEFAULT_ORDER;
882 console.log('Default samplers order loaded:', settings.samplers_priorities);887 console.log('Default samplers order loaded:', textgenerationwebui_settings.samplers_priorities);
883 saveSettingsDebounced();888 saveSettingsDebounced();
884 });889 });
885890
886 $('#textgen_type').on('change', function () {891 $('#textgen_type').on('change', function () {
887 const type = String($(this).val());892 const type = String($(this).val());
888 settings.type = type;893 textgenerationwebui_settings.type = type;
889894
890 if ([VLLM, APHRODITE, INFERMATICAI].includes(settings.type)) {895 if ([VLLM, APHRODITE, INFERMATICAI].includes(textgenerationwebui_settings.type)) {
891 $('#mirostat_mode_textgenerationwebui').attr('step', 2); //Aphro disallows mode 1896 $('#mirostat_mode_textgenerationwebui').attr('step', 2); //Aphro disallows mode 1
892 $('#do_sample_textgenerationwebui').prop('checked', true); //Aphro should always do sample; 'otherwise set temp to 0 to mimic no sample'897 $('#do_sample_textgenerationwebui').prop('checked', true); //Aphro should always do sample; 'otherwise set temp to 0 to mimic no sample'
893 $('#ban_eos_token_textgenerationwebui').prop('checked', false); //Aphro should not ban EOS, just ignore it; 'add token '2' to ban list do to this'898 $('#ban_eos_token_textgenerationwebui').prop('checked', false); //Aphro should not ban EOS, just ignore it; 'add token '2' to ban list do to this'
894 //special handling for vLLM/Aphrodite topK -1 disable state899 //special handling for vLLM/Aphrodite topK -1 disable state
895 $('#top_k_textgenerationwebui').attr('min', -1);900 $('#top_k_textgenerationwebui').attr('min', -1);
896 if ($('#top_k_textgenerationwebui').val() === '0' || settings['top_k'] === 0) {901 if ($('#top_k_textgenerationwebui').val() === '0' || textgenerationwebui_settings['top_k'] === 0) {
897 settings['top_k'] = -1;902 textgenerationwebui_settings['top_k'] = -1;
898 $('#top_k_textgenerationwebui').val('-1').trigger('input');903 $('#top_k_textgenerationwebui').val('-1').trigger('input');
899 }904 }
900 } else {905 } else {
901 $('#mirostat_mode_textgenerationwebui').attr('step', 1);906 $('#mirostat_mode_textgenerationwebui').attr('step', 1);
902 //undo special vLLM/Aphrodite setup for topK907 //undo special vLLM/Aphrodite setup for topK
903 $('#top_k_textgenerationwebui').attr('min', 0);908 $('#top_k_textgenerationwebui').attr('min', 0);
904 if ($('#top_k_textgenerationwebui').val() === '-1' || settings['top_k'] === -1) {909 if ($('#top_k_textgenerationwebui').val() === '-1' || textgenerationwebui_settings['top_k'] === -1) {
905 settings['top_k'] = 0;910 textgenerationwebui_settings['top_k'] = 0;
906 $('#top_k_textgenerationwebui').val('0').trigger('input');911 $('#top_k_textgenerationwebui').val('0').trigger('input');
907 }912 }
908 }913 }
@@ -913,7 +918,7 @@ export function initTextGenSettings() {
913918
914 $('#main_api').trigger('change');919 $('#main_api').trigger('change');
915920
916 if (!SERVER_INPUTS[type] || settings.server_urls[type]) {921 if (!SERVER_INPUTS[type] || textgenerationwebui_settings.server_urls[type]) {
917 $('#api_button_textgenerationwebui').trigger('click');922 $('#api_button_textgenerationwebui').trigger('click');
918 }923 }
919924
@@ -929,7 +934,7 @@ export function initTextGenSettings() {
929 $('#samplerResetButton').off('click').on('click', function () {934 $('#samplerResetButton').off('click').on('click', function () {
930 const inputs = {935 const inputs = {
931 'temp_textgenerationwebui': 1,936 'temp_textgenerationwebui': 1,
932 'top_k_textgenerationwebui': [INFERMATICAI, APHRODITE, VLLM].includes(settings.type) ? -1 : 0,937 'top_k_textgenerationwebui': [INFERMATICAI, APHRODITE, VLLM].includes(textgenerationwebui_settings.type) ? -1 : 0,
933 'top_p_textgenerationwebui': 1,938 'top_p_textgenerationwebui': 1,
934 'min_p_textgenerationwebui': 0,939 'min_p_textgenerationwebui': 0,
935 'rep_pen_textgenerationwebui': 1,940 'rep_pen_textgenerationwebui': 1,
@@ -1007,19 +1012,19 @@ export function initTextGenSettings() {
10071012
1008 if (isCheckbox) {1013 if (isCheckbox) {
1009 const value = $(this).prop('checked');1014 const value = $(this).prop('checked');
1010 settings[id] = value;1015 textgenerationwebui_settings[id] = value;
1011 }1016 }
1012 else if (isText) {1017 else if (isText) {
1013 const value = $(this).val();1018 const value = $(this).val();
1014 settings[id] = value;1019 textgenerationwebui_settings[id] = value;
1015 }1020 }
1016 else {1021 else {
1017 const value = Number($(this).val());1022 const value = Number($(this).val());
1018 $(`#${id}_counter_textgenerationwebui`).val(value);1023 $(`#${id}_counter_textgenerationwebui`).val(value);
1019 settings[id] = value;1024 textgenerationwebui_settings[id] = value;
1020 //special handling for vLLM/Aphrodite using -1 as disabled instead of 01025 //special handling for vLLM/Aphrodite using -1 as disabled instead of 0
1021 if ($(this).attr('id') === 'top_k_textgenerationwebui' && [INFERMATICAI, APHRODITE, VLLM].includes(settings.type) && value === 0) {1026 if ($(this).attr('id') === 'top_k_textgenerationwebui' && [INFERMATICAI, APHRODITE, VLLM].includes(textgenerationwebui_settings.type) && value === 0) {
1022 settings[id] = -1;1027 textgenerationwebui_settings[id] = -1;
1023 $(this).val(-1);1028 $(this).val(-1);
1024 }1029 }
1025 }1030 }
@@ -1027,7 +1032,7 @@ export function initTextGenSettings() {
1027 });1032 });
1028 }1033 }
10291034
1030 $('#textgen_logit_bias_new_entry').on('click', () => createNewLogitBiasEntry(settings.logit_bias, BIAS_KEY));1035 $('#textgen_logit_bias_new_entry').on('click', () => createNewLogitBiasEntry(textgenerationwebui_settings.logit_bias, BIAS_KEY));
10311036
1032 $('#openrouter_providers_text').on('change', function () {1037 $('#openrouter_providers_text').on('change', function () {
1033 const selectedProviders = $(this).val();1038 const selectedProviders = $(this).val();
@@ -1037,7 +1042,7 @@ export function initTextGenSettings() {
1037 return;1042 return;
1038 }1043 }
10391044
1040 settings.openrouter_providers = selectedProviders;1045 textgenerationwebui_settings.openrouter_providers = selectedProviders;
10411046
1042 saveSettingsDebounced();1047 saveSettingsDebounced();
1043 });1048 });
@@ -1086,10 +1091,10 @@ function showSamplerControls(apiType = null) {
1086 if (!typeSpecificControlled) $(this).show();1091 if (!typeSpecificControlled) $(this).show();
1087 });1092 });
10881093
1089 showTypeSpecificControls(apiType ?? settings.type);1094 showTypeSpecificControls(apiType ?? textgenerationwebui_settings.type);
10901095
1091 const prioritizeManualSamplerSelect = isSamplerManualPriorityEnabled(apiType ?? settings.type);1096 const prioritizeManualSamplerSelect = isSamplerManualPriorityEnabled(apiType ?? textgenerationwebui_settings.type);
1092 const samplersActivatedManually = getActiveManualApiSamplers(apiType ?? settings.type);1097 const samplersActivatedManually = getActiveManualApiSamplers(apiType ?? textgenerationwebui_settings.type);
10931098
1094 if (!samplersActivatedManually?.length || !prioritizeManualSamplerSelect) return;1099 if (!samplersActivatedManually?.length || !prioritizeManualSamplerSelect) return;
10951100
@@ -1150,13 +1155,13 @@ function insertMissingArrayItems(source, target) {
1150function setSettingByName(setting, value, trigger) {1155function setSettingByName(setting, value, trigger) {
1151 if ('extensions' === setting) {1156 if ('extensions' === setting) {
1152 value = value || {};1157 value = value || {};
1153 settings.extensions = value;1158 textgenerationwebui_settings.extensions = value;
1154 return;1159 return;
1155 }1160 }
11561161
1157 if ('json_schema' === setting) {1162 if ('json_schema' === setting) {
1158 settings.json_schema = value ?? null;1163 textgenerationwebui_settings.json_schema = value ?? null;
1159 $('#tabby_json_schema').val(value ? JSON.stringify(settings.json_schema, null, 2) : '');1164 $('#tabby_json_schema').val(value ? JSON.stringify(textgenerationwebui_settings.json_schema, null, 2) : '');
1160 return;1165 return;
1161 }1166 }
11621167
@@ -1167,7 +1172,7 @@ function setSettingByName(setting, value, trigger) {
1167 if ('sampler_order' === setting) {1172 if ('sampler_order' === setting) {
1168 value = Array.isArray(value) ? value : KOBOLDCPP_ORDER;1173 value = Array.isArray(value) ? value : KOBOLDCPP_ORDER;
1169 sortKoboldItemsByOrder(value);1174 sortKoboldItemsByOrder(value);
1170 settings.sampler_order = value;1175 textgenerationwebui_settings.sampler_order = value;
1171 return;1176 return;
1172 }1177 }
11731178
@@ -1175,7 +1180,7 @@ function setSettingByName(setting, value, trigger) {
1175 value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER;1180 value = Array.isArray(value) ? value : OOBA_DEFAULT_ORDER;
1176 insertMissingArrayItems(OOBA_DEFAULT_ORDER, value);1181 insertMissingArrayItems(OOBA_DEFAULT_ORDER, value);
1177 sortOobaItemsByOrder(value);1182 sortOobaItemsByOrder(value);
1178 settings.sampler_priority = value;1183 textgenerationwebui_settings.sampler_priority = value;
1179 return;1184 return;
1180 }1185 }
11811186
@@ -1183,7 +1188,7 @@ function setSettingByName(setting, value, trigger) {
1183 value = Array.isArray(value) ? value : APHRODITE_DEFAULT_ORDER;1188 value = Array.isArray(value) ? value : APHRODITE_DEFAULT_ORDER;
1184 insertMissingArrayItems(APHRODITE_DEFAULT_ORDER, value);1189 insertMissingArrayItems(APHRODITE_DEFAULT_ORDER, value);
1185 sortAphroditeItemsByOrder(value);1190 sortAphroditeItemsByOrder(value);
1186 settings.samplers_priorities = value;1191 textgenerationwebui_settings.samplers_priorities = value;
1187 return;1192 return;
1188 }1193 }
11891194
@@ -1191,12 +1196,12 @@ function setSettingByName(setting, value, trigger) {
1191 value = Array.isArray(value) ? value : LLAMACPP_DEFAULT_ORDER;1196 value = Array.isArray(value) ? value : LLAMACPP_DEFAULT_ORDER;
1192 insertMissingArrayItems(LLAMACPP_DEFAULT_ORDER, value);1197 insertMissingArrayItems(LLAMACPP_DEFAULT_ORDER, value);
1193 sortLlamacppItemsByOrder(value);1198 sortLlamacppItemsByOrder(value);
1194 settings.samplers = value;1199 textgenerationwebui_settings.samplers = value;
1195 return;1200 return;
1196 }1201 }
11971202
1198 if ('logit_bias' === setting) {1203 if ('logit_bias' === setting) {
1199 settings.logit_bias = Array.isArray(value) ? value : [];1204 textgenerationwebui_settings.logit_bias = Array.isArray(value) ? value : [];
1200 return;1205 return;
1201 }1206 }
12021207
@@ -1234,8 +1239,8 @@ function setSettingByName(setting, value, trigger) {
12341239
1235/**1240/**
1236 * Sends a streaming request for textgenerationwebui.1241 * Sends a streaming request for textgenerationwebui.
1237 * @param generate_data1242 * @param {object} generate_data
1238 * @param signal1243 * @param {AbortSignal} signal
1239 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}1244 * @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
1240 * @throws {Error} - If the response status is not OK, or from within the generator1245 * @throws {Error} - If the response status is not OK, or from within the generator
1241 */1246 */
@@ -1304,7 +1309,7 @@ export function parseTextgenLogprobs(token, logprobs) {
1304 return null;1309 return null;
1305 }1310 }
13061311
1307 switch (settings.type) {1312 switch (textgenerationwebui_settings.type) {
1308 case KOBOLDCPP:1313 case KOBOLDCPP:
1309 case TABBY:1314 case TABBY:
1310 case VLLM:1315 case VLLM:
@@ -1406,7 +1411,13 @@ function toIntArray(string) {
1406 return string.split(',').map(x => parseInt(x)).filter(x => !isNaN(x));1411 return string.split(',').map(x => parseInt(x)).filter(x => !isNaN(x));
1407}1412}
14081413
1409export function getTextGenModel() {1414/**
1415 * Gets the text generation model specified by the given text completion settings
1416 * @param {TextCompletionSettings} settings Text completion settings to use
1417 * @returns {string} model name
1418 */
1419export function getTextGenModel(settings = null) {
1420 settings = settings ?? textgenerationwebui_settings;
1410 switch (settings.type) {1421 switch (settings.type) {
1411 case OOBA:1422 case OOBA:
1412 if (settings.custom_model) {1423 if (settings.custom_model) {
@@ -1455,10 +1466,16 @@ export function getTextGenModel() {
1455}1466}
14561467
1457export function isJsonSchemaSupported() {1468export function isJsonSchemaSupported() {
1458 return [TABBY, LLAMACPP].includes(settings.type) && main_api === 'textgenerationwebui';1469 return [TABBY, LLAMACPP].includes(textgenerationwebui_settings.type) && main_api === 'textgenerationwebui';
1459}1470}
14601471
1461function isDynamicTemperatureSupported() {1472/**
1473 * Returns whether dynamic temperature is supported by the given text completion settings
1474 * @param {TextCompletionSettings} settings Text completion settings to use
1475 * @returns {boolean} Whether dynamic temperature supported
1476 */
1477function isDynamicTemperatureSupported(settings = null) {
1478 settings = settings ?? textgenerationwebui_settings;
1462 return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);1479 return settings.dynatemp && DYNATEMP_BLOCK?.dataset?.tgType?.includes(settings.type);
1463}1480}
14641481
@@ -1468,7 +1485,7 @@ function isDynamicTemperatureSupported() {
1468 * @returns {number} Number of logprobs to request1485 * @returns {number} Number of logprobs to request
1469 */1486 */
1470export function getLogprobsNumber(type = null) {1487export function getLogprobsNumber(type = null) {
1471 const selectedType = type ?? settings.type;1488 const selectedType = type ?? textgenerationwebui_settings.type;
1472 if (selectedType === VLLM || selectedType === INFERMATICAI) {1489 if (selectedType === VLLM || selectedType === INFERMATICAI) {
1473 return 5;1490 return 5;
1474 }1491 }
@@ -1504,10 +1521,25 @@ export function replaceMacrosInList(str) {
1504 }1521 }
1505}1522}
15061523
1507export async function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {1524/**
1525 * Build the generation parameter object for an text completion request
1526 * @param {TextCompletionSettings} settings Text completion settings to use
1527 * @param {string} model Model to use
1528 * @param {string} finalPrompt The final prompt to send
1529 * @param {number} maxTokens Max allowed generation tokens
1530 * @param {boolean} isImpersonate Whether this is for an impersonation
1531 * @param {boolean} isContinue Whether this is for a continue
1532 * @param {object} cfgValues Additional parameters (guidanceScale, negativePrompt)
1533 * @param {string} type Request type (impersonate, quiet, continue, etc)
1534 * @returns {object} Final generation parameters object appropriate for the text completion source
1535 */
1536export function createTextGenGenerationData(settings, model, finalPrompt = null, maxTokens = null, isImpersonate = false, isContinue = false, cfgValues = null, type = 'quiet') {
1537 settings = settings ?? textgenerationwebui_settings;
1538 model = model ?? getTextGenModel(settings);
1539
1508 const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';1540 const canMultiSwipe = !isContinue && !isImpersonate && type !== 'quiet';
1509 const dynatemp = isDynamicTemperatureSupported();1541 const dynatemp = isDynamicTemperatureSupported(settings);
1510 const { banned_tokens, banned_strings } = getCustomTokenBans();1542 const { banned_tokens, banned_strings } = getCustomTokenBans(settings);
1511 const jsonSchema = isObject(settings.json_schema)1543 const jsonSchema = isObject(settings.json_schema)
1512 ? settings.json_schema_allow_empty1544 ? settings.json_schema_allow_empty
1513 ? settings.json_schema1545 ? settings.json_schema
@@ -1516,10 +1548,10 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
15161548
1517 let params = {1549 let params = {
1518 'prompt': finalPrompt,1550 'prompt': finalPrompt,
1519 'model': getTextGenModel(),1551 'model': model,
1520 'max_new_tokens': maxTokens,1552 'max_new_tokens': maxTokens,
1521 'max_tokens': maxTokens,1553 'max_tokens': maxTokens,
1522 'logprobs': power_user.request_token_probabilities ? getLogprobsNumber() : undefined,1554 'logprobs': power_user.request_token_probabilities ? getLogprobsNumber(settings.type) : undefined,
1523 'temperature': dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,1555 'temperature': dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
1524 'top_p': settings.top_p,1556 'top_p': settings.top_p,
1525 'typical_p': settings.typical_p,1557 'typical_p': settings.typical_p,
@@ -1571,7 +1603,7 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
1571 banned_tokens,1603 banned_tokens,
1572 'banned_strings': banned_strings,1604 'banned_strings': banned_strings,
1573 'api_type': settings.type,1605 'api_type': settings.type,
1574 'api_server': getTextGenServer(),1606 'api_server': getTextGenServer(settings.type),
1575 'sampler_order': settings.type === textgen_types.KOBOLDCPP ? settings.sampler_order : undefined,1607 'sampler_order': settings.type === textgen_types.KOBOLDCPP ? settings.sampler_order : undefined,
1576 'xtc_threshold': settings.xtc_threshold,1608 'xtc_threshold': settings.xtc_threshold,
1577 'xtc_probability': settings.xtc_probability,1609 'xtc_probability': settings.xtc_probability,
@@ -1713,7 +1745,7 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
1713 }1745 }
17141746
1715 if (Array.isArray(settings.logit_bias) && settings.logit_bias.length) {1747 if (Array.isArray(settings.logit_bias) && settings.logit_bias.length) {
1716 const logitBias = BIAS_CACHE.get(BIAS_KEY) || calculateLogitBias();1748 const logitBias = BIAS_CACHE.get(BIAS_KEY) || calculateLogitBias(settings);
1717 BIAS_CACHE.set(BIAS_KEY, logitBias);1749 BIAS_CACHE.set(BIAS_KEY, logitBias);
1718 params.logit_bias = logitBias;1750 params.logit_bias = logitBias;
1719 }1751 }
@@ -1750,8 +1782,12 @@ export async function getTextGenGenerationData(finalPrompt, maxTokens, isImperso
1750 delete params.guided_json;1782 delete params.guided_json;
1751 }1783 }
1752 }1784 }
1785 return params;
1786}
17531787
1788export async function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type) {
1789 const model = getTextGenModel(textgenerationwebui_settings);
1790 const params = createTextGenGenerationData(textgenerationwebui_settings, model, finalPrompt, maxTokens, isImpersonate, isContinue, cfgValues, type);
1754 await eventSource.emit(event_types.TEXT_COMPLETION_SETTINGS_READY, params);1791 await eventSource.emit(event_types.TEXT_COMPLETION_SETTINGS_READY, params);
1755
1756 return params;1792 return params;
1757}1793}
public/scripts/tool-calling.js+33 -53
@@ -1,7 +1,7 @@
1import { DOMPurify } from '../lib.js';1import { DOMPurify } from '../lib.js';
22
3import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js';3import { addOneMessage, chat, event_types, eventSource, main_api, saveChatConditional, system_avatar, systemUserName } from '../script.js';
4import { chat_completion_sources, custom_prompt_post_processing_types, model_list, oai_settings } from './openai.js';4import { chat_completion_sources, custom_prompt_post_processing_types, getChatCompletionModel, model_list, oai_settings } from './openai.js';
5import { Popup } from './popup.js';5import { Popup } from './popup.js';
6import { SlashCommand } from './slash-commands/SlashCommand.js';6import { SlashCommand } from './slash-commands/SlashCommand.js';
7import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';7import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
@@ -589,66 +589,42 @@ export class ToolManager {
589589
590 /**590 /**
591 * Checks if tool calling is supported for the current settings and generation type.591 * Checks if tool calling is supported for the current settings and generation type.
592 * @param {ChatCompletionSettings} settings Optional chat completion settings
593 * @param {string} model Optional model name
592 * @returns {boolean} Whether tool calling is supported for the given type594 * @returns {boolean} Whether tool calling is supported for the given type
593 */595 */
594 static isToolCallingSupported() {596 static isToolCallingSupported(settings = null, model = null) {
595 if (main_api !== 'openai' || !oai_settings.function_calling) {597 settings = settings ?? oai_settings;
598 model = model ?? getChatCompletionModel(settings);
599
600 if (main_api !== 'openai' || !settings.function_calling) {
596 return false;601 return false;
597 }602 }
598603
599 // Post-processing will forcefully remove past tool calls from the prompt, making them useless604 // Post-processing will forcefully remove past tool calls from the prompt, making them useless
600 const { NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS } = custom_prompt_post_processing_types;605 const { NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS } = custom_prompt_post_processing_types;
601 const allowedPromptPostProcessing = [NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS];606 const allowedPromptPostProcessing = [NONE, MERGE_TOOLS, SEMI_TOOLS, STRICT_TOOLS];
602 if (!allowedPromptPostProcessing.includes(oai_settings.custom_prompt_post_processing)) {607 if (!allowedPromptPostProcessing.includes(settings.custom_prompt_post_processing)) {
603 return false;608 return false;
604 }609 }
605610
606 if (oai_settings.chat_completion_source === chat_completion_sources.POLLINATIONS && Array.isArray(model_list)) {611 const currentModel = Array.isArray(model_list) ? model_list.find(m => m.id === model) : null;
607 const currentModel = model_list.find(model => model.id === oai_settings.pollinations_model);612 if (currentModel) {
608 if (currentModel) {613 switch (settings.chat_completion_source) {
609 return currentModel.tools;614 case chat_completion_sources.POLLINATIONS:
610 }615 return currentModel.tools;
611 }616 case chat_completion_sources.FIREWORKS:
612617 return currentModel.supports_tools;
613 if (oai_settings.chat_completion_source === chat_completion_sources.FIREWORKS && Array.isArray(model_list)) {618 case chat_completion_sources.OPENROUTER:
614 const currentModel = model_list.find(model => model.id === oai_settings.fireworks_model);619 return currentModel.supported_parameters?.includes('tools');
615 if (currentModel) {620 case chat_completion_sources.MISTRALAI:
616 return currentModel.supports_tools;621 return currentModel.capabilities?.function_calling;
617 }622 case chat_completion_sources.AIMLAPI:
618 }623 return currentModel.features?.includes('openai/chat-completion.function');
619624 case chat_completion_sources.CHUTES:
620 if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER && Array.isArray(model_list)) {625 return currentModel.supported_features?.includes('tools');
621 const currentModel = model_list.find(model => model.id === oai_settings.openrouter_model);626 case chat_completion_sources.ELECTRONHUB:
622 if (Array.isArray(currentModel?.supported_parameters)) {627 return currentModel.metadata?.function_call;
623 return currentModel.supported_parameters.includes('tools');
624 }
625 }
626
627 if (oai_settings.chat_completion_source === chat_completion_sources.MISTRALAI && Array.isArray(model_list)) {
628 const currentModel = model_list.find(model => model.id === oai_settings.mistralai_model);
629 if (currentModel && currentModel.capabilities) {
630 return currentModel.capabilities.function_calling;
631 }
632 }
633
634 if (oai_settings.chat_completion_source === chat_completion_sources.AIMLAPI && Array.isArray(model_list)) {
635 const currentModel = model_list.find(model => model.id === oai_settings.aimlapi_model);
636 if (Array.isArray(currentModel?.features)) {
637 return currentModel.features.includes('openai/chat-completion.function');
638 }
639 }
640
641 if (oai_settings.chat_completion_source === chat_completion_sources.CHUTES && Array.isArray(model_list)) {
642 const currentModel = model_list.find(model => model.id === oai_settings.chutes_model);
643 if (currentModel) {
644 return currentModel.supported_features?.includes('tools');
645 }
646 }
647
648 if (oai_settings.chat_completion_source === chat_completion_sources.ELECTRONHUB && Array.isArray(model_list)) {
649 const currentModel = model_list.find(model => model.id === oai_settings.electronhub_model);
650 if (currentModel) {
651 return currentModel.metadata?.function_call;
652 }628 }
653 }629 }
654630
@@ -676,17 +652,21 @@ export class ToolManager {
676 chat_completion_sources.ZAI,652 chat_completion_sources.ZAI,
677 chat_completion_sources.SILICONFLOW,653 chat_completion_sources.SILICONFLOW,
678 ];654 ];
679 return supportedSources.includes(oai_settings.chat_completion_source);655 return supportedSources.includes(settings.chat_completion_source);
680 }656 }
681657
682 /**658 /**
683 * Checks if tool calls can be performed for the current settings and generation type.659 * Checks if tool calls can be performed for the current settings and generation type.
684 * @param {string} type Generation type660 * @param {string} type Generation type
661 * @param {ChatCompletionSettings} settings Optional chat completion settings
662 * @param {string} model Optional model name
685 * @returns {boolean} Whether tool calls can be performed for the given type663 * @returns {boolean} Whether tool calls can be performed for the given type
686 */664 */
687 static canPerformToolCalls(type) {665 static canPerformToolCalls(type, settings = null, model = null) {
666 settings = settings ?? oai_settings;
667 model = model ?? getChatCompletionModel(settings);
688 const noToolCallTypes = ['impersonate', 'quiet', 'continue'];668 const noToolCallTypes = ['impersonate', 'quiet', 'continue'];
689 const isSupported = ToolManager.isToolCallingSupported();669 const isSupported = ToolManager.isToolCallingSupported(settings, model);
690 return isSupported && !noToolCallTypes.includes(type);670 return isSupported && !noToolCallTypes.includes(type);
691 }671 }
692672