Merge pull request #3614 from SillyTavern/char-shallow Lazy load characters
Signed| @@ -1,8 +1,6 @@ | ||
| 1 | 1 | # -- DATA CONFIGURATION -- |
| 2 | 2 | # Root directory for user data storage |
| 3 | 3 | dataRoot: ./data |
| 4 | -# The maximum amount of memory that parsed character cards can use in MB | |
| 5 | -cardsCacheCapacity: 100 | |
| 6 | 4 | # -- SERVER CONFIGURATION -- |
| 7 | 5 | # Listen for incoming connections |
| 8 | 6 | listen: false |
| @@ -135,6 +133,14 @@ thumbnails: | ||
| 135 | 133 | # Maximum thumbnail dimensions per type [width, height] |
| 136 | 134 | dimensions: { 'bg': [160, 90], 'avatar': [96, 144] } |
| 137 | 135 | |
| 136 | +# PERFORMANCE-RELATED CONFIGURATION | |
| 137 | +performance: | |
| 138 | + # Enables lazy loading of character cards. Improves performances with large card libraries. | |
| 139 | + # May have compatibility issues with some extensions. | |
| 140 | + lazyLoadCharacters: false | |
| 141 | + # The maximum amount of memory that parsed character cards can use. Set to 0 to disable memory caching. | |
| 142 | + memoryCacheCapacity: '100mb' | |
| 143 | + | |
| 138 | 144 | # Allow secret keys exposure via API |
| 139 | 145 | allowKeysExposure: false |
| 140 | 146 | # Skip new default content checks |
| @@ -21,6 +21,7 @@ | ||
| 21 | 21 | "bing-translate-api": "^4.0.2", |
| 22 | 22 | "body-parser": "^1.20.2", |
| 23 | 23 | "bowser": "^2.11.0", |
| 24 | + "bytes": "^3.1.2", | |
| 24 | 25 | "chalk": "^5.4.1", |
| 25 | 26 | "command-exists": "^1.2.9", |
| 26 | 27 | "compression": "^1.8.0", |
| @@ -83,6 +84,7 @@ | ||
| 83 | 84 | }, |
| 84 | 85 | "devDependencies": { |
| 85 | 86 | "@types/archiver": "^6.0.3", |
| 87 | + "@types/bytes": "^3.1.5", | |
| 86 | 88 | "@types/command-exists": "^1.2.3", |
| 87 | 89 | "@types/compression": "^1.7.5", |
| 88 | 90 | "@types/cookie-parser": "^1.4.8", |
| @@ -1105,6 +1107,13 @@ | ||
| 1105 | 1107 | "@types/node": "*" |
| 1106 | 1108 | } |
| 1107 | 1109 | }, |
| 1110 | + "node_modules/@types/bytes": { | |
| 1111 | + "version": "3.1.5", | |
| 1112 | + "resolved": "https://registry.npmjs.org/@types/bytes/-/bytes-3.1.5.tgz", | |
| 1113 | + "integrity": "sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ==", | |
| 1114 | + "dev": true, | |
| 1115 | + "license": "MIT" | |
| 1116 | + }, | |
| 1108 | 1117 | "node_modules/@types/cacheable-request": { |
| 1109 | 1118 | "version": "6.0.3", |
| 1110 | 1119 | "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", |
| @@ -11,6 +11,7 @@ | ||
| 11 | 11 | "bing-translate-api": "^4.0.2", |
| 12 | 12 | "body-parser": "^1.20.2", |
| 13 | 13 | "bowser": "^2.11.0", |
| 14 | + "bytes": "^3.1.2", | |
| 14 | 15 | "chalk": "^5.4.1", |
| 15 | 16 | "command-exists": "^1.2.9", |
| 16 | 17 | "compression": "^1.8.0", |
| @@ -92,8 +93,8 @@ | ||
| 92 | 93 | "version": "1.12.12", |
| 93 | 94 | "scripts": { |
| 94 | 95 | "start": "node server.js", |
| 96 | + "debug": "node --inspect server.js", | |
| 95 | 97 | "electron": "electron ./src/electron", |
| 96 | - "debug": "node server.js --inspect", | |
| 97 | 98 | "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", |
| 98 | 99 | "start:bun": "bun server.js", |
| 99 | 100 | "start:no-csrf": "node server.js --disableCsrf", |
| @@ -113,6 +114,7 @@ | ||
| 113 | 114 | "main": "server.js", |
| 114 | 115 | "devDependencies": { |
| 115 | 116 | "@types/archiver": "^6.0.3", |
| 117 | + "@types/bytes": "^3.1.5", | |
| 116 | 118 | "@types/command-exists": "^1.2.3", |
| 117 | 119 | "@types/compression": "^1.7.5", |
| 118 | 120 | "@types/cookie-parser": "^1.4.8", |
| @@ -96,6 +96,11 @@ const keyMigrationMap = [ | ||
| 96 | 96 | newKey: 'logging.minLogLevel', |
| 97 | 97 | migrate: (value) => value, |
| 98 | 98 | }, |
| 99 | + { | |
| 100 | + oldKey: 'cardsCacheCapacity', | |
| 101 | + newKey: 'performance.memoryCacheCapacity', | |
| 102 | + migrate: (value) => `${value}mb`, | |
| 103 | + }, | |
| 99 | 104 | // uncomment one release after 1.12.13 |
| 100 | 105 | /* |
| 101 | 106 | { |
| @@ -1780,9 +1780,7 @@ export async function getCharacters() { | ||
| 1780 | 1780 | const response = await fetch('/api/characters/all', { |
| 1781 | 1781 | method: 'POST', |
| 1782 | 1782 | headers: getRequestHeaders(), |
| 1783 | 1783 | body: JSON.stringify({}), |
| 1784 | - '': '', | |
| 1785 | - }), | |
| 1786 | 1784 | }); |
| 1787 | 1785 | if (response.ok === true) { |
| 1788 | 1786 | characters.splice(0, characters.length); |
| @@ -3678,6 +3676,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro | ||
| 3678 | 3676 | setGenerationProgress(0); |
| 3679 | 3677 | generation_started = new Date(); |
| 3680 | 3678 | |
| 3679 | + // Prevent generation from shallow characters | |
| 3680 | + await unshallowCharacter(this_chid); | |
| 3681 | + | |
| 3681 | 3682 | // Occurs every time, even if the generation is aborted due to slash commands execution |
| 3682 | 3683 | await eventSource.emit(event_types.GENERATION_STARTED, type, { automatic_trigger, force_name2, quiet_prompt, quietToLoud, skipWIAN, force_chid, signal, quietImage }, dryRun); |
| 3683 | 3684 | |
| @@ -6724,9 +6725,43 @@ export function buildAvatarList(block, entities, { templateId = 'inline_avatar_t | ||
| 6724 | 6725 | } |
| 6725 | 6726 | } |
| 6726 | 6727 | |
| 6728 | +/** | |
| 6729 | + * Loads all the data of a shallow character. | |
| 6730 | + * @param {string|undefined} characterId Array index | |
| 6731 | + * @returns {Promise<void>} Promise that resolves when the character is unshallowed | |
| 6732 | + */ | |
| 6733 | +export async function unshallowCharacter(characterId) { | |
| 6734 | + if (characterId === undefined) { | |
| 6735 | + console.warn('Undefined character cannot be unshallowed'); | |
| 6736 | + return; | |
| 6737 | + } | |
| 6738 | + | |
| 6739 | + /** @type {import('./scripts/char-data.js').v1CharData} */ | |
| 6740 | + const character = characters[characterId]; | |
| 6741 | + if (!character) { | |
| 6742 | + console.warn('Character not found:', characterId); | |
| 6743 | + return; | |
| 6744 | + } | |
| 6745 | + | |
| 6746 | + // Character is not shallow | |
| 6747 | + if (!character.shallow) { | |
| 6748 | + return; | |
| 6749 | + } | |
| 6750 | + | |
| 6751 | + const avatar = character.avatar; | |
| 6752 | + if (!avatar) { | |
| 6753 | + console.warn('Character has no avatar field:', characterId); | |
| 6754 | + return; | |
| 6755 | + } | |
| 6756 | + | |
| 6757 | + await getOneCharacter(avatar); | |
| 6758 | +} | |
| 6759 | + | |
| 6727 | 6760 | export async function getChat() { |
| 6728 | 6761 | //console.log('/api/chats/get -- entered for -- ' + characters[this_chid].name); |
| 6729 | 6762 | try { |
| 6763 | + await unshallowCharacter(this_chid); | |
| 6764 | + | |
| 6730 | 6765 | const response = await $.ajax({ |
| 6731 | 6766 | type: 'POST', |
| 6732 | 6767 | url: '/api/chats/get', |
| @@ -316,7 +316,10 @@ export async function favsToHotswap() { | ||
| 316 | 316 | const entities = getEntitiesList({ doFilter: false }); |
| 317 | 317 | const container = $('#right-nav-panel .hotswap'); |
| 318 | 318 | |
| 319 | - const favs = entities.filter(x => x.item.fav || x.item.fav == 'true'); | |
| 319 | + // Hard limit is required because even if all hotswaps don't fit the screen, their images would still be loaded | |
| 320 | + // 25 is roughly calculated as the maximum number of favs that can fit an ultrawide monitor with the default theme | |
| 321 | + const FAVS_LIMIT = 25; | |
| 322 | + const favs = entities.filter(x => x.item.fav || x.item.fav == 'true').slice(0, FAVS_LIMIT); | |
| 320 | 323 | |
| 321 | 324 | //helpful instruction message if no characters are favorited |
| 322 | 325 | if (favs.length == 0) { |
| @@ -113,5 +113,6 @@ | ||
| 113 | 113 | * @property {string} chat - name of the current chat file chat |
| 114 | 114 | * @property {string} avatar - file name of the avatar image (acts as a unique identifier) |
| 115 | 115 | * @property {string} json_data - the full raw JSON data of the character |
| 116 | + * @property {boolean?} shallow - if the data is shallow (lazy-loaded) | |
| 116 | 117 | */ |
| 117 | 118 | export default 0;// now this file is a module |
| @@ -72,6 +72,7 @@ import { | ||
| 72 | 72 | animation_duration, |
| 73 | 73 | depth_prompt_role_default, |
| 74 | 74 | shouldAutoContinue, |
| 75 | + unshallowCharacter, | |
| 75 | 76 | } from '../script.js'; |
| 76 | 77 | import { printTagList, createTagMapFromList, applyTagsOnCharacterSelect, tag_map, applyTagsOnGroupSelect } from './tags.js'; |
| 77 | 78 | import { FILTER_TYPES, FilterHelper } from './filters.js'; |
| @@ -216,6 +217,7 @@ export async function getGroupChat(groupId, reload = false) { | ||
| 216 | 217 | |
| 217 | 218 | // Run validation before any loading |
| 218 | 219 | validateGroup(group); |
| 220 | + await unshallowGroupMembers(groupId); | |
| 219 | 221 | |
| 220 | 222 | const chat_id = group.chat_id; |
| 221 | 223 | const data = await loadGroupChat(chat_id); |
| @@ -824,6 +826,8 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) { | ||
| 824 | 826 | } |
| 825 | 827 | |
| 826 | 828 | try { |
| 829 | + await unshallowGroupMembers(selected_group); | |
| 830 | + | |
| 827 | 831 | throwIfAborted(); |
| 828 | 832 | hideSwipeButtons(); |
| 829 | 833 | is_group_generating = true; |
| @@ -1137,6 +1141,29 @@ export async function editGroup(id, immediately, reload = true) { | ||
| 1137 | 1141 | saveGroupDebounced(group, reload); |
| 1138 | 1142 | } |
| 1139 | 1143 | |
| 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 | + | |
| 1140 | 1167 | let groupAutoModeAbortController = null; |
| 1141 | 1168 | |
| 1142 | 1169 | async function groupChatAutoModeWorker() { |
| @@ -1158,9 +1185,9 @@ async function groupChatAutoModeWorker() { | ||
| 1158 | 1185 | await generateGroupWrapper(true, 'auto', { signal: groupAutoModeAbortController.signal }); |
| 1159 | 1186 | } |
| 1160 | 1187 | |
| 1161 | 1188 | async function modifyGroupMember(chat_idgroupId, groupMember, isDelete) { |
| 1162 | 1189 | const id = groupMember.data('id'); |
| 1163 | 1190 | const thisGroup = groups.find((x) => x.id == chat_idgroupId); |
| 1164 | 1191 | const membersArray = thisGroup?.members ?? newGroupMembers; |
| 1165 | 1192 | |
| 1166 | 1193 | if (isDelete) { |
| @@ -1173,6 +1200,7 @@ async function modifyGroupMember(chat_id, groupMember, isDelete) { | ||
| 1173 | 1200 | } |
| 1174 | 1201 | |
| 1175 | 1202 | if (openGroupId) { |
| 1203 | + await unshallowGroupMembers(openGroupId); | |
| 1176 | 1204 | await editGroup(openGroupId, false, false); |
| 1177 | 1205 | updateGroupAvatar(thisGroup); |
| 1178 | 1206 | } |
| @@ -1638,7 +1666,7 @@ async function onGroupActionClick(event) { | ||
| 1638 | 1666 | } |
| 1639 | 1667 | |
| 1640 | 1668 | if (action === 'view') { |
| 1641 | 1669 | await openCharacterDefinition(member); |
| 1642 | 1670 | } |
| 1643 | 1671 | |
| 1644 | 1672 | if (action === 'speak') { |
| @@ -1690,7 +1718,7 @@ export async function openGroupById(groupId) { | ||
| 1690 | 1718 | return false; |
| 1691 | 1719 | } |
| 1692 | 1720 | |
| 1693 | 1721 | async function openCharacterDefinition(characterSelect) { |
| 1694 | 1722 | if (is_group_generating) { |
| 1695 | 1723 | toastr.warning(t`Can't peek a character while group reply is being generated`); |
| 1696 | 1724 | console.warn('Can\'t peek a character def while group reply is being generated'); |
| @@ -1703,6 +1731,7 @@ function openCharacterDefinition(characterSelect) { | ||
| 1703 | 1731 | return; |
| 1704 | 1732 | } |
| 1705 | 1733 | |
| 1734 | + await unshallowCharacter(chid); | |
| 1706 | 1735 | setCharacterId(chid); |
| 1707 | 1736 | select_selected_character(chid); |
| 1708 | 1737 | // Gentle nudge to recalculate tokens |
| @@ -47,6 +47,7 @@ import { | ||
| 47 | 47 | updateMessageBlock, |
| 48 | 48 | printMessages, |
| 49 | 49 | clearChat, |
| 50 | + unshallowCharacter, | |
| 50 | 51 | } from '../script.js'; |
| 51 | 52 | import { |
| 52 | 53 | extension_settings, |
| @@ -55,7 +56,7 @@ import { | ||
| 55 | 56 | renderExtensionTemplateAsync, |
| 56 | 57 | writeExtensionField, |
| 57 | 58 | } from './extensions.js'; |
| 58 | 59 | import { groups, openGroupChat, selected_group, unshallowGroupMembers } from './group-chats.js'; |
| 59 | 60 | import { addLocaleData, getCurrentLocale, t, translate } from './i18n.js'; |
| 60 | 61 | import { hideLoader, showLoader } from './loader.js'; |
| 61 | 62 | import { MacrosParser } from './macros.js'; |
| @@ -210,6 +211,8 @@ export function getContext() { | ||
| 210 | 211 | clearChat, |
| 211 | 212 | ChatCompletionService, |
| 212 | 213 | TextCompletionService, |
| 214 | + unshallowCharacter, | |
| 215 | + unshallowGroupMembers, | |
| 213 | 216 | }; |
| 214 | 217 | } |
| 215 | 218 | |
| @@ -23,12 +23,13 @@ import { invalidateThumbnail } from './thumbnails.js'; | ||
| 23 | 23 | import { importRisuSprites } from './sprites.js'; |
| 24 | 24 | const defaultAvatarPath = './public/img/ai4.png'; |
| 25 | 25 | |
| 26 | -// KV-store for parsed character data | |
| 27 | -const cacheCapacity = Number(getConfigValue('cardsCacheCapacity', 100, 'number')); // MB | |
| 28 | 26 | // With 100 MB limit it would take roughly 3000 characters to reach this limit |
| 29 | -const characterDataCache = new MemoryLimitedMap(1024 * 1024 * cacheCapacity); | |
| 27 | +const memoryCacheCapacity = getConfigValue('performance.memoryCacheCapacity', '100mb'); | |
| 28 | +const memoryCache = new MemoryLimitedMap(memoryCacheCapacity); | |
| 30 | 29 | // Some Android devices require tighter memory management |
| 31 | 30 | const isAndroid = process.platform === 'android'; |
| 31 | +// Use shallow character data for the character list | |
| 32 | +const useShallowCharacters = !!getConfigValue('performance.lazyLoadCharacters', false, 'boolean'); | |
| 32 | 33 | |
| 33 | 34 | /** |
| 34 | 35 | * Reads the character card from the specified image file. |
| @@ -39,12 +40,12 @@ const isAndroid = process.platform === 'android'; | ||
| 39 | 40 | async function readCharacterData(inputFile, inputFormat = 'png') { |
| 40 | 41 | const stat = fs.statSync(inputFile); |
| 41 | 42 | const cacheKey = `${inputFile}-${stat.mtimeMs}`; |
| 42 | 43 | if (characterDataCachememoryCache.has(cacheKey)) { |
| 43 | 44 | return characterDataCachememoryCache.get(cacheKey); |
| 44 | 45 | } |
| 45 | 46 | |
| 46 | 47 | const result = parse(inputFile, inputFormat); |
| 47 | 48 | !isAndroid && characterDataCachememoryCache.set(cacheKey, result); |
| 48 | 49 | return result; |
| 49 | 50 | } |
| 50 | 51 | |
| @@ -60,12 +61,12 @@ async function readCharacterData(inputFile, inputFormat = 'png') { | ||
| 60 | 61 | async function writeCharacterData(inputFile, data, outputFile, request, crop = undefined) { |
| 61 | 62 | try { |
| 62 | 63 | // Reset the cache |
| 63 | 64 | for (const key of characterDataCachememoryCache.keys()) { |
| 64 | 65 | if (Buffer.isBuffer(inputFile)) { |
| 65 | 66 | break; |
| 66 | 67 | } |
| 67 | 68 | if (key.startsWith(inputFile)) { |
| 68 | 69 | characterDataCachememoryCache.delete(key); |
| 69 | 70 | break; |
| 70 | 71 | } |
| 71 | 72 | } |
| @@ -201,13 +202,44 @@ const calculateDataSize = (data) => { | ||
| 201 | 202 | }; |
| 202 | 203 | |
| 203 | 204 | /** |
| 205 | + * Only get fields that are used to display the character list. | |
| 206 | + * @param {object} character Character object | |
| 207 | + * @returns {{shallow: true, [key: string]: any}} Shallow character | |
| 208 | + */ | |
| 209 | +const toShallow = (character) => { | |
| 210 | + return { | |
| 211 | + shallow: true, | |
| 212 | + name: character.name, | |
| 213 | + avatar: character.avatar, | |
| 214 | + chat: character.chat, | |
| 215 | + fav: character.fav, | |
| 216 | + date_added: character.date_added, | |
| 217 | + create_date: character.create_date, | |
| 218 | + date_last_chat: character.date_last_chat, | |
| 219 | + chat_size: character.chat_size, | |
| 220 | + data_size: character.data_size, | |
| 221 | + data: { | |
| 222 | + name: _.get(character, 'data.name', ''), | |
| 223 | + character_version: _.get(character, 'data.character_version', ''), | |
| 224 | + creator: _.get(character, 'data.creator', ''), | |
| 225 | + creator_notes: _.get(character, 'data.creator_notes', ''), | |
| 226 | + extensions: { | |
| 227 | + fav: _.get(character, 'data.extensions.fav', false), | |
| 228 | + }, | |
| 229 | + }, | |
| 230 | + }; | |
| 231 | +}; | |
| 232 | + | |
| 233 | +/** | |
| 204 | 234 | * processCharacter - Process a given character, read its data and calculate its statistics. |
| 205 | 235 | * |
| 206 | 236 | * @param {string} item The name of the character. |
| 207 | 237 | * @param {import('../users.js').UserDirectoryList} directories User directories |
| 238 | + * @param {object} options Options for the character processing | |
| 239 | + * @param {boolean} options.shallow If true, only return the core character's metadata | |
| 208 | 240 | * @return {Promise<object>} A Promise that resolves when the character processing is done. |
| 209 | 241 | */ |
| 210 | 242 | const processCharacter = async (item, directories, { shallow }) => { |
| 211 | 243 | try { |
| 212 | 244 | const imgFile = path.join(directories.characters, item); |
| 213 | 245 | const imgData = await readCharacterData(imgFile); |
| @@ -226,7 +258,7 @@ const processCharacter = async (item, directories) => { | ||
| 226 | 258 | character['chat_size'] = chatSize; |
| 227 | 259 | character['date_last_chat'] = dateLastChat; |
| 228 | 260 | character['data_size'] = calculateDataSize(jsonObject?.data); |
| 229 | - return character; | |
| 261 | + return shallow ? toShallow(character) : character; | |
| 230 | 262 | } |
| 231 | 263 | catch (err) { |
| 232 | 264 | console.error(`Could not process character: ${item}`); |
| @@ -993,7 +1025,7 @@ router.post('/all', jsonParser, async function (request, response) { | ||
| 993 | 1025 | try { |
| 994 | 1026 | const files = fs.readdirSync(request.user.directories.characters); |
| 995 | 1027 | const pngFiles = files.filter(file => file.endsWith('.png')); |
| 996 | 1028 | const processingPromises = pngFiles.map(file => processCharacter(file, request.user.directories, { shallow: useShallowCharacters })); |
| 997 | 1029 | const data = (await Promise.all(processingPromises)).filter(c => c.name); |
| 998 | 1030 | return response.send(data); |
| 999 | 1031 | } catch (err) { |
| @@ -1012,7 +1044,7 @@ router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (req | ||
| 1012 | 1044 | return response.sendStatus(404); |
| 1013 | 1045 | } |
| 1014 | 1046 | |
| 1015 | 1047 | const data = await processCharacter(item, request.user.directories, { shallow: false }); |
| 1016 | 1048 | |
| 1017 | 1049 | return response.send(data); |
| 1018 | 1050 | } catch (err) { |
| @@ -1022,11 +1054,11 @@ router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (req | ||
| 1022 | 1054 | }); |
| 1023 | 1055 | |
| 1024 | 1056 | router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (request, response) { |
| 1025 | - if (!request.body) return response.sendStatus(400); | |
| 1057 | + try { | |
| 1058 | + if (!request.body) return response.sendStatus(400); | |
| 1026 | 1059 | |
| 1027 | 1060 | const characterDirectory = (request.body.avatar_url).replace('.png', ''); |
| 1028 | 1061 | |
| 1029 | - try { | |
| 1030 | 1062 | const chatsDirectory = path.join(request.user.directories.chats, characterDirectory); |
| 1031 | 1063 | |
| 1032 | 1064 | if (!fs.existsSync(chatsDirectory)) { |
| @@ -16,6 +16,7 @@ import mime from 'mime-types'; | ||
| 16 | 16 | import { default as simpleGit } from 'simple-git'; |
| 17 | 17 | import chalk from 'chalk'; |
| 18 | 18 | import { LOG_LEVELS } from './constants.js'; |
| 19 | +import bytes from 'bytes'; | |
| 19 | 20 | |
| 20 | 21 | /** |
| 21 | 22 | * Parsed config object. |
| @@ -856,14 +857,10 @@ export function setupLogLevel() { | ||
| 856 | 857 | export class MemoryLimitedMap { |
| 857 | 858 | /** |
| 858 | 859 | * Creates an instance of MemoryLimitedMap. |
| 859 | 860 | * @param {numberstring} maxMemoryInBytescacheCapacity - The maximum allowedMaximum memory usage in byteshuman-readable forformat string(e.g., values'1 GB'). |
| 860 | 861 | */ |
| 861 | 862 | constructor(maxMemoryInBytescacheCapacity) { |
| 862 | - if (typeof maxMemoryInBytes !== 'number' || maxMemoryInBytes <= 0 || isNaN(maxMemoryInBytes)) { | |
| 863 | + this.maxMemory = bytes.parse(cacheCapacity) ?? 0; | |
| 863 | - console.warn('Invalid maxMemoryInBytes, using a fallback value of 1 GB.'); | |
| 864 | - maxMemoryInBytes = 1024 * 1024 * 1024; // 1 GB | |
| 865 | - } | |
| 866 | - this.maxMemory = maxMemoryInBytes; | |
| 867 | 864 | this.currentMemory = 0; |
| 868 | 865 | this.map = new Map(); |
| 869 | 866 | this.queue = []; |
| @@ -886,6 +883,10 @@ export class MemoryLimitedMap { | ||
| 886 | 883 | * @param {string} value |
| 887 | 884 | */ |
| 888 | 885 | set(key, value) { |
| 886 | + if (this.maxMemory <= 0) { | |
| 887 | + return; | |
| 888 | + } | |
| 889 | + | |
| 889 | 890 | if (typeof key !== 'string' || typeof value !== 'string') { |
| 890 | 891 | return; |
| 891 | 892 | } |