Merge pull request #2667 from SillyTavern/dataroot-cache Move transformers.js model cache under the data root

fc02898a97897df3e1285989d603ea76f4760a37

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

Signed
5 files changed, +48 -16Ignore whitespace
index.d.ts+5 -0
@@ -9,6 +9,11 @@ declare global {
99 };
1010 }
1111 }
12+
13+ /**
14+ * The root directory for user data.
15+ */
16+ var DATA_ROOT: string;
1217}
1318
1419declare module 'express-session' {
package-lock.json+1 -1
@@ -42,7 +42,7 @@
4242 "rate-limiter-flexible": "^5.0.0",
4343 "response-time": "^2.3.2",
4444 "sanitize-filename": "^1.6.3",
4545 "sillytavern-transformers": "^2.14.6",
4646 "simple-git": "^3.19.1",
4747 "tiktoken": "^1.0.15",
4848 "vectra": "^0.2.2",
package.json+1 -1
@@ -32,7 +32,7 @@
3232 "rate-limiter-flexible": "^5.0.0",
3333 "response-time": "^2.3.2",
3434 "sanitize-filename": "^1.6.3",
3535 "sillytavern-transformers": "^2.14.6",
3636 "simple-git": "^3.19.1",
3737 "tiktoken": "^1.0.15",
3838 "vectra": "^0.2.2",
src/transformers.mjs+36 -3
@@ -1,6 +1,7 @@
11import { pipeline, env, RawImage, Pipeline } from 'sillytavern-transformers';
22import { getConfigValue } from './util.js';
33import path from 'path';
4+import fs from 'fs';
45
56configureTransformers();
67
@@ -48,7 +49,7 @@ const tasks = {
4849 configField: 'extras.textToSpeechModel',
4950 quantized: false,
5051 },
5152};
5253
5354/**
5455 * Gets a RawImage object from a base64-encoded image.
@@ -85,6 +86,36 @@ function getModelForTask(task) {
8586 }
8687}
8788
89+async function migrateCacheToDataDir() {
90+ const oldCacheDir = path.join(process.cwd(), 'cache');
91+ const newCacheDir = path.join(global.DATA_ROOT, '_cache');
92+
93+ if (!fs.existsSync(newCacheDir)) {
94+ fs.mkdirSync(newCacheDir, { recursive: true });
95+ }
96+
97+ if (fs.existsSync(oldCacheDir) && fs.statSync(oldCacheDir).isDirectory()) {
98+ const files = fs.readdirSync(oldCacheDir);
99+
100+ if (files.length === 0) {
101+ return;
102+ }
103+
104+ console.log('Migrating model cache files to data directory. Please wait...');
105+
106+ for (const file of files) {
107+ try {
108+ const oldPath = path.join(oldCacheDir, file);
109+ const newPath = path.join(newCacheDir, file);
110+ fs.cpSync(oldPath, newPath, { recursive: true, force: true });
111+ fs.rmSync(oldPath, { recursive: true, force: true });
112+ } catch (error) {
113+ console.warn('Failed to migrate cache file. The model will be re-downloaded.', error);
114+ }
115+ }
116+ }
117+}
118+
88119/**
89120 * Gets the transformers.js pipeline for a given task.
90121 * @param {import('sillytavern-transformers').PipelineType} task The task to get the pipeline for
@@ -92,6 +123,8 @@ function getModelForTask(task) {
92123 * @returns {Promise<Pipeline>} Pipeline for the task
93124 */
94125async function getPipeline(task, forceModel = '') {
126+ await migrateCacheToDataDir();
127+
95128 if (tasks[task].pipeline) {
96129 if (forceModel === '' || tasks[task].currentModel === forceModel) {
97130 return tasks[task].pipeline;
@@ -100,11 +133,11 @@ async function getPipeline(task, forceModel = '') {
100133 await tasks[task].pipeline.dispose();
101134 }
102135
103136 const cache_dircacheDir = path.join(processglobal.cwd()DATA_ROOT, 'cache_cache');
104137 const model = forceModel || getModelForTask(task);
105138 const localOnly = getConfigValue('extras.disableAutoDownload', false);
106139 console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
107140 const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
108141 tasks[task].pipeline = instance;
109142 tasks[task].currentModel = model;
110143 return instance;
src/users.js+5 -11
@@ -20,12 +20,6 @@ const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
2020const ANON_CSRF_SECRET = crypto.randomBytes(64).toString('base64');
2121
2222/**
23- * The root directory for user data.
24- * @type {string}
25- */
26-let DATA_ROOT = './data';
27-
28-/**
2923 * Cache for user directories.
3024 * @type {Map<string, UserDirectoryList>}
3125 */
@@ -138,7 +132,7 @@ async function migrateUserData() {
138132
139133 console.log();
140134 console.log(color.magenta('Preparing to migrate user data...'));
141135 console.log(`All public data will be moved to the ${global.DATA_ROOT} directory.`);
142136 console.log('This process may take a while depending on the amount of data to move.');
143137 console.log(`Backups will be placed in the ${PUBLIC_DIRECTORIES.backups} directory.`);
144138 console.log(`The process will start in ${TIMEOUT} seconds. Press Ctrl+C to cancel.`);
@@ -352,11 +346,11 @@ function toAvatarKey(handle) {
352346 * @returns {Promise<void>}
353347 */
354348async function initUserStorage(dataRoot) {
355349 global.DATA_ROOT = dataRoot;
356350 console.log('Using data root:', color.green(global.DATA_ROOT));
357351 console.log();
358352 await storage.init({
359353 dir: path.join(global.DATA_ROOT, '_storage'),
360354 ttl: false, // Never expire
361355 });
362356
@@ -457,7 +451,7 @@ function getUserDirectories(handle) {
457451
458452 const directories = structuredClone(USER_DIRECTORY_TEMPLATE);
459453 for (const key in directories) {
460454 directories[key] = path.join(global.DATA_ROOT, handle, USER_DIRECTORY_TEMPLATE[key]);
461455 }
462456 DIRECTORIES_CACHE.set(handle, directories);
463457 return directories;