diff --git a/.github/readme-zh_tw.md b/.github/readme-zh_tw.md index 1a0a9d220..1a4653d6e 100644 --- a/.github/readme-zh_tw.md +++ b/.github/readme-zh_tw.md @@ -1,5 +1,5 @@ > [!IMPORTANT] -> 這裡的資訊可能已經過時或不完整,僅供您參考。請使用英文版本以取得最新資訊。 +> 此處資訊可能已經過時或不完整,僅供您參考。請使用英文版本以取得最新資訊。 @@ -18,7 +18,7 @@ --- -SillyTavern 提供一個統一的前端介面,整合多種大型語言模型的 API(包括:KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistral 等)。同時具備行動裝置友善的佈局、視覺小說模式(Visual Novel Mode)、Automatic1111 與 ComfyUI 的影像生成 API 整合、TTS(語音合成)、世界資訊(Lorebook)、可自訂 UI、自動翻譯功能,以及強大的提示詞(prompt)設定選項和無限的第三方擴充潛力。 +SillyTavern 提供一個統一的前端介面,整合多種大型語言模型的 API(包括:KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistral 等)。同時具備對行動裝置友善的佈局、視覺小說模式(Visual Novel Mode)、Automatic1111 與 ComfyUI 的影像生成 API 整合、TTS(語音合成)、世界資訊(Lorebook)、可自訂 UI、自動翻譯功能,以及強大的提示詞(prompt)設定選項和無限的第三方擴充潛力。 我們擁有一個 [官方文件網站](https://docs.sillytavern.app/) 可以幫助解答絕大多數的使用問題,並幫助您順利入門。 @@ -30,18 +30,18 @@ SillyTavern 起源於 2023 年 2 月,作為 TavernAI 1.2.8 的分支版本發 ## 我們的願景 -1. 我們致力於賦予使用者對 LLM 提示詞的最大控制權與實用性,並認為學習過程中的挑戰是樂趣的一部分 +1. 我們致力於賦予使用者對 LLM 提示詞的最大控制權與實用性,並認為學習過程中的挑戰是樂趣的一部分。 2. 我們不提供任何線上或託管服務,也不會程式化追蹤任何使用者數據。 -3. SillyTavern 是由一群熱衷於 LLM 的開發者社群所打造的熱情專案,並將永遠保持免費與開源。 +3. SillyTavern 是由一群熱衷於 LLM 的開發者社群所打造的專案,並將永遠保持免費與開源。 ## 分支介紹 -SillyTavern 採用雙分支開發模式,確保為所有使用者提供流暢的使用體驗。 +SillyTavern 採用雙分支開發模式,確保為所有使用者提供流暢的體驗。 * `release`(穩定版):🌟 **推薦給大部分的使用者使用。** 此分支最為穩定,僅在主要版本發布時更新。適合大多數人,通常每月更新一次。 * `staging`(開發版):⚠️ **不建議普通使用者使用。** 此分支包含最新功能,但可能隨時出現問題。適合進階使用者與愛好者,每日多次更新。 -如果您不熟悉 git CLI 或對分支概念不清楚,請放心對您來說,`release`(穩定版)分支永遠是首選。 +如果您不熟悉 git CLI 或對分支概念不清楚,請放心,對您來說,`release`(穩定版)分支永遠是首選。 ## 使用 SillyTavern 需要什麼? @@ -84,18 +84,18 @@ SillyTavern 的硬體需求相當低。任何能夠運行 NodeJS 18 或更高版 ## 角色卡 -SillyTavern 的核心概念是「角色卡」(Character Cards)。角色卡是一組設定 LLM 行為的提示詞,用於 SillyTavern 中進行持續性對話。其功能類似於 ChatGPT 的 GPT 或 Poe 的聊天機器人。角色卡的內容可以是任何形式:抽象場景、針對特定任務設計的助手、知名人物,或者虛構角色。 +SillyTavern 的核心概念是「角色卡」(Character Cards)。角色卡是一組設定 LLM 行為的提示詞,用於 SillyTavern 中進行持續性對話。功能類似於 ChatGPT 的 GPT 或 Poe 的聊天機器人。角色卡的內容可以是任何形式:抽象場景、針對特定任務設計的助手、知名人物,或者虛構角色。 角色卡中唯一必填的項目是名稱欄位。若想與語言模型開始一般對話,您只需創建一個名稱為「Assistant」的新卡片,其餘欄位皆可保持空白。若希望進行更具主題性的對話,則可以提供語言模型背景資訊、行為模式、寫作風格以及特定情境來啟動聊天。 -如果您僅想進行快速對話而不選擇角色卡片,或想測試 LLM 的連線,則可在打開 SillyTavern 後,於歡迎頁面的輸入欄位中直接輸入您的提示內容。請注意,這類對話是暫時的,不會被永久保存。 +如果僅想進行快速對話而不選擇角色卡片,或想測試 LLM 的連線,則可在開啟 SillyTavern 後,於歡迎頁面的輸入欄位中直接輸入您的提示內容。請注意,這類對話是暫時的,不會被永久保存。 若想了解如何設定角色卡,可參考預設角色(如 Seraphina)或從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載社群製作的角色卡。 ## 核心功能 * 進階文本生成設定:內含許多社群製作的預設設定 -* 支援世界資訊(World Info):創建豐富的背景故事,或節省角色卡片中的 Token(符記)使用 +* 支援世界資訊(World Info):創建豐富的背景故事,或節省角色卡中的 Token(符元)使用 * 群組聊天:多角色聊天室,可讓角色與您或彼此對話 * 豐富的 UI 自定義選項:主題顏色、背景圖片、自定義 CSS 等 * 使用者設定:讓 AI 更了解您並提升沉浸感 @@ -110,7 +110,7 @@ SillyTavern 支持多種擴充功能。 * 聊天記錄自動摘要 * 自動化介面與聊天翻譯 * 穩定擴散(Stable Diffusion)、FLUX 和 DALL-E 的影像生成整合 -* 語音合成:AI 回應訊息可透過 ElevenLabs、Silero 或系統 TTS 語音合成 +* 語音合成:AI 回應的訊息可使用 ElevenLabs、Silero 或系統 TTS 語音合成 * 網頁搜尋功能:為提示詞添加真實世界的上下文資訊 * 更多擴展:可從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載 @@ -367,7 +367,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY * 經授權使用部分來自 CncAnon 的 TavernAITurbo 模組 * 視覺小說模式(Visual Novel Mode)的靈感,來源於 PepperTaco 的貢獻() * Noto Sans 字體由 Google 提供(OFL 許可) -* 主題圖示由 Font Awesome 提供(圖示:CC BY 4.0,字體:SIL OFL 1.1,代碼:MIT 許可) +* 主題圖示由 Font Awesome 提供(圖示:CC BY 4.0,字體:SIL OFL 1.1,程式碼:MIT 許可) * 預設資源來源於 @OtisAlejandro(包含角色 Seraphina 與知識書)與 @kallmeflocc(SillyTavern 官方 Discord 伺服器成員突破 10K 的慶祝背景) * Docker 安裝指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 編寫 diff --git a/.github/readme.md b/.github/readme.md index 28d3a390d..01a3ab8ba 100644 --- a/.github/readme.md +++ b/.github/readme.md @@ -274,7 +274,7 @@ You will need two mandatory directory mappings and a port mapping to allow Silly 1. Open your Command Line 2. Run the following command -`docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` +`docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'` > Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config. diff --git a/.gitignore b/.gitignore index 7d48fd879..7a88d5bec 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ access.log /vectors/ /cache/ public/css/user.css +public/error/ /plugins/ /data /default/scaffold @@ -52,3 +53,5 @@ public/scripts/extensions/third-party /certs .aider* .env +/StartDev.bat + diff --git a/default/config.yaml b/default/config.yaml index 189d1c48e..0945cfa2f 100644 --- a/default/config.yaml +++ b/default/config.yaml @@ -67,12 +67,14 @@ autheliaAuth: false # the username and passwords for basic auth are the same as those # for the individual accounts perUserBasicAuth: false +# Minimum log level to display in the terminal (DEBUG = 0, INFO = 1, WARN = 2, ERROR = 3) +minLogLevel: 0 # User session timeout *in seconds* (defaults to 24 hours). ## Set to a positive number to expire session after a certain time of inactivity ## Set to 0 to expire session when the browser is closed ## Set to a negative number to disable session expiration -sessionTimeout: 86400 +sessionTimeout: -1 # Used to sign session cookies. Will be auto-generated if not set cookieSecret: '' # Disable CSRF protection - NOT RECOMMENDED @@ -85,25 +87,39 @@ autorun: true # Avoids using 'localhost' for autorun in auto mode. # use if you don't have 'localhost' in your hosts file avoidLocalhost: false -# Disable thumbnail generation -disableThumbnails: false -# Thumbnail quality (0-100) -thumbnailsQuality: 95 -# Generate avatar thumbnails as PNG instead of JPG (preserves transparency but increases filesize by about 100%) -# Changing this only affects new thumbnails. To recreate the old ones, clear out your ST/thumbnails/ folder. -avatarThumbnailsPng: false + +## BACKUP CONFIGURATION +backups: + # Common settings for all backup types + common: + # Number of backups to keep for each chat and settings file + numberOfBackups: 50 + chat: + # Enable automatic chat backups + enabled: true + # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups. + maxTotalBackups: -1 + # Interval in milliseconds to throttle chat backups per user + throttleInterval: 10000 + +# THUMBNAILING CONFIGURATION +thumbnails: + # Enable thumbnail generation + enabled: true + # Image format of avatar thumbnails: + # * "jpg": best compression with adjustable quality, no transparency + # * "png": preserves transparency but increases filesize by about 100% + # Changing this only affects new thumbnails. To recreate the old ones, clear out /thumbnails folder in your user data. + format: "jpg" + # JPG thumbnail quality (0-100) + quality: 95 + # Maximum thumbnail dimensions per type [width, height] + dimensions: { 'bg': [160, 90], 'avatar': [96, 144] } + # Allow secret keys exposure via API allowKeysExposure: false # Skip new default content checks skipContentCheck: false -# Disable automatic chats backup -disableChatBackup: false -# Number of backups to keep for each chat and settings file -numberOfBackups: 50 -# Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups. -maxTotalChatBackups: -1 -# Interval in milliseconds to throttle chat backups per user -chatBackupThrottleInterval: 10000 # Allowed hosts for card downloads whitelistImportDomains: - localhost @@ -121,24 +137,26 @@ whitelistImportDomains: ## headers: ## User-Agent: "Googlebot/2.1 (+http://www.google.com/bot.html)" requestOverrides: [] -# -- EXTENSIONS CONFIGURATION -- -# Enable UI extensions -enableExtensions: true -# Automatically update extensions when a release version changes -enableExtensionsAutoUpdate: true + +# EXTENSIONS CONFIGURATION +extensions: + # Enable UI extensions + enabled: true + # Automatically update extensions when a release version changes + autoUpdate: true + models: + # Enables automatic model download from HuggingFace + autoDownload: true + # Additional models for extensions. Expects model IDs from HuggingFace model hub in ONNX format + classification: Cohee/distilbert-base-uncased-go-emotions-onnx + captioning: Xenova/vit-gpt2-image-captioning + embedding: Cohee/jina-embeddings-v2-base-en + speechToText: Xenova/whisper-small + textToSpeech: Xenova/speecht5_tts + # Additional model tokenizers can be downloaded on demand. # Disabling will fallback to another locally available tokenizer. enableDownloadableTokenizers: true -# Extension settings -extras: - # Disables automatic model download from HuggingFace - disableAutoDownload: false - # Extra models for plugins. Expects model IDs from HuggingFace model hub in ONNX format - classificationModel: Cohee/distilbert-base-uncased-go-emotions-onnx - captioningModel: Xenova/vit-gpt2-image-captioning - embeddingModel: Cohee/jina-embeddings-v2-base-en - speechToTextModel: Xenova/whisper-small - textToSpeechModel: Xenova/speecht5_tts # -- OPENAI CONFIGURATION -- # A placeholder message to use in strict prompt post-processing mode when the prompt doesn't start with a user message promptPlaceholder: "[Start a new chat]" diff --git a/default/content/backgrounds/_black.jpg b/default/content/backgrounds/_black.jpg index a451bc161..30ced914c 100644 Binary files a/default/content/backgrounds/_black.jpg and b/default/content/backgrounds/_black.jpg differ diff --git a/default/content/backgrounds/_white.jpg b/default/content/backgrounds/_white.jpg index a7c12e675..f363fbde0 100644 Binary files a/default/content/backgrounds/_white.jpg and b/default/content/backgrounds/_white.jpg differ diff --git a/default/content/index.json b/default/content/index.json index 6df03b78d..82387d0a9 100644 --- a/default/content/index.json +++ b/default/content/index.json @@ -782,5 +782,13 @@ { "filename": "presets/context/Mistral V7.json", "type": "context" + }, + { + "filename": "presets/instruct/DeepSeek-V2.5.json", + "type": "instruct" + }, + { + "filename": "presets/context/DeepSeek-V2.5.json", + "type": "context" } ] diff --git a/default/content/presets/context/DeepSeek-V2.5.json b/default/content/presets/context/DeepSeek-V2.5.json new file mode 100644 index 000000000..49efaba59 --- /dev/null +++ b/default/content/presets/context/DeepSeek-V2.5.json @@ -0,0 +1,11 @@ +{ + "story_string": "{{#if system}}{{system}}\n{{/if}}{{#if wiBefore}}{{wiBefore}}\n{{/if}}{{#if description}}{{description}}\n{{/if}}{{#if personality}}{{char}}'s personality: {{personality}}\n{{/if}}{{#if scenario}}Scenario: {{scenario}}\n{{/if}}{{#if wiAfter}}{{wiAfter}}\n{{/if}}{{#if persona}}{{persona}}\n{{/if}}{{trim}}\n", + "example_separator": "", + "chat_start": "", + "use_stop_strings": false, + "allow_jailbreak": false, + "always_force_name2": true, + "trim_sentences": false, + "single_line": false, + "name": "DeepSeek-V2.5" +} diff --git a/default/content/presets/instruct/DeepSeek-V2.5.json b/default/content/presets/instruct/DeepSeek-V2.5.json new file mode 100644 index 000000000..7990d13c0 --- /dev/null +++ b/default/content/presets/instruct/DeepSeek-V2.5.json @@ -0,0 +1,22 @@ +{ + "input_sequence": "<|User|>", + "output_sequence": "<|Assistant|>", + "first_output_sequence": "", + "last_output_sequence": "", + "system_sequence_prefix": "", + "system_sequence_suffix": "", + "stop_sequence": "", + "wrap": false, + "macro": true, + "names_behavior": "force", + "activation_regex": "", + "skip_examples": false, + "output_suffix": "<|end▁of▁sentence|>", + "input_suffix": "", + "system_sequence": "", + "system_suffix": "", + "user_alignment_message": "", + "last_system_sequence": "", + "system_same_as_user": true, + "name": "DeepSeek-V2.5" +} diff --git a/default/user.css b/default/public/css/user.css similarity index 100% rename from default/user.css rename to default/public/css/user.css diff --git a/default/public/error/forbidden-by-whitelist.html b/default/public/error/forbidden-by-whitelist.html new file mode 100644 index 000000000..70ff71852 --- /dev/null +++ b/default/public/error/forbidden-by-whitelist.html @@ -0,0 +1,22 @@ + + + + + Forbidden + + + +

Forbidden

+

+ If you are the system administrator, add your IP address to the + whitelist or disable whitelist mode by editing + config.yaml in the root directory of your installation. +

+
+

+ Connection from {{ipDetails}} has been blocked. This attempt + has been logged. +

+ + + diff --git a/default/public/error/unauthorized.html b/default/public/error/unauthorized.html new file mode 100644 index 000000000..e3fa5f94d --- /dev/null +++ b/default/public/error/unauthorized.html @@ -0,0 +1,17 @@ + + + + + Unauthorized + + + +

Unauthorized

+

+ If you are the system administrator, you can configure the + basicAuthUser credentials by editing + config.yaml in the root directory of your installation. +

+ + + diff --git a/default/public/error/url-not-found.html b/default/public/error/url-not-found.html new file mode 100644 index 000000000..87974145f --- /dev/null +++ b/default/public/error/url-not-found.html @@ -0,0 +1,15 @@ + + + + + Not found + + + +

Not found

+

+ The requested URL was not found on this server. +

+ + + diff --git a/index.d.ts b/index.d.ts index 015d8e353..35f34c22c 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,6 +1,24 @@ import { UserDirectoryList, User } from "./src/users"; +import { CsrfSyncedToken } from "csrf-sync"; declare global { + declare namespace CookieSessionInterfaces { + export interface CookieSessionObject { + /** + * The CSRF token for the session. + */ + csrfToken: CsrfSyncedToken; + /** + * Authenticated user handle. + */ + handle: string; + /** + * Last time the session was extended. + */ + touch: number; + } + } + namespace Express { export interface Request { user: { @@ -15,11 +33,3 @@ declare global { */ var DATA_ROOT: string; } - -declare module 'express-session' { - export interface SessionData { - handle: string; - touch: number; - // other properties... - } - } diff --git a/jsconfig.json b/jsconfig.json index 042caf675..4130eaedc 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -15,7 +15,7 @@ "**/node_modules/**", "**/dist/**", "**/.git/**", - "public/lib/**", + "public/**", "backups/**", "data/**", "cache/**", diff --git a/package-lock.json b/package-lock.json index 9410c323f..4e79f9f50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sillytavern", - "version": "1.12.10", + "version": "1.12.11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sillytavern", - "version": "1.12.10", + "version": "1.12.11", "hasInstallScript": true, "license": "AGPL-3.0", "dependencies": { @@ -26,7 +26,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.1.0", "cors": "^2.8.5", - "csrf-csrf": "^2.2.3", + "csrf-sync": "^4.0.3", "diff-match-patch": "^1.0.5", "dompurify": "^3.1.7", "droll": "^0.2.1", @@ -2987,10 +2987,10 @@ "node": "*" } }, - "node_modules/csrf-csrf": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/csrf-csrf/-/csrf-csrf-2.2.4.tgz", - "integrity": "sha512-LuhBmy5RfRmEfeqeYqgaAuS1eDpVtKZB/Eiec9xiKQLBynJxrGVRdM2yRT/YMl1Njo/yKh2L9AYsIwSlTPnx2A==", + "node_modules/csrf-sync": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csrf-sync/-/csrf-sync-4.0.3.tgz", + "integrity": "sha512-wXzltBBzt/7imzDt6ZT7G/axQG7jo4Sm0uXDUzFY8hR59qhDHdjqpW2hojS4oAVIZDzwlMQloIVCTJoDDh0wwA==", "license": "ISC", "dependencies": { "http-errors": "^2.0.0" diff --git a/package.json b/package.json index fc424f438..b14a487b8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.1.0", "cors": "^2.8.5", - "csrf-csrf": "^2.2.3", + "csrf-sync": "^4.0.3", "diff-match-patch": "^1.0.5", "dompurify": "^3.1.7", "droll": "^0.2.1", @@ -86,7 +86,7 @@ "type": "git", "url": "https://github.com/SillyTavern/SillyTavern.git" }, - "version": "1.12.10", + "version": "1.12.11", "scripts": { "start": "node server.js", "start:deno": "deno run --allow-run --allow-net --allow-read --allow-write --allow-sys --allow-env server.js", diff --git a/post-install.js b/post-install.js index f477df0e0..46ad160cb 100644 --- a/post-install.js +++ b/post-install.js @@ -28,6 +28,84 @@ const color = { white: (mess) => color.byNum(mess, 37), }; +const keyMigrationMap = [ + { + oldKey: 'disableThumbnails', + newKey: 'thumbnails.enabled', + migrate: (value) => !value, + }, + { + oldKey: 'thumbnailsQuality', + newKey: 'thumbnails.quality', + migrate: (value) => value, + }, + { + oldKey: 'avatarThumbnailsPng', + newKey: 'thumbnails.format', + migrate: (value) => (value ? 'png' : 'jpg'), + }, + { + oldKey: 'disableChatBackup', + newKey: 'backups.chat.enabled', + migrate: (value) => !value, + }, + { + oldKey: 'numberOfBackups', + newKey: 'backups.common.numberOfBackups', + migrate: (value) => value, + }, + { + oldKey: 'maxTotalChatBackups', + newKey: 'backups.chat.maxTotalBackups', + migrate: (value) => value, + }, + { + oldKey: 'chatBackupThrottleInterval', + newKey: 'backups.chat.throttleInterval', + migrate: (value) => value, + }, + { + oldKey: 'enableExtensions', + newKey: 'extensions.enabled', + migrate: (value) => value, + }, + { + oldKey: 'enableExtensionsAutoUpdate', + newKey: 'extensions.autoUpdate', + migrate: (value) => value, + }, + { + oldKey: 'extras.disableAutoDownload', + newKey: 'extensions.models.autoDownload', + migrate: (value) => !value, + }, + { + oldKey: 'extras.classificationModel', + newKey: 'extensions.models.classification', + migrate: (value) => value, + }, + { + oldKey: 'extras.captioningModel', + newKey: 'extensions.models.captioning', + migrate: (value) => value, + }, + { + oldKey: 'extras.embeddingModel', + newKey: 'extensions.models.embedding', + migrate: (value) => value, + }, + { + oldKey: 'extras.speechToTextModel', + newKey: 'extensions.models.speechToText', + migrate: (value) => value, + }, + { + oldKey: 'extras.textToSpeechModel', + newKey: 'extensions.models.textToSpeech', + migrate: (value) => value, + }, +]; + /** * Gets all keys from an object recursively. * @param {object} obj Object to get all keys from @@ -35,7 +113,7 @@ const color = { * @returns {string[]} Array of all keys in the object */ function getAllKeys(obj, prefix = '') { - if (typeof obj !== 'object' || Array.isArray(obj)) { + if (typeof obj !== 'object' || Array.isArray(obj) || obj === null) { return []; } @@ -83,6 +161,24 @@ function addMissingConfigValues() { const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8')); let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8')); + // Migrate old keys to new keys + const migratedKeys = []; + for (const { oldKey, newKey, migrate } of keyMigrationMap) { + if (_.has(config, oldKey)) { + const oldValue = _.get(config, oldKey); + const newValue = migrate(oldValue); + _.set(config, newKey, newValue); + _.unset(config, oldKey); + + migratedKeys.push({ + oldKey, + newKey, + oldValue, + newValue, + }); + } + } + // Get all keys from the original config const originalKeys = getAllKeys(config); @@ -95,11 +191,18 @@ function addMissingConfigValues() { // Find the keys that were added const addedKeys = _.difference(updatedKeys, originalKeys); - if (addedKeys.length === 0) { + if (addedKeys.length === 0 && migratedKeys.length === 0) { return; } - console.log('Adding missing config values to config.yaml:', addedKeys); + if (addedKeys.length > 0) { + console.log('Adding missing config values to config.yaml:', addedKeys); + } + + if (migratedKeys.length > 0) { + console.log('Migrating config values in config.yaml:', migratedKeys); + } + fs.writeFileSync('./config.yaml', yaml.stringify(config)); } catch (error) { console.error(color.red('FATAL: Could not add missing config values to config.yaml'), error); @@ -110,20 +213,60 @@ function addMissingConfigValues() { * Creates the default config files if they don't exist yet. */ function createDefaultFiles() { - const files = { - config: './config.yaml', - user: './public/css/user.css', - }; + /** + * @typedef DefaultItem + * @type {object} + * @property {'file' | 'directory'} type - Whether the item should be copied as a single file or merged into a directory structure. + * @property {string} defaultPath - The path to the default item (typically in `default/`). + * @property {string} productionPath - The path to the copied item for production use. + */ - for (const file of Object.values(files)) { + /** @type {DefaultItem[]} */ + const defaultItems = [ + { + type: 'file', + defaultPath: './default/config.yaml', + productionPath: './config.yaml', + }, + { + type: 'directory', + defaultPath: './default/public/', + productionPath: './public/', + }, + ]; + + for (const defaultItem of defaultItems) { try { - if (!fs.existsSync(file)) { - const defaultFilePath = path.join('./default', path.parse(file).base); - fs.copyFileSync(defaultFilePath, file); - console.log(color.green(`Created default file: ${file}`)); + if (defaultItem.type === 'file') { + if (!fs.existsSync(defaultItem.productionPath)) { + fs.copyFileSync( + defaultItem.defaultPath, + defaultItem.productionPath, + ); + console.log( + color.green(`Created default file: ${defaultItem.productionPath}`), + ); + } + } else if (defaultItem.type === 'directory') { + fs.cpSync(defaultItem.defaultPath, defaultItem.productionPath, { + force: false, // Don't overwrite existing files! + recursive: true, + }); + console.log( + color.green(`Synchronized missing files: ${defaultItem.productionPath}`), + ); + } else { + throw new Error( + 'FATAL: Unexpected default file format in `post-install.js#createDefaultFiles()`.', + ); } } catch (error) { - console.error(color.red(`FATAL: Could not write default file: ${file}`), error); + console.error( + color.red( + `FATAL: Could not write default ${defaultItem.type}: ${defaultItem.productionPath}`, + ), + error, + ); } } } diff --git a/public/css/promptmanager.css b/public/css/promptmanager.css index ad74397d4..f02b172b1 100644 --- a/public/css/promptmanager.css +++ b/public/css/promptmanager.css @@ -271,6 +271,24 @@ opacity: 0.8; } +#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt .drag-handle:not(.ui-sortable-handle) { + display: none; +} + +#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt:has(.drag-handle.ui-sortable-handle) { + position: relative; + padding-left: 20px; +} + +#completion_prompt_manager #completion_prompt_manager_list .completion_prompt_manager_prompt .drag-handle { + position: absolute; + height: 100%; + top: 0; + padding: 0 5px; + display: flex; + align-items: center; +} + #completion_prompt_manager_footer_append_prompt { font-size: 1em; } diff --git a/public/css/select2-overrides.css b/public/css/select2-overrides.css index e09e761c9..84f758d7c 100644 --- a/public/css/select2-overrides.css +++ b/public/css/select2-overrides.css @@ -100,6 +100,13 @@ border: 1px solid var(--SmartThemeBorderColor); } +.select2-container .select2-results .select2-results__option--disabled { + color: inherit; + background-color: inherit; + cursor: not-allowed; + filter: brightness(0.5); +} + .select2-container .select2-selection--multiple .select2-selection__choice, .select2-container .select2-selection--single .select2-selection__choice { border-radius: 5px; diff --git a/public/css/toggle-dependent.css b/public/css/toggle-dependent.css index b6cf2ace1..967aa55e6 100644 --- a/public/css/toggle-dependent.css +++ b/public/css/toggle-dependent.css @@ -472,6 +472,11 @@ label[for="trim_spaces"]:has(input:checked) i.warning { display: none; } +label[for="trim_spaces"]:not(:has(input:checked)) small { + color: var(--warning); + opacity: 1; +} + #claude_function_prefill_warning { display: none; color: red; diff --git a/public/img/vllm.svg b/public/img/vllm.svg new file mode 100644 index 000000000..764d5d4c8 --- /dev/null +++ b/public/img/vllm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/index.html b/public/index.html index 57562c9e7..b319ee253 100644 --- a/public/index.html +++ b/public/index.html @@ -1587,6 +1587,10 @@ Skip Special Tokens +