Merge branch 'staging' into reasoning-parsing-streaming

1156b648eb2713ed904fed45fff79b50cbb6a62d

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

11 files changed, +101 -8Showing whitespace changes
.github/ISSUE_TEMPLATE/bug-report.yml+2 -0
@@ -80,6 +80,8 @@ body:
80 required: true80 required: true
81 - label: I have checked the [docs](https://docs.sillytavern.app/) ![important](https://img.shields.io/badge/Important!-F6094E)81 - label: I have checked the [docs](https://docs.sillytavern.app/) ![important](https://img.shields.io/badge/Important!-F6094E)
82 required: true82 required: true
83 - label: I confirm that my issue is not related to third-party content, unofficial extension or patch. If in doubt, check with a new [user account](https://docs.sillytavern.app/administration/multi-user/) and with extensions disabled
84 required: true
8385
84 - type: markdown86 - type: markdown
85 attributes:87 attributes:
default/config.yaml+2 -0
@@ -210,3 +210,5 @@ claude:
210 cachingAtDepth: -1210 cachingAtDepth: -1
211# -- SERVER PLUGIN CONFIGURATION --211# -- SERVER PLUGIN CONFIGURATION --
212enableServerPlugins: false212enableServerPlugins: false
213# Attempt to automatically update server plugins on startup
214enableServerPluginsAutoUpdate: true
package-lock.json+2 -2
@@ -1,12 +1,12 @@
1{1{
2 "name": "sillytavern",2 "name": "sillytavern",
3 "version": "1.12.11",3 "version": "1.12.12",
4 "lockfileVersion": 3,4 "lockfileVersion": 3,
5 "requires": true,5 "requires": true,
6 "packages": {6 "packages": {
7 "": {7 "": {
8 "name": "sillytavern",8 "name": "sillytavern",
9 "version": "1.12.11",9 "version": "1.12.12",
10 "hasInstallScript": true,10 "hasInstallScript": true,
11 "license": "AGPL-3.0",11 "license": "AGPL-3.0",
12 "dependencies": {12 "dependencies": {
package.json+1 -1
@@ -87,7 +87,7 @@
87 "type": "git",87 "type": "git",
88 "url": "https://github.com/SillyTavern/SillyTavern.git"88 "url": "https://github.com/SillyTavern/SillyTavern.git"
89 },89 },
90 "version": "1.12.11",90 "version": "1.12.12",
91 "scripts": {91 "scripts": {
92 "start": "node server.js",92 "start": "node server.js",
93 "debug": "node server.js --inspect",93 "debug": "node server.js --inspect",
plugins.js+7 -0
@@ -48,6 +48,13 @@ async function updatePlugins() {
48 console.log(`Updating plugin ${color.green(directory)}...`);48 console.log(`Updating plugin ${color.green(directory)}...`);
49 const pluginPath = path.join(pluginsPath, directory);49 const pluginPath = path.join(pluginsPath, directory);
50 const pluginRepo = git(pluginPath);50 const pluginRepo = git(pluginPath);
51
52 const isRepo = await pluginRepo.checkIsRepo();
53 if (!isRepo) {
54 console.log(`Directory ${color.yellow(directory)} is not a Git repository`);
55 continue;
56 }
57
51 await pluginRepo.fetch();58 await pluginRepo.fetch();
52 const commitHash = await pluginRepo.revparse(['HEAD']);59 const commitHash = await pluginRepo.revparse(['HEAD']);
53 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);60 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);
public/script.js+4 -0
@@ -5931,6 +5931,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
5931 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5931 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5932 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5932 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5933 chat[chat.length - 1]['extra']['reasoning'] = reasoning;5933 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
5934 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
5934 if (power_user.message_token_count_enabled) {5935 if (power_user.message_token_count_enabled) {
5935 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];5936 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
5936 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);5937 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -5953,6 +5954,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
5953 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5954 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5954 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5955 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5955 chat[chat.length - 1]['extra']['reasoning'] = reasoning;5956 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
5957 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
5956 if (power_user.message_token_count_enabled) {5958 if (power_user.message_token_count_enabled) {
5957 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];5959 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
5958 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);5960 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -5972,6 +5974,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
5972 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5974 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5973 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5975 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5974 chat[chat.length - 1]['extra']['reasoning'] += reasoning;5976 chat[chat.length - 1]['extra']['reasoning'] += reasoning;
5977 // We don't know if the reasoning duration extended, so we don't update it here on purpose.
5975 if (power_user.message_token_count_enabled) {5978 if (power_user.message_token_count_enabled) {
5976 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];5979 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
5977 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);5980 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
@@ -5991,6 +5994,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
5991 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5994 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5992 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5995 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5993 chat[chat.length - 1]['extra']['reasoning'] = reasoning;5996 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
5997 chat[chat.length - 1]['extra']['reasoning_duration'] = null;
5994 if (power_user.trim_spaces) {5998 if (power_user.trim_spaces) {
5995 getMessage = getMessage.trim();5999 getMessage = getMessage.trim();
5996 }6000 }
public/scripts/extensions/quick-reply/src/ui/SettingsUi.js+1 -1
@@ -346,7 +346,7 @@ export class SettingsUi {
346 }346 }
347347
348 async addQrSet() {348 async addQrSet() {
349 const name = await Popup.show.input('Create a new World Info', 'Enter a name for the new Quick Reply Set:');349 const name = await Popup.show.input('Create a new Quick Reply Set', 'Enter a name for the new Quick Reply Set:');
350 if (name && name.length > 0) {350 if (name && name.length > 0) {
351 const oldQrs = QuickReplySet.get(name);351 const oldQrs = QuickReplySet.get(name);
352 if (oldQrs) {352 if (oldQrs) {
public/scripts/reasoning.js+1 -1
@@ -106,7 +106,7 @@ export function isHiddenReasoningModel() {
106 { name: 'gemini-2.0-pro-exp', func: FUNCS.startsWith },106 { name: 'gemini-2.0-pro-exp', func: FUNCS.startsWith },
107 ];107 ];
108108
109 const model = getChatCompletionModel();109 const model = getChatCompletionModel() || '';
110110
111 const isHidden = hiddenReasoningModels.some(({ name, func }) => func(model, name));111 const isHidden = hiddenReasoningModels.some(({ name, func }) => func(model, name));
112 return isHidden;112 return isHidden;
public/scripts/textgen-models.js+5 -0
@@ -58,9 +58,14 @@ const OPENROUTER_PROVIDERS = [
58 'Minimax',58 'Minimax',
59 'Nineteen',59 'Nineteen',
60 'Liquid',60 'Liquid',
61 'InferenceNet',
62 'Friendli',
63 'AionLabs',
64 'Alibaba',
61 'Nebius',65 'Nebius',
62 'Chutes',66 'Chutes',
63 'Kluster',67 'Kluster',
68 'Targon',
64 '01.AI',69 '01.AI',
65 'HuggingFace',70 'HuggingFace',
66 'Mancer',71 'Mancer',
src/endpoints/stable-diffusion.js+2 -1
@@ -1275,7 +1275,8 @@ falai.post('/generate', jsonParser, async (request, response) => {
1275 num_inference_steps: request.body.steps,1275 num_inference_steps: request.body.steps,
1276 seed: request.body.seed ?? null,1276 seed: request.body.seed ?? null,
1277 guidance_scale: request.body.guidance,1277 guidance_scale: request.body.guidance,
1278 enable_safety_checker: false,1278 enable_safety_checker: false, // Disable general safety checks
1279 safety_tolerance: 6 // Make Flux the least strict
1279 };1280 };
12801281
1281 console.debug('FAL.AI request:', requestBody);1282 console.debug('FAL.AI request:', requestBody);
src/plugin-loader.js+74 -2
@@ -3,8 +3,12 @@ import path from 'node:path';
3import url from 'node:url';3import url from 'node:url';
44
5import express from 'express';5import express from 'express';
6import { getConfigValue } from './util.js';6import { default as git } from 'simple-git';
7const enableServerPlugins = getConfigValue('enableServerPlugins', false);7import { sync as commandExistsSync } from 'command-exists';
8import { getConfigValue, color } from './util.js';
9
10const enableServerPlugins = !!getConfigValue('enableServerPlugins', false);
11const enableServerPluginsAutoUpdate = !!getConfigValue('enableServerPluginsAutoUpdate', true);
812
9/**13/**
10 * Map of loaded plugins.14 * Map of loaded plugins.
@@ -54,6 +58,8 @@ export async function loadPlugins(app, pluginsPath) {
54 return emptyFn;58 return emptyFn;
55 }59 }
5660
61 await updatePlugins(pluginsPath);
62
57 for (const file of files) {63 for (const file of files) {
58 const pluginFilePath = path.join(pluginsPath, file);64 const pluginFilePath = path.join(pluginsPath, file);
5965
@@ -70,6 +76,10 @@ export async function loadPlugins(app, pluginsPath) {
70 await loadFromFile(app, pluginFilePath, exitHooks);76 await loadFromFile(app, pluginFilePath, exitHooks);
71 }77 }
7278
79 if (loadedPlugins.size > 0) {
80 console.log(`${loadedPlugins.size} server plugin(s) are currently loaded. Make sure you know exactly what they do, and only install plugins from trusted sources!`);
81 }
82
73 // Call all plugin "exit" functions at once and wait for them to finish83 // Call all plugin "exit" functions at once and wait for them to finish
74 return () => Promise.all(exitHooks.map(exitFn => exitFn()));84 return () => Promise.all(exitHooks.map(exitFn => exitFn()));
75}85}
@@ -214,3 +224,65 @@ async function initPlugin(app, plugin, exitHooks) {
214224
215 return true;225 return true;
216}226}
227
228/**
229 * Automatically update all git plugins in the ./plugins directory
230 * @param {string} pluginsPath Path to plugins directory
231 */
232async function updatePlugins(pluginsPath) {
233 if (!enableServerPluginsAutoUpdate) {
234 return;
235 }
236
237 const directories = fs.readdirSync(pluginsPath)
238 .filter(file => !file.startsWith('.'))
239 .filter(file => fs.statSync(path.join(pluginsPath, file)).isDirectory());
240
241 if (directories.length === 0) {
242 return;
243 }
244
245 console.log(color.blue('Auto-updating server plugins... Set'), color.yellow('enableServerPluginsAutoUpdate: false'), color.blue('in config.yaml to disable this feature.'));
246
247 if (!commandExistsSync('git')) {
248 console.error(color.red('Git is not installed. Please install Git to enable auto-updating of server plugins.'));
249 return;
250 }
251
252 let pluginsToUpdate = 0;
253
254 for (const directory of directories) {
255 try {
256 const pluginPath = path.join(pluginsPath, directory);
257 const pluginRepo = git(pluginPath);
258
259 const isRepo = await pluginRepo.checkIsRepo();
260 if (!isRepo) {
261 continue;
262 }
263
264 await pluginRepo.fetch();
265 const commitHash = await pluginRepo.revparse(['HEAD']);
266 const trackingBranch = await pluginRepo.revparse(['--abbrev-ref', '@{u}']);
267 const log = await pluginRepo.log({
268 from: commitHash,
269 to: trackingBranch,
270 });
271
272 if (log.total === 0) {
273 continue;
274 }
275
276 pluginsToUpdate++;
277 await pluginRepo.pull();
278 const latestCommit = await pluginRepo.revparse(['HEAD']);
279 console.log(`Plugin ${color.green(directory)} updated to commit ${color.cyan(latestCommit)}`);
280 } catch (error) {
281 console.error(color.red(`Failed to update plugin ${directory}: ${error.message}`));
282 }
283 }
284
285 if (pluginsToUpdate === 0) {
286 console.log('All plugins are up to date.');
287 }
288}