mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into persona-improvements
This commit is contained in:
@@ -482,10 +482,10 @@ function highlightNewBackground(bg) {
|
||||
*/
|
||||
function setFittingClass(fitting) {
|
||||
const backgrounds = $('#bg1, #bg_custom');
|
||||
backgrounds.toggleClass('cover', fitting === 'cover');
|
||||
backgrounds.toggleClass('contain', fitting === 'contain');
|
||||
backgrounds.toggleClass('stretch', fitting === 'stretch');
|
||||
backgrounds.toggleClass('center', fitting === 'center');
|
||||
for (const option of ['cover', 'contain', 'stretch', 'center']) {
|
||||
backgrounds.toggleClass(option, option === fitting);
|
||||
}
|
||||
background_settings.fitting = fitting;
|
||||
}
|
||||
|
||||
function onBackgroundFilterInput() {
|
||||
|
@@ -466,7 +466,7 @@ async function processTtsQueue() {
|
||||
}
|
||||
|
||||
if (extension_settings.tts.skip_tags) {
|
||||
text = text.replace(/<.*?>.*?<\/.*?>/g, '').trim();
|
||||
text = text.replace(/<.*?>[\s\S]*?<\/.*?>/g, '').trim();
|
||||
}
|
||||
|
||||
if (!extension_settings.tts.pass_asterisks) {
|
||||
|
@@ -188,7 +188,7 @@ export async function generateKoboldWithStreaming(generate_data, signal) {
|
||||
if (data?.token) {
|
||||
text += data.token;
|
||||
}
|
||||
yield { text, swipes: [], toolCalls: [] };
|
||||
yield { text, swipes: [], toolCalls: [], state: {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -746,7 +746,7 @@ export async function generateNovelWithStreaming(generate_data, signal) {
|
||||
text += data.token;
|
||||
}
|
||||
|
||||
yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [] };
|
||||
yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [], state: {} };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -1096,8 +1096,8 @@ async function preparePromptsForChatCompletion({ Scenario, charPersonality, name
|
||||
// Unordered prompts without marker
|
||||
{ role: 'system', content: impersonationPrompt, identifier: 'impersonate' },
|
||||
{ role: 'system', content: quietPrompt, identifier: 'quietPrompt' },
|
||||
{ role: 'system', content: bias, identifier: 'bias' },
|
||||
{ role: 'system', content: groupNudge, identifier: 'groupNudge' },
|
||||
{ role: 'assistant', content: bias, identifier: 'bias' },
|
||||
];
|
||||
|
||||
// Tavern Extras - Summary
|
||||
@@ -2095,7 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
let text = '';
|
||||
const swipes = [];
|
||||
const toolCalls = [];
|
||||
const state = {};
|
||||
const state = { reasoning: '' };
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) return;
|
||||
@@ -2113,7 +2113,7 @@ async function sendOpenAIRequest(type, messages, signal) {
|
||||
|
||||
ToolManager.parseToolCalls(toolCalls, parsed);
|
||||
|
||||
yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls };
|
||||
yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls, state: state };
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2150,16 +2150,22 @@ function getStreamingReply(data, state) {
|
||||
if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
|
||||
return data?.delta?.text || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
|
||||
return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';
|
||||
if (oai_settings.show_thoughts) {
|
||||
state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');
|
||||
}
|
||||
return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
|
||||
return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
|
||||
const hadThoughts = state.hadThoughts;
|
||||
const thoughts = data.choices?.filter(x => oai_settings.show_thoughts || !x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '';
|
||||
const content = data.choices?.[0]?.delta?.content || '';
|
||||
state.hadThoughts = !!thoughts;
|
||||
const separator = hadThoughts && !thoughts ? '\n\n' : '';
|
||||
return [thoughts, separator, content].filter(x => x).join('\n\n');
|
||||
if (oai_settings.show_thoughts) {
|
||||
state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
|
||||
}
|
||||
return data.choices?.[0]?.delta?.content || '';
|
||||
} else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) {
|
||||
if (oai_settings.show_thoughts) {
|
||||
state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
|
||||
}
|
||||
return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
|
||||
} else {
|
||||
return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
|
||||
}
|
||||
|
@@ -253,6 +253,14 @@ let power_user = {
|
||||
content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.',
|
||||
},
|
||||
|
||||
reasoning: {
|
||||
add_to_prompts: false,
|
||||
prefix: '<think>\n',
|
||||
suffix: '\n</think>',
|
||||
separator: '\n\n',
|
||||
max_additions: 1,
|
||||
},
|
||||
|
||||
personas: {},
|
||||
default_persona: null,
|
||||
persona_descriptions: {},
|
||||
|
297
public/scripts/reasoning.js
Normal file
297
public/scripts/reasoning.js
Normal file
@@ -0,0 +1,297 @@
|
||||
import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
|
||||
import { t } from './i18n.js';
|
||||
import { MacrosParser } from './macros.js';
|
||||
import { Popup } from './popup.js';
|
||||
import { power_user } from './power-user.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { copyText } from './utils.js';
|
||||
|
||||
/**
|
||||
* Gets a message from a jQuery element.
|
||||
* @param {Element} element
|
||||
* @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}
|
||||
*/
|
||||
function getMessageFromJquery(element) {
|
||||
const messageBlock = $(element).closest('.mes');
|
||||
const messageId = Number(messageBlock.attr('mesid'));
|
||||
const message = chat[messageId];
|
||||
return { messageId: messageId, message, messageBlock };
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for adding reasoning to messages.
|
||||
* Keeps track of the number of reasoning additions.
|
||||
*/
|
||||
export class PromptReasoning {
|
||||
static REASONING_PLACEHOLDER = '\u200B';
|
||||
static REASONING_PLACEHOLDER_REGEX = new RegExp(`${PromptReasoning.REASONING_PLACEHOLDER}$`);
|
||||
|
||||
constructor() {
|
||||
this.counter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the limit of reasoning additions has been reached.
|
||||
* @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise.
|
||||
*/
|
||||
isLimitReached() {
|
||||
if (!power_user.reasoning.add_to_prompts) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.counter >= power_user.reasoning.max_additions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add reasoning to a message according to the power user settings.
|
||||
* @param {string} content Message content
|
||||
* @param {string} reasoning Message reasoning
|
||||
* @returns {string} Message content with reasoning
|
||||
*/
|
||||
addToMessage(content, reasoning) {
|
||||
// Disabled or reached limit of additions
|
||||
if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// No reasoning provided or a placeholder
|
||||
if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) {
|
||||
return content;
|
||||
}
|
||||
|
||||
// Increment the counter
|
||||
this.counter++;
|
||||
|
||||
// Substitute macros in variable parts
|
||||
const prefix = substituteParams(power_user.reasoning.prefix || '');
|
||||
const separator = substituteParams(power_user.reasoning.separator || '');
|
||||
const suffix = substituteParams(power_user.reasoning.suffix || '');
|
||||
|
||||
// Combine parts with reasoning and content
|
||||
return `${prefix}${reasoning}${suffix}${separator}${content}`;
|
||||
}
|
||||
}
|
||||
|
||||
function loadReasoningSettings() {
|
||||
$('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
|
||||
$('#reasoning_add_to_prompts').on('change', function () {
|
||||
power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#reasoning_prefix').val(power_user.reasoning.prefix);
|
||||
$('#reasoning_prefix').on('input', function () {
|
||||
power_user.reasoning.prefix = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#reasoning_suffix').val(power_user.reasoning.suffix);
|
||||
$('#reasoning_suffix').on('input', function () {
|
||||
power_user.reasoning.suffix = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#reasoning_separator').val(power_user.reasoning.separator);
|
||||
$('#reasoning_separator').on('input', function () {
|
||||
power_user.reasoning.separator = String($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#reasoning_max_additions').val(power_user.reasoning.max_additions);
|
||||
$('#reasoning_max_additions').on('input', function () {
|
||||
power_user.reasoning.max_additions = Number($(this).val());
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
}
|
||||
|
||||
function registerReasoningSlashCommands() {
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'reasoning-get',
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
helpString: t`Get the contents of a reasoning block of a message. Returns an empty string if the message does not have a reasoning block.`,
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Message ID. If not provided, the message ID of the last message is used.',
|
||||
typeList: ARGUMENT_TYPE.NUMBER,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
callback: (_args, value) => {
|
||||
const messageId = !isNaN(Number(value)) ? Number(value) : chat.length - 1;
|
||||
const message = chat[messageId];
|
||||
const reasoning = String(message?.extra?.reasoning ?? '');
|
||||
return reasoning.replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, '');
|
||||
},
|
||||
}));
|
||||
|
||||
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||
name: 'reasoning-set',
|
||||
returns: ARGUMENT_TYPE.STRING,
|
||||
helpString: t`Set the reasoning block of a message. Returns the reasoning block content.`,
|
||||
namedArgumentList: [
|
||||
SlashCommandNamedArgument.fromProps({
|
||||
name: 'at',
|
||||
description: 'Message ID. If not provided, the message ID of the last message is used.',
|
||||
typeList: ARGUMENT_TYPE.NUMBER,
|
||||
enumProvider: commonEnumProviders.messages(),
|
||||
}),
|
||||
],
|
||||
unnamedArgumentList: [
|
||||
SlashCommandArgument.fromProps({
|
||||
description: 'Reasoning block content.',
|
||||
typeList: ARGUMENT_TYPE.STRING,
|
||||
}),
|
||||
],
|
||||
callback: async (args, value) => {
|
||||
const messageId = !isNaN(Number(args[0])) ? Number(args[0]) : chat.length - 1;
|
||||
const message = chat[messageId];
|
||||
if (!message?.extra) {
|
||||
return '';
|
||||
}
|
||||
|
||||
message.extra.reasoning = String(value ?? '');
|
||||
await saveChatConditional();
|
||||
|
||||
closeMessageEditor('reasoning');
|
||||
updateMessageBlock(messageId, message);
|
||||
return message.extra.reasoning;
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
function registerReasoningMacros() {
|
||||
MacrosParser.registerMacro('reasoningPrefix', () => power_user.reasoning.prefix, t`Reasoning Prefix`);
|
||||
MacrosParser.registerMacro('reasoningSuffix', () => power_user.reasoning.suffix, t`Reasoning Suffix`);
|
||||
MacrosParser.registerMacro('reasoningSeparator', () => power_user.reasoning.separator, t`Reasoning Separator`);
|
||||
}
|
||||
|
||||
function setReasoningEventHandlers(){
|
||||
$(document).on('click', '.mes_reasoning_copy', (e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes_reasoning_edit', function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const { message, messageBlock } = getMessageFromJquery(this);
|
||||
if (!message?.extra) {
|
||||
return;
|
||||
}
|
||||
|
||||
const reasoning = String(message?.extra?.reasoning ?? '');
|
||||
const chatElement = document.getElementById('chat');
|
||||
const textarea = document.createElement('textarea');
|
||||
const reasoningBlock = messageBlock.find('.mes_reasoning');
|
||||
textarea.classList.add('reasoning_edit_textarea');
|
||||
textarea.value = reasoning.replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, '');
|
||||
$(textarea).insertBefore(reasoningBlock);
|
||||
|
||||
if (!CSS.supports('field-sizing', 'content')) {
|
||||
const resetHeight = function () {
|
||||
const scrollTop = chatElement.scrollTop;
|
||||
textarea.style.height = '0px';
|
||||
textarea.style.height = `${textarea.scrollHeight}px`;
|
||||
chatElement.scrollTop = scrollTop;
|
||||
};
|
||||
|
||||
textarea.addEventListener('input', resetHeight);
|
||||
resetHeight();
|
||||
}
|
||||
|
||||
textarea.focus();
|
||||
textarea.setSelectionRange(textarea.value.length, textarea.value.length);
|
||||
|
||||
const textareaRect = textarea.getBoundingClientRect();
|
||||
const chatRect = chatElement.getBoundingClientRect();
|
||||
|
||||
// Scroll if textarea bottom is below visible area
|
||||
if (textareaRect.bottom > chatRect.bottom) {
|
||||
const scrollOffset = textareaRect.bottom - chatRect.bottom;
|
||||
chatElement.scrollTop += scrollOffset;
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes_reasoning_edit_done', async function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const { message, messageId, messageBlock } = getMessageFromJquery(this);
|
||||
if (!message?.extra) {
|
||||
return;
|
||||
}
|
||||
|
||||
const textarea = messageBlock.find('.reasoning_edit_textarea');
|
||||
const reasoning = String(textarea.val());
|
||||
message.extra.reasoning = reasoning;
|
||||
await saveChatConditional();
|
||||
updateMessageBlock(messageId, message);
|
||||
textarea.remove();
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const { messageBlock } = getMessageFromJquery(this);
|
||||
const textarea = messageBlock.find('.reasoning_edit_textarea');
|
||||
textarea.remove();
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes_edit_add_reasoning', async function () {
|
||||
const { message, messageId } = getMessageFromJquery(this);
|
||||
if (!message?.extra) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.extra.reasoning) {
|
||||
toastr.info(t`Reasoning already exists.`, t`Edit Message`);
|
||||
return;
|
||||
}
|
||||
|
||||
message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
|
||||
await saveChatConditional();
|
||||
closeMessageEditor();
|
||||
updateMessageBlock(messageId, message);
|
||||
});
|
||||
|
||||
$(document).on('click', '.mes_reasoning_delete', async function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`);
|
||||
|
||||
if (!confirm) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { message, messageId } = getMessageFromJquery(this);
|
||||
if (!message?.extra) {
|
||||
return;
|
||||
}
|
||||
message.extra.reasoning = '';
|
||||
await saveChatConditional();
|
||||
updateMessageBlock(messageId, message);
|
||||
});
|
||||
|
||||
$(document).on('pointerup', '.mes_reasoning_copy', async function () {
|
||||
const { message } = getMessageFromJquery(this);
|
||||
const reasoning = String(message?.extra?.reasoning ?? '').replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, '');
|
||||
|
||||
if (!reasoning) {
|
||||
return;
|
||||
}
|
||||
|
||||
await copyText(reasoning);
|
||||
toastr.info(t`Copied!`, '', { timeOut: 2000 });
|
||||
});
|
||||
}
|
||||
|
||||
export function initReasoning() {
|
||||
loadReasoningSettings();
|
||||
setReasoningEventHandlers();
|
||||
registerReasoningSlashCommands();
|
||||
registerReasoningMacros();
|
||||
}
|
@@ -42,6 +42,7 @@ import {
|
||||
showMoreMessages,
|
||||
stopGeneration,
|
||||
substituteParams,
|
||||
syncCurrentSwipeInfoExtras,
|
||||
system_avatar,
|
||||
system_message_types,
|
||||
this_chid,
|
||||
@@ -2862,8 +2863,11 @@ async function addSwipeCallback(args, value) {
|
||||
const newSwipeId = lastMessage.swipes.length - 1;
|
||||
|
||||
if (isTrueBoolean(args.switch)) {
|
||||
// Make sure ad-hoc changes to extras are saved before swiping away
|
||||
syncCurrentSwipeInfoExtras();
|
||||
lastMessage.swipe_id = newSwipeId;
|
||||
lastMessage.mes = lastMessage.swipes[newSwipeId];
|
||||
lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {});
|
||||
}
|
||||
|
||||
await saveChatConditional();
|
||||
|
@@ -235,6 +235,21 @@ async function* parseStreamData(json) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (typeof json.choices[0].delta.reasoning === 'string' && json.choices[0].delta.reasoning.length > 0) {
|
||||
for (let j = 0; j < json.choices[0].delta.reasoning.length; j++) {
|
||||
const str = json.choices[0].delta.reasoning[j];
|
||||
const isLastSymbol = j === json.choices[0].delta.reasoning.length - 1;
|
||||
const choiceClone = structuredClone(json.choices[0]);
|
||||
choiceClone.delta.reasoning = str;
|
||||
choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : '';
|
||||
const choices = [choiceClone];
|
||||
yield {
|
||||
data: { ...json, choices },
|
||||
chunk: str,
|
||||
};
|
||||
}
|
||||
return;
|
||||
}
|
||||
else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
|
||||
for (let j = 0; j < json.choices[0].delta.content.length; j++) {
|
||||
const str = json.choices[0].delta.content[j];
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
activateSendButtons,
|
||||
addOneMessage,
|
||||
appendMediaToMessage,
|
||||
callPopup,
|
||||
characters,
|
||||
chat,
|
||||
@@ -41,6 +42,7 @@ import {
|
||||
substituteParamsExtended,
|
||||
this_chid,
|
||||
updateChatMetadata,
|
||||
updateMessageBlock,
|
||||
} from '../script.js';
|
||||
import {
|
||||
extension_settings,
|
||||
@@ -171,6 +173,8 @@ export function getContext() {
|
||||
getCharacters,
|
||||
uuidv4,
|
||||
humanizedDateTime,
|
||||
updateMessageBlock,
|
||||
appendMediaToMessage,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -986,6 +986,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
|
||||
let logprobs = null;
|
||||
const swipes = [];
|
||||
const toolCalls = [];
|
||||
const state = {};
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) return;
|
||||
@@ -1004,7 +1005,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
|
||||
logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);
|
||||
}
|
||||
|
||||
yield { text, swipes, logprobs, toolCalls };
|
||||
yield { text, swipes, logprobs, toolCalls, state };
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user