mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-03-13 10:30:14 +01:00
Add shallow character load mode
This commit is contained in:
parent
70f762c006
commit
383806325a
@ -4424,6 +4424,13 @@
|
||||
<option data-i18n="tag_import_existing" value="4">Existing</option>
|
||||
</select>
|
||||
</div>
|
||||
<label class="checkbox_label" for="shallow_characters" data-i18n="[title]Experimental feature. May behave unstable and have compatibility issues." title="Experimental feature. May behave unstable and have compatibility issues.">
|
||||
<input id="shallow_characters" type="checkbox" />
|
||||
<small data-i18n="Lazy Load Characters">
|
||||
Lazy Load Characters
|
||||
</small>
|
||||
<i class="fa-solid fa-flask"></i>
|
||||
</label>
|
||||
<label class="checkbox_label" for="fuzzy_search_checkbox" title="Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring." data-i18n="[title]Use fuzzy matching, and search characters in the list by all data fields, not just by a name substring">
|
||||
<input id="fuzzy_search_checkbox" type="checkbox" />
|
||||
<small data-i18n="Advanced Character Search">Advanced Character Search</small>
|
||||
|
@ -1781,7 +1781,7 @@ export async function getCharacters() {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
'': '',
|
||||
shallow: power_user.shallow_characters,
|
||||
}),
|
||||
});
|
||||
if (response.ok === true) {
|
||||
@ -3658,6 +3658,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
|
||||
setGenerationProgress(0);
|
||||
generation_started = new Date();
|
||||
|
||||
// Prevent generation from shallow characters
|
||||
await unshallowCharacter(this_chid);
|
||||
|
||||
// Occurs every time, even if the generation is aborted due to slash commands execution
|
||||
await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
|
||||
|
||||
@ -6704,9 +6707,42 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all the data of a shallow character.
|
||||
* @param {string|undefined} characterId Array index
|
||||
* @returns {Promise<void>} Promise that resolves when the character is unshallowed
|
||||
*/
|
||||
export async function unshallowCharacter(characterId) {
|
||||
if (characterId === undefined) {
|
||||
console.warn('Undefined character cannot be unshallowed');
|
||||
return;
|
||||
}
|
||||
|
||||
const character = characters[characterId];
|
||||
if (!character) {
|
||||
console.warn('Character not found:', characterId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Character is not shallow
|
||||
if (!character.shallow) {
|
||||
return;
|
||||
}
|
||||
|
||||
const avatar = character.avatar;
|
||||
if (!avatar) {
|
||||
console.warn('Character has no avatar field:', characterId);
|
||||
return;
|
||||
}
|
||||
|
||||
await getOneCharacter(avatar);
|
||||
}
|
||||
|
||||
export async function getChat() {
|
||||
//console.log('/api/chats/get -- entered for -- ' + characters[this_chid].name);
|
||||
try {
|
||||
await unshallowCharacter(this_chid);
|
||||
|
||||
const response = await $.ajax({
|
||||
type: 'POST',
|
||||
url: '/api/chats/get',
|
||||
|
@ -72,6 +72,7 @@ import {
|
||||
animation_duration,
|
||||
depth_prompt_role_default,
|
||||
shouldAutoContinue,
|
||||
unshallowCharacter,
|
||||
} from '../script.js';
|
||||
import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map, applyTagsOnGroupSelect } from './tags.js';
|
||||
import { FILTER_TYPES, FilterHelper } from './filters.js';
|
||||
@ -216,6 +217,7 @@ export async function getGroupChat(groupId, reload = false) {
|
||||
|
||||
// Run validation before any loading
|
||||
validateGroup(group);
|
||||
await unshallowGroupMembers(groupId);
|
||||
|
||||
const chat_id = group.chat_id;
|
||||
const data = await loadGroupChat(chat_id);
|
||||
@ -824,6 +826,8 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
|
||||
}
|
||||
|
||||
try {
|
||||
await unshallowGroupMembers(selected_group);
|
||||
|
||||
throwIfAborted();
|
||||
hideSwipeButtons();
|
||||
is_group_generating = true;
|
||||
@ -1137,6 +1141,29 @@ export async function editGroup(id, immediately, reload = true) {
|
||||
saveGroupDebounced(group, reload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshallows all definitions of group members.
|
||||
* @param {string} groupId Id of the group
|
||||
* @returns {Promise<void>} Promise that resolves when all group members are unshallowed
|
||||
*/
|
||||
export async function unshallowGroupMembers(groupId) {
|
||||
const group = groups.find(x => x.id == groupId);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
const members = group.members;
|
||||
if (!Array.isArray(members)) {
|
||||
return;
|
||||
}
|
||||
for (const member of members) {
|
||||
const index = characters.findIndex(x => x.avatar === member);
|
||||
if (index === -1) {
|
||||
continue;
|
||||
}
|
||||
await unshallowCharacter(String(index));
|
||||
}
|
||||
}
|
||||
|
||||
let groupAutoModeAbortController = null;
|
||||
|
||||
async function groupChatAutoModeWorker() {
|
||||
@ -1158,9 +1185,9 @@ async function groupChatAutoModeWorker() {
|
||||
await generateGroupWrapper(true, 'auto', { signal: groupAutoModeAbortController.signal });
|
||||
}
|
||||
|
||||
async function modifyGroupMember(chat_id, groupMember, isDelete) {
|
||||
async function modifyGroupMember(groupId, groupMember, isDelete) {
|
||||
const id = groupMember.data('id');
|
||||
const thisGroup = groups.find((x) => x.id == chat_id);
|
||||
const thisGroup = groups.find((x) => x.id == groupId);
|
||||
const membersArray = thisGroup?.members ?? newGroupMembers;
|
||||
|
||||
if (isDelete) {
|
||||
@ -1173,6 +1200,7 @@ async function modifyGroupMember(chat_id, groupMember, isDelete) {
|
||||
}
|
||||
|
||||
if (openGroupId) {
|
||||
await unshallowGroupMembers(openGroupId);
|
||||
await editGroup(openGroupId, false, false);
|
||||
updateGroupAvatar(thisGroup);
|
||||
}
|
||||
@ -1638,7 +1666,7 @@ async function onGroupActionClick(event) {
|
||||
}
|
||||
|
||||
if (action === 'view') {
|
||||
openCharacterDefinition(member);
|
||||
await openCharacterDefinition(member);
|
||||
}
|
||||
|
||||
if (action === 'speak') {
|
||||
@ -1690,7 +1718,7 @@ export async function openGroupById(groupId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function openCharacterDefinition(characterSelect) {
|
||||
async function openCharacterDefinition(characterSelect) {
|
||||
if (is_group_generating) {
|
||||
toastr.warning(t`Can't peek a character while group reply is being generated`);
|
||||
console.warn('Can\'t peek a character def while group reply is being generated');
|
||||
@ -1703,6 +1731,7 @@ function openCharacterDefinition(characterSelect) {
|
||||
return;
|
||||
}
|
||||
|
||||
await unshallowCharacter(chid);
|
||||
setCharacterId(chid);
|
||||
select_selected_character(chid);
|
||||
// Gentle nudge to recalculate tokens
|
||||
|
@ -314,6 +314,7 @@ let power_user = {
|
||||
forbid_external_media: true,
|
||||
external_media_allowed_overrides: [],
|
||||
external_media_forbidden_overrides: [],
|
||||
shallow_characters: false,
|
||||
};
|
||||
|
||||
let themes = [];
|
||||
@ -1595,6 +1596,7 @@ async function loadPowerUserSettings(settings, data) {
|
||||
$('#auto-connect-checkbox').prop('checked', power_user.auto_connect);
|
||||
$('#auto-load-chat-checkbox').prop('checked', power_user.auto_load_chat);
|
||||
$('#forbid_external_media').prop('checked', power_user.forbid_external_media);
|
||||
$('#shallow_characters').prop('checked', power_user.shallow_characters);
|
||||
|
||||
for (const theme of themes) {
|
||||
const option = document.createElement('option');
|
||||
@ -3888,6 +3890,12 @@ $(document).ready(() => {
|
||||
await exportTheme();
|
||||
});
|
||||
|
||||
$('#shallow_characters').on('input', function () {
|
||||
power_user.shallow_characters = !!$(this).prop('checked');
|
||||
saveSettingsDebounced();
|
||||
toastr.info('Reload the page for this setting to take effect');
|
||||
});
|
||||
|
||||
$(document).on('click', '#debug_table [data-debug-function]', function () {
|
||||
const functionId = $(this).data('debug-function');
|
||||
const functionRecord = debug_functions.find(f => f.functionId === functionId);
|
||||
|
@ -47,6 +47,7 @@ import {
|
||||
updateMessageBlock,
|
||||
printMessages,
|
||||
clearChat,
|
||||
unshallowCharacter,
|
||||
} from '../script.js';
|
||||
import {
|
||||
extension_settings,
|
||||
@ -55,7 +56,7 @@ import {
|
||||
renderExtensionTemplateAsync,
|
||||
writeExtensionField,
|
||||
} from './extensions.js';
|
||||
import { groups, openGroupChat, selected_group } from './group-chats.js';
|
||||
import { groups, openGroupChat, selected_group, unshallowGroupMembers } from './group-chats.js';
|
||||
import { addLocaleData, getCurrentLocale, t, translate } from './i18n.js';
|
||||
import { hideLoader, showLoader } from './loader.js';
|
||||
import { MacrosParser } from './macros.js';
|
||||
@ -210,6 +211,8 @@ export function getContext() {
|
||||
clearChat,
|
||||
ChatCompletionService,
|
||||
TextCompletionService,
|
||||
unshallowCharacter,
|
||||
unshallowGroupMembers,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -200,14 +200,45 @@ const calculateDataSize = (data) => {
|
||||
return typeof data === 'object' ? Object.values(data).reduce((acc, val) => acc + String(val).length, 0) : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Only get fields that are used to display the character list.
|
||||
* @param {object} character Character object
|
||||
* @returns {{shallow: true, [key: string]: any}} Shallow character
|
||||
*/
|
||||
const toShallow = (character) => {
|
||||
return {
|
||||
name: character.name,
|
||||
avatar: character.avatar,
|
||||
chat: character.chat,
|
||||
fav: character.fav,
|
||||
date_added: character.date_added,
|
||||
create_date: character.create_date,
|
||||
date_last_chat: character.date_last_chat,
|
||||
chat_size: character.chat_size,
|
||||
data_size: character.data_size,
|
||||
data: {
|
||||
name: _.get(character, 'data.name', ''),
|
||||
character_version: _.get(character, 'data.character_version', ''),
|
||||
creator: _.get(character, 'data.creator', ''),
|
||||
creator_notes: _.get(character, 'data.creator_notes', ''),
|
||||
extensions: {
|
||||
fav: _.get(character, 'data.extensions.fav', false),
|
||||
},
|
||||
},
|
||||
shallow: true,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* processCharacter - Process a given character, read its data and calculate its statistics.
|
||||
*
|
||||
* @param {string} item The name of the character.
|
||||
* @param {import('../users.js').UserDirectoryList} directories User directories
|
||||
* @param {object} options Options for the character processing
|
||||
* @param {boolean} options.shallow If true, only return the core character's metadata
|
||||
* @return {Promise<object>} A Promise that resolves when the character processing is done.
|
||||
*/
|
||||
const processCharacter = async (item, directories) => {
|
||||
const processCharacter = async (item, directories, { shallow }) => {
|
||||
try {
|
||||
const imgFile = path.join(directories.characters, item);
|
||||
const imgData = await readCharacterData(imgFile);
|
||||
@ -226,7 +257,7 @@ const processCharacter = async (item, directories) => {
|
||||
character['chat_size'] = chatSize;
|
||||
character['date_last_chat'] = dateLastChat;
|
||||
character['data_size'] = calculateDataSize(jsonObject?.data);
|
||||
return character;
|
||||
return shallow ? toShallow(character) : character;
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Could not process character: ${item}`);
|
||||
@ -991,9 +1022,10 @@ router.post('/delete', jsonParser, validateAvatarUrlMiddleware, async function (
|
||||
*/
|
||||
router.post('/all', jsonParser, async function (request, response) {
|
||||
try {
|
||||
const shallow = !!request.body.shallow;
|
||||
const files = fs.readdirSync(request.user.directories.characters);
|
||||
const pngFiles = files.filter(file => file.endsWith('.png'));
|
||||
const processingPromises = pngFiles.map(file => processCharacter(file, request.user.directories));
|
||||
const processingPromises = pngFiles.map(file => processCharacter(file, request.user.directories, { shallow }));
|
||||
const data = (await Promise.all(processingPromises)).filter(c => c.name);
|
||||
return response.send(data);
|
||||
} catch (err) {
|
||||
@ -1012,7 +1044,7 @@ router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (req
|
||||
return response.sendStatus(404);
|
||||
}
|
||||
|
||||
const data = await processCharacter(item, request.user.directories);
|
||||
const data = await processCharacter(item, request.user.directories, { shallow: false });
|
||||
|
||||
return response.send(data);
|
||||
} catch (err) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user