Add shallow character load mode

383806325a0dca99ad4b4fa3f257c868600ff0ab

Cohee <18619528+Cohee1207@users.noreply.github.com>

6 files changed, +125 -10Showing whitespace changes
public/index.html+7 -0
@@ -4424,6 +4424,13 @@
44244424 <option data-i18n="tag_import_existing" value="4">Existing</option>
44254425 </select>
44264426 </div>
4427+ <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.">
4428+ <input id="shallow_characters" type="checkbox" />
4429+ <small data-i18n="Lazy Load Characters">
4430+ Lazy Load Characters
4431+ </small>
4432+ <i class="fa-solid fa-flask"></i>
4433+ </label>
44274434 <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">
44284435 <input id="fuzzy_search_checkbox" type="checkbox" />
44294436 <small data-i18n="Advanced Character Search">Advanced Character Search</small>
public/script.js+37 -1
@@ -1781,7 +1781,7 @@ export async function getCharacters() {
17811781 method: 'POST',
17821782 headers: getRequestHeaders(),
17831783 body: JSON.stringify({
17841784 ''shallow: ''power_user.shallow_characters,
17851785 }),
17861786 });
17871787 if (response.ok === true) {
@@ -3658,6 +3658,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
36583658 setGenerationProgress(0);
36593659 generation_started = new Date();
36603660
3661+ // Prevent generation from shallow characters
3662+ await unshallowCharacter(this_chid);
3663+
36613664 // Occurs every time, even if the generation is aborted due to slash commands execution
36623665 await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun);
36633666
@@ -6704,9 +6707,42 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t
67046707 }
67056708}
67066709
6710+/**
6711+ * Loads all the data of a shallow character.
6712+ * @param {string|undefined} characterId Array index
6713+ * @returns {Promise<void>} Promise that resolves when the character is unshallowed
6714+ */
6715+export async function unshallowCharacter(characterId) {
6716+ if (characterId === undefined) {
6717+ console.warn('Undefined character cannot be unshallowed');
6718+ return;
6719+ }
6720+
6721+ const character = characters[characterId];
6722+ if (!character) {
6723+ console.warn('Character not found:', characterId);
6724+ return;
6725+ }
6726+
6727+ // Character is not shallow
6728+ if (!character.shallow) {
6729+ return;
6730+ }
6731+
6732+ const avatar = character.avatar;
6733+ if (!avatar) {
6734+ console.warn('Character has no avatar field:', characterId);
6735+ return;
6736+ }
6737+
6738+ await getOneCharacter(avatar);
6739+}
6740+
67076741export async function getChat() {
67086742 //console.log('/api/chats/get -- entered for -- ' + characters[this_chid].name);
67096743 try {
6744+ await unshallowCharacter(this_chid);
6745+
67106746 const response = await $.ajax({
67116747 type: 'POST',
67126748 url: '/api/chats/get',
public/scripts/group-chats.js+33 -4
@@ -72,6 +72,7 @@ import {
7272 animation_duration,
7373 depth_prompt_role_default,
7474 shouldAutoContinue,
75+ unshallowCharacter,
7576} from '../script.js';
7677import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map, applyTagsOnGroupSelect } from './tags.js';
7778import { FILTER_TYPES, FilterHelper } from './filters.js';
@@ -216,6 +217,7 @@ export async function getGroupChat(groupId, reload = false) {
216217
217218 // Run validation before any loading
218219 validateGroup(group);
220+ await unshallowGroupMembers(groupId);
219221
220222 const chat_id = group.chat_id;
221223 const data = await loadGroupChat(chat_id);
@@ -824,6 +826,8 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
824826 }
825827
826828 try {
829+ await unshallowGroupMembers(selected_group);
830+
827831 throwIfAborted();
828832 hideSwipeButtons();
829833 is_group_generating = true;
@@ -1137,6 +1141,29 @@ export async function editGroup(id, immediately, reload = true) {
11371141 saveGroupDebounced(group, reload);
11381142}
11391143
1144+/**
1145+ * Unshallows all definitions of group members.
1146+ * @param {string} groupId Id of the group
1147+ * @returns {Promise<void>} Promise that resolves when all group members are unshallowed
1148+ */
1149+export async function unshallowGroupMembers(groupId) {
1150+ const group = groups.find(x => x.id == groupId);
1151+ if (!group) {
1152+ return;
1153+ }
1154+ const members = group.members;
1155+ if (!Array.isArray(members)) {
1156+ return;
1157+ }
1158+ for (const member of members) {
1159+ const index = characters.findIndex(x => x.avatar === member);
1160+ if (index === -1) {
1161+ continue;
1162+ }
1163+ await unshallowCharacter(String(index));
1164+ }
1165+}
1166+
11401167let groupAutoModeAbortController = null;
11411168
11421169async function groupChatAutoModeWorker() {
@@ -1158,9 +1185,9 @@ async function groupChatAutoModeWorker() {
11581185 await generateGroupWrapper(true, 'auto', { signal: groupAutoModeAbortController.signal });
11591186}
11601187
11611188async function modifyGroupMember(chat_idgroupId, groupMember, isDelete) {
11621189 const id = groupMember.data('id');
11631190 const thisGroup = groups.find((x) => x.id == chat_idgroupId);
11641191 const membersArray = thisGroup?.members ?? newGroupMembers;
11651192
11661193 if (isDelete) {
@@ -1173,6 +1200,7 @@ async function modifyGroupMember(chat_id, groupMember, isDelete) {
11731200 }
11741201
11751202 if (openGroupId) {
1203+ await unshallowGroupMembers(openGroupId);
11761204 await editGroup(openGroupId, false, false);
11771205 updateGroupAvatar(thisGroup);
11781206 }
@@ -1638,7 +1666,7 @@ async function onGroupActionClick(event) {
16381666 }
16391667
16401668 if (action === 'view') {
16411669 await openCharacterDefinition(member);
16421670 }
16431671
16441672 if (action === 'speak') {
@@ -1690,7 +1718,7 @@ export async function openGroupById(groupId) {
16901718 return false;
16911719}
16921720
16931721async function openCharacterDefinition(characterSelect) {
16941722 if (is_group_generating) {
16951723 toastr.warning(t`Can't peek a character while group reply is being generated`);
16961724 console.warn('Can\'t peek a character def while group reply is being generated');
@@ -1703,6 +1731,7 @@ function openCharacterDefinition(characterSelect) {
17031731 return;
17041732 }
17051733
1734+ await unshallowCharacter(chid);
17061735 setCharacterId(chid);
17071736 select_selected_character(chid);
17081737 // Gentle nudge to recalculate tokens
public/scripts/power-user.js+8 -0
@@ -314,6 +314,7 @@ let power_user = {
314314 forbid_external_media: true,
315315 external_media_allowed_overrides: [],
316316 external_media_forbidden_overrides: [],
317+ shallow_characters: false,
317318};
318319
319320let themes = [];
@@ -1595,6 +1596,7 @@ async function loadPowerUserSettings(settings, data) {
15951596 $('#auto-connect-checkbox').prop('checked', power_user.auto_connect);
15961597 $('#auto-load-chat-checkbox').prop('checked', power_user.auto_load_chat);
15971598 $('#forbid_external_media').prop('checked', power_user.forbid_external_media);
1599+ $('#shallow_characters').prop('checked', power_user.shallow_characters);
15981600
15991601 for (const theme of themes) {
16001602 const option = document.createElement('option');
@@ -3888,6 +3890,12 @@ $(document).ready(() => {
38883890 await exportTheme();
38893891 });
38903892
3893+ $('#shallow_characters').on('input', function () {
3894+ power_user.shallow_characters = !!$(this).prop('checked');
3895+ saveSettingsDebounced();
3896+ toastr.info('Reload the page for this setting to take effect');
3897+ });
3898+
38913899 $(document).on('click', '#debug_table [data-debug-function]', function () {
38923900 const functionId = $(this).data('debug-function');
38933901 const functionRecord = debug_functions.find(f => f.functionId === functionId);
public/scripts/st-context.js+4 -1
@@ -47,6 +47,7 @@ import {
4747 updateMessageBlock,
4848 printMessages,
4949 clearChat,
50+ unshallowCharacter,
5051} from '../script.js';
5152import {
5253 extension_settings,
@@ -55,7 +56,7 @@ import {
5556 renderExtensionTemplateAsync,
5657 writeExtensionField,
5758} from './extensions.js';
5859import { groups, openGroupChat, selected_group, unshallowGroupMembers } from './group-chats.js';
5960import { addLocaleData, getCurrentLocale, t, translate } from './i18n.js';
6061import { hideLoader, showLoader } from './loader.js';
6162import { MacrosParser } from './macros.js';
@@ -210,6 +211,8 @@ export function getContext() {
210211 clearChat,
211212 ChatCompletionService,
212213 TextCompletionService,
214+ unshallowCharacter,
215+ unshallowGroupMembers,
213216 };
214217}
215218
src/endpoints/characters.js+36 -4
@@ -201,13 +201,44 @@ const calculateDataSize = (data) => {
201201};
202202
203203/**
204+ * Only get fields that are used to display the character list.
205+ * @param {object} character Character object
206+ * @returns {{shallow: true, [key: string]: any}} Shallow character
207+ */
208+const toShallow = (character) => {
209+ return {
210+ name: character.name,
211+ avatar: character.avatar,
212+ chat: character.chat,
213+ fav: character.fav,
214+ date_added: character.date_added,
215+ create_date: character.create_date,
216+ date_last_chat: character.date_last_chat,
217+ chat_size: character.chat_size,
218+ data_size: character.data_size,
219+ data: {
220+ name: _.get(character, 'data.name', ''),
221+ character_version: _.get(character, 'data.character_version', ''),
222+ creator: _.get(character, 'data.creator', ''),
223+ creator_notes: _.get(character, 'data.creator_notes', ''),
224+ extensions: {
225+ fav: _.get(character, 'data.extensions.fav', false),
226+ },
227+ },
228+ shallow: true,
229+ };
230+};
231+
232+/**
204233 * processCharacter - Process a given character, read its data and calculate its statistics.
205234 *
206235 * @param {string} item The name of the character.
207236 * @param {import('../users.js').UserDirectoryList} directories User directories
237+ * @param {object} options Options for the character processing
238+ * @param {boolean} options.shallow If true, only return the core character's metadata
208239 * @return {Promise<object>} A Promise that resolves when the character processing is done.
209240 */
210241const processCharacter = async (item, directories, { shallow }) => {
211242 try {
212243 const imgFile = path.join(directories.characters, item);
213244 const imgData = await readCharacterData(imgFile);
@@ -226,7 +257,7 @@ const processCharacter = async (item, directories) => {
226257 character['chat_size'] = chatSize;
227258 character['date_last_chat'] = dateLastChat;
228259 character['data_size'] = calculateDataSize(jsonObject?.data);
229- return character;
260+ return shallow ? toShallow(character) : character;
230261 }
231262 catch (err) {
232263 console.error(`Could not process character: ${item}`);
@@ -991,9 +1022,10 @@ router.post('/delete', jsonParser, validateAvatarUrlMiddleware, async function (
9911022 */
9921023router.post('/all', jsonParser, async function (request, response) {
9931024 try {
1025+ const shallow = !!request.body.shallow;
9941026 const files = fs.readdirSync(request.user.directories.characters);
9951027 const pngFiles = files.filter(file => file.endsWith('.png'));
9961028 const processingPromises = pngFiles.map(file => processCharacter(file, request.user.directories, { shallow }));
9971029 const data = (await Promise.all(processingPromises)).filter(c => c.name);
9981030 return response.send(data);
9991031 } catch (err) {
@@ -1012,7 +1044,7 @@ router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (req
10121044 return response.sendStatus(404);
10131045 }
10141046
10151047 const data = await processCharacter(item, request.user.directories, { shallow: false });
10161048
10171049 return response.send(data);
10181050 } catch (err) {