Merge branch 'staging' into woo-yeah

9a2968d1eb10e1fd6d05e79551ba3258cfadbf4f

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

13 files changed, +138 -53Showing whitespace changes
default/config.yaml+15 -13
@@ -133,24 +133,26 @@ whitelistImportDomains:
133## headers:133## headers:
134## User-Agent: "Googlebot/2.1 (+http://www.google.com/bot.html)"134## User-Agent: "Googlebot/2.1 (+http://www.google.com/bot.html)"
135requestOverrides: []135requestOverrides: []
136# -- EXTENSIONS CONFIGURATION --136
137# EXTENSIONS CONFIGURATION
138extensions:
137 # Enable UI extensions139 # Enable UI extensions
138enableExtensions: true140 enabled: true
139 # Automatically update extensions when a release version changes141 # Automatically update extensions when a release version changes
140enableExtensionsAutoUpdate: true142 autoUpdate: true
143 models:
144 # Enables automatic model download from HuggingFace
145 autoDownload: true
146 # Additional models for extensions. Expects model IDs from HuggingFace model hub in ONNX format
147 classification: Cohee/distilbert-base-uncased-go-emotions-onnx
148 captioning: Xenova/vit-gpt2-image-captioning
149 embedding: Cohee/jina-embeddings-v2-base-en
150 speechToText: Xenova/whisper-small
151 textToSpeech: Xenova/speecht5_tts
152
141# Additional model tokenizers can be downloaded on demand.153# Additional model tokenizers can be downloaded on demand.
142# Disabling will fallback to another locally available tokenizer.154# Disabling will fallback to another locally available tokenizer.
143enableDownloadableTokenizers: true155enableDownloadableTokenizers: true
144# Extension settings
145extras:
146 # Disables automatic model download from HuggingFace
147 disableAutoDownload: false
148 # Extra models for plugins. Expects model IDs from HuggingFace model hub in ONNX format
149 classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx
150 captioningModel: Xenova/vit-gpt2-image-captioning
151 embeddingModel: Cohee/jina-embeddings-v2-base-en
152 speechToTextModel: Xenova/whisper-small
153 textToSpeechModel: Xenova/speecht5_tts
154# -- OPENAI CONFIGURATION --156# -- OPENAI CONFIGURATION --
155# A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message157# A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message
156promptPlaceholder: "[Start a new chat]"158promptPlaceholder: "[Start a new chat]"
jsconfig.json+1 -1
@@ -15,7 +15,7 @@
15 "**/node_modules/**",15 "**/node_modules/**",
16 "**/dist/**",16 "**/dist/**",
17 "**/.git/**",17 "**/.git/**",
18 "public/lib/**",18 "public/**",
19 "backups/**",19 "backups/**",
20 "data/**",20 "data/**",
21 "cache/**",21 "cache/**",
post-install.js+40 -0
@@ -64,6 +64,46 @@ const keyMigrationMap = [
64 newKey: 'backups.chat.throttleInterval',64 newKey: 'backups.chat.throttleInterval',
65 migrate: (value) => value,65 migrate: (value) => value,
66 },66 },
67 {
68 oldKey: 'enableExtensions',
69 newKey: 'extensions.enabled',
70 migrate: (value) => value,
71 },
72 {
73 oldKey: 'enableExtensionsAutoUpdate',
74 newKey: 'extensions.autoUpdate',
75 migrate: (value) => value,
76 },
77 {
78 oldKey: 'extras.disableAutoDownload',
79 newKey: 'extensions.models.autoDownload',
80 migrate: (value) => !value,
81 },
82 {
83 oldKey: 'extras.classificationModel',
84 newKey: 'extensions.models.classification',
85 migrate: (value) => value,
86 },
87 {
88 oldKey: 'extras.captioningModel',
89 newKey: 'extensions.models.captioning',
90 migrate: (value) => value,
91 },
92 {
93 oldKey: 'extras.embeddingModel',
94 newKey: 'extensions.models.embedding',
95 migrate: (value) => value,
96 },
97 {
98 oldKey: 'extras.speechToTextModel',
99 newKey: 'extensions.models.speechToText',
100 migrate: (value) => value,
101 },
102 {
103 oldKey: 'extras.textToSpeechModel',
104 newKey: 'extensions.models.textToSpeech',
105 migrate: (value) => value,
106 },
67];107];
68108
69/**109/**
public/locales/zh-cn.json+8 -8
@@ -1191,9 +1191,9 @@
1191 "welcome_message_part_8": "您可随时通过",1191 "welcome_message_part_8": "您可随时通过",
1192 "welcome_message_part_9": "图标来更改此设置。",1192 "welcome_message_part_9": "图标来更改此设置。",
1193 "Persona Name:": "用户角色名称:",1193 "Persona Name:": "用户角色名称:",
1194 "Temporarily disable automatic replies from this character": "暂时禁用此角色的自动回复",1194 "Temporarily disable automatic replies from this character": "临时禁言此角色",
1195 "Enable automatic replies from this character": "启用此角色的自动回复",1195 "Enable automatic replies from this character": "解除禁言此角色",
1196 "Trigger a message from this character": "从此角色触发消息",1196 "Trigger a message from this character": "强制触发该角色发言",
1197 "Move up": "向上移动",1197 "Move up": "向上移动",
1198 "Move down": "向下移动",1198 "Move down": "向下移动",
1199 "View character card": "查看角色卡片",1199 "View character card": "查看角色卡片",
@@ -1838,7 +1838,7 @@
1838 "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装",1838 "Enter the Git URL of the extension to install": "输入扩展程序的 Git URL 以安装",
1839 "Disclaimer:": "免责声明:",1839 "Disclaimer:": "免责声明:",
1840 "Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.": "使用外部的扩展程序可能存在意料外的副作用和安全隐患。在导入扩展程序前,请一定确认其来源可信。我们不为第三方扩展程序造成的任何损失负责。",1840 "Please be aware that using external extensions can have unintended side effects and may pose security risks. Always make sure you trust the source before importing an extension. We are not responsible for any damage caused by third-party extensions.": "使用外部的扩展程序可能存在意料外的副作用和安全隐患。在导入扩展程序前,请一定确认其来源可信。我们不为第三方扩展程序造成的任何损失负责。",
1841 "Prompt Itemization": "将提示词分条",1841 "Prompt Itemization": "提示词拆分",
1842 "Show Raw Prompt": "显示原始提示词",1842 "Show Raw Prompt": "显示原始提示词",
1843 "Copy Prompt": "复制提示词",1843 "Copy Prompt": "复制提示词",
1844 "Show Prompt Differences": "显示提示词差异",1844 "Show Prompt Differences": "显示提示词差异",
@@ -2045,8 +2045,8 @@
2045 "Post a GitHub issue": "在 GitHub 发布问题",2045 "Post a GitHub issue": "在 GitHub 发布问题",
2046 "Contact the developers": "联系开发者",2046 "Contact the developers": "联系开发者",
2047 "If you're connected to an API, try asking me something!": "若您已经配置好API,尝试发送些什么吧!",2047 "If you're connected to an API, try asking me something!": "若您已经配置好API,尝试发送些什么吧!",
2048 "Title/Memo": "标题/备忘录",2048 "Title/Memo": "标题(备忘)",
2049 "Strategy": "Strategy",2049 "Strategy": "触发策略",
2050 "Position": "位置",2050 "Position": "插入位置",
2051 "Trigger %": "触发率 %"2051 "Trigger %": "触发概率%"
2052}2052}
public/scripts/st-context.js+6 -2
@@ -12,6 +12,7 @@ import {
12 extension_prompts,12 extension_prompts,
13 Generate,13 Generate,
14 generateQuietPrompt,14 generateQuietPrompt,
15 getCharacters,
15 getCurrentChatId,16 getCurrentChatId,
16 getRequestHeaders,17 getRequestHeaders,
17 getThumbnailUrl,18 getThumbnailUrl,
@@ -55,7 +56,7 @@ import { MacrosParser } from './macros.js';
55import { oai_settings } from './openai.js';56import { oai_settings } from './openai.js';
56import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';57import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
57import { power_user, registerDebugFunction } from './power-user.js';58import { power_user, registerDebugFunction } from './power-user.js';
58import { isMobile, shouldSendOnEnter } from './RossAscends-mods.js';59import { humanizedDateTime, isMobile, shouldSendOnEnter } from './RossAscends-mods.js';
59import { ScraperManager } from './scrapers.js';60import { ScraperManager } from './scrapers.js';
60import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';61import { executeSlashCommands, executeSlashCommandsWithOptions, registerSlashCommand } from './slash-commands.js';
61import { SlashCommand } from './slash-commands/SlashCommand.js';62import { SlashCommand } from './slash-commands/SlashCommand.js';
@@ -65,7 +66,7 @@ import { tag_map, tags } from './tags.js';
65import { textgenerationwebui_settings } from './textgen-settings.js';66import { textgenerationwebui_settings } from './textgen-settings.js';
66import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';67import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
67import { ToolManager } from './tool-calling.js';68import { ToolManager } from './tool-calling.js';
68import { timestampToMoment } from './utils.js';69import { timestampToMoment, uuidv4 } from './utils.js';
6970
70export function getContext() {71export function getContext() {
71 return {72 return {
@@ -167,6 +168,9 @@ export function getContext() {
167 chatCompletionSettings: oai_settings,168 chatCompletionSettings: oai_settings,
168 textCompletionSettings: textgenerationwebui_settings,169 textCompletionSettings: textgenerationwebui_settings,
169 powerUserSettings: power_user,170 powerUserSettings: power_user,
171 getCharacters,
172 uuidv4,
173 humanizedDateTime,
170 };174 };
171}175}
172176
src/endpoints/avatars.js+2 -1
@@ -9,6 +9,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic';
9import { jsonParser, urlencodedParser } from '../express-common.js';9import { jsonParser, urlencodedParser } from '../express-common.js';
10import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';10import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
11import { getImages, tryParse } from '../util.js';11import { getImages, tryParse } from '../util.js';
12import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
1213
13export const router = express.Router();14export const router = express.Router();
1415
@@ -17,7 +18,7 @@ router.post('/get', jsonParser, function (request, response) {
17 response.send(JSON.stringify(images));18 response.send(JSON.stringify(images));
18});19});
1920
20router.post('/delete', jsonParser, function (request, response) {21router.post('/delete', jsonParser, getFileNameValidationFunction('avatar'), function (request, response) {
21 if (!request.body) return response.sendStatus(400);22 if (!request.body) return response.sendStatus(400);
2223
23 if (request.body.avatar !== sanitize(request.body.avatar)) {24 if (request.body.avatar !== sanitize(request.body.avatar)) {
src/endpoints/backgrounds.js+2 -1
@@ -7,6 +7,7 @@ import sanitize from 'sanitize-filename';
7import { jsonParser, urlencodedParser } from '../express-common.js';7import { jsonParser, urlencodedParser } from '../express-common.js';
8import { invalidateThumbnail } from './thumbnails.js';8import { invalidateThumbnail } from './thumbnails.js';
9import { getImages } from '../util.js';9import { getImages } from '../util.js';
10import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
1011
11export const router = express.Router();12export const router = express.Router();
1213
@@ -15,7 +16,7 @@ router.post('/all', jsonParser, function (request, response) {
15 response.send(JSON.stringify(images));16 response.send(JSON.stringify(images));
16});17});
1718
18router.post('/delete', jsonParser, function (request, response) {19router.post('/delete', jsonParser, getFileNameValidationFunction('bg'), function (request, response) {
19 if (!request.body) return response.sendStatus(400);20 if (!request.body) return response.sendStatus(400);
2021
21 if (request.body.bg !== sanitize(request.body.bg)) {22 if (request.body.bg !== sanitize(request.body.bg)) {
src/endpoints/characters.js+10 -9
@@ -14,6 +14,7 @@ import jimp from 'jimp';
1414
15import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';15import { AVATAR_WIDTH, AVATAR_HEIGHT } from '../constants.js';
16import { jsonParser, urlencodedParser } from '../express-common.js';16import { jsonParser, urlencodedParser } from '../express-common.js';
17import { default as validateAvatarUrlMiddleware, getFileNameValidationFunction } from '../middleware/validateFileName.js';
17import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js';18import { deepMerge, humanizedISO8601DateTime, tryParse, extractFileFromZipBuffer, MemoryLimitedMap, getConfigValue } from '../util.js';
18import { TavernCardValidator } from '../validator/TavernCardValidator.js';19import { TavernCardValidator } from '../validator/TavernCardValidator.js';
19import { parse, write } from '../character-card-parser.js';20import { parse, write } from '../character-card-parser.js';
@@ -756,7 +757,7 @@ router.post('/create', urlencodedParser, async function (request, response) {
756 }757 }
757});758});
758759
759router.post('/rename', jsonParser, async function (request, response) {760router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
760 if (!request.body.avatar_url || !request.body.new_name) {761 if (!request.body.avatar_url || !request.body.new_name) {
761 return response.sendStatus(400);762 return response.sendStatus(400);
762 }763 }
@@ -803,7 +804,7 @@ router.post('/rename', jsonParser, async function (request, response) {
803 }804 }
804});805});
805806
806router.post('/edit', urlencodedParser, async function (request, response) {807router.post('/edit', urlencodedParser, validateAvatarUrlMiddleware, async function (request, response) {
807 if (!request.body) {808 if (!request.body) {
808 console.error('Error: no response body detected');809 console.error('Error: no response body detected');
809 response.status(400).send('Error: no response body detected');810 response.status(400).send('Error: no response body detected');
@@ -852,7 +853,7 @@ router.post('/edit', urlencodedParser, async function (request, response) {
852 * @param {Object} response - The HTTP response object.853 * @param {Object} response - The HTTP response object.
853 * @returns {void}854 * @returns {void}
854 */855 */
855router.post('/edit-attribute', jsonParser, async function (request, response) {856router.post('/edit-attribute', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
856 console.log(request.body);857 console.log(request.body);
857 if (!request.body) {858 if (!request.body) {
858 console.error('Error: no response body detected');859 console.error('Error: no response body detected');
@@ -898,7 +899,7 @@ router.post('/edit-attribute', jsonParser, async function (request, response) {
898 *899 *
899 * @returns {void}900 * @returns {void}
900 * */901 * */
901router.post('/merge-attributes', jsonParser, async function (request, response) {902router.post('/merge-attributes', jsonParser, getFileNameValidationFunction('avatar'), async function (request, response) {
902 try {903 try {
903 const update = request.body;904 const update = request.body;
904 const avatarPath = path.join(request.user.directories.characters, update.avatar);905 const avatarPath = path.join(request.user.directories.characters, update.avatar);
@@ -929,7 +930,7 @@ router.post('/merge-attributes', jsonParser, async function (request, response)
929 }930 }
930});931});
931932
932router.post('/delete', jsonParser, async function (request, response) {933router.post('/delete', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
933 if (!request.body || !request.body.avatar_url) {934 if (!request.body || !request.body.avatar_url) {
934 return response.sendStatus(400);935 return response.sendStatus(400);
935 }936 }
@@ -992,7 +993,7 @@ router.post('/all', jsonParser, async function (request, response) {
992 }993 }
993});994});
994995
995router.post('/get', jsonParser, async function (request, response) {996router.post('/get', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
996 try {997 try {
997 if (!request.body) return response.sendStatus(400);998 if (!request.body) return response.sendStatus(400);
998 const item = request.body.avatar_url;999 const item = request.body.avatar_url;
@@ -1011,7 +1012,7 @@ router.post('/get', jsonParser, async function (request, response) {
1011 }1012 }
1012});1013});
10131014
1014router.post('/chats', jsonParser, async function (request, response) {1015router.post('/chats', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
1015 if (!request.body) return response.sendStatus(400);1016 if (!request.body) return response.sendStatus(400);
10161017
1017 const characterDirectory = (request.body.avatar_url).replace('.png', '');1018 const characterDirectory = (request.body.avatar_url).replace('.png', '');
@@ -1160,7 +1161,7 @@ router.post('/import', urlencodedParser, async function (request, response) {
1160 }1161 }
1161});1162});
11621163
1163router.post('/duplicate', jsonParser, async function (request, response) {1164router.post('/duplicate', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
1164 try {1165 try {
1165 if (!request.body.avatar_url) {1166 if (!request.body.avatar_url) {
1166 console.log('avatar URL not found in request body');1167 console.log('avatar URL not found in request body');
@@ -1207,7 +1208,7 @@ router.post('/duplicate', jsonParser, async function (request, response) {
1207 }1208 }
1208});1209});
12091210
1210router.post('/export', jsonParser, async function (request, response) {1211router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
1211 try {1212 try {
1212 if (!request.body.format || !request.body.avatar_url) {1213 if (!request.body.format || !request.body.avatar_url) {
1213 return response.sendStatus(400);1214 return response.sendStatus(400);
src/endpoints/chats.js+8 -7
@@ -9,6 +9,7 @@ import { sync as writeFileAtomicSync } from 'write-file-atomic';
9import _ from 'lodash';9import _ from 'lodash';
1010
11import { jsonParser, urlencodedParser } from '../express-common.js';11import { jsonParser, urlencodedParser } from '../express-common.js';
12import validateAvatarUrlMiddleware from '../middleware/validateFileName.js';
12import {13import {
13 getConfigValue,14 getConfigValue,
14 humanizedISO8601DateTime,15 humanizedISO8601DateTime,
@@ -294,7 +295,7 @@ function importRisuChat(userName, characterName, jsonData) {
294295
295export const router = express.Router();296export const router = express.Router();
296297
297router.post('/save', jsonParser, function (request, response) {298router.post('/save', jsonParser, validateAvatarUrlMiddleware, function (request, response) {
298 try {299 try {
299 const directoryName = String(request.body.avatar_url).replace('.png', '');300 const directoryName = String(request.body.avatar_url).replace('.png', '');
300 const chatData = request.body.chat;301 const chatData = request.body.chat;
@@ -310,7 +311,7 @@ router.post('/save', jsonParser, function (request, response) {
310 }311 }
311});312});
312313
313router.post('/get', jsonParser, function (request, response) {314router.post('/get', jsonParser, validateAvatarUrlMiddleware, function (request, response) {
314 try {315 try {
315 const dirName = String(request.body.avatar_url).replace('.png', '');316 const dirName = String(request.body.avatar_url).replace('.png', '');
316 const directoryPath = path.join(request.user.directories.chats, dirName);317 const directoryPath = path.join(request.user.directories.chats, dirName);
@@ -347,7 +348,7 @@ router.post('/get', jsonParser, function (request, response) {
347});348});
348349
349350
350router.post('/rename', jsonParser, async function (request, response) {351router.post('/rename', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
351 if (!request.body || !request.body.original_file || !request.body.renamed_file) {352 if (!request.body || !request.body.original_file || !request.body.renamed_file) {
352 return response.sendStatus(400);353 return response.sendStatus(400);
353 }354 }
@@ -372,7 +373,7 @@ router.post('/rename', jsonParser, async function (request, response) {
372 return response.send({ ok: true, sanitizedFileName });373 return response.send({ ok: true, sanitizedFileName });
373});374});
374375
375router.post('/delete', jsonParser, function (request, response) {376router.post('/delete', jsonParser, validateAvatarUrlMiddleware, function (request, response) {
376 const dirName = String(request.body.avatar_url).replace('.png', '');377 const dirName = String(request.body.avatar_url).replace('.png', '');
377 const fileName = String(request.body.chatfile);378 const fileName = String(request.body.chatfile);
378 const filePath = path.join(request.user.directories.chats, dirName, sanitize(fileName));379 const filePath = path.join(request.user.directories.chats, dirName, sanitize(fileName));
@@ -388,7 +389,7 @@ router.post('/delete', jsonParser, function (request, response) {
388 return response.send('ok');389 return response.send('ok');
389});390});
390391
391router.post('/export', jsonParser, async function (request, response) {392router.post('/export', jsonParser, validateAvatarUrlMiddleware, async function (request, response) {
392 if (!request.body.file || (!request.body.avatar_url && request.body.is_group === false)) {393 if (!request.body.file || (!request.body.avatar_url && request.body.is_group === false)) {
393 return response.sendStatus(400);394 return response.sendStatus(400);
394 }395 }
@@ -478,7 +479,7 @@ router.post('/group/import', urlencodedParser, function (request, response) {
478 }479 }
479});480});
480481
481router.post('/import', urlencodedParser, function (request, response) {482router.post('/import', urlencodedParser, validateAvatarUrlMiddleware, function (request, response) {
482 if (!request.body) return response.sendStatus(400);483 if (!request.body) return response.sendStatus(400);
483484
484 const format = request.body.file_type;485 const format = request.body.file_type;
@@ -626,7 +627,7 @@ router.post('/group/save', jsonParser, (request, response) => {
626 return response.send({ ok: true });627 return response.send({ ok: true });
627});628});
628629
629router.post('/search', jsonParser, function (request, response) {630router.post('/search', jsonParser, validateAvatarUrlMiddleware, function (request, response) {
630 try {631 try {
631 const { query, avatar_url, group_id } = request.body;632 const { query, avatar_url, group_id } = request.body;
632 let chatFiles = [];633 let chatFiles = [];
src/endpoints/settings.js+5 -4
@@ -9,9 +9,10 @@ import { SETTINGS_FILE } from '../constants.js';
9import { getConfigValue, generateTimestamp, removeOldBackups } from '../util.js';9import { getConfigValue, generateTimestamp, removeOldBackups } from '../util.js';
10import { jsonParser } from '../express-common.js';10import { jsonParser } from '../express-common.js';
11import { getAllUserHandles, getUserDirectories } from '../users.js';11import { getAllUserHandles, getUserDirectories } from '../users.js';
12import { getFileNameValidationFunction } from '../middleware/validateFileName.js';
1213
13const ENABLE_EXTENSIONS = getConfigValue('enableExtensions', true);14const ENABLE_EXTENSIONS = !!getConfigValue('extensions.enabled', true);
14const ENABLE_EXTENSIONS_AUTO_UPDATE = getConfigValue('enableExtensionsAutoUpdate', true);15const ENABLE_EXTENSIONS_AUTO_UPDATE = !!getConfigValue('extensions.autoUpdate', true);
15const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);16const ENABLE_ACCOUNTS = getConfigValue('enableUserAccounts', false);
1617
17// 10 minutes18// 10 minutes
@@ -296,7 +297,7 @@ router.post('/get-snapshots', jsonParser, async (request, response) => {
296 }297 }
297});298});
298299
299router.post('/load-snapshot', jsonParser, async (request, response) => {300router.post('/load-snapshot', jsonParser, getFileNameValidationFunction('name'), async (request, response) => {
300 try {301 try {
301 const userFilesPattern = getFilePrefix(request.user.profile.handle);302 const userFilesPattern = getFilePrefix(request.user.profile.handle);
302303
@@ -330,7 +331,7 @@ router.post('/make-snapshot', jsonParser, async (request, response) => {
330 }331 }
331});332});
332333
333router.post('/restore-snapshot', jsonParser, async (request, response) => {334router.post('/restore-snapshot', jsonParser, getFileNameValidationFunction('name'), async (request, response) => {
334 try {335 try {
335 const userFilesPattern = getFilePrefix(request.user.profile.handle);336 const userFilesPattern = getFilePrefix(request.user.profile.handle);
336337
src/endpoints/vectors.js+1 -1
@@ -164,7 +164,7 @@ function getSourceSettings(source, request) {
164 };164 };
165 case 'transformers':165 case 'transformers':
166 return {166 return {
167 model: getConfigValue('extras.embeddingModel', ''),167 model: getConfigValue('extensions.models.embedding', ''),
168 };168 };
169 case 'palm':169 case 'palm':
170 return {170 return {
src/middleware/validateFileName.js+34 -0
@@ -0,0 +1,34 @@
1import path from 'node:path';
2
3/**
4 * Gets a middleware function that validates the field in the request body.
5 * @param {string} fieldName Field name
6 * @returns {import('express').RequestHandler} Middleware function
7 */
8export function getFileNameValidationFunction(fieldName) {
9 /**
10 * Validates the field in the request body.
11 * @param {import('express').Request} req Request object
12 * @param {import('express').Response} res Response object
13 * @param {import('express').NextFunction} next Next middleware
14 */
15 return function validateAvatarUrlMiddleware(req, res, next) {
16 if (req.body && fieldName in req.body && typeof req.body[fieldName] === 'string') {
17 const forbiddenRegExp = path.sep === '/' ? /[/\x00]/ : /[/\x00\\]/;
18 if (forbiddenRegExp.test(req.body[fieldName])) {
19 console.error('An error occurred while validating the request body', {
20 handle: req.user.profile.handle,
21 path: req.originalUrl,
22 field: fieldName,
23 value: req.body[fieldName],
24 });
25 return res.sendStatus(400);
26 }
27 }
28
29 next();
30 };
31}
32
33const avatarUrlValidationFunction = getFileNameValidationFunction('avatar_url');
34export default avatarUrlValidationFunction;
src/transformers.js+6 -6
@@ -19,31 +19,31 @@ const tasks = {
19 'text-classification': {19 'text-classification': {
20 defaultModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx',20 defaultModel: 'Cohee/distilbert-base-uncased-go-emotions-onnx',
21 pipeline: null,21 pipeline: null,
22 configField: 'extras.classificationModel',22 configField: 'extensions.models.classification',
23 quantized: true,23 quantized: true,
24 },24 },
25 'image-to-text': {25 'image-to-text': {
26 defaultModel: 'Xenova/vit-gpt2-image-captioning',26 defaultModel: 'Xenova/vit-gpt2-image-captioning',
27 pipeline: null,27 pipeline: null,
28 configField: 'extras.captioningModel',28 configField: 'extensions.models.captioning',
29 quantized: true,29 quantized: true,
30 },30 },
31 'feature-extraction': {31 'feature-extraction': {
32 defaultModel: 'Xenova/all-mpnet-base-v2',32 defaultModel: 'Xenova/all-mpnet-base-v2',
33 pipeline: null,33 pipeline: null,
34 configField: 'extras.embeddingModel',34 configField: 'extensions.models.embedding',
35 quantized: true,35 quantized: true,
36 },36 },
37 'automatic-speech-recognition': {37 'automatic-speech-recognition': {
38 defaultModel: 'Xenova/whisper-small',38 defaultModel: 'Xenova/whisper-small',
39 pipeline: null,39 pipeline: null,
40 configField: 'extras.speechToTextModel',40 configField: 'extensions.models.speechToText',
41 quantized: true,41 quantized: true,
42 },42 },
43 'text-to-speech': {43 'text-to-speech': {
44 defaultModel: 'Xenova/speecht5_tts',44 defaultModel: 'Xenova/speecht5_tts',
45 pipeline: null,45 pipeline: null,
46 configField: 'extras.textToSpeechModel',46 configField: 'extensions.models.textToSpeech',
47 quantized: false,47 quantized: false,
48 },48 },
49};49};
@@ -132,7 +132,7 @@ export async function getPipeline(task, forceModel = '') {
132132
133 const cacheDir = path.join(globalThis.DATA_ROOT, '_cache');133 const cacheDir = path.join(globalThis.DATA_ROOT, '_cache');
134 const model = forceModel || getModelForTask(task);134 const model = forceModel || getModelForTask(task);
135 const localOnly = getConfigValue('extras.disableAutoDownload', false);135 const localOnly = !getConfigValue('extensions.models.autoDownload', true);
136 console.log('Initializing transformers.js pipeline for task', task, 'with model', model);136 console.log('Initializing transformers.js pipeline for task', task, 'with model', model);
137 const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });137 const instance = await pipeline(task, model, { cache_dir: cacheDir, quantized: tasks[task].quantized ?? true, local_files_only: localOnly });
138 tasks[task].pipeline = instance;138 tasks[task].pipeline = instance;