Merge branch 'staging' of https://github.com/joenunezb/SillyTavern into optimize/improve-search

This commit is contained in:
Joe 2024-11-26 18:00:28 -08:00
commit 78c55558af
15 changed files with 177 additions and 79 deletions

View File

@ -229,6 +229,46 @@ SillyTavern сохраняет ключи от ваших API в файле `sec
1. Зайдите в файл `config.yaml` и установите `allowKeysExposure` в положение `true`. 1. Зайдите в файл `config.yaml` и установите `allowKeysExposure` в положение `true`.
2. Перезапустите сервер SillyTavern. 2. Перезапустите сервер SillyTavern.
## Аргументы командной строки
Вы можете передавать аргументы командной строки при запуске сервера SillyTavern, чтобы переопределять настройки из `config.yaml`.
### Примеры
```shell
node server.js --port 8000 --listen false
# или
npm run start -- --port 8000 --listen false
# или (только на Windows)
Start.bat --port 8000 --listen false
```
### Поддерживаемые аргументы
| Аргумент | Описание | Тип |
|-------------------------|----------------------------------------------------------------------------------------------------------------|----------|
| `--version` | Показывает номер версии. | boolean |
| `--enableIPv6` | Включает IPv6. | boolean |
| `--enableIPv4` | Включает IPv4. | boolean |
| `--port` | Устанавливает порт, котрый будет использовать SillyTavern. Если не указан, то используется yaml-конфиг 'port'. | number |
| `--dnsPreferIPv6` | Отдает предпочтение IPv6 для dns. Если не указан, то используется yaml-конфиг 'preferIPv6'. | boolean |
| `--autorun` | Автоматический запуск SillyTavern в браузере. Если не указан, то используется yaml-конфиг 'autorun'. | boolean |
| `--autorunHostname` | Имя хоста автозапуска, лучше оставить на 'auto'. | string |
| `--autorunPortOverride` | Переопределяет порт для автозапуска. | string |
| `--listen` | SillyTavern будет прослушивать все сетевые интерфейсы. Если не указан, то используется yaml-конфиг 'listen'. | boolean |
| `--corsProxy` | Включает CORS-прокси. Если не указан, то используется yaml-конфиг 'enableCorsProxy'. | boolean |
| `--disableCsrf` | Отключает защиту от CSRF. | boolean |
| `--ssl` | Включает SSL. | boolean |
| `--certPath` | Путь к файлу c сертификатом. | string |
| `--keyPath` | Путь к файлу с закрытым ключом. | string |
| `--whitelist` | Включает режим белого списка. | boolean |
| `--dataRoot` | Корневой каталог для хранения данных. | string |
| `--avoidLocalhost` | Избегает использования 'localhost' для автозапуска в режиме 'auto'. | boolean |
| `--basicAuthMode` | Включает простую аутентификацию. | boolean |
| `--requestProxyEnabled` | Разрешает использование прокси для исходящих запросов. | boolean |
| `--requestProxyUrl` | URL-адрес прокси (протоколы HTTP или SOCKS). | string |
| `--requestProxyBypass` | Bypass список прокси (список хостов, разделенных пробелами). | array |
## Удалённое подключение ## Удалённое подключение
В основном этим пользуются тогда, когда хотят использовать SillyTavern с телефона, запустив сервер SillyTavern на стационарном ПК в той же Wi-Fi-сети. В основном этим пользуются тогда, когда хотят использовать SillyTavern с телефона, запустив сервер SillyTavern на стационарном ПК в той же Wi-Fi-сети.

View File

@ -230,7 +230,6 @@
"show_external_models": false, "show_external_models": false,
"assistant_prefill": "", "assistant_prefill": "",
"assistant_impersonation": "", "assistant_impersonation": "",
"human_sysprompt_message": "Let's get started. Please generate your response based on the information and instructions provided above.",
"claude_use_sysprompt": false, "claude_use_sysprompt": false,
"use_alt_scale": false, "use_alt_scale": false,
"squash_system_messages": false, "squash_system_messages": false,

View File

@ -1291,6 +1291,14 @@
<input class="neo-range-slider" type="range" id="epsilon_cutoff_textgenerationwebui" name="volume" min="0" max="9" step="0.01"> <input class="neo-range-slider" type="range" id="epsilon_cutoff_textgenerationwebui" name="volume" min="0" max="9" step="0.01">
<input class="neo-range-input" type="number" min="0" max="9" step="0.01" data-for="epsilon_cutoff_textgenerationwebui" id="epsilon_cutoff_counter_textgenerationwebui"> <input class="neo-range-input" type="number" min="0" max="9" step="0.01" data-for="epsilon_cutoff_textgenerationwebui" id="epsilon_cutoff_counter_textgenerationwebui">
</div> </div>
<div data-tg-type="aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<small>
<span data-i18n="Top nsigma">Top nsigma</span>
<div class="fa-solid fa-circle-info opacity50p" title="A sampling method that filters logits based on their statistical properties. It keeps tokens within n standard deviations of the maximum logit value, providing a simpler alternative to top-p/top-k sampling while maintaining sampling stability across different temperatures."></div>
</small>
<input class="neo-range-slider" type="range" id="nsigma_textgenerationwebui" name="volume" min="0" max="5" step="0.01">
<input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="nsigma_textgenerationwebui" id="nsigma_counter_textgenerationwebui">
</div>
<div data-tg-type="ooba,mancer,aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> <div data-tg-type="ooba,mancer,aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<small> <small>
<span data-i18n="Eta Cutoff">Eta Cutoff</span> <span data-i18n="Eta Cutoff">Eta Cutoff</span>
@ -1334,12 +1342,12 @@
<input class="neo-range-slider" type="range" id="presence_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" /> <input class="neo-range-slider" type="range" id="presence_pen_textgenerationwebui" name="volume" min="-2" max="2" step="0.01" />
<input class="neo-range-input" type="number" min="-2" max="2" step="0.01" data-for="presence_pen_textgenerationwebui" id="presence_pen_counter_textgenerationwebui"> <input class="neo-range-input" type="number" min="-2" max="2" step="0.01" data-for="presence_pen_textgenerationwebui" id="presence_pen_counter_textgenerationwebui">
</div> </div>
<div data-tg-type="ooba" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> <div data-tg-type="aphrodite, ooba" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<small data-i18n="No Repeat Ngram Size">No Repeat Ngram Size</small> <small data-i18n="No Repeat Ngram Size">No Repeat Ngram Size</small>
<input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1"> <input class="neo-range-slider" type="range" id="no_repeat_ngram_size_textgenerationwebui" name="volume" min="0" max="20" step="1">
<input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="no_repeat_ngram_size_textgenerationwebui" id="no_repeat_ngram_size_counter_textgenerationwebui"> <input class="neo-range-input" type="number" min="0" max="20" step="1" data-for="no_repeat_ngram_size_textgenerationwebui" id="no_repeat_ngram_size_counter_textgenerationwebui">
</div> </div>
<div data-tg-type="tabby" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0"> <div data-tg-type="tabby, aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis30p flexGrow flexShrink gap0">
<small data-i18n="Skew">Skew</small> <small data-i18n="Skew">Skew</small>
<input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" /> <input class="neo-range-slider" type="range" id="skew_textgenerationwebui" name="volume" min="-5" max="5" step="0.01" />
<input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui"> <input class="neo-range-input" type="number" min="-5" max="5" step="0.01" data-for="skew_textgenerationwebui" id="skew_counter_textgenerationwebui">
@ -1394,7 +1402,7 @@
</div> </div>
</div> </div>
<div data-tg-type="ooba, koboldcpp, tabby, llamacpp" 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"> <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> <label data-i18n="DRY Repetition Penalty">DRY Repetition Penalty</label>
<a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank"> <a href="https://github.com/oobabooga/text-generation-webui/pull/5677" target="_blank">
@ -1951,15 +1959,6 @@
Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt. Send the system prompt for supported models. If disabled, the user message is added to the beginning of the prompt.
</span> </span>
</div> </div>
<div id="claude_human_sysprompt_message_block" class="wide100p">
<div class="range-block-title openai_restorable">
<span data-i18n="User first message">User first message</span>
<div id="claude_human_sysprompt_message_restore" title="Restore User first message" data-i18n="[title]Restore User first message" class="right_menu_button">
<div class="fa-solid fa-clock-rotate-left"></div>
</div>
</div>
<textarea id="claude_human_sysprompt_textarea" class="text_pole textarea_compact autoSetHeight" rows="2" data-i18n="[placeholder]Human message" placeholder="Human message, instruction, etc.&#10;Adds nothing when empty, i.e. requires a new prompt with the role 'user'."></textarea>
</div>
</div> </div>
</div> </div>
<div class="range-block m-t-1" data-source="openai,openrouter,scale,custom"> <div class="range-block m-t-1" data-source="openai,openrouter,scale,custom">
@ -2805,9 +2804,6 @@
<option value="claude-3-haiku-20240307">claude-3-haiku-20240307</option> <option value="claude-3-haiku-20240307">claude-3-haiku-20240307</option>
<option value="claude-2.1">claude-2.1</option> <option value="claude-2.1">claude-2.1</option>
<option value="claude-2.0">claude-2.0</option> <option value="claude-2.0">claude-2.0</option>
<option value="claude-1.3">claude-1.3</option>
<option value="claude-instant-1.2">claude-instant-1.2</option>
<option value="claude-instant-1.1">claude-instant-1.1</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>

View File

@ -2707,8 +2707,7 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q
quietName: quietName, quietName: quietName,
}; };
originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1; originalResponseLength = responseLengthCustomized ? saveResponseLength(main_api, responseLength) : -1;
const generateFinished = await Generate('quiet', options); return await Generate('quiet', options);
return generateFinished;
} finally { } finally {
if (responseLengthCustomized) { if (responseLengthCustomized) {
restoreResponseLength(main_api, originalResponseLength); restoreResponseLength(main_api, originalResponseLength);
@ -3363,9 +3362,9 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
let data = {}; let data = {};
if (api == 'koboldhorde') { if (api === 'koboldhorde') {
data = await generateHorde(prompt, generateData, abortController.signal, false); data = await generateHorde(prompt, generateData, abortController.signal, false);
} else if (api == 'openai') { } else if (api === 'openai') {
data = await sendOpenAIRequest('quiet', generateData, abortController.signal); data = await sendOpenAIRequest('quiet', generateData, abortController.signal);
} else { } else {
const generateUrl = getGenerateUrl(api); const generateUrl = getGenerateUrl(api);
@ -3378,13 +3377,15 @@ export async function generateRaw(prompt, api, instructOverride, quietToLoud, sy
}); });
if (!response.ok) { if (!response.ok) {
const error = await response.json(); throw await response.json();
throw error;
} }
data = await response.json(); data = await response.json();
} }
// should only happen for text completions
// other frontend paths do not return data if calling the backend fails,
// they throw things instead
if (data.error) { if (data.error) {
throw new Error(data.response); throw new Error(data.response);
} }
@ -4436,6 +4437,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
return Promise.resolve(); return Promise.resolve();
} }
/**
* Saves itemized prompt bits and calls streaming or non-streaming generation API.
* @returns {Promise<void|*|Awaited<*>|String|{fromStream}|string|undefined|Object>}
* @throws {Error|object} Error with message text, or Error with response JSON (OAI/Horde), or the actual response JSON (novel|textgenerationwebui|kobold)
*/
async function finishGenerating() { async function finishGenerating() {
if (power_user.console_log_prompts) { if (power_user.console_log_prompts) {
console.log(generate_data.prompt); console.log(generate_data.prompt);
@ -4547,6 +4553,12 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
return finishGenerating().then(onSuccess, onError); return finishGenerating().then(onSuccess, onError);
/**
* Handles the successful response from the generation API.
* @param data
* @returns {Promise<String|{fromStream}|*|string|string|void|Awaited<*>|undefined>}
* @throws {Error} Throws an error if the response data contains an error message
*/
async function onSuccess(data) { async function onSuccess(data) {
if (!data) return; if (!data) return;
@ -4556,6 +4568,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
let messageChunk = ''; let messageChunk = '';
// if an error was returned in data (textgenwebui), show it and throw it
if (data.error) { if (data.error) {
unblockGeneration(type); unblockGeneration(type);
generatedPromptCache = ''; generatedPromptCache = '';
@ -4670,9 +4683,15 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk }); return Object.defineProperty(new String(getMessage), 'messageChunk', { value: messageChunk });
} }
/**
* Exception handler for finishGenerating
* @param {Error|object} exception Error or response JSON
* @throws {Error|object} Re-throws the exception
*/
function onError(exception) { function onError(exception) {
// if the response JSON was thrown (novel|textgenerationwebui|kobold), show the error message
if (typeof exception?.error?.message === 'string') { if (typeof exception?.error?.message === 'string') {
toastr.error(exception.error.message, t`Error`, { timeOut: 10000, extendedTimeOut: 20000 }); toastr.error(exception.error.message, t`Text generation error`, { timeOut: 10000, extendedTimeOut: 20000 });
} }
generatedPromptCache = ''; generatedPromptCache = '';
@ -5340,6 +5359,7 @@ function setInContextMessages(lastmsg, type) {
* @param {string} type Generation type * @param {string} type Generation type
* @param {object} data Generation data * @param {object} data Generation data
* @returns {Promise<object>} Response data from the API * @returns {Promise<object>} Response data from the API
* @throws {Error|object}
*/ */
export async function sendGenerationRequest(type, data) { export async function sendGenerationRequest(type, data) {
if (main_api === 'openai') { if (main_api === 'openai') {
@ -5359,12 +5379,10 @@ export async function sendGenerationRequest(type, data) {
}); });
if (!response.ok) { if (!response.ok) {
const error = await response.json(); throw await response.json();
throw error;
} }
const responseData = await response.json(); return await response.json();
return responseData;
} }
/** /**
@ -5396,6 +5414,7 @@ export async function sendStreamingRequest(type, data) {
* Gets the generation endpoint URL for the specified API. * Gets the generation endpoint URL for the specified API.
* @param {string} api API name * @param {string} api API name
* @returns {string} Generation URL * @returns {string} Generation URL
* @throws {Error} If the API is unknown
*/ */
function getGenerateUrl(api) { function getGenerateUrl(api) {
switch (api) { switch (api) {

View File

@ -65,6 +65,11 @@ const parse_derivation = derivation => (typeof derivation === 'string') ? {
} : derivation; } : derivation;
export async function deriveTemplatesFromChatTemplate(chat_template, hash) { export async function deriveTemplatesFromChatTemplate(chat_template, hash) {
if (chat_template.trim() === '') {
console.log('Missing chat template.');
return null;
}
if (hash in hash_derivations) { if (hash in hash_derivations) {
return parse_derivation(hash_derivations[hash]); return parse_derivation(hash_derivations[hash]);
} }

View File

@ -2373,6 +2373,7 @@ function ensureSelectionExists(setting, selector) {
* @param {string} [message] Chat message * @param {string} [message] Chat message
* @param {function} [callback] Callback function * @param {function} [callback] Callback function
* @returns {Promise<string|undefined>} Image path * @returns {Promise<string|undefined>} Image path
* @throws {Error} If the prompt or image generation fails
*/ */
async function generatePicture(initiator, args, trigger, message, callback) { async function generatePicture(initiator, args, trigger, message, callback) {
if (!trigger || trigger.trim().length === 0) { if (!trigger || trigger.trim().length === 0) {
@ -2391,7 +2392,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
trigger = trigger.trim(); trigger = trigger.trim();
const generationType = getGenerationType(trigger); const generationType = getGenerationType(trigger);
const generationTypeKey = Object.keys(generationMode).find(key => generationMode[key] === generationType); const generationTypeKey = Object.keys(generationMode).find(key => generationMode[key] === generationType);
console.log(`Generation mode ${generationTypeKey} triggered with "${trigger}"`); console.log(`Image generation mode ${generationTypeKey} triggered with "${trigger}"`);
const quietPrompt = getQuietPrompt(generationType, trigger); const quietPrompt = getQuietPrompt(generationType, trigger);
const context = getContext(); const context = getContext();
@ -2428,6 +2429,8 @@ async function generatePicture(initiator, args, trigger, message, callback) {
try { try {
const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); }; const combineNegatives = (prefix) => { negativePromptPrefix = combinePrefixes(negativePromptPrefix, prefix); };
// generate the text prompt for the image
const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives); const prompt = await getPrompt(generationType, message, trigger, quietPrompt, combineNegatives);
console.log('Processed image prompt:', prompt); console.log('Processed image prompt:', prompt);
@ -2438,11 +2441,16 @@ async function generatePicture(initiator, args, trigger, message, callback) {
args._abortController.addEventListener('abort', stopListener); args._abortController.addEventListener('abort', stopListener);
} }
// generate the image
imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal); imagePath = await sendGenerationRequest(generationType, prompt, negativePromptPrefix, characterName, callback, initiator, abortController.signal);
} catch (err) { } catch (err) {
console.trace(err); console.trace(err);
toastr.error('SD prompt text generation failed. Reason: ' + err, 'Image Generation'); // errors here are most likely due to text generation failure
throw new Error('SD prompt text generation failed. Reason: ' + err); // sendGenerationRequest mostly deals with its own errors
const reason = err.error?.message || err.message || 'Unknown error';
const errorText = 'SD prompt text generation failed. ' + reason;
toastr.error(errorText, 'Image Generation');
throw new Error(errorText);
} }
finally { finally {
$(stopButton).hide(); $(stopButton).hide();
@ -2513,7 +2521,7 @@ function restoreOriginalDimensions(savedParams) {
*/ */
async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) { async function getPrompt(generationType, message, trigger, quietPrompt, combineNegatives) {
let prompt; let prompt;
console.log('getPrompt: Generation mode', generationType, 'triggered with', trigger);
switch (generationType) { switch (generationType) {
case generationMode.RAW_LAST: case generationMode.RAW_LAST:
prompt = message || getRawLastMessage(); prompt = message || getRawLastMessage();
@ -2729,7 +2737,7 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
throw new Error('Endpoint did not return image data.'); throw new Error('Endpoint did not return image data.');
} }
} catch (err) { } catch (err) {
console.error(err); console.error('Image generation request error: ', err);
toastr.error('Image generation failed. Please try again.' + '\n\n' + String(err), 'Image Generation'); toastr.error('Image generation failed. Please try again.' + '\n\n' + String(err), 'Image Generation');
return; return;
} }

View File

@ -181,6 +181,14 @@ function setContextSizePreview() {
} }
} }
/** Generates text using the Horde API.
* @param {string} prompt
* @param params
* @param signal
* @param reportProgress
* @returns {Promise<{text: *, workerName: string}>}
* @throws {Error}
*/
async function generateHorde(prompt, params, signal, reportProgress) { async function generateHorde(prompt, params, signal, reportProgress) {
validateHordeModel(); validateHordeModel();
delete params.prompt; delete params.prompt;

View File

@ -99,7 +99,6 @@ const default_wi_format = '{0}';
const default_new_chat_prompt = '[Start a new Chat]'; const default_new_chat_prompt = '[Start a new Chat]';
const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]'; const default_new_group_chat_prompt = '[Start a new group chat. Group members: {{group}}]';
const default_new_example_chat_prompt = '[Example Chat]'; const default_new_example_chat_prompt = '[Example Chat]';
const default_claude_human_sysprompt_message = 'Let\'s get started. Please generate your response based on the information and instructions provided above.';
const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]'; const default_continue_nudge_prompt = '[Continue the following message. Do not include ANY parts of the original message. Use capitalization and punctuation as if your reply is a part of the original message: {{lastChatMessage}}]';
const default_bias = 'Default (none)'; const default_bias = 'Default (none)';
const default_personality_format = '[{{char}}\'s personality: {{personality}}]'; const default_personality_format = '[{{char}}\'s personality: {{personality}}]';
@ -276,7 +275,6 @@ const default_settings = {
proxy_password: '', proxy_password: '',
assistant_prefill: '', assistant_prefill: '',
assistant_impersonation: '', assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message,
claude_use_sysprompt: false, claude_use_sysprompt: false,
use_makersuite_sysprompt: true, use_makersuite_sysprompt: true,
use_alt_scale: false, use_alt_scale: false,
@ -353,7 +351,6 @@ const oai_settings = {
proxy_password: '', proxy_password: '',
assistant_prefill: '', assistant_prefill: '',
assistant_impersonation: '', assistant_impersonation: '',
human_sysprompt_message: default_claude_human_sysprompt_message,
claude_use_sysprompt: false, claude_use_sysprompt: false,
use_makersuite_sysprompt: true, use_makersuite_sysprompt: true,
use_alt_scale: false, use_alt_scale: false,
@ -1313,6 +1310,11 @@ export async function prepareOpenAIMessages({
return [chat, promptManager.tokenHandler.counts]; return [chat, promptManager.tokenHandler.counts];
} }
/**
* Handles errors during streaming requests.
* @param {Response} response
* @param {string} decoded - response text or decoded stream data
*/
function tryParseStreamingError(response, decoded) { function tryParseStreamingError(response, decoded) {
try { try {
const data = JSON.parse(decoded); const data = JSON.parse(decoded);
@ -1324,6 +1326,9 @@ function tryParseStreamingError(response, decoded) {
checkQuotaError(data); checkQuotaError(data);
checkModerationError(data); checkModerationError(data);
// these do not throw correctly (equiv to Error("[object Object]"))
// if trying to fix "[object Object]" displayed to users, start here
if (data.error) { if (data.error) {
toastr.error(data.error.message || response.statusText, 'Chat Completion API'); toastr.error(data.error.message || response.statusText, 'Chat Completion API');
throw new Error(data); throw new Error(data);
@ -1339,15 +1344,22 @@ function tryParseStreamingError(response, decoded) {
} }
} }
async function checkQuotaError(data) { /**
const errorText = await renderTemplateAsync('quotaError'); * Checks if the response contains a quota error and displays a popup if it does.
* @param data
* @returns {void}
* @throws {object} - response JSON
*/
function checkQuotaError(data) {
if (!data) { if (!data) {
return; return;
} }
if (data.quota_error) { if (data.quota_error) {
callPopup(errorText, 'text'); renderTemplateAsync('quotaError').then((html) => Popup.show.text('Quota Error', html));
// this does not throw correctly (equiv to Error("[object Object]"))
// if trying to fix "[object Object]" displayed to users, start here
throw new Error(data); throw new Error(data);
} }
} }
@ -1766,6 +1778,15 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) {
return data.output; return data.output;
} }
/**
* Send a chat completion request to backend
* @param {string} type (impersonate, quiet, continue, etc)
* @param {Array} messages
* @param {AbortSignal?} signal
* @returns {Promise<unknown>}
* @throws {Error}
*/
async function sendOpenAIRequest(type, messages, signal) { async function sendOpenAIRequest(type, messages, signal) {
// Provide default abort signal // Provide default abort signal
if (!signal) { if (!signal) {
@ -1868,7 +1889,6 @@ async function sendOpenAIRequest(type, messages, signal) {
generate_data['top_k'] = Number(oai_settings.top_k_openai); generate_data['top_k'] = Number(oai_settings.top_k_openai);
generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt; generate_data['claude_use_sysprompt'] = oai_settings.claude_use_sysprompt;
generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings. generate_data['stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
generate_data['human_sysprompt_message'] = substituteParams(oai_settings.human_sysprompt_message);
// Don't add a prefill on quiet gens (summarization) and when using continue prefill. // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) { if (!isQuiet && !(isContinue && oai_settings.continue_prefill)) {
generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill); generate_data['assistant_prefill'] = isImpersonate ? substituteParams(oai_settings.assistant_impersonation) : substituteParams(oai_settings.assistant_prefill);
@ -2028,12 +2048,13 @@ async function sendOpenAIRequest(type, messages, signal) {
else { else {
const data = await response.json(); const data = await response.json();
await checkQuotaError(data); checkQuotaError(data);
checkModerationError(data); checkModerationError(data);
if (data.error) { if (data.error) {
toastr.error(data.error.message || response.statusText, t`API returned an error`); const message = data.error.message || response.statusText || t`Unknown error`;
throw new Error(data); toastr.error(message, t`API returned an error`);
throw new Error(message);
} }
if (type !== 'quiet') { if (type !== 'quiet') {
@ -3005,7 +3026,6 @@ function loadOpenAISettings(data, settings) {
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password; oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill; oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation; oai_settings.assistant_impersonation = settings.assistant_impersonation ?? default_settings.assistant_impersonation;
oai_settings.human_sysprompt_message = settings.human_sysprompt_message ?? default_settings.human_sysprompt_message;
oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining; oai_settings.image_inlining = settings.image_inlining ?? default_settings.image_inlining;
oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality; oai_settings.inline_image_quality = settings.inline_image_quality ?? default_settings.inline_image_quality;
oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check; oai_settings.bypass_status_check = settings.bypass_status_check ?? default_settings.bypass_status_check;
@ -3045,7 +3065,6 @@ function loadOpenAISettings(data, settings) {
$('#openai_proxy_password').val(oai_settings.proxy_password); $('#openai_proxy_password').val(oai_settings.proxy_password);
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill); $('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation); $('#claude_assistant_impersonation').val(oai_settings.assistant_impersonation);
$('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
$('#openai_image_inlining').prop('checked', oai_settings.image_inlining); $('#openai_image_inlining').prop('checked', oai_settings.image_inlining);
$('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check); $('#openai_bypass_status_check').prop('checked', oai_settings.bypass_status_check);
@ -3375,7 +3394,6 @@ async function saveOpenAIPreset(name, settings, triggerUi = true) {
show_external_models: settings.show_external_models, show_external_models: settings.show_external_models,
assistant_prefill: settings.assistant_prefill, assistant_prefill: settings.assistant_prefill,
assistant_impersonation: settings.assistant_impersonation, assistant_impersonation: settings.assistant_impersonation,
human_sysprompt_message: settings.human_sysprompt_message,
claude_use_sysprompt: settings.claude_use_sysprompt, claude_use_sysprompt: settings.claude_use_sysprompt,
use_makersuite_sysprompt: settings.use_makersuite_sysprompt, use_makersuite_sysprompt: settings.use_makersuite_sysprompt,
use_alt_scale: settings.use_alt_scale, use_alt_scale: settings.use_alt_scale,
@ -3800,7 +3818,6 @@ function onSettingsPresetChange() {
proxy_password: ['#openai_proxy_password', 'proxy_password', false], proxy_password: ['#openai_proxy_password', 'proxy_password', false],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false], assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false], assistant_impersonation: ['#claude_assistant_impersonation', 'assistant_impersonation', false],
human_sysprompt_message: ['#claude_human_sysprompt_textarea', 'human_sysprompt_message', false],
claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true], claude_use_sysprompt: ['#claude_use_sysprompt', 'claude_use_sysprompt', true],
use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true], use_makersuite_sysprompt: ['#use_makersuite_sysprompt', 'use_makersuite_sysprompt', true],
use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true], use_alt_scale: ['#use_alt_scale', 'use_alt_scale', true],
@ -4652,10 +4669,6 @@ function toggleChatCompletionForms() {
const validSources = $(this).data('source').split(','); const validSources = $(this).data('source').split(',');
$(this).toggle(validSources.includes(oai_settings.chat_completion_source)); $(this).toggle(validSources.includes(oai_settings.chat_completion_source));
}); });
if (chat_completion_sources.CLAUDE == oai_settings.chat_completion_source) {
$('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
}
} }
async function testApiConnection() { async function testApiConnection() {
@ -5011,7 +5024,6 @@ export function initOpenAI() {
$('#claude_use_sysprompt').on('change', function () { $('#claude_use_sysprompt').on('change', function () {
oai_settings.claude_use_sysprompt = !!$('#claude_use_sysprompt').prop('checked'); oai_settings.claude_use_sysprompt = !!$('#claude_use_sysprompt').prop('checked');
$('#claude_human_sysprompt_message_block').toggle(oai_settings.claude_use_sysprompt);
saveSettingsDebounced(); saveSettingsDebounced();
}); });
@ -5088,12 +5100,6 @@ export function initOpenAI() {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#claude_human_sysprompt_message_restore').on('click', function () {
oai_settings.human_sysprompt_message = default_claude_human_sysprompt_message;
$('#claude_human_sysprompt_textarea').val(oai_settings.human_sysprompt_message);
saveSettingsDebounced();
});
$('#newgroupchat_prompt_restore').on('click', function () { $('#newgroupchat_prompt_restore').on('click', function () {
oai_settings.new_group_chat_prompt = default_new_group_chat_prompt; oai_settings.new_group_chat_prompt = default_new_group_chat_prompt;
$('#newgroupchat_prompt_textarea').val(oai_settings.new_group_chat_prompt); $('#newgroupchat_prompt_textarea').val(oai_settings.new_group_chat_prompt);
@ -5185,11 +5191,6 @@ export function initOpenAI() {
saveSettingsDebounced(); saveSettingsDebounced();
}); });
$('#claude_human_sysprompt_textarea').on('input', function () {
oai_settings.human_sysprompt_message = String($('#claude_human_sysprompt_textarea').val());
saveSettingsDebounced();
});
$('#openrouter_use_fallback').on('input', function () { $('#openrouter_use_fallback').on('input', function () {
oai_settings.openrouter_use_fallback = !!$(this).prop('checked'); oai_settings.openrouter_use_fallback = !!$(this).prop('checked');
saveSettingsDebounced(); saveSettingsDebounced();

View File

@ -658,6 +658,10 @@ async function CreateZenSliders(elmnt) {
numSteps = 50; numSteps = 50;
decimals = 1; decimals = 1;
} }
if (sliderID == 'nsigma') {
numSteps = 50;
decimals = 1;
}
//customize steps //customize steps
if (sliderID == 'mirostat_mode_textgenerationwebui' || if (sliderID == 'mirostat_mode_textgenerationwebui' ||
sliderID == 'mirostat_mode_kobold') { sliderID == 'mirostat_mode_kobold') {
@ -702,6 +706,7 @@ async function CreateZenSliders(elmnt) {
sliderID == 'penalty_alpha_textgenerationwebui' || sliderID == 'penalty_alpha_textgenerationwebui' ||
sliderID == 'length_penalty_textgenerationwebui' || sliderID == 'length_penalty_textgenerationwebui' ||
sliderID == 'epsilon_cutoff_textgenerationwebui' || sliderID == 'epsilon_cutoff_textgenerationwebui' ||
sliderID == 'nsigma' ||
sliderID == 'rep_pen_range' || sliderID == 'rep_pen_range' ||
sliderID == 'eta_cutoff_textgenerationwebui' || sliderID == 'eta_cutoff_textgenerationwebui' ||
sliderID == 'top_a_textgenerationwebui' || sliderID == 'top_a_textgenerationwebui' ||

View File

@ -1,5 +1,4 @@
import { escapeRegex } from '../utils.js'; import { escapeRegex } from '../utils.js';
import { SlashCommand } from './SlashCommand.js';
import { SlashCommandParser } from './SlashCommandParser.js'; import { SlashCommandParser } from './SlashCommandParser.js';
export class SlashCommandBrowser { export class SlashCommandBrowser {
@ -30,7 +29,7 @@ export class SlashCommandBrowser {
this.details?.remove(); this.details?.remove();
this.details = null; this.details = null;
let query = inp.value.trim(); let query = inp.value.trim();
if (query.slice(-1) == '"' && !/(?:^|\s+)"/.test(query)) { if (query.slice(-1) === '"' && !/(?:^|\s+)"/.test(query)) {
query = `"${query}`; query = `"${query}`;
} }
let fuzzyList = []; let fuzzyList = [];
@ -59,7 +58,7 @@ export class SlashCommandBrowser {
cmd.helpString, cmd.helpString,
]; ];
const find = ()=>targets.find(t=>(fuzzyList.find(f=>f.test(t)) ?? quotedList.find(q=>t.includes(q))) !== undefined) !== undefined; const find = ()=>targets.find(t=>(fuzzyList.find(f=>f.test(t)) ?? quotedList.find(q=>t.includes(q))) !== undefined) !== undefined;
if (fuzzyList.length + quotedList.length == 0 || find()) { if (fuzzyList.length + quotedList.length === 0 || find()) {
this.itemMap[cmd.name].classList.remove('isFiltered'); this.itemMap[cmd.name].classList.remove('isFiltered');
} else { } else {
this.itemMap[cmd.name].classList.add('isFiltered'); this.itemMap[cmd.name].classList.add('isFiltered');
@ -78,7 +77,7 @@ export class SlashCommandBrowser {
list.classList.add('autoComplete'); list.classList.add('autoComplete');
this.cmdList = Object this.cmdList = Object
.keys(SlashCommandParser.commands) .keys(SlashCommandParser.commands)
.filter(key => SlashCommandParser.commands[key].name == key) // exclude aliases .filter(key => SlashCommandParser.commands[key].name === key) // exclude aliases
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())) .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
.map(key => SlashCommandParser.commands[key]) .map(key => SlashCommandParser.commands[key])
; ;
@ -97,7 +96,7 @@ export class SlashCommandBrowser {
} }
} }
} }
if (this.details != details) { if (this.details !== details) {
Array.from(list.querySelectorAll('.selected')).forEach(it=>it.classList.remove('selected')); Array.from(list.querySelectorAll('.selected')).forEach(it=>it.classList.remove('selected'));
item.classList.add('selected'); item.classList.add('selected');
this.details?.remove(); this.details?.remove();
@ -124,7 +123,7 @@ export class SlashCommandBrowser {
parent.append(this.dom); parent.append(this.dom);
this.mo = new MutationObserver(muts=>{ this.mo = new MutationObserver(muts=>{
if (muts.find(mut=>Array.from(mut.removedNodes).find(it=>it == this.dom || it.contains(this.dom)))) { if (muts.find(mut=>Array.from(mut.removedNodes).find(it=>it === this.dom || it.contains(this.dom)))) {
this.mo.disconnect(); this.mo.disconnect();
window.removeEventListener('keydown', boundHandler); window.removeEventListener('keydown', boundHandler);
} }
@ -136,7 +135,7 @@ export class SlashCommandBrowser {
} }
handleKeyDown(evt) { handleKeyDown(evt) {
if (!evt.shiftKey && !evt.altKey && evt.ctrlKey && evt.key.toLowerCase() == 'f') { if (!evt.shiftKey && !evt.altKey && evt.ctrlKey && evt.key.toLowerCase() === 'f') {
if (!this.dom.closest('body')) return; if (!this.dom.closest('body')) return;
if (this.dom.closest('.mes') && !this.dom.closest('.last_mes')) return; if (this.dom.closest('.mes') && !this.dom.closest('.last_mes')) return;
evt.preventDefault(); evt.preventDefault();

View File

@ -193,6 +193,7 @@ const settings = {
openrouter_allow_fallbacks: true, openrouter_allow_fallbacks: true,
xtc_threshold: 0.1, xtc_threshold: 0.1,
xtc_probability: 0, xtc_probability: 0,
nsigma: 0.0,
featherless_model: '', featherless_model: '',
}; };
@ -265,6 +266,7 @@ export const setting_names = [
'openrouter_allow_fallbacks', 'openrouter_allow_fallbacks',
'xtc_threshold', 'xtc_threshold',
'xtc_probability', 'xtc_probability',
'nsigma',
]; ];
const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba'); const DYNATEMP_BLOCK = document.getElementById('dynatemp_block_ooba');
@ -880,6 +882,13 @@ function setSettingByName(setting, value, trigger) {
} }
} }
/**
* Sends a streaming request for textgenerationwebui.
* @param generate_data
* @param signal
* @returns {Promise<(function(): AsyncGenerator<{swipes: [], text: string, toolCalls: [], logprobs: {token: string, topLogprobs: Candidate[]}|null}, void, *>)|*>}
* @throws {Error} - If the response status is not OK, or from within the generator
*/
async function generateTextGenWithStreaming(generate_data, signal) { async function generateTextGenWithStreaming(generate_data, signal) {
generate_data.stream = true; generate_data.stream = true;
@ -995,6 +1004,7 @@ export function parseTabbyLogprobs(data) {
* @param {Response} response - Response from the server. * @param {Response} response - Response from the server.
* @param {string} decoded - Decoded response body. * @param {string} decoded - Decoded response body.
* @returns {void} Nothing. * @returns {void} Nothing.
* @throws {Error} If the response contains an error message, throws Error with the message.
*/ */
function tryParseStreamingError(response, decoded) { function tryParseStreamingError(response, decoded) {
let data = {}; let data = {};
@ -1178,6 +1188,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'sampler_order': settings.type === textgen_types.KOBOLDCPP ? settings.sampler_order : undefined, 'sampler_order': settings.type === textgen_types.KOBOLDCPP ? settings.sampler_order : undefined,
'xtc_threshold': settings.xtc_threshold, 'xtc_threshold': settings.xtc_threshold,
'xtc_probability': settings.xtc_probability, 'xtc_probability': settings.xtc_probability,
'nsigma': settings.nsigma,
}; };
const nonAphroditeParams = { const nonAphroditeParams = {
'rep_pen': settings.rep_pen, 'rep_pen': settings.rep_pen,
@ -1245,7 +1256,9 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'dynatemp_exponent': dynatemp ? settings.dynatemp_exponent : undefined, 'dynatemp_exponent': dynatemp ? settings.dynatemp_exponent : undefined,
'xtc_threshold': settings.xtc_threshold, 'xtc_threshold': settings.xtc_threshold,
'xtc_probability': settings.xtc_probability, 'xtc_probability': settings.xtc_probability,
'nsigma': settings.nsigma,
'custom_token_bans': toIntArray(banned_tokens), 'custom_token_bans': toIntArray(banned_tokens),
'no_repeat_ngram_size': settings.no_repeat_ngram_size,
}; };
if (settings.type === OPENROUTER) { if (settings.type === OPENROUTER) {

View File

@ -102,7 +102,7 @@ async function sendClaudeRequest(request, response) {
const additionalHeaders = {}; const additionalHeaders = {};
const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0; const useTools = request.body.model.startsWith('claude-3') && Array.isArray(request.body.tools) && request.body.tools.length > 0;
const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt; const useSystemPrompt = (request.body.model.startsWith('claude-2') || request.body.model.startsWith('claude-3')) && request.body.claude_use_sysprompt;
const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, request.body.human_sysprompt_message, request.body.char_name, request.body.user_name); const convertedPrompt = convertClaudeMessages(request.body.messages, request.body.assistant_prefill, useSystemPrompt, useTools, request.body.char_name, request.body.user_name);
// Add custom stop sequences // Add custom stop sequences
const stopSequences = []; const stopSequences = [];
if (Array.isArray(request.body.stop)) { if (Array.isArray(request.body.stop)) {
@ -1051,8 +1051,12 @@ router.post('/generate', jsonParser, function (request, response) {
} }
} catch (error) { } catch (error) {
console.log('Generation failed', error); console.log('Generation failed', error);
const message = error.code === 'ECONNREFUSED'
? `Connection refused: ${error.message}`
: error.message || 'Unknown error occurred';
if (!response.headersSent) { if (!response.headersSent) {
response.send({ error: true }); response.status(502).send({ error: { message, ...error } });
} else { } else {
response.end(); response.end();
} }
@ -1068,7 +1072,7 @@ router.post('/generate', jsonParser, function (request, response) {
const message = errorResponse.statusText || 'Unknown error occurred'; const message = errorResponse.statusText || 'Unknown error occurred';
const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota'; const quota_error = errorResponse.status === 429 && errorData?.error?.type === 'insufficient_quota';
console.log(message, responseText); console.log('Chat completion request error: ', message, responseText);
if (!response.headersSent) { if (!response.headersSent) {
response.send({ error: { message }, quota_error: quota_error }); response.send({ error: { message }, quota_error: quota_error });

View File

@ -24,6 +24,8 @@ const defaultAvatarPath = './public/img/ai4.png';
// KV-store for parsed character data // KV-store for parsed character data
const characterDataCache = new Map(); const characterDataCache = new Map();
// Some Android devices require tighter memory management
const isAndroid = process.platform === 'android';
/** /**
* Reads the character card from the specified image file. * Reads the character card from the specified image file.
@ -39,7 +41,7 @@ async function readCharacterData(inputFile, inputFormat = 'png') {
} }
const result = parse(inputFile, inputFormat); const result = parse(inputFile, inputFormat);
characterDataCache.set(cacheKey, result); !isAndroid && characterDataCache.set(cacheKey, result);
return result; return result;
} }

View File

@ -6,7 +6,7 @@ import { publicLibConfig } from '../../webpack.config.js';
export default function getWebpackServeMiddleware() { export default function getWebpackServeMiddleware() {
const compiler = webpack(publicLibConfig); const compiler = webpack(publicLibConfig);
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production' || process.platform === 'android') {
compiler.hooks.done.tap('serve', () => { compiler.hooks.done.tap('serve', () => {
if (compiler.watching) { if (compiler.watching) {
compiler.watching.close(() => { }); compiler.watching.close(() => { });

View File

@ -91,11 +91,10 @@ export function convertClaudePrompt(messages, addAssistantPostfix, addAssistantP
* @param {string} prefillString User determined prefill string * @param {string} prefillString User determined prefill string
* @param {boolean} useSysPrompt See if we want to use a system prompt * @param {boolean} useSysPrompt See if we want to use a system prompt
* @param {boolean} useTools See if we want to use tools * @param {boolean} useTools See if we want to use tools
* @param {string} humanMsgFix Add Human message between system prompt and assistant.
* @param {string} charName Character name * @param {string} charName Character name
* @param {string} userName User name * @param {string} userName User name
*/ */
export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, humanMsgFix, charName = '', userName = '') { export function convertClaudeMessages(messages, prefillString, useSysPrompt, useTools, charName, userName) {
let systemPrompt = []; let systemPrompt = [];
if (useSysPrompt) { if (useSysPrompt) {
// Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array. // Collect all the system messages up until the first instance of a non-system message, and then remove them from the messages array.
@ -122,10 +121,10 @@ export function convertClaudeMessages(messages, prefillString, useSysPrompt, use
// Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message. // Check if the first message in the array is of type user, if not, interject with humanMsgFix or a blank message.
// Also prevents erroring out if the messages array is empty. // Also prevents erroring out if the messages array is empty.
if (messages.length === 0 || (messages.length > 0 && messages[0].role !== 'user')) { if (messages.length === 0) {
messages.unshift({ messages.unshift({
role: 'user', role: 'user',
content: humanMsgFix || PROMPT_PLACEHOLDER, content: PROMPT_PLACEHOLDER,
}); });
} }
} }