mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	#284 Add a button to expose private keys
This commit is contained in:
		
							
								
								
									
										12
									
								
								config.conf
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								config.conf
									
									
									
									
									
								
							| @@ -8,7 +8,17 @@ const disableThumbnails = false; //Disables the generation of thumbnails, opting | |||||||
| const autorun = true; //Autorun in the browser. true/false | const autorun = true; //Autorun in the browser. true/false | ||||||
| const enableExtensions = true; //Enables support for TavernAI-extras project | const enableExtensions = true; //Enables support for TavernAI-extras project | ||||||
| const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine. | const listen = true; // If true, Can be access from other device or PC. otherwise can be access only from hosting machine. | ||||||
|  | const allowKeysExposure = false; // If true, private API keys could be fetched to the frontend. | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   port, whitelist, whitelistMode, basicAuthMode, basicAuthUser, autorun, enableExtensions, listen, disableThumbnails |   port, | ||||||
|  |   whitelist,  | ||||||
|  |   whitelistMode, | ||||||
|  |   basicAuthMode, | ||||||
|  |   basicAuthUser, | ||||||
|  |   autorun, | ||||||
|  |   enableExtensions, | ||||||
|  |   listen, | ||||||
|  |   disableThumbnails, | ||||||
|  |   allowKeysExposure, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1009,7 +1009,7 @@ | |||||||
|                                     Enter <span class="monospace">0000000000</span> to use anonymous mode. |                                     Enter <span class="monospace">0000000000</span> to use anonymous mode. | ||||||
|                                 </h5> |                                 </h5> | ||||||
|                                 <input id="horde_api_key" name="horde_api_key" class="text_pole" maxlength="500" type="text" placeholder="0000000000"> |                                 <input id="horde_api_key" name="horde_api_key" class="text_pole" maxlength="500" type="text" placeholder="0000000000"> | ||||||
|                                 <div class="neutral_warning">Your API key will removed from here after you reload the page for privacy reasons.</div> |                                 <div class="neutral_warning">For privacy reasons, your API key will hidden after you reload the page.</div> | ||||||
|                                 <h4 class="horde_model_title"> |                                 <h4 class="horde_model_title"> | ||||||
|                                     Model |                                     Model | ||||||
|                                     <div id="horde_refresh" title="Refresh models" class="right_menu_button"> |                                     <div id="horde_refresh" title="Refresh models" class="right_menu_button"> | ||||||
| @@ -1040,7 +1040,7 @@ | |||||||
|                                 </ol> |                                 </ol> | ||||||
|                             </span> |                             </span> | ||||||
|                             <input id="api_key_novel" name="api_key_novel" class="text_pole" maxlength="500" size="35" type="text"> |                             <input id="api_key_novel" name="api_key_novel" class="text_pole" maxlength="500" size="35" type="text"> | ||||||
|                             <div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div> |                             <div class="neutral_warning">For privacy reasons, your API key will hidden after you reload the page.</div> | ||||||
|                             <input id="api_button_novel" class="menu_button" type="submit" value="Connect"> |                             <input id="api_button_novel" class="menu_button" type="submit" value="Connect"> | ||||||
|                             <div id="api_loading_novel" class="api-load-icon fa-solid fa-hourglass fa-spin"></div> |                             <div id="api_loading_novel" class="api-load-icon fa-solid fa-hourglass fa-spin"></div> | ||||||
|                             <h4>Novel AI Model |                             <h4>Novel AI Model | ||||||
| @@ -1094,7 +1094,7 @@ | |||||||
|                                 </ol> |                                 </ol> | ||||||
|                             </span> |                             </span> | ||||||
|                             <input id="api_key_openai" name="api_key_openai" class="text_pole" maxlength="500" value="" type="text"> |                             <input id="api_key_openai" name="api_key_openai" class="text_pole" maxlength="500" value="" type="text"> | ||||||
|                             <div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div> |                             <div class="neutral_warning">For privacy reasons, your API key will hidden after you reload the page.</div> | ||||||
|                             <input id="api_button_openai" class="menu_button" type="submit" value="Connect"> |                             <input id="api_button_openai" class="menu_button" type="submit" value="Connect"> | ||||||
|                             <div id="api_loading_openai" class=" api-load-icon fa-solid fa-hourglass fa-spin"></div> |                             <div id="api_loading_openai" class=" api-load-icon fa-solid fa-hourglass fa-spin"></div> | ||||||
|                         </form> |                         </form> | ||||||
| @@ -1131,7 +1131,7 @@ | |||||||
|                         </span> |                         </span> | ||||||
|                         <div class="widthFreeExpand"> |                         <div class="widthFreeExpand"> | ||||||
|                             <input id="poe_token" class="text_pole" type="text" maxlength="100" /> |                             <input id="poe_token" class="text_pole" type="text" maxlength="100" /> | ||||||
|                             <div class="neutral_warning">Your API key will removed from here after you click "Connect" for privacy reasons.</div> |                             <div class="neutral_warning">For privacy reasons, your API key will hidden after you reload the page.</div> | ||||||
|                         </div> |                         </div> | ||||||
|  |  | ||||||
|                         <input id="poe_connect" class="menu_button" type="button" value="Connect" /> |                         <input id="poe_connect" class="menu_button" type="button" value="Connect" /> | ||||||
| @@ -1153,9 +1153,12 @@ | |||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" /> |                 <div class="flex-container alignitemscenter spaceBetween wide100p"> | ||||||
|                     Auto-connect to Last Server |                     <label for="auto-connect-checkbox" class="checkbox_label"><input id="auto-connect-checkbox" type="checkbox" /> | ||||||
|                 </label> |                         Auto-connect to Last Server | ||||||
|  |                     </label> | ||||||
|  |                     <a id="viewSecrets" href="javascript:void(0);">View hidden API keys</a> | ||||||
|  |                 </div> | ||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { getRequestHeaders } from "../script.js"; | import { callPopup, getRequestHeaders } from "../script.js"; | ||||||
|  |  | ||||||
| export const SECRET_KEYS = { | export const SECRET_KEYS = { | ||||||
|     HORDE: 'api_key_horde', |     HORDE: 'api_key_horde', | ||||||
| @@ -19,14 +19,37 @@ function updateSecretDisplay() { | |||||||
|         const validSecret = !!secret_state[secret_key]; |         const validSecret = !!secret_state[secret_key]; | ||||||
|         const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';  |         const placeholder = validSecret ? '✔️ Key saved' : '❌ Missing key';  | ||||||
|         $(input_selector).attr('placeholder', placeholder); |         $(input_selector).attr('placeholder', placeholder); | ||||||
|  |  | ||||||
|         // Horde doesn't have a connect button |  | ||||||
|         if (secret_key !== SECRET_KEYS.HORDE) { |  | ||||||
|             $(input_selector).val(''); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | async function viewSecrets() { | ||||||
|  |     const response = await fetch('/viewsecrets', { | ||||||
|  |         method: 'POST', | ||||||
|  |         headers: getRequestHeaders(), | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     if (response.status == 403) { | ||||||
|  |         callPopup('<h3>Forbidden</h3><p>To view your API keys here, set the value of allowKeysExposure to true in config.conf file and restart the SillyTavern server.</p>', 'text'); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!response.ok) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $('#dialogue_popup').addClass('wide_dialogue_popup'); | ||||||
|  |     const data = await response.json(); | ||||||
|  |     const table = document.createElement('table'); | ||||||
|  |     table.classList.add('responsiveTable'); | ||||||
|  |     $(table).append('<thead><th>Key</th><th>Value</th></thead>'); | ||||||
|  |      | ||||||
|  |     for (const [key,value] of Object.entries(data)) { | ||||||
|  |         $(table).append(`<tr><td>${DOMPurify.sanitize(key)}</td><td>${DOMPurify.sanitize(value)}</td></tr>`); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     callPopup(table.outerHTML, 'text'); | ||||||
|  | } | ||||||
|  |  | ||||||
| export let secret_state = {}; | export let secret_state = {}; | ||||||
|  |  | ||||||
| export async function writeSecret(key, value) { | export async function writeSecret(key, value) { | ||||||
| @@ -65,3 +88,7 @@ export async function readSecretState() { | |||||||
|         console.error('Could not read secrets file'); |         console.error('Could not read secrets file'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | jQuery(() => { | ||||||
|  |     $('#viewSecrets').on('click', viewSecrets); | ||||||
|  | }); | ||||||
| @@ -109,6 +109,24 @@ body { | |||||||
|     background-clip: content-box; |     background-clip: content-box; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | table.responsiveTable { | ||||||
|  |     width: 100%; | ||||||
|  |     margin: 10px 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .responsiveTable tr { | ||||||
|  |     display: flex; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .responsiveTable, | ||||||
|  | .responsiveTable th, | ||||||
|  | .responsiveTable td { | ||||||
|  |     flex: 1; | ||||||
|  |     border: 1px solid; | ||||||
|  |     border-collapse: collapse; | ||||||
|  |     word-break: break-all; | ||||||
|  |     padding: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
| .sysHR { | .sysHR { | ||||||
|     border-top: 2px solid grey; |     border-top: 2px solid grey; | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								readme.md
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								readme.md
									
									
									
									
									
								
							| @@ -125,6 +125,17 @@ Get in touch with the developers directly: | |||||||
|   1. Run the `start.sh` script. |   1. Run the `start.sh` script. | ||||||
|   2. Enjoy. |   2. Enjoy. | ||||||
|  |  | ||||||
|  | ## API keys management | ||||||
|  |  | ||||||
|  | SillyTavern saves your API keys to a `secrets.json` file in the server directory. | ||||||
|  |  | ||||||
|  | By default they will not be exposed to a frontend after you enter them and reload the page. | ||||||
|  |  | ||||||
|  | In order to enable viewing your keys by clicking a button in the API block: | ||||||
|  |  | ||||||
|  | 1. Set the value of `allowKeysExposure` to `true` in `config.conf` file. | ||||||
|  | 2. Restart the SillyTavern server. | ||||||
|  |  | ||||||
| ## Remote connections | ## Remote connections | ||||||
|  |  | ||||||
| Most often this is for people who want to use SillyTavern on their mobile phones while at home. | Most often this is for people who want to use SillyTavern on their mobile phones while at home. | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								server.js
									
									
									
									
									
								
							| @@ -77,6 +77,7 @@ const whitelistMode = config.whitelistMode; | |||||||
| const autorun = config.autorun && !cliArguments.ssl; | const autorun = config.autorun && !cliArguments.ssl; | ||||||
| const enableExtensions = config.enableExtensions; | const enableExtensions = config.enableExtensions; | ||||||
| const listen = config.listen; | const listen = config.listen; | ||||||
|  | const allowKeysExposure = config.allowKeysExposure; | ||||||
|  |  | ||||||
| const axios = require('axios'); | const axios = require('axios'); | ||||||
| const tiktoken = require('@dqbd/tiktoken'); | const tiktoken = require('@dqbd/tiktoken'); | ||||||
| @@ -2894,6 +2895,27 @@ app.post('/generate_horde', jsonParser, async (request, response) => { | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | app.post('/viewsecrets', jsonParser, async (_, response) => { | ||||||
|  |     if (!allowKeysExposure) { | ||||||
|  |         console.error('secrets.json could not be viewed unless the value of allowKeysExposure in config.conf is set to true'); | ||||||
|  |         return response.sendStatus(403); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (!fs.existsSync(SECRETS_FILE)) { | ||||||
|  |         console.error('secrets.json does not exist'); | ||||||
|  |         return response.sendStatus(404); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         const fileContents = fs.readFileSync(SECRETS_FILE); | ||||||
|  |         const secrets = JSON.parse(fileContents); | ||||||
|  |         return response.send(secrets); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error(error); | ||||||
|  |         return response.sendStatus(500); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
| function writeSecret(key, value) { | function writeSecret(key, value) { | ||||||
|     if (!fs.existsSync(SECRETS_FILE)) { |     if (!fs.existsSync(SECRETS_FILE)) { | ||||||
|         const emptyFile = JSON.stringify({}); |         const emptyFile = JSON.stringify({}); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user