mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branch 'staging' into pygimport
This commit is contained in:
@@ -2270,12 +2270,21 @@ export async function generateQuietPrompt(quiet_prompt, quietToLoud, skipWIAN, q
|
|||||||
return generateFinished;
|
return generateFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes slash commands and returns the new text and whether the generation was interrupted.
|
||||||
|
* @param {string} message Text to be sent
|
||||||
|
* @returns {Promise<boolean>} Whether the message sending was interrupted
|
||||||
|
*/
|
||||||
async function processCommands(message) {
|
async function processCommands(message) {
|
||||||
|
if (!message || !message.trim().startsWith('/')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const previousText = String($('#send_textarea').val());
|
const previousText = String($('#send_textarea').val());
|
||||||
const result = await executeSlashCommands(message);
|
const result = await executeSlashCommands(message);
|
||||||
|
|
||||||
if (!result || typeof result !== 'object') {
|
if (!result || typeof result !== 'object') {
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentText = String($('#send_textarea').val());
|
const currentText = String($('#send_textarea').val());
|
||||||
@@ -2878,7 +2887,7 @@ async function Generate(type, { automatic_trigger, force_name2, quiet_prompt, qu
|
|||||||
let message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
let message_already_generated = isImpersonate ? `${name1}: ` : `${name2}: `;
|
||||||
|
|
||||||
if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) {
|
if (!(dryRun || type == 'regenerate' || type == 'swipe' || type == 'quiet')) {
|
||||||
const interruptedByCommand = await processCommands($('#send_textarea').val());
|
const interruptedByCommand = await processCommands(String($('#send_textarea').val()));
|
||||||
|
|
||||||
if (interruptedByCommand) {
|
if (interruptedByCommand) {
|
||||||
//$("#send_textarea").val('').trigger('input');
|
//$("#send_textarea").val('').trigger('input');
|
||||||
@@ -7764,7 +7773,13 @@ async function connectAPISlash(_, text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function processDroppedFiles(files) {
|
/**
|
||||||
|
* Imports supported files dropped into the app window.
|
||||||
|
* @param {File[]} files Array of files to process
|
||||||
|
* @param {boolean?} preserveFileNames Whether to preserve original file names
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function processDroppedFiles(files, preserveFileNames = false) {
|
||||||
const allowedMimeTypes = [
|
const allowedMimeTypes = [
|
||||||
'application/json',
|
'application/json',
|
||||||
'image/png',
|
'image/png',
|
||||||
@@ -7776,14 +7791,20 @@ export async function processDroppedFiles(files) {
|
|||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if (allowedMimeTypes.includes(file.type)) {
|
if (allowedMimeTypes.includes(file.type)) {
|
||||||
await importCharacter(file);
|
await importCharacter(file, preserveFileNames);
|
||||||
} else {
|
} else {
|
||||||
toastr.warning('Unsupported file type: ' + file.name);
|
toastr.warning('Unsupported file type: ' + file.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importCharacter(file) {
|
/**
|
||||||
|
* Imports a character from a file.
|
||||||
|
* @param {File} file File to import
|
||||||
|
* @param {boolean?} preserveFileName Whether to preserve original file name
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function importCharacter(file, preserveFileName = false) {
|
||||||
const ext = file.name.match(/\.(\w+)$/);
|
const ext = file.name.match(/\.(\w+)$/);
|
||||||
if (!ext || !(['json', 'png', 'yaml', 'yml'].includes(ext[1].toLowerCase()))) {
|
if (!ext || !(['json', 'png', 'yaml', 'yml'].includes(ext[1].toLowerCase()))) {
|
||||||
return;
|
return;
|
||||||
@@ -7794,6 +7815,7 @@ async function importCharacter(file) {
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('avatar', file);
|
formData.append('avatar', file);
|
||||||
formData.append('file_type', format);
|
formData.append('file_type', format);
|
||||||
|
formData.append('preserve_file_name', String(preserveFileName));
|
||||||
|
|
||||||
const data = await jQuery.ajax({
|
const data = await jQuery.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
@@ -7851,9 +7873,9 @@ async function importFromURL(items, files) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doImpersonate() {
|
async function doImpersonate(_, prompt) {
|
||||||
$('#send_textarea').val('');
|
$('#send_textarea').val('');
|
||||||
$('#option_impersonate').trigger('click', { fromSlashCommand: true });
|
$('#option_impersonate').trigger('click', { fromSlashCommand: true, additionalPrompt: prompt });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function doDeleteChat() {
|
async function doDeleteChat() {
|
||||||
@@ -8020,7 +8042,7 @@ jQuery(async function () {
|
|||||||
|
|
||||||
registerSlashCommand('dupe', DupeChar, [], '– duplicates the currently selected character', true, true);
|
registerSlashCommand('dupe', DupeChar, [], '– duplicates the currently selected character', true, true);
|
||||||
registerSlashCommand('api', connectAPISlash, [], `<span class="monospace">(${Object.keys(CONNECT_API_MAP).join(', ')})</span> – connect to an API`, true, true);
|
registerSlashCommand('api', connectAPISlash, [], `<span class="monospace">(${Object.keys(CONNECT_API_MAP).join(', ')})</span> – connect to an API`, true, true);
|
||||||
registerSlashCommand('impersonate', doImpersonate, ['imp'], '– calls an impersonation response', true, true);
|
registerSlashCommand('impersonate', doImpersonate, ['imp'], '<span class="monospace">[prompt]</span> – calls an impersonation response, with an optional additional prompt', true, true);
|
||||||
registerSlashCommand('delchat', doDeleteChat, [], '– deletes the current chat', true, true);
|
registerSlashCommand('delchat', doDeleteChat, [], '– deletes the current chat', true, true);
|
||||||
registerSlashCommand('getchatname', doGetChatName, [], '– returns the name of the current chat file into the pipe', false, true);
|
registerSlashCommand('getchatname', doGetChatName, [], '– returns the name of the current chat file into the pipe', false, true);
|
||||||
registerSlashCommand('closechat', doCloseChat, [], '– closes the current chat', true, true);
|
registerSlashCommand('closechat', doCloseChat, [], '– closes the current chat', true, true);
|
||||||
@@ -8669,6 +8691,13 @@ jQuery(async function () {
|
|||||||
const fromSlashCommand = customData?.fromSlashCommand || false;
|
const fromSlashCommand = customData?.fromSlashCommand || false;
|
||||||
var id = $(this).attr('id');
|
var id = $(this).attr('id');
|
||||||
|
|
||||||
|
// Check whether a custom prompt was provided via custom data (for example through a slash command)
|
||||||
|
const additionalPrompt = customData?.additionalPrompt?.trim() || undefined;
|
||||||
|
const buildOrFillAdditionalArgs = (args = {}) => ({
|
||||||
|
...args,
|
||||||
|
...(additionalPrompt !== undefined && { quiet_prompt: additionalPrompt, quietToLoud: true }),
|
||||||
|
});
|
||||||
|
|
||||||
if (id == 'option_select_chat') {
|
if (id == 'option_select_chat') {
|
||||||
if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) {
|
if ((selected_group && !is_group_generating) || (this_chid !== undefined && !is_send_press) || fromSlashCommand) {
|
||||||
await displayPastChats();
|
await displayPastChats();
|
||||||
@@ -8704,7 +8733,7 @@ jQuery(async function () {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
is_send_press = true;
|
is_send_press = true;
|
||||||
Generate('regenerate');
|
Generate('regenerate', buildOrFillAdditionalArgs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8712,14 +8741,14 @@ jQuery(async function () {
|
|||||||
else if (id == 'option_impersonate') {
|
else if (id == 'option_impersonate') {
|
||||||
if (is_send_press == false || fromSlashCommand) {
|
if (is_send_press == false || fromSlashCommand) {
|
||||||
is_send_press = true;
|
is_send_press = true;
|
||||||
Generate('impersonate');
|
Generate('impersonate', buildOrFillAdditionalArgs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (id == 'option_continue') {
|
else if (id == 'option_continue') {
|
||||||
if (is_send_press == false || fromSlashCommand) {
|
if (is_send_press == false || fromSlashCommand) {
|
||||||
is_send_press = true;
|
is_send_press = true;
|
||||||
Generate('continue');
|
Generate('continue', buildOrFillAdditionalArgs());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -236,7 +236,7 @@ async function installAsset(url, assetType, filename) {
|
|||||||
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
|
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
|
||||||
const blob = await result.blob();
|
const blob = await result.blob();
|
||||||
const file = new File([blob], filename, { type: blob.type });
|
const file = new File([blob], filename, { type: blob.type });
|
||||||
await processDroppedFiles([file]);
|
await processDroppedFiles([file], true);
|
||||||
console.debug(DEBUG_PREFIX, 'Character downloaded.');
|
console.debug(DEBUG_PREFIX, 'Character downloaded.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -119,7 +119,7 @@ const scale_max = 8191;
|
|||||||
const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
const claude_max = 9000; // We have a proper tokenizer, so theoretically could be larger (up to 9k)
|
||||||
const claude_100k_max = 99000;
|
const claude_100k_max = 99000;
|
||||||
let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af
|
let ai21_max = 9200; //can easily fit 9k gpt tokens because j2's tokenizer is efficient af
|
||||||
const unlocked_max = 100 * 1024;
|
const unlocked_max = max_200k;
|
||||||
const oai_max_temp = 2.0;
|
const oai_max_temp = 2.0;
|
||||||
const claude_max_temp = 1.0; //same as j2
|
const claude_max_temp = 1.0; //same as j2
|
||||||
const j2_max_topk = 10.0;
|
const j2_max_topk = 10.0;
|
||||||
|
@@ -140,7 +140,7 @@ const getSlashCommandsHelp = parser.getHelpString.bind(parser);
|
|||||||
|
|
||||||
parser.addCommand('?', helpCommandCallback, ['help'], ' – get help on macros, chat formatting and commands', true, true);
|
parser.addCommand('?', helpCommandCallback, ['help'], ' – get help on macros, chat formatting and commands', true, true);
|
||||||
parser.addCommand('name', setNameCallback, ['persona'], '<span class="monospace">(name)</span> – sets user name and persona avatar (if set)', true, true);
|
parser.addCommand('name', setNameCallback, ['persona'], '<span class="monospace">(name)</span> – sets user name and persona avatar (if set)', true, true);
|
||||||
parser.addCommand('sync', syncCallback, [], ' – syncs user name in user-attributed messages in the current chat', true, true);
|
parser.addCommand('sync', syncCallback, [], ' – syncs the user persona in user-attributed messages in the current chat', true, true);
|
||||||
parser.addCommand('lock', bindCallback, ['bind'], ' – locks/unlocks a persona (name and avatar) to the current chat', true, true);
|
parser.addCommand('lock', bindCallback, ['bind'], ' – locks/unlocks a persona (name and avatar) to the current chat', true, true);
|
||||||
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed', false, true);
|
parser.addCommand('bg', setBackgroundCallback, ['background'], '<span class="monospace">(filename)</span> – sets a background according to filename, partial names allowed', false, true);
|
||||||
parser.addCommand('sendas', sendMessageAs, [], ' – sends message as a specific character. Uses character avatar if it exists in the characters list. Example that will send "Hello, guys!" from "Chloe": <tt>/sendas name="Chloe" Hello, guys!</tt>', true, true);
|
parser.addCommand('sendas', sendMessageAs, [], ' – sends message as a specific character. Uses character avatar if it exists in the characters list. Example that will send "Hello, guys!" from "Chloe": <tt>/sendas name="Chloe" Hello, guys!</tt>', true, true);
|
||||||
@@ -150,7 +150,7 @@ parser.addCommand('comment', sendCommentMessage, [], '<span class="monospace">(t
|
|||||||
parser.addCommand('single', setStoryModeCallback, ['story'], ' – sets the message style to single document mode without names or avatars visible', true, true);
|
parser.addCommand('single', setStoryModeCallback, ['story'], ' – sets the message style to single document mode without names or avatars visible', true, true);
|
||||||
parser.addCommand('bubble', setBubbleModeCallback, ['bubbles'], ' – sets the message style to bubble chat mode', true, true);
|
parser.addCommand('bubble', setBubbleModeCallback, ['bubbles'], ' – sets the message style to bubble chat mode', true, true);
|
||||||
parser.addCommand('flat', setFlatModeCallback, ['default'], ' – sets the message style to flat chat mode', true, true);
|
parser.addCommand('flat', setFlatModeCallback, ['default'], ' – sets the message style to flat chat mode', true, true);
|
||||||
parser.addCommand('continue', continueChatCallback, ['cont'], ' – continues the last message in the chat', true, true);
|
parser.addCommand('continue', continueChatCallback, ['cont'], '<span class="monospace">[prompt]</span> – continues the last message in the chat, with an optional additional prompt', true, true);
|
||||||
parser.addCommand('go', goToCharacterCallback, ['char'], '<span class="monospace">(name)</span> – opens up a chat with the character or group by its name', true, true);
|
parser.addCommand('go', goToCharacterCallback, ['char'], '<span class="monospace">(name)</span> – opens up a chat with the character or group by its name', true, true);
|
||||||
parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">(prompt)</span> – generates a system message using a specified prompt', true, true);
|
parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">(prompt)</span> – generates a system message using a specified prompt', true, true);
|
||||||
parser.addCommand('ask', askCharacter, [], '<span class="monospace">(prompt)</span> – asks a specified character card a prompt', true, true);
|
parser.addCommand('ask', askCharacter, [], '<span class="monospace">(prompt)</span> – asks a specified character card a prompt', true, true);
|
||||||
@@ -1168,7 +1168,7 @@ async function openChat(id) {
|
|||||||
await reloadCurrentChat();
|
await reloadCurrentChat();
|
||||||
}
|
}
|
||||||
|
|
||||||
function continueChatCallback() {
|
function continueChatCallback(_, prompt) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
|
await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
|
||||||
@@ -1179,7 +1179,7 @@ function continueChatCallback() {
|
|||||||
|
|
||||||
// Prevent infinite recursion
|
// Prevent infinite recursion
|
||||||
$('#send_textarea').val('').trigger('input');
|
$('#send_textarea').val('').trigger('input');
|
||||||
$('#option_continue').trigger('click', { fromSlashCommand: true });
|
$('#option_continue').trigger('click', { fromSlashCommand: true, additionalPrompt: prompt });
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
@@ -783,6 +783,17 @@ function getPngName(file) {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the preserved name for the uploaded file if the request is valid.
|
||||||
|
* @param {import("express").Request} request - Express request object
|
||||||
|
* @returns {string | undefined} - The preserved name if the request is valid, otherwise undefined
|
||||||
|
*/
|
||||||
|
function getPreservedName(request) {
|
||||||
|
return request.body.file_type === 'png' && request.body.preserve_file_name === 'true' && request.file?.originalname
|
||||||
|
? path.parse(request.file.originalname).name
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
router.post('/import', urlencodedParser, async function (request, response) {
|
router.post('/import', urlencodedParser, async function (request, response) {
|
||||||
if (!request.body || !request.file) return response.sendStatus(400);
|
if (!request.body || !request.file) return response.sendStatus(400);
|
||||||
|
|
||||||
@@ -790,6 +801,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
|
|||||||
let filedata = request.file;
|
let filedata = request.file;
|
||||||
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
||||||
let format = request.body.file_type;
|
let format = request.body.file_type;
|
||||||
|
const preservedFileName = getPreservedName(request);
|
||||||
|
|
||||||
if (format == 'yaml' || format == 'yml') {
|
if (format == 'yaml' || format == 'yml') {
|
||||||
try {
|
try {
|
||||||
@@ -881,7 +893,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
|
|||||||
let jsonData = JSON.parse(img_data);
|
let jsonData = JSON.parse(img_data);
|
||||||
|
|
||||||
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
||||||
png_name = getPngName(jsonData.name);
|
png_name = preservedFileName || getPngName(jsonData.name);
|
||||||
|
|
||||||
if (jsonData.spec !== undefined) {
|
if (jsonData.spec !== undefined) {
|
||||||
console.log('Found a v2 character file.');
|
console.log('Found a v2 character file.');
|
||||||
|
Reference in New Issue
Block a user