Merge branch 'staging' into fix-bogus-sorting
| @@ -1,5 +1,5 @@ | |||
| 1 | > [!IMPORTANT] | 1 | > [!IMPORTANT] |
| 2 | > 這裡的資訊可能已經過時或不完整,僅供您參考。請使用英文版本以取得最新資訊。 | 2 | > 此處資訊可能已經過時或不完整,僅供您參考。請使用英文版本以取得最新資訊。 |
| 3 | 3 | ||
| 4 | <a name="readme-top"></a> | 4 | <a name="readme-top"></a> |
| 5 | 5 | ||
| @@ -18,7 +18,7 @@ | |||
| 18 | 18 | ||
| 19 | --- | 19 | --- |
| 20 | 20 | ||
| 21 | SillyTavern 提供一個統一的前端介面,整合多種大型語言模型的 API(包括:KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistral 等)。同時具備行動裝置友善的佈局、視覺小說模式(Visual Novel Mode)、Automatic1111 與 ComfyUI 的影像生成 API 整合、TTS(語音合成)、世界資訊(Lorebook)、可自訂 UI、自動翻譯功能,以及強大的提示詞(prompt)設定選項和無限的第三方擴充潛力。 | 21 | SillyTavern 提供一個統一的前端介面,整合多種大型語言模型的 API(包括:KoboldAI/CPP、Horde、NovelAI、Ooba、Tabby、OpenAI、OpenRouter、Claude、Mistral 等)。同時具備對行動裝置友善的佈局、視覺小說模式(Visual Novel Mode)、Automatic1111 與 ComfyUI 的影像生成 API 整合、TTS(語音合成)、世界資訊(Lorebook)、可自訂 UI、自動翻譯功能,以及強大的提示詞(prompt)設定選項和無限的第三方擴充潛力。 |
| 22 | 22 | ||
| 23 | 我們擁有一個 [官方文件網站](https://docs.sillytavern.app/) 可以幫助解答絕大多數的使用問題,並幫助您順利入門。 | 23 | 我們擁有一個 [官方文件網站](https://docs.sillytavern.app/) 可以幫助解答絕大多數的使用問題,並幫助您順利入門。 |
| 24 | 24 | ||
| @@ -30,18 +30,18 @@ SillyTavern 起源於 2023 年 2 月,作為 TavernAI 1.2.8 的分支版本發 | |||
| 30 | 30 | ||
| 31 | ## 我們的願景 | 31 | ## 我們的願景 |
| 32 | 32 | ||
| 33 | 1. 我們致力於賦予使用者對 LLM 提示詞的最大控制權與實用性,並認為學習過程中的挑戰是樂趣的一部分 | 33 | 1. 我們致力於賦予使用者對 LLM 提示詞的最大控制權與實用性,並認為學習過程中的挑戰是樂趣的一部分。 |
| 34 | 2. 我們不提供任何線上或託管服務,也不會程式化追蹤任何使用者數據。 | 34 | 2. 我們不提供任何線上或託管服務,也不會程式化追蹤任何使用者數據。 |
| 35 | 3. SillyTavern 是由一群熱衷於 LLM 的開發者社群所打造的熱情專案,並將永遠保持免費與開源。 | 35 | 3. SillyTavern 是由一群熱衷於 LLM 的開發者社群所打造的專案,並將永遠保持免費與開源。 |
| 36 | 36 | ||
| 37 | ## 分支介紹 | 37 | ## 分支介紹 |
| 38 | 38 | ||
| 39 | SillyTavern 採用雙分支開發模式,確保為所有使用者提供流暢的使用體驗。 | 39 | SillyTavern 採用雙分支開發模式,確保為所有使用者提供流暢的體驗。 |
| 40 | 40 | ||
| 41 | * `release`(穩定版):🌟 **推薦給大部分的使用者使用。** 此分支最為穩定,僅在主要版本發布時更新。適合大多數人,通常每月更新一次。 | 41 | * `release`(穩定版):🌟 **推薦給大部分的使用者使用。** 此分支最為穩定,僅在主要版本發布時更新。適合大多數人,通常每月更新一次。 |
| 42 | * `staging`(開發版):⚠️ **不建議普通使用者使用。** 此分支包含最新功能,但可能隨時出現問題。適合進階使用者與愛好者,每日多次更新。 | 42 | * `staging`(開發版):⚠️ **不建議普通使用者使用。** 此分支包含最新功能,但可能隨時出現問題。適合進階使用者與愛好者,每日多次更新。 |
| 43 | 43 | ||
| 44 | 如果您不熟悉 git CLI 或對分支概念不清楚,請放心對您來說,`release`(穩定版)分支永遠是首選。 | 44 | 如果您不熟悉 git CLI 或對分支概念不清楚,請放心,對您來說,`release`(穩定版)分支永遠是首選。 |
| 45 | 45 | ||
| 46 | ## 使用 SillyTavern 需要什麼? | 46 | ## 使用 SillyTavern 需要什麼? |
| 47 | 47 | ||
| @@ -84,18 +84,18 @@ SillyTavern 的硬體需求相當低。任何能夠運行 NodeJS 18 或更高版 | |||
| 84 | 84 | ||
| 85 | ## 角色卡 | 85 | ## 角色卡 |
| 86 | 86 | ||
| 87 | SillyTavern 的核心概念是「角色卡」(Character Cards)。角色卡是一組設定 LLM 行為的提示詞,用於 SillyTavern 中進行持續性對話。其功能類似於 ChatGPT 的 GPT 或 Poe 的聊天機器人。角色卡的內容可以是任何形式:抽象場景、針對特定任務設計的助手、知名人物,或者虛構角色。 | 87 | SillyTavern 的核心概念是「角色卡」(Character Cards)。角色卡是一組設定 LLM 行為的提示詞,用於 SillyTavern 中進行持續性對話。功能類似於 ChatGPT 的 GPT 或 Poe 的聊天機器人。角色卡的內容可以是任何形式:抽象場景、針對特定任務設計的助手、知名人物,或者虛構角色。 |
| 88 | 88 | ||
| 89 | 角色卡中唯一必填的項目是名稱欄位。若想與語言模型開始一般對話,您只需創建一個名稱為「Assistant」的新卡片,其餘欄位皆可保持空白。若希望進行更具主題性的對話,則可以提供語言模型背景資訊、行為模式、寫作風格以及特定情境來啟動聊天。 | 89 | 角色卡中唯一必填的項目是名稱欄位。若想與語言模型開始一般對話,您只需創建一個名稱為「Assistant」的新卡片,其餘欄位皆可保持空白。若希望進行更具主題性的對話,則可以提供語言模型背景資訊、行為模式、寫作風格以及特定情境來啟動聊天。 |
| 90 | 90 | ||
| 91 | 如果您僅想進行快速對話而不選擇角色卡片,或想測試 LLM 的連線,則可在打開 SillyTavern 後,於歡迎頁面的輸入欄位中直接輸入您的提示內容。請注意,這類對話是暫時的,不會被永久保存。 | 91 | 如果僅想進行快速對話而不選擇角色卡片,或想測試 LLM 的連線,則可在開啟 SillyTavern 後,於歡迎頁面的輸入欄位中直接輸入您的提示內容。請注意,這類對話是暫時的,不會被永久保存。 |
| 92 | 92 | ||
| 93 | 若想了解如何設定角色卡,可參考預設角色(如 Seraphina)或從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載社群製作的角色卡。 | 93 | 若想了解如何設定角色卡,可參考預設角色(如 Seraphina)或從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載社群製作的角色卡。 |
| 94 | 94 | ||
| 95 | ## 核心功能 | 95 | ## 核心功能 |
| 96 | 96 | ||
| 97 | * 進階文本生成設定:內含許多社群製作的預設設定 | 97 | * 進階文本生成設定:內含許多社群製作的預設設定 |
| 98 | * 支援世界資訊(World Info):創建豐富的背景故事,或節省角色卡片中的 Token(符記)使用 | 98 | * 支援世界資訊(World Info):創建豐富的背景故事,或節省角色卡中的 Token(符元)使用 |
| 99 | * 群組聊天:多角色聊天室,可讓角色與您或彼此對話 | 99 | * 群組聊天:多角色聊天室,可讓角色與您或彼此對話 |
| 100 | * 豐富的 UI 自定義選項:主題顏色、背景圖片、自定義 CSS 等 | 100 | * 豐富的 UI 自定義選項:主題顏色、背景圖片、自定義 CSS 等 |
| 101 | * 使用者設定:讓 AI 更了解您並提升沉浸感 | 101 | * 使用者設定:讓 AI 更了解您並提升沉浸感 |
| @@ -110,7 +110,7 @@ SillyTavern 支持多種擴充功能。 | |||
| 110 | * 聊天記錄自動摘要 | 110 | * 聊天記錄自動摘要 |
| 111 | * 自動化介面與聊天翻譯 | 111 | * 自動化介面與聊天翻譯 |
| 112 | * 穩定擴散(Stable Diffusion)、FLUX 和 DALL-E 的影像生成整合 | 112 | * 穩定擴散(Stable Diffusion)、FLUX 和 DALL-E 的影像生成整合 |
| 113 | * 語音合成:AI 回應訊息可透過 ElevenLabs、Silero 或系統 TTS 語音合成 | 113 | * 語音合成:AI 回應的訊息可使用 ElevenLabs、Silero 或系統 TTS 語音合成 |
| 114 | * 網頁搜尋功能:為提示詞添加真實世界的上下文資訊 | 114 | * 網頁搜尋功能:為提示詞添加真實世界的上下文資訊 |
| 115 | * 更多擴展:可從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載 | 115 | * 更多擴展:可從「下載擴充功能 & 資源」(Download Extensions & Assets)選單中下載 |
| 116 | 116 | ||
| @@ -367,7 +367,7 @@ This program is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| 367 | * 經授權使用部分來自 CncAnon 的 TavernAITurbo 模組 | 367 | * 經授權使用部分來自 CncAnon 的 TavernAITurbo 模組 |
| 368 | * 視覺小說模式(Visual Novel Mode)的靈感,來源於 PepperTaco 的貢獻(<https://github.com/peppertaco/Tavern/>) | 368 | * 視覺小說模式(Visual Novel Mode)的靈感,來源於 PepperTaco 的貢獻(<https://github.com/peppertaco/Tavern/>) |
| 369 | * Noto Sans 字體由 Google 提供(OFL 許可) | 369 | * Noto Sans 字體由 Google 提供(OFL 許可) |
| 370 | * 主題圖示由 Font Awesome <https://fontawesome.com> 提供(圖示:CC BY 4.0,字體:SIL OFL 1.1,代碼:MIT 許可) | 370 | * 主題圖示由 Font Awesome <https://fontawesome.com> 提供(圖示:CC BY 4.0,字體:SIL OFL 1.1,程式碼:MIT 許可) |
| 371 | * 預設資源來源於 @OtisAlejandro(包含角色 Seraphina 與知識書)與 @kallmeflocc(SillyTavern 官方 Discord 伺服器成員突破 10K 的慶祝背景) | 371 | * 預設資源來源於 @OtisAlejandro(包含角色 Seraphina 與知識書)與 @kallmeflocc(SillyTavern 官方 Discord 伺服器成員突破 10K 的慶祝背景) |
| 372 | * Docker 安裝指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 編寫 | 372 | * Docker 安裝指南由 [@mrguymiah](https://github.com/mrguymiah) 和 [@Bronya-Rand](https://github.com/Bronya-Rand) 編寫 |
| 373 | 373 | ||
| @@ -84,6 +84,20 @@ autorun: true | |||
| 84 | # use if you don't have 'localhost' in your hosts file | 84 | # use if you don't have 'localhost' in your hosts file |
| 85 | avoidLocalhost: false | 85 | avoidLocalhost: false |
| 86 | 86 | ||
| 87 | ## BACKUP CONFIGURATION | ||
| 88 | backups: | ||
| 89 | # Common settings for all backup types | ||
| 90 | common: | ||
| 91 | # Number of backups to keep for each chat and settings file | ||
| 92 | numberOfBackups: 50 | ||
| 93 | chat: | ||
| 94 | # Enable automatic chat backups | ||
| 95 | enabled: true | ||
| 96 | # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups. | ||
| 97 | maxTotalBackups: -1 | ||
| 98 | # Interval in milliseconds to throttle chat backups per user | ||
| 99 | throttleInterval: 10000 | ||
| 100 | |||
| 87 | # THUMBNAILING CONFIGURATION | 101 | # THUMBNAILING CONFIGURATION |
| 88 | thumbnails: | 102 | thumbnails: |
| 89 | # Enable thumbnail generation | 103 | # Enable thumbnail generation |
| @@ -102,14 +116,6 @@ thumbnails: | |||
| 102 | allowKeysExposure: false | 116 | allowKeysExposure: false |
| 103 | # Skip new default content checks | 117 | # Skip new default content checks |
| 104 | skipContentCheck: false | 118 | skipContentCheck: false |
| 105 | # Disable automatic chats backup | ||
| 106 | disableChatBackup: false | ||
| 107 | # Number of backups to keep for each chat and settings file | ||
| 108 | numberOfBackups: 50 | ||
| 109 | # Maximum number of chat backups to keep per user (starting from the most recent). Set to -1 to keep all backups. | ||
| 110 | maxTotalChatBackups: -1 | ||
| 111 | # Interval in milliseconds to throttle chat backups per user | ||
| 112 | chatBackupThrottleInterval: 10000 | ||
| 113 | # Allowed hosts for card downloads | 119 | # Allowed hosts for card downloads |
| 114 | whitelistImportDomains: | 120 | whitelistImportDomains: |
| 115 | - localhost | 121 | - localhost |
| @@ -44,6 +44,26 @@ const keyMigrationMap = [ | |||
| 44 | newKey: 'thumbnails.format', | 44 | newKey: 'thumbnails.format', |
| 45 | migrate: (value) => (value ? 'png' : 'jpg'), | 45 | migrate: (value) => (value ? 'png' : 'jpg'), |
| 46 | }, | 46 | }, |
| 47 | { | ||
| 48 | oldKey: 'disableChatBackup', | ||
| 49 | newKey: 'backups.chat.enabled', | ||
| 50 | migrate: (value) => !value, | ||
| 51 | }, | ||
| 52 | { | ||
| 53 | oldKey: 'numberOfBackups', | ||
| 54 | newKey: 'backups.common.numberOfBackups', | ||
| 55 | migrate: (value) => value, | ||
| 56 | }, | ||
| 57 | { | ||
| 58 | oldKey: 'maxTotalChatBackups', | ||
| 59 | newKey: 'backups.chat.maxTotalBackups', | ||
| 60 | migrate: (value) => value, | ||
| 61 | }, | ||
| 62 | { | ||
| 63 | oldKey: 'chatBackupThrottleInterval', | ||
| 64 | newKey: 'backups.chat.throttleInterval', | ||
| 65 | migrate: (value) => value, | ||
| 66 | }, | ||
| 47 | ]; | 67 | ]; |
| 48 | 68 | ||
| 49 | /** | 69 | /** |
| @@ -308,14 +308,20 @@ async function sendMakerSuiteRequest(request, response) { | |||
| 308 | ) && request.body.use_makersuite_sysprompt; | 308 | ) && request.body.use_makersuite_sysprompt; |
| 309 | 309 | ||
| 310 | const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request)); | 310 | const prompt = convertGooglePrompt(request.body.messages, model, should_use_system_prompt, getPromptNames(request)); |
| 311 | let safetySettings = GEMINI_SAFETY; | ||
| 312 | |||
| 313 | if (model.includes('gemini-2.0-flash-exp')) { | ||
| 314 | safetySettings = GEMINI_SAFETY.map(setting => ({ ...setting, threshold: 'OFF' })); | ||
| 315 | } | ||
| 316 | |||
| 311 | let body = { | 317 | let body = { |
| 312 | contents: prompt.contents, | 318 | contents: prompt.contents, |
| 313 | safetySettings: GEMINI_SAFETY, | 319 | safetySettings: safetySettings, |
| 314 | generationConfig: generationConfig, | 320 | generationConfig: generationConfig, |
| 315 | }; | 321 | }; |
| 316 | 322 | ||
| 317 | if (should_use_system_prompt) { | 323 | if (should_use_system_prompt) { |
| 318 | body.system_instruction = prompt.system_instruction; | 324 | body.systemInstruction = prompt.system_instruction; |
| 319 | } | 325 | } |
| 320 | 326 | ||
| 321 | return body; | 327 | return body; |
| @@ -18,9 +18,9 @@ import { | |||
| 18 | formatBytes, | 18 | formatBytes, |
| 19 | } from '../util.js'; | 19 | } from '../util.js'; |
| 20 | 20 | ||
| 21 | const isBackupDisabled = getConfigValue('disableChatBackup', false); | 21 | const isBackupEnabled = !!getConfigValue('backups.chat.enabled', true); |
| 22 | const maxTotalChatBackups = Number(getConfigValue('maxTotalChatBackups', -1)); | 22 | const maxTotalChatBackups = Number(getConfigValue('backups.chat.maxTotalBackups', -1)); |
| 23 | const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000); | 23 | const throttleInterval = Number(getConfigValue('backups.chat.throttleInterval', 10_000)); |
| 24 | 24 | ||
| 25 | /** | 25 | /** |
| 26 | * Saves a chat to the backups directory. | 26 | * Saves a chat to the backups directory. |
| @@ -31,7 +31,7 @@ const throttleInterval = getConfigValue('chatBackupThrottleInterval', 10_000); | |||
| 31 | function backupChat(directory, name, chat) { | 31 | function backupChat(directory, name, chat) { |
| 32 | try { | 32 | try { |
| 33 | 33 | ||
| 34 | if (isBackupDisabled) { | 34 | if (!isBackupEnabled) { |
| 35 | return; | 35 | return; |
| 36 | } | 36 | } |
| 37 | 37 | ||
| @@ -389,10 +389,10 @@ export function generateTimestamp() { | |||
| 389 | * Remove old backups with the given prefix from a specified directory. | 389 | * Remove old backups with the given prefix from a specified directory. |
| 390 | * @param {string} directory The root directory to remove backups from. | 390 | * @param {string} directory The root directory to remove backups from. |
| 391 | * @param {string} prefix File prefix to filter backups by. | 391 | * @param {string} prefix File prefix to filter backups by. |
| 392 | * @param {number?} limit Maximum number of backups to keep. If null, the limit is determined by the `numberOfBackups` config value. | 392 | * @param {number?} limit Maximum number of backups to keep. If null, the limit is determined by the `backups.common.numberOfBackups` config value. |
| 393 | */ | 393 | */ |
| 394 | export function removeOldBackups(directory, prefix, limit = null) { | 394 | export function removeOldBackups(directory, prefix, limit = null) { |
| 395 | const MAX_BACKUPS = limit ?? Number(getConfigValue('numberOfBackups', 50)); | 395 | const MAX_BACKUPS = limit ?? Number(getConfigValue('backups.common.numberOfBackups', 50)); |
| 396 | 396 | ||
| 397 | let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); | 397 | let files = fs.readdirSync(directory).filter(f => f.startsWith(prefix)); |
| 398 | if (files.length > MAX_BACKUPS) { | 398 | if (files.length > MAX_BACKUPS) { |