mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Permanent assistant autocreation and temporary chat restore
This commit is contained in:
@ -282,7 +282,7 @@ import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
|
|||||||
import { getContext } from './scripts/st-context.js';
|
import { getContext } from './scripts/st-context.js';
|
||||||
import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js';
|
import { extractReasoningFromData, initReasoning, parseReasoningInSwipes, PromptReasoning, ReasoningHandler, removeReasoningFromString, updateReasoningUI } from './scripts/reasoning.js';
|
||||||
import { accountStorage } from './scripts/util/AccountStorage.js';
|
import { accountStorage } from './scripts/util/AccountStorage.js';
|
||||||
import { initWelcomeScreen } from './scripts/welcome-screen.js';
|
import { initWelcomeScreen, openPermanentAssistantChat, openPermanentAssistantCard } from './scripts/welcome-screen.js';
|
||||||
|
|
||||||
// API OBJECT FOR EXTERNAL WIRING
|
// API OBJECT FOR EXTERNAL WIRING
|
||||||
globalThis.SillyTavern = {
|
globalThis.SillyTavern = {
|
||||||
@ -2041,7 +2041,7 @@ export async function sendTextareaMessage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (textareaText && !selected_group && this_chid === undefined && name2 !== neutralCharacterName) {
|
if (textareaText && !selected_group && this_chid === undefined && name2 !== neutralCharacterName) {
|
||||||
await newAssistantChat();
|
await newAssistantChat({ temporary: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
Generate(generateType);
|
Generate(generateType);
|
||||||
@ -2883,7 +2883,14 @@ export async function processCommands(message) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendSystemMessage(type, text, extra = {}) {
|
/**
|
||||||
|
* Gets a system message by type.
|
||||||
|
* @param {string} type Type of system message
|
||||||
|
* @param {string} [text] Text to be sent
|
||||||
|
* @param {object} [extra] Additional data to be added to the message
|
||||||
|
* @returns {object} System message object
|
||||||
|
*/
|
||||||
|
export function getSystemMessageByType(type, text, extra = {}) {
|
||||||
const systemMessage = system_messages[type];
|
const systemMessage = system_messages[type];
|
||||||
|
|
||||||
if (!systemMessage) {
|
if (!systemMessage) {
|
||||||
@ -2906,7 +2913,17 @@ export function sendSystemMessage(type, text, extra = {}) {
|
|||||||
|
|
||||||
newMessage.extra = Object.assign(newMessage.extra, extra);
|
newMessage.extra = Object.assign(newMessage.extra, extra);
|
||||||
newMessage.extra.type = type;
|
newMessage.extra.type = type;
|
||||||
|
return newMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a system message to the chat.
|
||||||
|
* @param {string} type Type of system message
|
||||||
|
* @param {string} [text] Text to be sent
|
||||||
|
* @param {object} [extra] Additional data to be added to the message
|
||||||
|
*/
|
||||||
|
export function sendSystemMessage(type, text, extra = {}) {
|
||||||
|
const newMessage = getSystemMessageByType(type, text, extra);
|
||||||
chat.push(newMessage);
|
chat.push(newMessage);
|
||||||
addOneMessage(newMessage);
|
addOneMessage(newMessage);
|
||||||
is_send_press = false;
|
is_send_press = false;
|
||||||
@ -10113,8 +10130,17 @@ async function removeCharacterFromUI() {
|
|||||||
saveSettingsDebounced();
|
saveSettingsDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function newAssistantChat() {
|
/**
|
||||||
|
* Creates a new assistant chat.
|
||||||
|
* @param {object} params - Parameters for the new assistant chat
|
||||||
|
* @param {boolean} [params.temporary=false] I need a temporary secretary
|
||||||
|
* @returns {Promise<void>} - A promise that resolves when the new assistant chat is created
|
||||||
|
*/
|
||||||
|
export async function newAssistantChat({ temporary = false } = {}) {
|
||||||
await clearChat();
|
await clearChat();
|
||||||
|
if (!temporary) {
|
||||||
|
return openPermanentAssistantChat();
|
||||||
|
}
|
||||||
chat.splice(0, chat.length);
|
chat.splice(0, chat.length);
|
||||||
chat_metadata = {};
|
chat_metadata = {};
|
||||||
setCharacterName(neutralCharacterName);
|
setCharacterName(neutralCharacterName);
|
||||||
@ -10427,7 +10453,7 @@ jQuery(async function () {
|
|||||||
if (chatId) {
|
if (chatId) {
|
||||||
return reject('Not in a temporary chat');
|
return reject('Not in a temporary chat');
|
||||||
}
|
}
|
||||||
await newAssistantChat();
|
await newAssistantChat({ temporary: true });
|
||||||
return resolve('');
|
return resolve('');
|
||||||
};
|
};
|
||||||
eventSource.once(event_types.CHAT_CHANGED, eventCallback);
|
eventSource.once(event_types.CHAT_CHANGED, eventCallback);
|
||||||
@ -11094,6 +11120,9 @@ jQuery(async function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (id == 'option_select_chat') {
|
if (id == 'option_select_chat') {
|
||||||
|
if (this_chid === undefined && !is_send_press) {
|
||||||
|
await openPermanentAssistantCard();
|
||||||
|
}
|
||||||
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();
|
||||||
//this is just to avoid the shadow for past chat view when using /delchat
|
//this is just to avoid the shadow for past chat view when using /delchat
|
||||||
@ -11124,7 +11153,7 @@ jQuery(async function () {
|
|||||||
await doNewChat({ deleteCurrentChat: deleteCurrentChat });
|
await doNewChat({ deleteCurrentChat: deleteCurrentChat });
|
||||||
}
|
}
|
||||||
if (!selected_group && this_chid === undefined && !is_send_press) {
|
if (!selected_group && this_chid === undefined && !is_send_press) {
|
||||||
await newAssistantChat();
|
await newAssistantChat({ temporary: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,9 @@ import {
|
|||||||
neutralCharacterName,
|
neutralCharacterName,
|
||||||
updateChatMetadata,
|
updateChatMetadata,
|
||||||
system_message_types,
|
system_message_types,
|
||||||
|
getSystemMessageByType,
|
||||||
|
printMessages,
|
||||||
|
clearChat,
|
||||||
} from '../script.js';
|
} from '../script.js';
|
||||||
import { selected_group } from './group-chats.js';
|
import { selected_group } from './group-chats.js';
|
||||||
import { power_user } from './power-user.js';
|
import { power_user } from './power-user.js';
|
||||||
@ -37,6 +40,7 @@ import {
|
|||||||
saveBase64AsFile,
|
saveBase64AsFile,
|
||||||
extractTextFromOffice,
|
extractTextFromOffice,
|
||||||
download,
|
download,
|
||||||
|
getFileText,
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
|
import { extension_settings, renderExtensionTemplateAsync, saveMetadataDebounced } from './extensions.js';
|
||||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||||
@ -1497,6 +1501,35 @@ jQuery(function () {
|
|||||||
download(chatToSave.map((m) => JSON.stringify(m)).join('\n'), `Assistant - ${humanizedDateTime()}.jsonl`, 'application/json');
|
download(chatToSave.map((m) => JSON.stringify(m)).join('\n'), `Assistant - ${humanizedDateTime()}.jsonl`, 'application/json');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(document).on('click', '.assistant_note_import', async function () {
|
||||||
|
const importFile = async () => {
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const text = await getFileText(file);
|
||||||
|
const lines = text.split('\n').filter(line => line.trim() !== '');
|
||||||
|
const messages = lines.map(line => JSON.parse(line));
|
||||||
|
const metadata = messages.shift()?.chat_metadata || {};
|
||||||
|
messages.unshift(getSystemMessageByType(system_message_types.ASSISTANT_NOTE));
|
||||||
|
await clearChat();
|
||||||
|
chat.splice(0, chat.length, ...messages);
|
||||||
|
updateChatMetadata(metadata, true);
|
||||||
|
await printMessages();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error importing assistant chat:', error);
|
||||||
|
toastr.error(t`It's either corrupted or not a valid JSONL file.`, t`Failed to import chat`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const fileInput = document.createElement('input');
|
||||||
|
fileInput.type = 'file';
|
||||||
|
fileInput.accept = '.jsonl';
|
||||||
|
fileInput.addEventListener('change', importFile);
|
||||||
|
fileInput.click();
|
||||||
|
});
|
||||||
|
|
||||||
// Do not change. #attachFile is added by extension.
|
// Do not change. #attachFile is added by extension.
|
||||||
$(document).on('click', '#attachFile', function () {
|
$(document).on('click', '#attachFile', function () {
|
||||||
$('#file_form_input').trigger('click');
|
$('#file_form_input').trigger('click');
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
<div data-type="assistant_note">
|
<div data-type="assistant_note">
|
||||||
<div>
|
<div class="assistant_note_title">
|
||||||
<b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
|
<b data-i18n="Note:">Note:</b> <span data-i18n="this chat is temporary and will be deleted as soon as you leave it.">this chat is temporary and will be deleted as soon as you leave it.</span>
|
||||||
<span data-i18n="Click the button to save it as a file.">Click the button to save it as a file.</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="assistant_note_export menu_button menu_button_icon" data-i18n="[title]Export as JSONL" title="Export as JSONL">
|
<button class="assistant_note_import menu_button menu_button_icon margin0" data-i18n="[title]Import from JSONL" title="Import from JSONL">
|
||||||
|
<i class="fa-solid fa-file-import"></i>
|
||||||
|
<span data-i18n="Load">Load</span>
|
||||||
|
</button>
|
||||||
|
<button class="assistant_note_export menu_button menu_button_icon margin0" data-i18n="[title]Export as JSONL" title="Export as JSONL">
|
||||||
<i class="fa-solid fa-file-export"></i>
|
<i class="fa-solid fa-file-export"></i>
|
||||||
</div>
|
<span data-i18n="Save">Save</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,21 +1,28 @@
|
|||||||
import {
|
import {
|
||||||
characters,
|
characters,
|
||||||
displayVersion,
|
displayVersion,
|
||||||
|
doNewChat,
|
||||||
event_types,
|
event_types,
|
||||||
eventSource,
|
eventSource,
|
||||||
|
getCharacters,
|
||||||
getCurrentChatId,
|
getCurrentChatId,
|
||||||
getRequestHeaders,
|
getRequestHeaders,
|
||||||
getThumbnailUrl,
|
getThumbnailUrl,
|
||||||
|
is_send_press,
|
||||||
|
neutralCharacterName,
|
||||||
newAssistantChat,
|
newAssistantChat,
|
||||||
openCharacterChat,
|
openCharacterChat,
|
||||||
selectCharacterById,
|
selectCharacterById,
|
||||||
sendSystemMessage,
|
sendSystemMessage,
|
||||||
system_message_types,
|
system_message_types,
|
||||||
} from '../script.js';
|
} from '../script.js';
|
||||||
|
import { is_group_generating } from './group-chats.js';
|
||||||
import { t } from './i18n.js';
|
import { t } from './i18n.js';
|
||||||
import { renderTemplateAsync } from './templates.js';
|
import { renderTemplateAsync } from './templates.js';
|
||||||
import { timestampToMoment } from './utils.js';
|
import { timestampToMoment } from './utils.js';
|
||||||
|
|
||||||
|
const permanentAssistantAvatar = 'default_Assistant.png';
|
||||||
|
|
||||||
export async function openWelcomeScreen() {
|
export async function openWelcomeScreen() {
|
||||||
const currentChatId = getCurrentChatId();
|
const currentChatId = getCurrentChatId();
|
||||||
if (currentChatId !== undefined) {
|
if (currentChatId !== undefined) {
|
||||||
@ -28,7 +35,7 @@ export async function openWelcomeScreen() {
|
|||||||
|
|
||||||
async function sendWelcomePanel() {
|
async function sendWelcomePanel() {
|
||||||
try {
|
try {
|
||||||
const chatElement = document.getElementById('chat');
|
const chatElement = document.getElementById('chat');
|
||||||
if (!chatElement) {
|
if (!chatElement) {
|
||||||
console.error('Chat element not found');
|
console.error('Chat element not found');
|
||||||
return;
|
return;
|
||||||
@ -36,7 +43,7 @@ async function sendWelcomePanel() {
|
|||||||
const chats = await getRecentChats();
|
const chats = await getRecentChats();
|
||||||
const templateData = {
|
const templateData = {
|
||||||
chats,
|
chats,
|
||||||
empty: !chats.length ,
|
empty: !chats.length,
|
||||||
version: displayVersion,
|
version: displayVersion,
|
||||||
};
|
};
|
||||||
const template = await renderTemplateAsync('welcomePanel', templateData);
|
const template = await renderTemplateAsync('welcomePanel', templateData);
|
||||||
@ -51,7 +58,7 @@ async function sendWelcomePanel() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
fragment.querySelector('button.openTemporaryChat').addEventListener('click', () => {
|
fragment.querySelector('button.openTemporaryChat').addEventListener('click', () => {
|
||||||
void newAssistantChat();
|
void newAssistantChat({ temporary: true });
|
||||||
});
|
});
|
||||||
chatElement.append(fragment.firstChild);
|
chatElement.append(fragment.firstChild);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -114,7 +121,7 @@ async function getRecentChats() {
|
|||||||
/** @type {RecentChat[]} */
|
/** @type {RecentChat[]} */
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
data.sort((a, b) => b.last_mes - a.last_mes).forEach((chat, index) => {
|
data.sort((a, b) => b.last_mes - a.last_mes).forEach((chat, index) => {
|
||||||
const character = characters.find(x => x.avatar === chat.avatar);
|
const character = characters.find(x => x.avatar === chat.avatar);
|
||||||
if (!character) {
|
if (!character) {
|
||||||
console.warn(`Character not found for chat: ${chat.file_name}`);
|
console.warn(`Character not found for chat: ${chat.file_name}`);
|
||||||
@ -133,6 +140,72 @@ async function getRecentChats() {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function openPermanentAssistantChat({ tryCreate = true } = {}) {
|
||||||
|
const characterId = characters.findIndex(x => x.avatar === permanentAssistantAvatar);
|
||||||
|
if (characterId === -1) {
|
||||||
|
if (!tryCreate) {
|
||||||
|
console.error(`Character not found for avatar ID: ${permanentAssistantAvatar}. Cannot create.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`Character not found for avatar ID: ${permanentAssistantAvatar}. Creating new assistant.`);
|
||||||
|
await createPermanentAssistant();
|
||||||
|
return openPermanentAssistantChat({ tryCreate: false });
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('Error creating permanent assistant:', error);
|
||||||
|
toastr.error(t`Failed to create ${neutralCharacterName}. See console for details.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await selectCharacterById(characterId);
|
||||||
|
await doNewChat({ deleteCurrentChat: false });
|
||||||
|
console.log(`Opened permanent assistant chat for ${neutralCharacterName}.`, getCurrentChatId());
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error opening permanent assistant chat:', error);
|
||||||
|
toastr.error(t`Failed to open permanent assistant chat. See console for details.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPermanentAssistant() {
|
||||||
|
if (is_group_generating || is_send_press) {
|
||||||
|
throw new Error(t`Cannot create while generating.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('ch_name', neutralCharacterName);
|
||||||
|
formData.append('file_name', permanentAssistantAvatar.replace('.png', ''));
|
||||||
|
|
||||||
|
const headers = getRequestHeaders();
|
||||||
|
delete headers['Content-Type'];
|
||||||
|
|
||||||
|
const fetchResult = await fetch('/api/characters/create', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: headers,
|
||||||
|
body: formData,
|
||||||
|
cache: 'no-cache',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fetchResult.ok) {
|
||||||
|
throw new Error(t`Creation request did not succeed.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await getCharacters();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openPermanentAssistantCard() {
|
||||||
|
const characterId = characters.findIndex(x => x.avatar === permanentAssistantAvatar);
|
||||||
|
if (characterId === -1) {
|
||||||
|
toastr.info(t`Assistant not found. Try sending a chat message.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await selectCharacterById(characterId);
|
||||||
|
}
|
||||||
|
|
||||||
export function initWelcomeScreen() {
|
export function initWelcomeScreen() {
|
||||||
const events = [event_types.CHAT_CHANGED, event_types.APP_READY];
|
const events = [event_types.CHAT_CHANGED, event_types.APP_READY];
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
@ -6061,12 +6061,12 @@ body:not(.movingUI) .drawer-content.maximized {
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 5px;
|
||||||
padding: 0 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mes_text div[data-type="assistant_note"]:has(.assistant_note_export)>div:not(.assistant_note_export) {
|
.mes_text div[data-type="assistant_note"]:has(.assistant_note_export) > div {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.oneline-dropdown label {
|
.oneline-dropdown label {
|
||||||
|
@ -577,10 +577,10 @@ function charaFormatData(data, directories) {
|
|||||||
_.set(char, 'mes_example', data.mes_example || '');
|
_.set(char, 'mes_example', data.mes_example || '');
|
||||||
|
|
||||||
// Old ST extension fields (for backward compatibility, will be deprecated)
|
// Old ST extension fields (for backward compatibility, will be deprecated)
|
||||||
_.set(char, 'creatorcomment', data.creator_notes);
|
_.set(char, 'creatorcomment', data.creator_notes || '');
|
||||||
_.set(char, 'avatar', 'none');
|
_.set(char, 'avatar', 'none');
|
||||||
_.set(char, 'chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
|
_.set(char, 'chat', data.ch_name + ' - ' + humanizedISO8601DateTime());
|
||||||
_.set(char, 'talkativeness', data.talkativeness);
|
_.set(char, 'talkativeness', data.talkativeness || 0.5);
|
||||||
_.set(char, 'fav', data.fav == 'true');
|
_.set(char, 'fav', data.fav == 'true');
|
||||||
_.set(char, 'tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : data.tags || []);
|
_.set(char, 'tags', typeof data.tags == 'string' ? (data.tags.split(',').map(x => x.trim()).filter(x => x)) : data.tags || []);
|
||||||
|
|
||||||
@ -604,7 +604,7 @@ function charaFormatData(data, directories) {
|
|||||||
_.set(char, 'data.alternate_greetings', getAlternateGreetings(data));
|
_.set(char, 'data.alternate_greetings', getAlternateGreetings(data));
|
||||||
|
|
||||||
// ST extension fields to V2 object
|
// ST extension fields to V2 object
|
||||||
_.set(char, 'data.extensions.talkativeness', data.talkativeness);
|
_.set(char, 'data.extensions.talkativeness', data.talkativeness || 0.5);
|
||||||
_.set(char, 'data.extensions.fav', data.fav == 'true');
|
_.set(char, 'data.extensions.fav', data.fav == 'true');
|
||||||
_.set(char, 'data.extensions.world', data.world || '');
|
_.set(char, 'data.extensions.world', data.world || '');
|
||||||
|
|
||||||
@ -1006,7 +1006,7 @@ router.post('/create', async function (request, response) {
|
|||||||
request.body.ch_name = sanitize(request.body.ch_name);
|
request.body.ch_name = sanitize(request.body.ch_name);
|
||||||
|
|
||||||
const char = JSON.stringify(charaFormatData(request.body, request.user.directories));
|
const char = JSON.stringify(charaFormatData(request.body, request.user.directories));
|
||||||
const internalName = getPngName(request.body.ch_name, request.user.directories);
|
const internalName = request.body.file_name || getPngName(request.body.ch_name, request.user.directories);
|
||||||
const avatarName = `${internalName}.png`;
|
const avatarName = `${internalName}.png`;
|
||||||
const chatsPath = path.join(request.user.directories.chats, internalName);
|
const chatsPath = path.join(request.user.directories.chats, internalName);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user