Compare commits

..

1 Commits

Author SHA1 Message Date
Cohee
ba1c69d7a7 [wip] Update transformers.js 2025-04-15 21:14:09 +03:00
25 changed files with 736 additions and 1202 deletions

View File

@@ -30,8 +30,7 @@ jobs:
# https://github.com/marketplace/actions/checkout
uses: actions/checkout@v4.2.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.head_ref }}
- name: Setup Node.js
# Setup Node.js environment

View File

@@ -155,7 +155,6 @@ whitelistImportDomains:
- cdn.discordapp.com
- files.catbox.moe
- raw.githubusercontent.com
- char-archive.evulid.cc
# API request overrides (for KoboldAI and Text Completion APIs)
## Note: host includes the port number if it's not the default (80 or 443)
## Format is an array of objects:

1500
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
"@adobe/css-tools": "^4.4.2",
"@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3",
"@huggingface/transformers": "^3.4.2",
"@iconfu/svg-inject": "^1.2.3",
"@jimp/core": "^1.6.0",
"@jimp/js-bmp": "^1.6.0",
@@ -74,7 +75,6 @@
"sanitize-filename": "^1.6.3",
"seedrandom": "^3.0.5",
"showdown": "^2.1.0",
"sillytavern-transformers": "2.14.6",
"simple-git": "^3.27.0",
"slidetoggle": "^4.0.0",
"tiktoken": "^1.0.20",
@@ -100,6 +100,9 @@
},
"node-fetch": {
"whatwg-url": "^14.0.0"
},
"@huggingface/transformers": {
"onnxruntime-node": "https://github.com/Cohee1207/onnxruntime/releases/download/1.20.1/onnxruntime-node-1.20.1.tgz"
}
},
"name": "sillytavern",
@@ -109,7 +112,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.12.14",
"version": "1.12.13",
"scripts": {
"start": "node server.js",
"debug": "node --inspect server.js",

View File

@@ -332,7 +332,7 @@ try {
// 1. Create default config files
createDefaultFiles();
// 2. Copy transformers WASM binaries from node_modules
copyWasmFiles();
// copyWasmFiles();
// 3. Add missing config values
addMissingConfigValues();
} catch (error) {

View File

@@ -1419,7 +1419,7 @@
</div>
</div>
<div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp, dreamgen" id="dryBlock" class="wide100p">
<div data-tg-type="aphrodite, ooba, koboldcpp, tabby, llamacpp" id="dryBlock" class="wide100p">
<h4 class="wide100p textAlignCenter" title="DRY penalizes tokens that would extend the end of the input into a sequence that has previously occurred in the input. Set multiplier to 0 to disable." data-i18n="[title]DRY_Repetition_Penalty_desc">
<label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
<a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@@ -1574,7 +1574,7 @@
<div class="fa-solid fa-circle-info opacity50p " data-i18n="[title]Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative" title="Add the bos_token to the beginning of prompts. Disabling this can make the replies more creative."></div>
</label>
</label>
<label data-tg-type="ooba, llamacpp, tabby, koboldcpp, dreamgen" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui">
<label data-tg-type="ooba, llamacpp, tabby, koboldcpp" class="checkbox_label flexGrow flexShrink" for="ban_eos_token_textgenerationwebui">
<input type="checkbox" id="ban_eos_token_textgenerationwebui" />
<label>
<small data-i18n="Ban EOS Token">Ban EOS Token</small>
@@ -2762,7 +2762,7 @@
<option value="xai">xAI (Grok)</option>
</optgroup>
</select>
<div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek,xai">
<div class="inline-drawer wide100p" data-source="openai,claude,mistralai,makersuite,deepseek">
<div class="inline-drawer-toggle inline-drawer-header">
<b data-i18n="Reverse Proxy">Reverse Proxy</b>
<div class="fa-solid fa-circle-chevron-down inline-drawer-icon down"></div>
@@ -2825,7 +2825,7 @@
</div>
</div>
</div>
<div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek,xai">
<div id="ReverseProxyWarningMessage" data-source="openai,claude,mistralai,makersuite,deepseek">
<div class="reverse_proxy_warning">
<b>
<div data-i18n="Using a proxy that you're not running yourself is a risk to your data privacy.">
@@ -2897,7 +2897,7 @@
<option value="gpt-4.1-nano">gpt-4.1-nano</option>
<option value="gpt-4.1-nano-2025-04-14">gpt-4.1-nano-2025-04-14</option>
</optgroup>
<optgroup label="o1">
<optgroup label="o1 and o1-mini">
<option value="o1">o1</option>
<option value="o1-2024-12-17">o1-2024-12-17</option>
<option value="o1-mini">o1-mini</option>
@@ -2906,15 +2906,9 @@
<option value="o1-preview-2024-09-12">o1-preview-2024-09-12</option>
</optgroup>
<optgroup label="o3">
<option value="o3">o3</option>
<option value="o3-2025-04-16">o3-2025-04-16</option>
<option value="o3-mini">o3-mini</option>
<option value="o3-mini-2025-01-31">o3-mini-2025-01-31</option>
</optgroup>
<optgroup label="o4">
<option value="o4-mini">o4-mini</option>
<option value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
</optgroup>
<optgroup label="GPT-4.5">
<option value="gpt-4.5-preview">gpt-4.5-preview</option>
<option value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
@@ -3162,7 +3156,6 @@
<option value="gemini-2.5-pro-exp-03-25">Gemini 2.5 Pro Experimental 2025-03-25</option>
<option value="gemini-2.0-pro-exp">Gemini 2.0 Pro Experimental</option>
<option value="gemini-2.0-pro-exp-02-05">Gemini 2.0 Pro Experimental 2025-02-05</option>
<option value="gemini-2.5-flash-preview-04-17">Gemini 2.5 Flash Preview 2025-04-17</option>
<option value="gemini-2.0-flash-lite-preview">Gemini 2.0 Flash-Lite Preview</option>
<option value="gemini-2.0-flash-lite-preview-02-05">Gemini 2.0 Flash-Lite Preview 2025-02-05</option>
<option value="gemini-2.0-flash-001">Gemini 2.0 Flash [001]</option>

View File

@@ -11847,8 +11847,8 @@ jQuery(async function () {
return;
}
const drawer = $(this).closest('.inline-drawer');
const icon = drawer.find('>.inline-drawer-header .inline-drawer-icon');
const drawerContent = drawer.find('>.inline-drawer-content');
const icon = drawer.find('.inline-drawer-icon');
const drawerContent = drawer.find('.inline-drawer-content');
icon.toggleClass('down up');
icon.toggleClass('fa-circle-chevron-down fa-circle-chevron-up');
drawerContent.stop().slideToggle({

View File

@@ -39,7 +39,7 @@ To install a single 3rd party extension, use the &quot;Install Extensions&quot;
<span data-i18n="Characters">Characters</span>
</div>
</div>
<div id="assets_menu">
<div class="inline-drawer-content" id="assets_menu">
</div>
</div>
</div>

View File

@@ -60,10 +60,6 @@
<option data-type="openai" value="chatgpt-4o-latest">chatgpt-4o-latest</option>
<option data-type="openai" value="o1">o1</option>
<option data-type="openai" value="o1-2024-12-17">o1-2024-12-17</option>
<option data-type="openai" value="o3">o3</option>
<option data-type="openai" value="o3-2025-04-16">o3-2025-04-16</option>
<option data-type="openai" value="o4-mini">o4-mini</option>
<option data-type="openai" value="o4-mini-2025-04-16">o4-mini-2025-04-16</option>
<option data-type="openai" value="gpt-4.5-preview">gpt-4.5-preview</option>
<option data-type="openai" value="gpt-4.5-preview-2025-02-27">gpt-4.5-preview-2025-02-27</option>
<option data-type="anthropic" value="claude-3-7-sonnet-latest">claude-3-7-sonnet-latest</option>
@@ -80,7 +76,6 @@
<option data-type="google" value="gemini-2.5-pro-exp-03-25">gemini-2.5-pro-exp-03-25</option>
<option data-type="google" value="gemini-2.0-pro-exp">gemini-2.0-pro-exp</option>
<option data-type="google" value="gemini-2.0-pro-exp-02-05">gemini-2.0-pro-exp-02-05</option>
<option data-type="google" value="gemini-2.5-flash-preview-04-17">gemini-2.5-flash-preview-04-17</option>
<option data-type="google" value="gemini-2.0-flash-lite-preview">gemini-2.0-flash-lite-preview</option>
<option data-type="google" value="gemini-2.0-flash-lite-preview-02-05">gemini-2.0-flash-lite-preview-02-05</option>
<option data-type="google" value="gemini-2.0-flash">gemini-2.0-flash</option>

View File

@@ -76,7 +76,7 @@
<div id="tts_voicemap_block">
</div>
<hr>
<form id="tts_provider_settings">
<form id="tts_provider_settings" class="inline-drawer-content">
</form>
<div class="tts_buttons">
<input id="tts_voices" class="menu_button" type="submit" value="Available voices" />

View File

@@ -79,10 +79,6 @@ class SystemTtsProvider {
// Config //
//########//
// Static constants for the simulated default voice
static BROWSER_DEFAULT_VOICE_ID = '__browser_default__';
static BROWSER_DEFAULT_VOICE_NAME = 'System Default Voice';
settings;
ready = false;
voices = [];
@@ -172,97 +168,51 @@ class SystemTtsProvider {
//#################//
fetchTtsVoiceObjects() {
if (!('speechSynthesis' in window)) {
return Promise.resolve([]);
return [];
}
return new Promise((resolve) => {
setTimeout(() => {
let voices = speechSynthesis.getVoices();
const voices = speechSynthesis
.getVoices()
.sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
.map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
if (voices.length === 0) {
// Edge compat: Provide default when voices empty
console.warn('SystemTTS: getVoices() returned empty list. Providing browser default option.');
const defaultVoice = {
name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
preview_url: false,
lang: navigator.language || 'en-US',
};
resolve([defaultVoice]);
} else {
const mappedVoices = voices
.sort((a, b) => a.lang.localeCompare(b.lang) || a.name.localeCompare(b.name))
.map(x => ({ name: x.name, voice_id: x.voiceURI, preview_url: false, lang: x.lang }));
resolve(mappedVoices);
}
}, 50);
resolve(voices);
}, 1);
});
}
previewTtsVoice(voiceId) {
if (!('speechSynthesis' in window)) {
throw new Error('Speech synthesis API is not supported');
throw 'Speech synthesis API is not supported';
}
let voice = null;
if (voiceId !== SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID) {
const voices = speechSynthesis.getVoices();
voice = voices.find(x => x.voiceURI === voiceId);
const voice = speechSynthesis.getVoices().find(x => x.voiceURI === voiceId);
if (!voice && voices.length > 0) {
console.warn(`SystemTTS Preview: Voice ID "${voiceId}" not found among available voices. Using browser default.`);
} else if (!voice && voices.length === 0) {
console.warn('SystemTTS Preview: Voice list is empty. Using browser default.');
}
} else {
console.log('SystemTTS Preview: Using browser default voice as requested.');
if (!voice) {
throw `TTS Voice id ${voiceId} not found`;
}
speechSynthesis.cancel();
const langForPreview = voice ? voice.lang : (navigator.language || 'en-US');
const text = getPreviewString(langForPreview);
const text = getPreviewString(voice.lang);
const utterance = new SpeechSynthesisUtterance(text);
if (voice) {
utterance.voice = voice;
}
utterance.voice = voice;
utterance.rate = this.settings.rate || 1;
utterance.pitch = this.settings.pitch || 1;
utterance.onerror = (event) => {
console.error(`SystemTTS Preview Error: ${event.error}`, event);
};
speechSynthesis.speak(utterance);
}
async getVoice(voiceName) {
if (!('speechSynthesis' in window)) {
return { voice_id: null, name: 'API Not Supported' };
}
if (voiceName === SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME) {
return {
voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
};
return { voice_id: null };
}
const voices = speechSynthesis.getVoices();
if (voices.length === 0) {
console.warn('SystemTTS: Empty voice list, using default fallback');
return {
voice_id: SystemTtsProvider.BROWSER_DEFAULT_VOICE_ID,
name: SystemTtsProvider.BROWSER_DEFAULT_VOICE_NAME,
};
}
const match = voices.find(x => x.name == voiceName);
if (!match) {
throw new Error(`SystemTTS getVoice: TTS Voice name "${voiceName}" not found`);
throw `TTS Voice name ${voiceName} not found`;
}
return { voice_id: match.voiceURI, name: match.name };
@@ -287,6 +237,7 @@ class SystemTtsProvider {
speechUtteranceChunker(utterance, {
chunkLength: 200,
}, function () {
//some code to execute when done
resolve(silence);
console.log('System TTS done');
});

View File

@@ -55,8 +55,6 @@ const getBatchSize = () => ['transformers', 'palm', 'ollama'].includes(settings.
const settings = {
// For both
source: 'transformers',
alt_endpoint_url: '',
use_alt_endpoint: false,
include_wi: false,
togetherai_model: 'togethercomputer/m2-bert-80M-32k-retrieval',
openai_model: 'text-embedding-ada-002',
@@ -111,7 +109,6 @@ const settings = {
const moduleWorker = new ModuleWorkerWrapper(synchronizeChat);
const webllmProvider = new WebLlmVectorProvider();
const cachedSummaries = new Map();
const vectorApiRequiresUrl = ['llamacpp', 'vllm', 'ollama', 'koboldcpp'];
/**
* Gets the Collection ID for a file embedded in the chat.
@@ -780,14 +777,14 @@ function getVectorsRequestBody(args = {}) {
break;
case 'ollama':
body.model = extension_settings.vectors.ollama_model;
body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
body.keep = !!extension_settings.vectors.ollama_keep;
break;
case 'llamacpp':
body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
break;
case 'vllm':
body.apiUrl = settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.VLLM];
body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.VLLM];
body.model = extension_settings.vectors.vllm_model;
break;
case 'webllm':
@@ -886,18 +883,11 @@ function throwIfSourceInvalid() {
throw new Error('Vectors: API key missing', { cause: 'api_key_missing' });
}
if (vectorApiRequiresUrl.includes(settings.source) && settings.use_alt_endpoint) {
if (!settings.alt_endpoint_url) {
throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
}
}
else {
if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||
settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||
settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] ||
settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {
throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
}
if (settings.source === 'ollama' && !textgenerationwebui_settings.server_urls[textgen_types.OLLAMA] ||
settings.source === 'vllm' && !textgenerationwebui_settings.server_urls[textgen_types.VLLM] ||
settings.source === 'koboldcpp' && !textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP] ||
settings.source === 'llamacpp' && !textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP]) {
throw new Error('Vectors: API URL missing', { cause: 'api_url_missing' });
}
if (settings.source === 'ollama' && !settings.ollama_model || settings.source === 'vllm' && !settings.vllm_model) {
@@ -1097,7 +1087,6 @@ function toggleSettings() {
$('#webllm_vectorsModel').toggle(settings.source === 'webllm');
$('#koboldcpp_vectorsModel').toggle(settings.source === 'koboldcpp');
$('#google_vectorsModel').toggle(settings.source === 'palm');
$('#vector_altEndpointUrl').toggle(vectorApiRequiresUrl.includes(settings.source));
if (settings.source === 'webllm') {
loadWebLlmModels();
}
@@ -1176,7 +1165,7 @@ async function createKoboldCppEmbeddings(items) {
headers: getRequestHeaders(),
body: JSON.stringify({
items: items,
server: settings.use_alt_endpoint ? settings.alt_endpoint_url : textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP],
server: textgenerationwebui_settings.server_urls[textgen_types.KOBOLDCPP],
}),
});
@@ -1478,16 +1467,6 @@ jQuery(async () => {
saveSettingsDebounced();
toggleSettings();
});
$('#vector_altEndpointUrl_enabled').prop('checked', settings.use_alt_endpoint).on('input', () => {
settings.use_alt_endpoint = $('#vector_altEndpointUrl_enabled').prop('checked');
Object.assign(extension_settings.vectors, settings);
saveSettingsDebounced();
});
$('#vector_altEndpoint_address').val(settings.alt_endpoint_url).on('change', () => {
settings.alt_endpoint_url = String($('#vector_altEndpoint_address').val());
Object.assign(extension_settings.vectors, settings);
saveSettingsDebounced();
});
$('#api_key_nomicai').on('click', async () => {
const popupText = 'NomicAI API Key:';
const key = await callGenericPopup(popupText, POPUP_TYPE.INPUT, '', {

View File

@@ -25,16 +25,6 @@
<option value="webllm" data-i18n="WebLLM Extension">WebLLM Extension</option>
</select>
</div>
<div class="flex-container flexFlowColumn" id="vector_altEndpointUrl">
<label class="checkbox_label" for="vector_altEndpointUrl_enabled" title="Enable secondary endpoint URL usage, instead of the main one.">
<input id="vector_altEndpointUrl_enabled" type="checkbox" class="checkbox">
<span data-i18n="Use secondary URL">Use secondary URL</span>
</label>
<label for="vector_altEndpoint_address" data-i18n="Secondary Embedding endpoint URL">
Secondary Embedding endpoint URL
</label>
<input id="vector_altEndpoint_address" class="text_pole" type="text" placeholder="e.g. http://localhost:5001" />
</div>
<div class="flex-container flexFlowColumn" id="webllm_vectorsModel">
<label for="vectors_webllm_model" data-i18n="Vectorization Model">
Vectorization Model

View File

@@ -1412,9 +1412,9 @@ export async function prepareOpenAIMessages({
await populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples });
} catch (error) {
if (error instanceof TokenBudgetExceededError) {
toastr.error(t`Mandatory prompts exceed the context size.`);
chatCompletion.log('Mandatory prompts exceed the context size.');
promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token limit or disable custom prompts.`;
toastr.error(t`An error occurred while counting tokens: Token budget exceeded.`);
chatCompletion.log('Token budget exceeded.');
promptManager.error = t`Not enough free tokens for mandatory prompts. Raise your token Limit or disable custom prompts.`;
} else if (error instanceof InvalidCharacterNameError) {
toastr.warning(t`An error occurred while counting tokens: Invalid character name`);
chatCompletion.log('Invalid character name');
@@ -2039,7 +2039,7 @@ async function sendOpenAIRequest(type, messages, signal) {
}
// Proxy is only supported for Claude, OpenAI, Mistral, and Google MakerSuite
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) {
await validateReverseProxy();
generate_data['reverse_proxy'] = oai_settings.reverse_proxy;
generate_data['proxy_password'] = oai_settings.proxy_password;
@@ -2180,27 +2180,25 @@ async function sendOpenAIRequest(type, messages, signal) {
generate_data['seed'] = oai_settings.seed;
}
if (isOAI && /^(o1|o3|o4)/.test(oai_settings.openai_model)) {
if (isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) {
generate_data.messages.forEach((msg) => {
if (msg.role === 'system') {
msg.role = 'user';
}
});
generate_data.max_completion_tokens = generate_data.max_tokens;
delete generate_data.max_tokens;
delete generate_data.logprobs;
delete generate_data.top_logprobs;
delete generate_data.stop;
delete generate_data.logit_bias;
delete generate_data.n;
delete generate_data.temperature;
delete generate_data.top_p;
delete generate_data.frequency_penalty;
delete generate_data.presence_penalty;
if (oai_settings.openai_model.startsWith('o1')) {
generate_data.messages.forEach((msg) => {
if (msg.role === 'system') {
msg.role = 'user';
}
});
delete generate_data.n;
delete generate_data.tools;
delete generate_data.tool_choice;
}
delete generate_data.tools;
delete generate_data.tool_choice;
delete generate_data.stop;
delete generate_data.logit_bias;
}
await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
@@ -2236,8 +2234,7 @@ async function sendOpenAIRequest(type, messages, signal) {
if (Array.isArray(parsed?.choices) && parsed?.choices?.[0]?.index > 0) {
const swipeIndex = parsed.choices[0].index - 1;
// FIXME: state.reasoning should be an array to support multi-swipe
swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state, { overrideShowThoughts: false });
swipes[swipeIndex] = (swipes[swipeIndex] || '') + getStreamingReply(parsed, state);
} else {
text += getStreamingReply(parsed, state);
}
@@ -3548,7 +3545,7 @@ async function getStatusOpen() {
chat_completion_source: oai_settings.chat_completion_source,
};
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK, chat_completion_sources.XAI].includes(oai_settings.chat_completion_source)) {
if (oai_settings.reverse_proxy && [chat_completion_sources.CLAUDE, chat_completion_sources.OPENAI, chat_completion_sources.MISTRALAI, chat_completion_sources.MAKERSUITE, chat_completion_sources.DEEPSEEK].includes(oai_settings.chat_completion_source)) {
await validateReverseProxy();
}
@@ -4131,12 +4128,9 @@ function getMaxContextOpenAI(value) {
else if (value.includes('gpt-4.1')) {
return max_1mil;
}
else if (value.startsWith('o1')) {
else if (value.startsWith('o1') || value.startsWith('o3')) {
return max_128k;
}
else if (value.startsWith('o4') || value.startsWith('o3')) {
return max_200k;
}
else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
return max_128k;
}
@@ -4451,7 +4445,7 @@ async function onModelChange() {
$('#openai_max_context').attr('max', max_32k);
} else if (value.includes('gemini-1.5-pro') || value.includes('gemini-exp-1206') || value.includes('gemini-2.0-pro')) {
$('#openai_max_context').attr('max', max_2mil);
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.5-flash-preview-04-17') || value.includes('gemini-2.5-pro-exp-03-25') || value.includes('gemini-2.5-pro-preview-03-25')) {
} else if (value.includes('gemini-1.5-flash') || value.includes('gemini-2.0-flash') || value.includes('gemini-2.5-pro-exp-03-25') || value.includes('gemini-2.5-pro-preview-03-25')) {
$('#openai_max_context').attr('max', max_1mil);
} else if (value.includes('gemini-1.0-pro') || value === 'gemini-pro') {
$('#openai_max_context').attr('max', max_32k);
@@ -4949,7 +4943,7 @@ async function onConnectButtonClick(e) {
await writeSecret(SECRET_KEYS.XAI, api_key_xai);
}
if (!secret_state[SECRET_KEYS.XAI] && !oai_settings.reverse_proxy) {
if (!secret_state[SECRET_KEYS.XAI]) {
console.log('No secret key saved for XAI');
return;
}
@@ -5103,7 +5097,6 @@ export function isImageInliningSupported() {
'gemini-2.5-pro-preview-03-25',
'gemini-2.0-pro-exp',
'gemini-2.0-pro-exp-02-05',
'gemini-2.5-flash-preview-04-17',
'gemini-2.0-flash-lite-preview',
'gemini-2.0-flash-lite-preview-02-05',
'gemini-2.0-flash',
@@ -5156,15 +5149,11 @@ export function isImageInliningSupported() {
'grok-2-vision',
'grok-vision',
'gpt-4.1',
'o3',
'o3-2025-04-16',
'o4-mini',
'o4-mini-2025-04-16',
];
switch (oai_settings.chat_completion_source) {
case chat_completion_sources.OPENAI:
return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview') && !oai_settings.openai_model.includes('o3-mini'));
return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));
case chat_completion_sources.MAKERSUITE:
return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
case chat_completion_sources.CLAUDE:

View File

@@ -928,10 +928,6 @@ export function getCurrentDreamGenModelTokenizer() {
return tokenizers.YI;
} else if (model.id.startsWith('opus-v1-xl')) {
return tokenizers.LLAMA;
} else if (model.id.startsWith('lucid-v1-medium')) {
return tokenizers.NEMO;
} else if (model.id.startsWith('lucid-v1-extra-large')) {
return tokenizers.LLAMA3;
} else {
return tokenizers.MISTRAL;
}

View File

@@ -1338,18 +1338,6 @@ function registerWorldInfoSlashCommands() {
}
}
async function getGlobalBooksCallback() {
if (!selected_world_info?.length) {
return JSON.stringify([]);
}
let entries = selected_world_info.slice();
console.debug(`[WI] Selected global world info has ${entries.length} entries`, selected_world_info);
return JSON.stringify(entries);
}
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'world',
callback: onWorldInfoChange,
@@ -1391,13 +1379,6 @@ function registerWorldInfoSlashCommands() {
],
aliases: ['getchatlore', 'getchatwi'],
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'getglobalbooks',
callback: getGlobalBooksCallback,
returns: 'list of selected lorebook names',
helpString: 'Get a list of names of the selected global lorebooks and pass it down the pipe.',
aliases: ['getgloballore', 'getglobalwi'],
}));
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
name: 'getpersonabook',
callback: getPersonaBookCallback,

View File

@@ -268,6 +268,23 @@ export const FEATHERLESS_KEYS = [
'guided_whitespace_pattern',
];
// https://dreamgen.com/docs/api#openai-text
export const DREAMGEN_KEYS = [
'model',
'prompt',
'max_tokens',
'temperature',
'top_p',
'top_k',
'min_p',
'repetition_penalty',
'frequency_penalty',
'presence_penalty',
'stop',
'stream',
'minimum_message_content_tokens',
];
// https://docs.together.ai/reference/completions
export const TOGETHERAI_KEYS = [
'model',

View File

@@ -250,7 +250,7 @@ async function sendClaudeRequest(request, response) {
if (!generateResponse.ok) {
const generateResponseText = await generateResponse.text();
console.warn(color.red(`Claude API returned error: ${generateResponse.status} ${generateResponse.statusText}\n${generateResponseText}\n${divider}`));
return response.status(500).send({ error: true });
return response.status(generateResponse.status).send({ error: true });
}
/** @type {any} */
@@ -366,7 +366,6 @@ async function sendMakerSuiteRequest(request, response) {
const useSystemPrompt = !useMultiModal && (
model.includes('gemini-2.5-pro') ||
model.includes('gemini-2.5-flash') ||
model.includes('gemini-2.0-pro') ||
model.includes('gemini-2.0-flash') ||
model.includes('gemini-2.0-flash-thinking-exp') ||
@@ -464,7 +463,7 @@ async function sendMakerSuiteRequest(request, response) {
} else {
if (!generateResponse.ok) {
console.warn(`Google AI Studio API returned error: ${generateResponse.status} ${generateResponse.statusText} ${await generateResponse.text()}`);
return response.status(500).send({ error: true });
return response.status(generateResponse.status).send({ error: true });
}
/** @type {any} */
@@ -831,100 +830,6 @@ async function sendDeepSeekRequest(request, response) {
}
}
/**
* Sends a request to XAI API.
* @param {express.Request} request Express request
* @param {express.Response} response Express response
*/
async function sendXaiRequest(request, response) {
const apiUrl = new URL(request.body.reverse_proxy || API_XAI).toString();
const apiKey = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI);
if (!apiKey && !request.body.reverse_proxy) {
console.warn('xAI API key is missing.');
return response.status(400).send({ error: true });
}
const controller = new AbortController();
request.socket.removeAllListeners('close');
request.socket.on('close', function () {
controller.abort();
});
try {
let bodyParams = {};
if (request.body.logprobs > 0) {
bodyParams['top_logprobs'] = request.body.logprobs;
bodyParams['logprobs'] = true;
}
if (Array.isArray(request.body.tools) && request.body.tools.length > 0) {
bodyParams['tools'] = request.body.tools;
bodyParams['tool_choice'] = request.body.tool_choice;
}
if (Array.isArray(request.body.stop) && request.body.stop.length > 0) {
bodyParams['stop'] = request.body.stop;
}
if (['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) {
bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low';
}
const processedMessages = request.body.messages = convertXAIMessages(request.body.messages, getPromptNames(request));
const requestBody = {
'messages': processedMessages,
'model': request.body.model,
'temperature': request.body.temperature,
'max_tokens': request.body.max_tokens,
'max_completion_tokens': request.body.max_completion_tokens,
'stream': request.body.stream,
'presence_penalty': request.body.presence_penalty,
'frequency_penalty': request.body.frequency_penalty,
'top_p': request.body.top_p,
'seed': request.body.seed,
'n': request.body.n,
...bodyParams,
};
const config = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + apiKey,
},
body: JSON.stringify(requestBody),
signal: controller.signal,
};
console.debug('xAI request:', requestBody);
const generateResponse = await fetch(apiUrl + '/chat/completions', config);
if (request.body.stream) {
forwardFetchResponse(generateResponse, response);
} else {
if (!generateResponse.ok) {
const errorText = await generateResponse.text();
console.warn(`xAI API returned error: ${generateResponse.status} ${generateResponse.statusText} ${errorText}`);
const errorJson = tryParse(errorText) ?? { error: true };
return response.status(500).send(errorJson);
}
const generateResponseJson = await generateResponse.json();
console.debug('xAI response:', generateResponseJson);
return response.send(generateResponseJson);
}
} catch (error) {
console.error('Error communicating with xAI API: ', error);
if (!response.headersSent) {
response.send({ error: true });
} else {
response.end();
}
}
}
export const router = express.Router();
@@ -970,9 +875,8 @@ router.post('/status', async function (request, response_getstatus_openai) {
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.DEEPSEEK);
headers = {};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.XAI) {
api_url = new URL(request.body.reverse_proxy || API_XAI);
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(request.user.directories, SECRET_KEYS.XAI);
headers = {};
api_url = API_XAI;
api_key_openai = readSecret(request.user.directories, SECRET_KEYS.XAI);
} else {
console.warn('This chat completion source is not supported yet.');
return response_getstatus_openai.status(400).send({ error: true });
@@ -1140,7 +1044,6 @@ router.post('/generate', function (request, response) {
case CHAT_COMPLETION_SOURCES.MISTRALAI: return sendMistralAIRequest(request, response);
case CHAT_COMPLETION_SOURCES.COHERE: return sendCohereRequest(request, response);
case CHAT_COMPLETION_SOURCES.DEEPSEEK: return sendDeepSeekRequest(request, response);
case CHAT_COMPLETION_SOURCES.XAI: return sendXaiRequest(request, response);
}
let apiUrl;
@@ -1252,6 +1155,12 @@ router.post('/generate', function (request, response) {
apiKey = readSecret(request.user.directories, SECRET_KEYS.ZEROONEAI);
headers = {};
bodyParams = {};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.XAI) {
apiUrl = API_XAI;
apiKey = readSecret(request.user.directories, SECRET_KEYS.XAI);
headers = {};
bodyParams = {};
request.body.messages = convertXAIMessages(request.body.messages, getPromptNames(request));
} else {
console.warn('This chat completion source is not supported yet.');
return response.status(400).send({ error: true });
@@ -1259,11 +1168,17 @@ router.post('/generate', function (request, response) {
// A few of OpenAIs reasoning models support reasoning effort
if ([CHAT_COMPLETION_SOURCES.CUSTOM, CHAT_COMPLETION_SOURCES.OPENAI].includes(request.body.chat_completion_source)) {
if (['o1', 'o3-mini', 'o3-mini-2025-01-31', 'o4-mini', 'o4-mini-2025-04-16', 'o3', 'o3-2025-04-16'].includes(request.body.model)) {
if (['o1', 'o3-mini', 'o3-mini-2025-01-31'].includes(request.body.model)) {
bodyParams['reasoning_effort'] = request.body.reasoning_effort;
}
}
if ([CHAT_COMPLETION_SOURCES.XAI].includes(request.body.chat_completion_source)) {
if (['grok-3-mini-beta', 'grok-3-mini-fast-beta'].includes(request.body.model)) {
bodyParams['reasoning_effort'] = request.body.reasoning_effort === 'high' ? 'high' : 'low';
}
}
if (!apiKey && !request.body.reverse_proxy && request.body.chat_completion_source !== CHAT_COMPLETION_SOURCES.CUSTOM) {
console.warn('OpenAI API key is missing.');
return response.status(400).send({ error: true });

View File

@@ -10,6 +10,7 @@ import {
INFERMATICAI_KEYS,
OPENROUTER_KEYS,
VLLM_KEYS,
DREAMGEN_KEYS,
FEATHERLESS_KEYS,
OPENAI_KEYS,
} from '../../constants.js';
@@ -339,6 +340,9 @@ router.post('/generate', async function (request, response) {
}
if (request.body.api_type === TEXTGEN_TYPES.DREAMGEN) {
request.body = _.pickBy(request.body, (_, key) => DREAMGEN_KEYS.includes(key));
// NOTE: DreamGen sometimes get confused by the unusual formatting in the character cards.
request.body.stop?.push('### User', '## User');
args.body = JSON.stringify(request.body);
}
@@ -446,7 +450,7 @@ ollama.post('/download', async function (request, response) {
if (!fetchResponse.ok) {
console.error('Download error:', fetchResponse.status, fetchResponse.statusText);
return response.status(500).send({ error: true });
return response.status(fetchResponse.status).send({ error: true });
}
console.debug('Ollama pull response:', await fetchResponse.json());
@@ -655,14 +659,14 @@ tabby.post('/download', async function (request, response) {
}
} else {
console.error('API Permission error:', permissionResponse.status, permissionResponse.statusText);
return response.status(500).send({ error: true });
return response.status(permissionResponse.status).send({ error: true });
}
const fetchResponse = await fetch(`${baseUrl}/v1/download`, args);
if (!fetchResponse.ok) {
console.error('Download error:', fetchResponse.status, fetchResponse.statusText);
return response.status(500).send({ error: true });
return response.status(fetchResponse.status).send({ error: true });
}
return response.send({ ok: true });

View File

@@ -540,21 +540,9 @@ async function downloadGenericPng(url) {
if (result.ok) {
const buffer = Buffer.from(await result.arrayBuffer());
let fileName = sanitize(result.url.split('?')[0].split('/').reverse()[0]);
const fileName = sanitize(result.url.split('?')[0].split('/').reverse()[0]);
const contentType = result.headers.get('content-type') || 'image/png'; //yoink it from AICC function lol
// The `importCharacter()` function detects the MIME (content-type) of the file
// using its file extension. The problem is that not all third-party APIs serve
// their cards with a `.png` extension. To support more third-party sites,
// dynamically append the `.png` extension to the filename if it doesn't
// already have a file extension.
if (contentType === 'image/png') {
const ext = fileName.match(/\.(\w+)$/); // Same regex used by `importCharacter()`
if (!ext) {
fileName += '.png';
}
}
return {
buffer: buffer,
fileName: fileName,
@@ -706,11 +694,10 @@ router.post('/importURL', async (request, response) => {
type = 'character';
result = await downloadRisuCharacter(uuid);
} else if (isGeneric) {
console.info('Downloading from generic url:', url);
console.info('Downloading from generic url.');
type = 'character';
result = await downloadGenericPng(url);
} else {
console.error(`Received an import for "${getHostFromUrl(url)}", but site is not whitelisted. This domain must be added to the config key "whitelistImportDomains" to allow import from this source.`);
return response.sendStatus(404);
}

View File

@@ -45,7 +45,7 @@ router.post('/caption-image', async (request, response) => {
if (!result.ok) {
const error = await result.json();
console.error(`Google AI Studio API returned error: ${result.status} ${result.statusText}`, error);
return response.status(500).send({ error: true });
return response.status(result.status).send({ error: true });
}
/** @type {any} */

View File

@@ -270,7 +270,7 @@ router.post('/generate', async function (req, res) {
// ignore
}
return res.status(500).send({ error: { message } });
return res.status(response.status).send({ error: { message } });
}
/** @type {any} */

View File

@@ -407,10 +407,6 @@ export function getTokenizerModel(requestModel) {
return 'o1';
}
if (requestModel.includes('o3') || requestModel.includes('o4-mini')) {
return 'o1';
}
if (requestModel.includes('gpt-4o') || requestModel.includes('chatgpt-4o-latest')) {
return 'gpt-4o';
}

View File

@@ -364,7 +364,6 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) {
'gemini-2.5-pro-exp-03-25',
'gemini-2.0-pro-exp',
'gemini-2.0-pro-exp-02-05',
'gemini-2.5-flash-preview-04-17',
'gemini-2.0-flash-lite-preview',
'gemini-2.0-flash-lite-preview-02-05',
'gemini-2.0-flash',
@@ -509,12 +508,7 @@ export function convertGooglePrompt(messages, model, useSysPrompt, names) {
if (index > 0 && message.role === contents[contents.length - 1].role) {
parts.forEach((part) => {
if (part.text) {
const textPart = contents[contents.length - 1].parts.find(p => typeof p.text === 'string');
if (textPart) {
textPart.text += '\n\n' + part.text;
} else {
contents[contents.length - 1].parts.push(part);
}
contents[contents.length - 1].parts[0].text += '\n\n' + part.text;
}
if (part.inlineData || part.functionCall || part.functionResponse) {
contents[contents.length - 1].parts.push(part);

View File

@@ -3,16 +3,16 @@ import fs from 'node:fs';
import process from 'node:process';
import { Buffer } from 'node:buffer';
import { pipeline, env, RawImage } from 'sillytavern-transformers';
import { pipeline, env, RawImage } from '@huggingface/transformers';
import { getConfigValue } from './util.js';
configureTransformers();
function configureTransformers() {
// Limit the number of threads to 1 to avoid issues on Android
env.backends.onnx.wasm.numThreads = 1;
// env.backends.onnx.wasm.numThreads = 1;
// Use WASM from a local folder to avoid CDN connections
env.backends.onnx.wasm.wasmPaths = path.join(process.cwd(), 'dist') + path.sep;
// env.backends.onnx.wasm.wasmPaths = path.join(process.cwd(), 'dist') + path.sep;
}
const tasks = {
@@ -115,9 +115,9 @@ async function migrateCacheToDataDir() {
/**
* Gets the transformers.js pipeline for a given task.
* @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for
* @param {import('@huggingface/transformers').PipelineType} task The task to get the pipeline for
* @param {string} forceModel The model to use for the pipeline, if any
* @returns {Promise<import('sillytavern-transformers').Pipeline>} The transformers.js pipeline
* @returns {Promise<import('@huggingface/transformers').Pipeline>} The transformers.js pipeline
*/
export async function getPipeline(task, forceModel = '') {
await migrateCacheToDataDir();