From 441e5c6f7e45ca1294108785d95e9de63548af39 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Mon, 17 Feb 2025 23:41:59 +0200 Subject: [PATCH 01/15] Move webpack cache to data root --- docker/build-lib.js | 2 +- package-lock.json | 39 +++++++++++++-- package.json | 1 + src/middleware/webpack-serve.js | 13 +++-- webpack.config.js | 88 +++++++++++++++++++++------------ 5 files changed, 101 insertions(+), 42 deletions(-) diff --git a/docker/build-lib.js b/docker/build-lib.js index 7676ec075..95137125e 100644 --- a/docker/build-lib.js +++ b/docker/build-lib.js @@ -1,4 +1,4 @@ import getWebpackServeMiddleware from '../src/middleware/webpack-serve.js'; const middleware = getWebpackServeMiddleware(); -await middleware.runWebpackCompiler(); +await middleware.runWebpackCompiler(true); diff --git a/package-lock.json b/package-lock.json index 0c1415443..af55caae2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "ip-matching": "^2.1.2", "ip-regex": "^5.0.0", "ipaddr.js": "^2.0.1", + "is-docker": "^3.0.0", "jimp": "^0.22.10", "localforage": "^1.10.0", "lodash": "^4.17.21", @@ -4649,15 +4650,15 @@ "license": "MIT" }, "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4734,6 +4735,21 @@ "node": ">=8" } }, + "node_modules/is-wsl/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -5518,6 +5534,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open/node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/openai": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/openai/-/openai-4.17.4.tgz", diff --git a/package.json b/package.json index 35617b74a..37fdc5e38 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "ip-matching": "^2.1.2", "ip-regex": "^5.0.0", "ipaddr.js": "^2.0.1", + "is-docker": "^3.0.0", "jimp": "^0.22.10", "localforage": "^1.10.0", "lodash": "^4.17.21", diff --git a/src/middleware/webpack-serve.js b/src/middleware/webpack-serve.js index 950e0a941..c62c39486 100644 --- a/src/middleware/webpack-serve.js +++ b/src/middleware/webpack-serve.js @@ -1,11 +1,8 @@ import path from 'node:path'; import webpack from 'webpack'; -import { publicLibConfig } from '../../webpack.config.js'; +import getPublicLibConfig from '../../webpack.config.js'; export default function getWebpackServeMiddleware() { - const outputPath = publicLibConfig.output?.path; - const outputFile = publicLibConfig.output?.filename; - /** * A very spartan recreation of webpack-dev-middleware. * @param {import('express').Request} req Request object. @@ -14,6 +11,10 @@ export default function getWebpackServeMiddleware() { * @type {import('express').RequestHandler} */ function devMiddleware(req, res, next) { + const publicLibConfig = getPublicLibConfig(); + const outputPath = publicLibConfig.output?.path; + const outputFile = publicLibConfig.output?.filename; + if (req.method === 'GET' && path.parse(req.path).base === outputFile) { return res.sendFile(outputFile, { root: outputPath }); } @@ -23,9 +24,11 @@ export default function getWebpackServeMiddleware() { /** * Wait until Webpack is done compiling. + * @param {boolean} [forceDist=false] Whether to force the use the /dist folder. * @returns {Promise} */ - devMiddleware.runWebpackCompiler = () => { + devMiddleware.runWebpackCompiler = (forceDist = false) => { + const publicLibConfig = getPublicLibConfig(forceDist); const compiler = webpack(publicLibConfig); return new Promise((resolve) => { diff --git a/webpack.config.js b/webpack.config.js index 187c7ded9..242c1568e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,35 +1,59 @@ import process from 'node:process'; import path from 'node:path'; +import os from 'node:os'; +import isDocker from 'is-docker'; -/** @type {import('webpack').Configuration} */ -export const publicLibConfig = { - mode: 'production', - entry: './public/lib.js', - cache: { - type: 'filesystem', - cacheDirectory: path.resolve(process.cwd(), 'dist/webpack'), - store: 'pack', - compression: 'gzip', - }, - devtool: false, - watch: false, - module: {}, - stats: { - preset: 'minimal', - assets: false, - modules: false, - colors: true, - timings: true, - }, - experiments: { - outputModule: true, - }, - performance: { - hints: false, - }, - output: { - path: path.resolve(process.cwd(), 'dist'), - filename: 'lib.js', - libraryTarget: 'module', - }, -}; +/** + * Get the Webpack configuration for the public/lib.js file. + * @param {boolean} forceDist Whether to force the use the /dist folder. + * @returns {import('webpack').Configuration} + * */ +export default function getPublicLibConfig(forceDist = false) { + function getCacheDirectory() { + // Docker got cache pre-baked into the image. + if (forceDist || isDocker()) { + return path.resolve(process.cwd(), 'dist/webpack'); + } + + // Data root is set (should be the case 99.99% of the time). + if (typeof globalThis.DATA_ROOT === 'string') { + return path.resolve(globalThis.DATA_ROOT, '_cache', 'webpack'); + } + + // Fallback to the system temp directory. + return path.resolve(os.tmpdir(), 'webpack'); + } + + const cacheDirectory = getCacheDirectory(); + return { + mode: 'production', + entry: './public/lib.js', + cache: { + type: 'filesystem', + cacheDirectory: cacheDirectory, + store: 'pack', + compression: 'gzip', + }, + devtool: false, + watch: false, + module: {}, + stats: { + preset: 'minimal', + assets: false, + modules: false, + colors: true, + timings: true, + }, + experiments: { + outputModule: true, + }, + performance: { + hints: false, + }, + output: { + path: path.resolve(process.cwd(), 'dist'), + filename: 'lib.js', + libraryTarget: 'module', + }, + }; +} From 0f461289800642d6262bbb06cb1a86007d11bb94 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Tue, 18 Feb 2025 20:24:07 +0200 Subject: [PATCH 02/15] Set lib.js output path to data --- webpack.config.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 242c1568e..3579a3cae 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,30 +1,43 @@ import process from 'node:process'; import path from 'node:path'; -import os from 'node:os'; import isDocker from 'is-docker'; /** * Get the Webpack configuration for the public/lib.js file. + * 1. Docker has got cache and the output file pre-baked. + * 2. Non-Docker environments use the global DATA_ROOT variable to determine the cache and output directories. * @param {boolean} forceDist Whether to force the use the /dist folder. * @returns {import('webpack').Configuration} + * @throws {Error} If the DATA_ROOT variable is not set. * */ export default function getPublicLibConfig(forceDist = false) { function getCacheDirectory() { - // Docker got cache pre-baked into the image. if (forceDist || isDocker()) { return path.resolve(process.cwd(), 'dist/webpack'); } - // Data root is set (should be the case 99.99% of the time). if (typeof globalThis.DATA_ROOT === 'string') { - return path.resolve(globalThis.DATA_ROOT, '_cache', 'webpack'); + return path.resolve(globalThis.DATA_ROOT, '_webpack', 'cache'); } - // Fallback to the system temp directory. - return path.resolve(os.tmpdir(), 'webpack'); + throw new Error('DATA_ROOT variable is not set.'); + } + + function getOutputDirectory() { + if (forceDist || isDocker()) { + return path.resolve(process.cwd(), 'dist'); + } + + if (typeof globalThis.DATA_ROOT === 'string') { + return path.resolve(globalThis.DATA_ROOT, '_webpack', 'output'); + } + + throw new Error('DATA_ROOT variable is not set.'); } const cacheDirectory = getCacheDirectory(); + const outputDirectory = getOutputDirectory(); + return { mode: 'production', entry: './public/lib.js', @@ -51,7 +64,7 @@ export default function getPublicLibConfig(forceDist = false) { hints: false, }, output: { - path: path.resolve(process.cwd(), 'dist'), + path: outputDirectory, filename: 'lib.js', libraryTarget: 'module', }, From 947d4f215e0697683b4b803bb928967a7b0614a9 Mon Sep 17 00:00:00 2001 From: Sneha C Date: Wed, 19 Feb 2025 20:13:05 +0400 Subject: [PATCH 03/15] autoswipe length tooltip clarify that it is # of characters --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index b05a88caf..ea1267429 100644 --- a/public/index.html +++ b/public/index.html @@ -4666,7 +4666,7 @@ Enabled Minimum generated message length - + Blacklisted words
From e4269f5d1d0d5f90f0ef81b6c1e681a9e8824296 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 19 Feb 2025 21:17:03 +0200 Subject: [PATCH 04/15] Update i18n --- public/locales/ar-sa.json | 2 +- public/locales/de-de.json | 2 +- public/locales/es-es.json | 2 +- public/locales/fr-fr.json | 2 +- public/locales/is-is.json | 2 +- public/locales/it-it.json | 2 +- public/locales/ja-jp.json | 2 +- public/locales/ko-kr.json | 2 +- public/locales/nl-nl.json | 2 +- public/locales/pt-pt.json | 2 +- public/locales/ru-ru.json | 4 ++-- public/locales/uk-ua.json | 2 +- public/locales/vi-vn.json | 2 +- public/locales/zh-cn.json | 2 +- public/locales/zh-tw.json | 2 +- 15 files changed, 16 insertions(+), 16 deletions(-) diff --git a/public/locales/ar-sa.json b/public/locales/ar-sa.json index c0bc382f1..8d72e8e0b 100644 --- a/public/locales/ar-sa.json +++ b/public/locales/ar-sa.json @@ -709,7 +709,7 @@ "Auto-swipe": "السحب التلقائي", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "تمكين وظيفة السحب التلقائي. الإعدادات في هذا القسم تؤثر فقط عند تمكين السحب التلقائي", "Minimum generated message length": "الحد الأدنى لطول الرسالة المولدة", - "If the generated message is shorter than this, trigger an auto-swipe": "إذا كانت الرسالة المولدة أقصر من هذا، فتحريض السحب التلقائي", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "إذا كانت الرسالة المولدة أقصر من هذا، فتحريض السحب التلقائي", "Blacklisted words": "الكلمات الممنوعة", "words you dont want generated separated by comma ','": "الكلمات التي لا تريد توليدها مفصولة بفاصلة ','", "Blacklisted word count to swipe": "عدد الكلمات الممنوعة للسحب", diff --git a/public/locales/de-de.json b/public/locales/de-de.json index d1ba2fb0b..816ddc9a0 100644 --- a/public/locales/de-de.json +++ b/public/locales/de-de.json @@ -709,7 +709,7 @@ "Auto-swipe": "Automatisches Wischen", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Aktiviere die Auto-Wisch-Funktion. Einstellungen in diesem Abschnitt haben nur dann Auswirkungen, wenn das automatische Wischen aktiviert ist", "Minimum generated message length": "Minimale generierte Nachrichtenlänge", - "If the generated message is shorter than this, trigger an auto-swipe": "Wenn die generierte Nachricht kürzer ist als diese, löse automatisches Wischen aus", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Wenn die generierte Nachricht kürzer ist als diese, löse automatisches Wischen aus", "Blacklisted words": "Verbotene Wörter", "words you dont want generated separated by comma ','": "Wörter, die du nicht generiert haben möchtest, durch Komma ',' getrennt", "Blacklisted word count to swipe": "Anzahl der verbotenen Wörter, um zu wischen", diff --git a/public/locales/es-es.json b/public/locales/es-es.json index 5794c1e7f..80a276781 100644 --- a/public/locales/es-es.json +++ b/public/locales/es-es.json @@ -709,7 +709,7 @@ "Auto-swipe": "Deslizamiento automático", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Habilitar la función de deslizamiento automático. La configuración en esta sección solo tiene efecto cuando el deslizamiento automático está habilitado", "Minimum generated message length": "Longitud mínima del mensaje generado", - "If the generated message is shorter than this, trigger an auto-swipe": "Si el mensaje generado es más corto que esto, activar un deslizamiento automático", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Si el mensaje generado es más corto que esto, activar un deslizamiento automático", "Blacklisted words": "Palabras prohibidas", "words you dont want generated separated by comma ','": "palabras que no desea generar separadas por coma ','", "Blacklisted word count to swipe": "Número de palabras prohibidas para deslizar", diff --git a/public/locales/fr-fr.json b/public/locales/fr-fr.json index 76566526c..972469ab1 100644 --- a/public/locales/fr-fr.json +++ b/public/locales/fr-fr.json @@ -656,7 +656,7 @@ "Auto-swipe": "Balayage automatique", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Activer la fonction de balayage automatique. Les paramètres de cette section n'ont d'effet que lorsque le balayage automatique est activé", "Minimum generated message length": "Longueur minimale du message généré", - "If the generated message is shorter than this, trigger an auto-swipe": "Si le message généré est plus court que cela, déclenchez un balayage automatique", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Si le message généré est plus court que cela, déclenchez un balayage automatique", "Blacklisted words": "Mots en liste noire", "words you dont want generated separated by comma ','": "mots que vous ne voulez pas générer séparés par des virgules ','", "Blacklisted word count to swipe": "Nombre de mots en liste noire pour balayer", diff --git a/public/locales/is-is.json b/public/locales/is-is.json index 4bfc1e7b7..6bc0784fd 100644 --- a/public/locales/is-is.json +++ b/public/locales/is-is.json @@ -709,7 +709,7 @@ "Auto-swipe": "Sjálfvirkur sveip", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Virkjaðu sjálfvirka sveiflugerð. Stillingar í þessum hluta hafa aðeins áhrif þegar sjálfvirkur sveiflugerð er virk", "Minimum generated message length": "Lágmarks lengd á mynduðum skilaboðum", - "If the generated message is shorter than this, trigger an auto-swipe": "Ef mynduðu skilaboðin eru styttri en þessi, kallaðu fram sjálfvirkar sveiflugerðar", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Ef mynduðu skilaboðin eru styttri en þessi, kallaðu fram sjálfvirkar sveiflugerðar", "Blacklisted words": "Svört orð", "words you dont want generated separated by comma ','": "orð sem þú vilt ekki að framleiða aðskilin með kommu ','", "Blacklisted word count to swipe": "Fjöldi svörtra orða til að sveipa", diff --git a/public/locales/it-it.json b/public/locales/it-it.json index e833745b7..34764fc77 100644 --- a/public/locales/it-it.json +++ b/public/locales/it-it.json @@ -709,7 +709,7 @@ "Auto-swipe": "Auto-swipe", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Abilita la funzione di auto-swipe. Le impostazioni in questa sezione hanno effetto solo quando l'auto-swipe è abilitato", "Minimum generated message length": "Lunghezza minima del messaggio generato", - "If the generated message is shorter than this, trigger an auto-swipe": "Se il messaggio generato è più breve di questo, attiva un'automatica rimozione", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Se il messaggio generato è più breve di questo, attiva un'automatica rimozione", "Blacklisted words": "Parole in blacklist", "words you dont want generated separated by comma ','": "parole che non vuoi generate separate da virgola ','", "Blacklisted word count to swipe": "Numero di parole in blacklist per attivare un'automatica rimozione", diff --git a/public/locales/ja-jp.json b/public/locales/ja-jp.json index b30a77b06..32cca7c5c 100644 --- a/public/locales/ja-jp.json +++ b/public/locales/ja-jp.json @@ -709,7 +709,7 @@ "Auto-swipe": "オートスワイプ", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "自動スワイプ機能を有効にします。このセクションの設定は、自動スワイプが有効になっている場合にのみ効果があります", "Minimum generated message length": "生成されたメッセージの最小長", - "If the generated message is shorter than this, trigger an auto-swipe": "生成されたメッセージがこれよりも短い場合、自動スワイプをトリガーします", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "生成されたメッセージがこれよりも短い場合、自動スワイプをトリガーします", "Blacklisted words": "ブラックリストされた単語", "words you dont want generated separated by comma ','": "コンマ ',' で区切られた生成したくない単語", "Blacklisted word count to swipe": "スワイプするブラックリストされた単語の数", diff --git a/public/locales/ko-kr.json b/public/locales/ko-kr.json index fde95a200..da3398fdd 100644 --- a/public/locales/ko-kr.json +++ b/public/locales/ko-kr.json @@ -724,7 +724,7 @@ "Auto-swipe": "자동 스와이프", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "자동 스와이프 기능을 활성화합니다. 이 섹션의 설정은 자동 스와이프가 활성화되었을 때만 영향을 미칩니다", "Minimum generated message length": "생성된 메시지 최소 길이", - "If the generated message is shorter than this, trigger an auto-swipe": "생성된 메시지가이보다 짧으면 자동 스와이프를 트리거합니다", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "생성된 메시지가이보다 짧으면 자동 스와이프를 트리거합니다", "Blacklisted words": "금지어", "words you dont want generated separated by comma ','": "쉼표로 구분된 생성하지 않으려는 단어", "Blacklisted word count to swipe": "스와이프할 금지어 개수", diff --git a/public/locales/nl-nl.json b/public/locales/nl-nl.json index 317447f11..0cfb52348 100644 --- a/public/locales/nl-nl.json +++ b/public/locales/nl-nl.json @@ -709,7 +709,7 @@ "Auto-swipe": "Automatisch vegen", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Schakel de automatische-vegen functie in. Instellingen in dit gedeelte hebben alleen effect wanneer automatisch vegen is ingeschakeld", "Minimum generated message length": "Minimale gegenereerde berichtlengte", - "If the generated message is shorter than this, trigger an auto-swipe": "Als het gegenereerde bericht korter is dan dit, activeer dan een automatische veeg", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Als het gegenereerde bericht korter is dan dit, activeer dan een automatische veeg", "Blacklisted words": "Verboden woorden", "words you dont want generated separated by comma ','": "woorden die je niet gegenereerd wilt hebben gescheiden door komma ','", "Blacklisted word count to swipe": "Aantal verboden woorden om te vegen", diff --git a/public/locales/pt-pt.json b/public/locales/pt-pt.json index cea383fcc..aee17bb7d 100644 --- a/public/locales/pt-pt.json +++ b/public/locales/pt-pt.json @@ -709,7 +709,7 @@ "Auto-swipe": "Auto-swipe", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Ativar a função de auto-swipe. As configurações nesta seção só têm efeito quando o auto-swipe está ativado", "Minimum generated message length": "Comprimento mínimo da mensagem gerada", - "If the generated message is shorter than this, trigger an auto-swipe": "Se a mensagem gerada for mais curta que isso, acione um auto-swipe", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Se a mensagem gerada for mais curta que isso, acione um auto-swipe", "Blacklisted words": "Palavras proibidas", "words you dont want generated separated by comma ','": "palavras que você não quer geradas separadas por vírgula ','", "Blacklisted word count to swipe": "Contagem de palavras proibidas para swipe", diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json index 45e6a76b2..1474e2f54 100644 --- a/public/locales/ru-ru.json +++ b/public/locales/ru-ru.json @@ -426,7 +426,7 @@ "Requests logprobs from the API for the Token Probabilities feature": "Запросить логпробы из API для функции Token Probabilities.", "Automatically reject and re-generate AI message based on configurable criteria": "Автоматическое отклонение и повторная генерация сообщений AI на основе настраиваемых критериев.", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Включить авто-свайп. Настройки в этом разделе действуют только при включенном авто-свайпе.", - "If the generated message is shorter than this, trigger an auto-swipe": "Если сгенерированное сообщение короче этого значения, срабатывает авто-свайп.", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Если сгенерированное сообщение короче этого значения, срабатывает авто-свайп.", "Reload and redraw the currently open chat": "Перезагрузить и перерисовать открытый в данный момент чат.", "Auto-Expand Message Actions": "Развернуть действия", "Persona Management": "Управление персоной", @@ -2205,4 +2205,4 @@ "Tokenized text:": "Токенизированный текст:", "Token IDs:": "Идентификаторы токенов:", "Tokens:": "Токенов:" -} \ No newline at end of file +} diff --git a/public/locales/uk-ua.json b/public/locales/uk-ua.json index ba371421d..e7873d0e1 100644 --- a/public/locales/uk-ua.json +++ b/public/locales/uk-ua.json @@ -709,7 +709,7 @@ "Auto-swipe": "Автоматичний змах", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Вмикає функцію автоматичного змаху. Налаштування в цьому розділі діють лише тоді, коли увімкнено автоматичний змах", "Minimum generated message length": "Мінімальна довжина згенерованого повідомлення", - "If the generated message is shorter than this, trigger an auto-swipe": "Якщо згенероване повідомлення коротше за це, викликайте автоматичний змаху", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Якщо згенероване повідомлення коротше за це, викликайте автоматичний змаху", "Blacklisted words": "Список заборонених слів", "words you dont want generated separated by comma ','": "слова, які ви не хочете генерувати, розділені комою ','", "Blacklisted word count to swipe": "Кількість заборонених слів для змаху", diff --git a/public/locales/vi-vn.json b/public/locales/vi-vn.json index f9a24d365..44ab6909f 100644 --- a/public/locales/vi-vn.json +++ b/public/locales/vi-vn.json @@ -709,7 +709,7 @@ "Auto-swipe": "Tự động vuốt", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "Bật chức năng tự động vuốt. Các cài đặt trong phần này chỉ có tác dụng khi tự động vuốt được bật", "Minimum generated message length": "Độ dài tối thiểu của tin nhắn được tạo", - "If the generated message is shorter than this, trigger an auto-swipe": "Nếu tin nhắn được tạo ra ngắn hơn điều này, kích hoạt tự động vuốt", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "Nếu tin nhắn được tạo ra ngắn hơn điều này, kích hoạt tự động vuốt", "Blacklisted words": "Từ trong danh sách đen", "words you dont want generated separated by comma ','": "các từ bạn không muốn được tạo ra được phân tách bằng dấu phẩy ','", "Blacklisted word count to swipe": "Số từ trong danh sách đen để vuốt", diff --git a/public/locales/zh-cn.json b/public/locales/zh-cn.json index 464bd18ca..52fc707e0 100644 --- a/public/locales/zh-cn.json +++ b/public/locales/zh-cn.json @@ -804,7 +804,7 @@ "Auto-swipe": "自动滑动", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "启用自动滑动功能。仅当启用自动滑动时,本节中的设置才会生效", "Minimum generated message length": "生成的消息的最小长度", - "If the generated message is shorter than this, trigger an auto-swipe": "如果生成的消息短于此长度,则触发自动滑动", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "如果生成的消息短于此长度,则触发自动滑动", "Blacklisted words": "屏蔽词", "words you dont want generated separated by comma ','": "不想生成的词语,用半角逗号“,”分隔", "Blacklisted word count to swipe": "触发滑动的黑名单词语数量", diff --git a/public/locales/zh-tw.json b/public/locales/zh-tw.json index 3f4f00b7c..666725d07 100644 --- a/public/locales/zh-tw.json +++ b/public/locales/zh-tw.json @@ -710,7 +710,7 @@ "Auto-swipe": "自動滑動", "Enable the auto-swipe function. Settings in this section only have an effect when auto-swipe is enabled": "啟用自動滑動功能。此部分的設定僅在啟用自動滑動時有效。", "Minimum generated message length": "生成訊息的最小長度", - "If the generated message is shorter than this, trigger an auto-swipe": "如果生成的訊息比這個短,將觸發自動滑動。", + "If the generated message is shorter than these many characters, trigger an auto-swipe": "如果生成的訊息比這個短,將觸發自動滑動。", "Blacklisted words": "黑名單詞語", "words you dont want generated separated by comma ','": "您不想生成的文字,使用逗號分隔", "Blacklisted word count to swipe": "滑動的黑名單詞語數量", From 380bd4ef4bc5099f705226305d628e112399e8ad Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 19 Feb 2025 22:03:43 +0200 Subject: [PATCH 05/15] refactor: update runWebpackCompiler to accept options object --- docker/build-lib.js | 2 +- src/middleware/webpack-serve.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docker/build-lib.js b/docker/build-lib.js index 95137125e..c56983b8b 100644 --- a/docker/build-lib.js +++ b/docker/build-lib.js @@ -1,4 +1,4 @@ import getWebpackServeMiddleware from '../src/middleware/webpack-serve.js'; const middleware = getWebpackServeMiddleware(); -await middleware.runWebpackCompiler(true); +await middleware.runWebpackCompiler({ forceDist: true }); diff --git a/src/middleware/webpack-serve.js b/src/middleware/webpack-serve.js index c62c39486..31556d363 100644 --- a/src/middleware/webpack-serve.js +++ b/src/middleware/webpack-serve.js @@ -24,10 +24,11 @@ export default function getWebpackServeMiddleware() { /** * Wait until Webpack is done compiling. - * @param {boolean} [forceDist=false] Whether to force the use the /dist folder. + * @param {object} param Parameters. + * @param {boolean} [param.forceDist] Whether to force the use the /dist folder. * @returns {Promise} */ - devMiddleware.runWebpackCompiler = (forceDist = false) => { + devMiddleware.runWebpackCompiler = ({ forceDist = false } = {}) => { const publicLibConfig = getPublicLibConfig(forceDist); const compiler = webpack(publicLibConfig); From 3d8a897c191c05eb5b63ffb663f81e494aa52646 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:42:28 +0200 Subject: [PATCH 06/15] Auto-extend session cookie every 30 minutes --- public/scripts/user.js | 26 ++++++++++++++++++++++++++ server.js | 8 +++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/public/scripts/user.js b/public/scripts/user.js index 4aa61967b..705542940 100644 --- a/public/scripts/user.js +++ b/public/scripts/user.js @@ -9,6 +9,9 @@ import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './uti export let currentUser = null; export let accountsEnabled = false; +// Extend the session every 30 minutes +const SESSION_EXTEND_INTERVAL = 30 * 60 * 1000; + /** * Enable or disable user account controls in the UI. * @param {boolean} isEnabled User account controls enabled @@ -894,6 +897,24 @@ async function slugify(text) { } } +/** + * Pings the server to extend the user session. + */ +async function extendUserSession() { + try { + const response = await fetch('/api/ping?extend=1', { + method: 'GET', + headers: getRequestHeaders(), + }); + + if (!response.ok) { + throw new Error('Ping did not succeed', { cause: response.status }); + } + } catch (error) { + console.error('Failed to extend user session', error); + } +} + jQuery(() => { $('#logout_button').on('click', () => { logout(); @@ -904,4 +925,9 @@ jQuery(() => { $('#account_button').on('click', () => { openUserProfile(); }); + setInterval(async () => { + if (currentUser) { + await extendUserSession(); + } + }, SESSION_EXTEND_INTERVAL); }); diff --git a/server.js b/server.js index c890b650b..b83a79235 100644 --- a/server.js +++ b/server.js @@ -556,7 +556,13 @@ app.use('/api/users', usersPublicRouter); // Everything below this line requires authentication app.use(requireLoginMiddleware); -app.get('/api/ping', (_, response) => response.sendStatus(204)); +app.get('/api/ping', (request, response) => { + if (request.query.extend && request.session) { + request.session.touch = Date.now(); + } + + response.sendStatus(204); +}); // File uploads app.use(multer({ dest: uploadsPath, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); From bad806312dcf530ceb5d65edd9c0558a4e7c810e Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Wed, 19 Feb 2025 23:42:28 +0200 Subject: [PATCH 07/15] Auto-extend session cookie every 30 minutes --- public/scripts/user.js | 26 ++++++++++++++++++++++++++ server.js | 8 +++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/public/scripts/user.js b/public/scripts/user.js index 4aa61967b..705542940 100644 --- a/public/scripts/user.js +++ b/public/scripts/user.js @@ -9,6 +9,9 @@ import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './uti export let currentUser = null; export let accountsEnabled = false; +// Extend the session every 30 minutes +const SESSION_EXTEND_INTERVAL = 30 * 60 * 1000; + /** * Enable or disable user account controls in the UI. * @param {boolean} isEnabled User account controls enabled @@ -894,6 +897,24 @@ async function slugify(text) { } } +/** + * Pings the server to extend the user session. + */ +async function extendUserSession() { + try { + const response = await fetch('/api/ping?extend=1', { + method: 'GET', + headers: getRequestHeaders(), + }); + + if (!response.ok) { + throw new Error('Ping did not succeed', { cause: response.status }); + } + } catch (error) { + console.error('Failed to extend user session', error); + } +} + jQuery(() => { $('#logout_button').on('click', () => { logout(); @@ -904,4 +925,9 @@ jQuery(() => { $('#account_button').on('click', () => { openUserProfile(); }); + setInterval(async () => { + if (currentUser) { + await extendUserSession(); + } + }, SESSION_EXTEND_INTERVAL); }); diff --git a/server.js b/server.js index 18f5cba1a..950907ae8 100644 --- a/server.js +++ b/server.js @@ -556,7 +556,13 @@ app.use('/api/users', usersPublicRouter); // Everything below this line requires authentication app.use(requireLoginMiddleware); -app.get('/api/ping', (_, response) => response.sendStatus(204)); +app.get('/api/ping', (request, response) => { + if (request.query.extend && request.session) { + request.session.touch = Date.now(); + } + + response.sendStatus(204); +}); // File uploads app.use(multer({ dest: uploadsPath, limits: { fieldSize: 10 * 1024 * 1024 } }).single('avatar')); From 3f5b63bba05b48707f2ae7c0a71b71411a55ee5e Mon Sep 17 00:00:00 2001 From: KevinSun <30999421+Zhen-Bo@users.noreply.github.com> Date: Fri, 21 Feb 2025 03:11:44 +0800 Subject: [PATCH 08/15] Feature: Add configurable X-Real-IP header support for rate limiting (#3504) * fix: correct client IP detection behind reverse proxy * Revert "fix: correct client IP detection behind reverse proxy" This reverts commit 72075062402eadb32c9e349df9bc92bfe4546ce3. * feat: support X-Real-IP header for reverse proxy setups * feat: add option to use x-real-ip for rate limiting behind reverse proxy * docs: update rate limiting configuration comments for X-Real-IP usage * refactor: extract getIpAddress function to reduce code duplication * revert(whitelist): rate limit settings shouldn't affect whitelist --- default/config.yaml | 5 +++++ src/endpoints/users-public.js | 17 ++++++++++------- src/express-common.js | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/default/config.yaml b/default/config.yaml index 5d7597a6c..d730bfbdb 100644 --- a/default/config.yaml +++ b/default/config.yaml @@ -85,6 +85,11 @@ cookieSecret: '' disableCsrfProtection: false # Disable startup security checks - NOT RECOMMENDED securityOverride: false +# -- RATE LIMITING CONFIGURATION -- +rateLimiting: + # Use X-Real-IP header instead of socket IP for rate limiting + # Only enable this if you are using a properly configured reverse proxy (like Nginx/traefik/Caddy) + preferRealIpHeader: false # -- ADVANCED CONFIGURATION -- # Open the browser automatically autorun: true diff --git a/src/endpoints/users-public.js b/src/endpoints/users-public.js index 8370e5748..2927e9bea 100644 --- a/src/endpoints/users-public.js +++ b/src/endpoints/users-public.js @@ -3,13 +3,16 @@ import crypto from 'node:crypto'; import storage from 'node-persist'; import express from 'express'; import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'; -import { jsonParser, getIpFromRequest } from '../express-common.js'; +import { jsonParser, getIpFromRequest, getRealIpFromHeader } from '../express-common.js'; import { color, Cache, getConfigValue } from '../util.js'; import { KEY_PREFIX, getUserAvatar, toKey, getPasswordHash, getPasswordSalt } from '../users.js'; const DISCREET_LOGIN = getConfigValue('enableDiscreetLogin', false); +const PREFER_REAL_IP_HEADER = getConfigValue('rateLimiting.preferRealIpHeader', false); const MFA_CACHE = new Cache(5 * 60 * 1000); +const getIpAddress = (request) => PREFER_REAL_IP_HEADER ? getRealIpFromHeader(request) : getIpFromRequest(request); + export const router = express.Router(); const loginLimiter = new RateLimiterMemory({ points: 5, @@ -60,7 +63,7 @@ router.post('/login', jsonParser, async (request, response) => { return response.status(400).json({ error: 'Missing required fields' }); } - const ip = getIpFromRequest(request); + const ip = getIpAddress(request); await loginLimiter.consume(ip); /** @type {import('../users.js').User} */ @@ -92,7 +95,7 @@ router.post('/login', jsonParser, async (request, response) => { return response.json({ handle: user.handle }); } catch (error) { if (error instanceof RateLimiterRes) { - console.error('Login failed: Rate limited from', getIpFromRequest(request)); + console.error('Login failed: Rate limited from', getIpAddress(request)); return response.status(429).send({ error: 'Too many attempts. Try again later or recover your password.' }); } @@ -108,7 +111,7 @@ router.post('/recover-step1', jsonParser, async (request, response) => { return response.status(400).json({ error: 'Missing required fields' }); } - const ip = getIpFromRequest(request); + const ip = getIpAddress(request); await recoverLimiter.consume(ip); /** @type {import('../users.js').User} */ @@ -132,7 +135,7 @@ router.post('/recover-step1', jsonParser, async (request, response) => { return response.sendStatus(204); } catch (error) { if (error instanceof RateLimiterRes) { - console.error('Recover step 1 failed: Rate limited from', getIpFromRequest(request)); + console.error('Recover step 1 failed: Rate limited from', getIpAddress(request)); return response.status(429).send({ error: 'Too many attempts. Try again later or contact your admin.' }); } @@ -150,7 +153,7 @@ router.post('/recover-step2', jsonParser, async (request, response) => { /** @type {import('../users.js').User} */ const user = await storage.getItem(toKey(request.body.handle)); - const ip = getIpFromRequest(request); + const ip = getIpAddress(request); if (!user) { console.error('Recover step 2 failed: User', request.body.handle, 'not found'); @@ -186,7 +189,7 @@ router.post('/recover-step2', jsonParser, async (request, response) => { return response.sendStatus(204); } catch (error) { if (error instanceof RateLimiterRes) { - console.error('Recover step 2 failed: Rate limited from', getIpFromRequest(request)); + console.error('Recover step 2 failed: Rate limited from', getIpAddress(request)); return response.status(429).send({ error: 'Too many attempts. Try again later or contact your admin.' }); } diff --git a/src/express-common.js b/src/express-common.js index 630d62c59..9717450a1 100644 --- a/src/express-common.js +++ b/src/express-common.js @@ -25,3 +25,17 @@ export function getIpFromRequest(req) { } return clientIp; } + +/** + * Gets the IP address of the client when behind reverse proxy using x-real-ip header, falls back to socket remote address. + * This function should be used when the application is running behind a reverse proxy (e.g., Nginx, traefik, Caddy...). + * @param {import('express').Request} req Request object + * @returns {string} IP address of the client + */ +export function getRealIpFromHeader(req) { + if (req.headers['x-real-ip']) { + return req.headers['x-real-ip'].toString(); + } + + return getIpFromRequest(req); +} From 8ad7b5dcc58465135ae5c8b7ed522280c892cd65 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:13:23 +0200 Subject: [PATCH 09/15] Check if git repo root in plugin update --- plugins.js | 4 ++-- src/plugin-loader.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins.js b/plugins.js index b296ffd3c..92b04977c 100644 --- a/plugins.js +++ b/plugins.js @@ -8,7 +8,7 @@ import path from 'node:path'; import process from 'node:process'; import { fileURLToPath } from 'node:url'; -import { default as git } from 'simple-git'; +import { default as git, CheckRepoActions } from 'simple-git'; import { color } from './src/util.js'; const __dirname = import.meta.dirname ?? path.dirname(fileURLToPath(import.meta.url)); @@ -49,7 +49,7 @@ async function updatePlugins() { const pluginPath = path.join(pluginsPath, directory); const pluginRepo = git(pluginPath); - const isRepo = await pluginRepo.checkIsRepo(); + const isRepo = await pluginRepo.checkIsRepo(CheckRepoActions.IS_REPO_ROOT); if (!isRepo) { console.log(`Directory ${color.yellow(directory)} is not a Git repository`); continue; diff --git a/src/plugin-loader.js b/src/plugin-loader.js index 744c3396d..65df1e571 100644 --- a/src/plugin-loader.js +++ b/src/plugin-loader.js @@ -3,7 +3,7 @@ import path from 'node:path'; import url from 'node:url'; import express from 'express'; -import { default as git } from 'simple-git'; +import { default as git, CheckRepoActions } from 'simple-git'; import { sync as commandExistsSync } from 'command-exists'; import { getConfigValue, color } from './util.js'; @@ -256,7 +256,7 @@ async function updatePlugins(pluginsPath) { const pluginPath = path.join(pluginsPath, directory); const pluginRepo = git(pluginPath); - const isRepo = await pluginRepo.checkIsRepo(); + const isRepo = await pluginRepo.checkIsRepo(CheckRepoActions.IS_REPO_ROOT); if (!isRepo) { continue; } From ab4d296b229538e6aaae3ce9c41a21e2b451ded5 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Thu, 20 Feb 2025 23:18:03 +0200 Subject: [PATCH 10/15] fix: return empty strings for branch name and commit hash in error response --- src/endpoints/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/endpoints/extensions.js b/src/endpoints/extensions.js index 9c96065c4..95e5e7eba 100644 --- a/src/endpoints/extensions.js +++ b/src/endpoints/extensions.js @@ -230,7 +230,7 @@ router.post('/version', jsonParser, async (request, response) => { } catch (error) { // it is not a git repo, or has no commits yet, or is a bare repo // not possible to update it, most likely can't get the branch name either - return response.send({ currentBranchName: null, currentCommitHash, isUpToDate: true, remoteUrl: null }); + return response.send({ currentBranchName: '', currentCommitHash: '', isUpToDate: true, remoteUrl: '' }); } const currentBranch = await git.cwd(extensionPath).branch(); From aadae85a2a2f5e5fa9e34a2cb9815c1d2a0c066a Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Feb 2025 02:52:45 +0200 Subject: [PATCH 11/15] eventSource: Add autofire on emit for APP_READY --- public/lib/eventemitter.js | 45 ++++++++++++++++++++++++++++++++++++-- public/script.js | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/public/lib/eventemitter.js b/public/lib/eventemitter.js index abf2b8dbc..03dd3bd09 100644 --- a/public/lib/eventemitter.js +++ b/public/lib/eventemitter.js @@ -24,10 +24,22 @@ if (typeof Array.prototype.indexOf === 'function') { /* Polyfill EventEmitter. */ -var EventEmitter = function () { +/** + * Creates an event emitter. + * @param {string[]} autoFireAfterEmit Auto-fire event names + */ +var EventEmitter = function (autoFireAfterEmit = []) { this.events = {}; + this.autoFireLastArgs = new Map(); + this.autoFireAfterEmit = new Set(autoFireAfterEmit); }; +/** + * Adds a listener to an event. + * @param {string} event Event name + * @param {function} listener Event listener + * @returns + */ EventEmitter.prototype.on = function (event, listener) { // Unknown event used by external libraries? if (event === undefined) { @@ -40,6 +52,10 @@ EventEmitter.prototype.on = function (event, listener) { } this.events[event].push(listener); + + if (this.autoFireAfterEmit.has(event) && this.autoFireLastArgs.has(event)) { + listener.apply(this, this.autoFireLastArgs.get(event)); + } }; /** @@ -60,6 +76,10 @@ EventEmitter.prototype.makeLast = function (event, listener) { } events.push(listener); + + if (this.autoFireAfterEmit.has(event) && this.autoFireLastArgs.has(event)) { + listener.apply(this, this.autoFireLastArgs.get(event)); + } } /** @@ -80,8 +100,17 @@ EventEmitter.prototype.makeFirst = function (event, listener) { } events.unshift(listener); + + if (this.autoFireAfterEmit.has(event) && this.autoFireLastArgs.has(event)) { + listener.apply(this, this.autoFireLastArgs.get(event)); + } } +/** + * Removes a listener from an event. + * @param {string} event Event name + * @param {function} listener Event listener + */ EventEmitter.prototype.removeListener = function (event, listener) { var idx; @@ -94,6 +123,10 @@ EventEmitter.prototype.removeListener = function (event, listener) { } }; +/** + * Emits an event with optional arguments. + * @param {string} event Event name + */ EventEmitter.prototype.emit = async function (event) { let args = [].slice.call(arguments, 1); if (localStorage.getItem('eventTracing') === 'true') { @@ -118,6 +151,10 @@ EventEmitter.prototype.emit = async function (event) { } } } + + if (this.autoFireAfterEmit.has(event)) { + this.autoFireLastArgs.set(event, args); + } }; EventEmitter.prototype.emitAndWait = function (event) { @@ -144,10 +181,14 @@ EventEmitter.prototype.emitAndWait = function (event) { } } } + + if (this.autoFireAfterEmit.has(event)) { + this.autoFireLastArgs.set(event, args); + } }; EventEmitter.prototype.once = function (event, listener) { - this.on(event, function g () { + this.on(event, function g() { this.removeListener(event, g); listener.apply(this, arguments); }); diff --git a/public/script.js b/public/script.js index d86945523..cf37389fb 100644 --- a/public/script.js +++ b/public/script.js @@ -512,7 +512,7 @@ export const event_types = { TOOL_CALLS_RENDERED: 'tool_calls_rendered', }; -export const eventSource = new EventEmitter(); +export const eventSource = new EventEmitter([event_types.APP_READY]); eventSource.on(event_types.CHAT_CHANGED, processChatSlashCommands); From b17fdcbfd9fdf6e542e83943cc27538a70960113 Mon Sep 17 00:00:00 2001 From: Cohee <18619528+Cohee1207@users.noreply.github.com> Date: Fri, 21 Feb 2025 12:46:49 +0000 Subject: [PATCH 12/15] Fix assistant chat export format --- public/scripts/chats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/chats.js b/public/scripts/chats.js index 042030f2c..a6f2432d2 100644 --- a/public/scripts/chats.js +++ b/public/scripts/chats.js @@ -1487,7 +1487,7 @@ jQuery(function () { ...chat.filter(x => x?.extra?.type !== system_message_types.ASSISTANT_NOTE), ]; - download(JSON.stringify(chatToSave, null, 4), `Assistant - ${humanizedDateTime()}.json`, 'application/json'); + download(chatToSave.map((m) => JSON.stringify(m)).join('\n'), `Assistant - ${humanizedDateTime()}.jsonl`, 'application/json'); }); // Do not change. #attachFile is added by extension. From 29c71fe8f17edfcc37e66a37a2fa723221f63c10 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Feb 2025 17:30:56 +0100 Subject: [PATCH 13/15] Update reasoning block coloring to CSS vars --- public/style.css | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/public/style.css b/public/style.css index 6278d9960..3b07f593d 100644 --- a/public/style.css +++ b/public/style.css @@ -55,6 +55,9 @@ --interactable-outline-color: var(--white100); --interactable-outline-color-faint: var(--white20a); + --reasoning-mix-rate: 50%; + --reasoning-mix-color: var(--grey30); + /*Default Theme, will be changed by ToolCool Color Picker*/ --SmartThemeBodyColor: rgb(220, 220, 210); @@ -354,7 +357,7 @@ input[type='checkbox']:focus-visible { padding-left: 14px; margin-bottom: 0.5em; overflow-y: auto; - color: var(--SmartThemeEmColor); + color: color-mix(in srgb, var(--SmartThemeBodyColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); } .mes_reasoning_details { @@ -374,18 +377,6 @@ input[type='checkbox']:focus-visible { margin-bottom: 0; } -.mes_reasoning em, -.mes_reasoning i, -.mes_reasoning u, -.mes_reasoning q, -.mes_reasoning blockquote { - filter: saturate(0.5); -} - -.mes_reasoning_details .mes_reasoning em { - color: color-mix(in srgb, var(--SmartThemeEmColor) 67%, var(--SmartThemeBlurTintColor) 33%); -} - .mes_reasoning_header_block { flex-grow: 1; } @@ -461,26 +452,36 @@ input[type='checkbox']:focus-visible { } .mes_text i, -.mes_text em, +.mes_text em { + color: var(--SmartThemeEmColor); +} .mes_reasoning i, .mes_reasoning em { - color: var(--SmartThemeEmColor); + color: color-mix(in srgb, var(--SmartThemeEmColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); } .mes_text q i, .mes_text q em { color: inherit; } - -.mes_text u, -.mes_reasoning u { - color: var(--SmartThemeUnderlineColor); +.mes_reasoning q i, +.mes_reasoning q em { + color: color-mix(in srgb, var(--SmartThemeQuoteColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); } -.mes_text q, -.mes_reasoning q { +.mes_text u { + color: var(--SmartThemeUnderlineColor); +} +.mes_reasoning u { + color: color-mix(in srgb, var(--SmartThemeUnderlineColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); +} + +.mes_text q { color: var(--SmartThemeQuoteColor); } +.mes_reasoning q { + color: color-mix(in srgb, var(--SmartThemeQuoteColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); +} .mes_text font[color] em, .mes_text font[color] i, From b0a2f241d20a826ef2e5c3d0cf8e3c88151cb4af Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Feb 2025 18:01:25 +0100 Subject: [PATCH 14/15] Fix hidden reasoning not allowing manual parsing --- public/scripts/reasoning.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js index 44927d83f..2d4711db2 100644 --- a/public/scripts/reasoning.js +++ b/public/scripts/reasoning.js @@ -338,14 +338,15 @@ export class ReasoningHandler { return mesChanged; } - if (this.state === ReasoningState.None) { + if (this.state === ReasoningState.None || this.#isHiddenReasoningModel) { // If streamed message starts with the opening, cut it out and put all inside reasoning if (message.mes.startsWith(power_user.reasoning.prefix) && message.mes.length > power_user.reasoning.prefix.length) { this.#isParsingReasoning = true; // Manually set starting state here, as we might already have received the ending suffix this.state = ReasoningState.Thinking; - this.startTime = this.initialTime; + this.startTime = this.startTime ?? this.initialTime; + this.endTime = null; } } From 0cc0d6763ed95f9a0e331858838aef7f05c39c08 Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Fri, 21 Feb 2025 19:40:36 +0100 Subject: [PATCH 15/15] Use hsl instead of color-mix for reasoning css --- public/style.css | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/public/style.css b/public/style.css index 3b07f593d..bafc9e288 100644 --- a/public/style.css +++ b/public/style.css @@ -55,8 +55,9 @@ --interactable-outline-color: var(--white100); --interactable-outline-color-faint: var(--white20a); - --reasoning-mix-rate: 50%; - --reasoning-mix-color: var(--grey30); + --reasoning-body-color: var(--SmartThemeEmColor); + --reasoning-em-color: color-mix(in srgb, var(--SmartThemeEmColor) 67%, var(--SmartThemeBlurTintColor) 33%); + --reasoning-saturation: 0.5; /*Default Theme, will be changed by ToolCool Color Picker*/ @@ -351,13 +352,13 @@ input[type='checkbox']:focus-visible { .mes_reasoning { display: block; - border-left: 2px solid var(--SmartThemeEmColor); + border-left: 2px solid var(--reasoning-body-color); border-radius: 2px; padding: 5px; padding-left: 14px; margin-bottom: 0.5em; overflow-y: auto; - color: color-mix(in srgb, var(--SmartThemeBodyColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); + color: hsl(from var(--reasoning-body-color) h calc(s * var(--reasoning-saturation)) l); } .mes_reasoning_details { @@ -457,7 +458,7 @@ input[type='checkbox']:focus-visible { } .mes_reasoning i, .mes_reasoning em { - color: color-mix(in srgb, var(--SmartThemeEmColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); + color: hsl(from var(--reasoning-em-color) h calc(s * var(--reasoning-saturation)) l); } .mes_text q i, @@ -466,21 +467,21 @@ input[type='checkbox']:focus-visible { } .mes_reasoning q i, .mes_reasoning q em { - color: color-mix(in srgb, var(--SmartThemeQuoteColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); + color: hsl(from var(--SmartThemeQuoteColor) h calc(s * var(--reasoning-saturation)) l); } .mes_text u { color: var(--SmartThemeUnderlineColor); } .mes_reasoning u { - color: color-mix(in srgb, var(--SmartThemeUnderlineColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); + color: hsl(from var(--SmartThemeUnderlineColor) h calc(s * var(--reasoning-saturation)) l); } .mes_text q { color: var(--SmartThemeQuoteColor); } .mes_reasoning q { - color: color-mix(in srgb, var(--SmartThemeQuoteColor) var(--reasoning-mix-rate), var(--reasoning-mix-color)); + color: hsl(from var(--SmartThemeQuoteColor) h calc(s * var(--reasoning-saturation)) l); } .mes_text font[color] em,