├── manifest.json ├── README.md ├── style.css ├── rewrite_settings.html └── index.js /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "Rewrite Extension", 3 | "loading_order": 39, 4 | "requires": [], 5 | "optional": [], 6 | "js": "index.js", 7 | "css": "style.css", 8 | "author": "SplitClover", 9 | "version": "1.3.0", 10 | "homePage": "" 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rewrite Extension for SillyTavern 2 | 3 | > [!IMPORTANT] 4 | > **2025.09.02** - This is extension is no longer maintained for the foreseeable future. 5 | 6 | ## Overview 7 | 8 | The Rewrite Extension enhances the chat experience in SillyTavern by allowing users to dynamically rewrite, shorten, or expand selected text within messages. Works for chat completion, text completion and NovelAI. 9 | 10 | ## Features 11 | 12 | - Custom {{rewrite}} macro that contains the selected text 13 | - Custom {{targetmessage}} macro that contains the full targeted message 14 | - Custom {{rewritecount}} macro that returns a numeric (39) count of words selected 15 | - Rewrite, shorten, or expand selected text in chat messages, with an added delete button 16 | - Convenient undo button 17 | - Real-time streaming of rewritten text 18 | - Temporary highlighting of modified text for easy identification 19 | - Ability to abort ongoing rewrites 20 | 21 | ## Installation 22 | 23 | Use SillyTavern's built-in extension installer: 24 | `https://github.com/splitclover/rewrite-extension` 25 | 26 | ## Usage 27 | 28 | To use the Rewrite Extension: 29 | 30 | 1. Configure the extension (see below) 31 | 2. Select text within a single(!) chat message 32 | 3. A context menu will appear with options to Rewrite, Shorten, or Expand 33 | 4. Choose the desired option 34 | 5. The selected text will be replaced with the AI-generated modification 35 | 36 | ## Configuration 37 | 38 | 1. Open the Extension tab -> Rewrite Extension 39 | 2. Set presets for Rewrite, Shorten, and Expand operations 40 | 3. Adjust the highlight duration for modified text 41 | 42 | ## Contributing 43 | 44 | Contributions to improve the Rewrite Extension are welcome. Please fork the repository and submit a pull request with your changes. -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @keyframes vibrantWavyBackground { 2 | 0% { 3 | background-position: 0% 50%; 4 | box-shadow: 0 0 10px rgba(26, 35, 126, 0.7); 5 | } 6 | 25% { 7 | background-position: 50% 100%; 8 | box-shadow: 0 0 15px rgba(74, 20, 140, 0.7); 9 | } 10 | 50% { 11 | background-position: 100% 50%; 12 | box-shadow: 0 0 20px rgba(26, 35, 126, 0.7); 13 | } 14 | 75% { 15 | background-position: 50% 0%; 16 | box-shadow: 0 0 15px rgba(74, 20, 140, 0.7); 17 | } 18 | 100% { 19 | background-position: 0% 50%; 20 | box-shadow: 0 0 10px rgba(26, 35, 126, 0.7); 21 | } 22 | } 23 | 24 | .animated-highlight { 25 | background: linear-gradient(45deg, #1a237e, #4a148c, #7b1fa2, #4a148c, #1a237e); 26 | background-size: 300% 300%; 27 | animation: vibrantWavyBackground 6s ease infinite; 28 | border-radius: 8px; 29 | padding: 2px 6px; 30 | color: white; 31 | display: inline-block; 32 | box-decoration-break: clone; 33 | -webkit-box-decoration-break: clone; 34 | transition: all 0.3s ease; 35 | } 36 | 37 | .rewrite-extension_block textarea.text_pole { 38 | width: 100%; 39 | min-height: 60px; 40 | resize: vertical; 41 | padding: 5px; 42 | box-sizing: border-box; 43 | } 44 | 45 | .rewrite-extension-settings .flex-container { 46 | display: flex; 47 | align-items: center; 48 | margin-bottom: 10px; 49 | position: relative; 50 | } 51 | 52 | .rewrite-extension-settings label { 53 | flex-grow: 1; 54 | margin-right: 10px; 55 | } 56 | 57 | .rewrite-extension-settings .tooltip-icon { 58 | font-size: var(--mainFontSize); 59 | color: var(--SmartThemeBodyColor); 60 | cursor: pointer; 61 | margin-right: 10px; 62 | } 63 | 64 | .rewrite-extension-settings .tooltip-icon:hover::after { 65 | content: attr(data-tooltip); 66 | position: absolute; 67 | right: 30px; /* Adjust this value as needed */ 68 | top: 50%; 69 | transform: translateY(-50%); 70 | background-color: var(--SmartThemeBlurTintColor); 71 | color: var(--SmartThemeBodyColor); 72 | padding: 8px 12px; 73 | border-radius: 5px; 74 | font-size: calc(var(--mainFontSize) * 0.9); 75 | font-weight: normal; 76 | font-family: var(--mainFontFamily); 77 | white-space: normal; 78 | max-width: 250px; 79 | z-index: 1000; 80 | text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor); 81 | border: 1px solid var(--SmartThemeBorderColor); 82 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 83 | } 84 | 85 | .rewrite-extension-settings .inline-drawer-content > div { 86 | margin-bottom: 15px; 87 | } 88 | 89 | .rewrite-extension-settings .checkbox-container { 90 | justify-content: space-between; 91 | } 92 | 93 | .rewrite-extension-settings .checkbox-label-group { 94 | display: flex; 95 | align-items: center; 96 | } 97 | 98 | .rewrite-extension-settings .checkbox-label-group label { 99 | margin-left: 5px; 100 | order: 1; 101 | } 102 | 103 | .rewrite-extension-settings .checkbox-label-group input[type="checkbox"] { 104 | order: 0; 105 | margin-right: 0; 106 | } 107 | 108 | .rewrite-extension-settings .checkbox-container .tooltip-icon { 109 | margin-left: 10px; 110 | } 111 | 112 | .rewrite-extension-settings .checkbox-container .tooltip-icon:hover::after { 113 | right: 25px; 114 | } 115 | 116 | .rewrite-extension_block .flex-container > div { 117 | display: flex; 118 | align-items: center; 119 | justify-content: space-between; 120 | } 121 | 122 | .rewrite-extension_block .flex-container > div .tooltip-icon { 123 | margin-left: 5px; 124 | } 125 | 126 | .rewrite-extension-settings .inline-drawer { 127 | width: 100%; 128 | } 129 | 130 | .rewrite-extension-settings .inline-drawer-content { 131 | width: 100%; 132 | } 133 | 134 | .rewrite-extension-settings .inline-drawer-toggle { 135 | width: 100%; 136 | display: flex; 137 | justify-content: space-between; 138 | align-items: center; 139 | cursor: pointer; 140 | padding: 5px 0; 141 | } 142 | 143 | .rewrite-extension-settings .inline-drawer-content .checkbox-container { 144 | display: flex; 145 | justify-content: flex-start; 146 | align-items: center; 147 | margin-bottom: 5px; 148 | } 149 | 150 | .rewrite-extension-settings .inline-drawer-content .checkbox-container input[type="checkbox"] { 151 | margin-right: 5px; 152 | } 153 | 154 | .rewrite-extension-settings .inline-drawer-content .checkbox-container label { 155 | margin-right: 0; 156 | } 157 | 158 | /* Ensure the tooltip doesn't go off-screen on smaller screens */ 159 | @media (max-width: 768px) { 160 | .rewrite-extension-settings .tooltip-icon:hover::after { 161 | right: 0; 162 | left: 50%; 163 | transform: translateX(-50%); 164 | top: 100%; 165 | margin-top: 5px; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /rewrite_settings.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | Rewrite Extension 5 |
6 |
7 |
8 |
9 | 10 | 11 | 15 |
16 | 17 |
18 |
19 | 20 | 21 | 24 |
25 | 26 |
27 | 28 | 31 |
32 | 33 |
34 | 35 | 38 |
39 | 40 |
41 | 42 | 45 |
46 | 47 |
48 |
49 | 50 | 51 |
52 | 53 |
54 |
55 | 56 | 77 | 78 |
79 | 80 | 81 |
82 |
83 | 84 | 85 |
86 | 87 |
88 | 89 |
90 |
91 | 92 | 93 |
94 | 95 |
96 | 97 |
98 |
99 | 100 | 101 |
102 | 103 |
104 | 105 | 106 |
107 |
108 | 109 | 110 |
111 |
112 | 113 | 114 |
115 |
116 | 117 | 118 |
119 |
120 | 121 | 122 |
123 |
124 | 125 | 171 | 172 |
173 |
174 |
175 | 176 | 177 |
178 |
179 | 180 | 181 |
182 |
183 |
184 | 185 | 186 |
187 |
188 | 189 |
190 | 191 | 192 | 193 |
194 | 195 |
196 |
197 |
198 |
199 | Button visibility 200 |
201 |
202 | 224 |
225 |
226 |
227 |
228 |
229 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { sendOpenAIRequest, oai_settings } from "../../../openai.js"; 2 | import { extractAllWords } from "../../../utils.js"; 3 | import { getTokenCount } from "../../../tokenizers.js"; 4 | import { getNovelGenerationData, generateNovelWithStreaming, nai_settings } from "../../../nai-settings.js"; 5 | import { generateHorde, MIN_LENGTH } from "../../../horde.js"; 6 | import { getTextGenGenerationData, generateTextGenWithStreaming } from "../../../textgen-settings.js"; 7 | import { 8 | main_api, 9 | novelai_settings, 10 | novelai_setting_names, 11 | eventSource, 12 | event_types, 13 | saveSettingsDebounced, 14 | messageFormatting, 15 | addCopyToCodeBlocks, 16 | getRequestHeaders, 17 | generateRaw, 18 | } from "../../../../script.js"; 19 | import { extension_settings, getContext } from "../../../extensions.js"; 20 | import { getRegexedString, regex_placement } from '../../regex/engine.js'; // Import from built-in regex extension 21 | 22 | const extensionName = "rewrite-extension"; 23 | const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`; 24 | 25 | const undo_steps = 15; 26 | 27 | // Default settings 28 | const defaultSettings = { 29 | rewritePreset: "", 30 | shortenPreset: "", 31 | expandPreset: "", 32 | customPreset: "", 33 | highlightDuration: 3000, 34 | selectedModel: "chat_completion", 35 | textRewritePrompt: `[INST]Rewrite this section of text: """{{rewrite}}""" while keeping the same content, general style and length. Do not list alternatives and only print the result without prefix or suffix.[/INST] 36 | 37 | Sure, here is only the rewritten text without any comments: `, 38 | textShortenPrompt: `[INST]Rewrite this section of text: """{{rewrite}}""" while keeping the same content, general style. Do not list alternatives and only print the result without prefix or suffix. Shorten it by roughly 20%.[/INST] 39 | 40 | Sure, here is only the rewritten text without any comments: `, 41 | textExpandPrompt: `[INST]Rewrite this section of text: """{{rewrite}}""" while keeping the same content, general style. Do not list alternatives and only print the result without prefix or suffix. Lengthen it by roughly 20%.[/INST] 42 | 43 | Sure, here is only the rewritten text without any comments: `, 44 | textCustomPrompt: `[INST]Rewrite this section of text: """{{rewrite}}""" according to the following instructions: "{{custom_instructions}}". Keep the general style. Do not list alternatives and only print the result without prefix or suffix.[/INST] 45 | 46 | Sure, here is only the rewritten text without any comments: `, 47 | useStreaming: true, 48 | useDynamicTokens: true, 49 | dynamicTokenMode: 'multiplicative', 50 | rewriteTokens: 100, 51 | shortenTokens: 50, 52 | expandTokens: 150, 53 | customTokens: 100, 54 | rewriteTokensAdd: 0, 55 | shortenTokensAdd: -50, 56 | expandTokensAdd: 50, 57 | customTokensAdd: 0, 58 | rewriteTokensMult: 1.05, 59 | shortenTokensMult: 0.8, 60 | expandTokensMult: 1.5, 61 | customTokensMult: 1.0, 62 | removePrefix: `"`, 63 | removeSuffix: `"`, 64 | overrideMaxTokens: true, 65 | showRewrite: true, 66 | showShorten: true, 67 | showExpand: true, 68 | showCustom: true, 69 | showDelete: true, 70 | applyRegexOnRewrite: true, // New setting to control regex application 71 | }; 72 | 73 | let rewriteMenu = null; 74 | let lastSelection = null; 75 | let abortController; 76 | 77 | let changeHistory = []; 78 | 79 | // Load settings 80 | function loadSettings() { 81 | extension_settings[extensionName] = extension_settings[extensionName] || {}; 82 | 83 | // Helper function to get a setting with a default value 84 | const getSetting = (key, defaultValue) => { 85 | return extension_settings[extensionName][key] !== undefined 86 | ? extension_settings[extensionName][key] 87 | : defaultValue; 88 | }; 89 | 90 | // Load settings, using defaults if not set 91 | $("#rewrite_preset").val(getSetting('rewritePreset', defaultSettings.rewritePreset)); 92 | $("#shorten_preset").val(getSetting('shortenPreset', defaultSettings.shortenPreset)); 93 | $("#expand_preset").val(getSetting('expandPreset', defaultSettings.expandPreset)); 94 | $("#custom_preset").val(getSetting('customPreset', defaultSettings.customPreset)); 95 | $("#highlight_duration").val(getSetting('highlightDuration', defaultSettings.highlightDuration)); 96 | $("#rewrite_extension_model_select").val(getSetting('selectedModel', defaultSettings.selectedModel)); 97 | $("#text_rewrite_prompt").val(getSetting('textRewritePrompt', defaultSettings.textRewritePrompt)); 98 | $("#text_shorten_prompt").val(getSetting('textShortenPrompt', defaultSettings.textShortenPrompt)); 99 | $("#text_expand_prompt").val(getSetting('textExpandPrompt', defaultSettings.textExpandPrompt)); 100 | $("#text_custom_prompt").val(getSetting('textCustomPrompt', defaultSettings.textCustomPrompt)); 101 | $("#use_streaming").prop('checked', getSetting('useStreaming', defaultSettings.useStreaming)); 102 | $("#use_dynamic_tokens").prop('checked', getSetting('useDynamicTokens', defaultSettings.useDynamicTokens)); 103 | $("#dynamic_token_mode").val(getSetting('dynamicTokenMode', defaultSettings.dynamicTokenMode)); 104 | $("#rewrite_tokens").val(getSetting('rewriteTokens', defaultSettings.rewriteTokens)); 105 | $("#shorten_tokens").val(getSetting('shortenTokens', defaultSettings.shortenTokens)); 106 | $("#expand_tokens").val(getSetting('expandTokens', defaultSettings.expandTokens)); 107 | $("#custom_tokens").val(getSetting('customTokens', defaultSettings.customTokens)); 108 | $("#rewrite_tokens_add").val(getSetting('rewriteTokensAdd', defaultSettings.rewriteTokensAdd)); 109 | $("#shorten_tokens_add").val(getSetting('shortenTokensAdd', defaultSettings.shortenTokensAdd)); 110 | $("#expand_tokens_add").val(getSetting('expandTokensAdd', defaultSettings.expandTokensAdd)); 111 | $("#custom_tokens_add").val(getSetting('customTokensAdd', defaultSettings.customTokensAdd)); 112 | $("#rewrite_tokens_mult").val(getSetting('rewriteTokensMult', defaultSettings.rewriteTokensMult)); 113 | $("#shorten_tokens_mult").val(getSetting('shortenTokensMult', defaultSettings.shortenTokensMult)); 114 | $("#expand_tokens_mult").val(getSetting('expandTokensMult', defaultSettings.expandTokensMult)); 115 | $("#custom_tokens_mult").val(getSetting('customTokensMult', defaultSettings.customTokensMult)); 116 | $("#remove_prefix").val(getSetting('removePrefix', defaultSettings.removePrefix)); 117 | $("#remove_suffix").val(getSetting('removeSuffix', defaultSettings.removeSuffix)); 118 | $("#override_max_tokens").prop('checked', getSetting('overrideMaxTokens', defaultSettings.overrideMaxTokens)); 119 | $("#show_rewrite").prop('checked', getSetting('showRewrite', defaultSettings.showRewrite)); 120 | $("#show_shorten").prop('checked', getSetting('showShorten', defaultSettings.showShorten)); 121 | $("#show_expand").prop('checked', getSetting('showExpand', defaultSettings.showExpand)); 122 | $("#show_custom").prop('checked', getSetting('showCustom', defaultSettings.showCustom)); 123 | $("#show_delete").prop('checked', getSetting('showDelete', defaultSettings.showDelete)); 124 | $("#apply_regex_on_rewrite").prop('checked', getSetting('applyRegexOnRewrite', defaultSettings.applyRegexOnRewrite)); // Load new setting 125 | 126 | // Update the UI based on loaded settings 127 | updateModelSettings(); 128 | updateTokenSettings(); 129 | } 130 | 131 | function saveSettings() { 132 | extension_settings[extensionName] = { 133 | rewritePreset: $("#rewrite_preset").val(), 134 | shortenPreset: $("#shorten_preset").val(), 135 | expandPreset: $("#expand_preset").val(), 136 | customPreset: $("#custom_preset").val(), 137 | highlightDuration: parseInt($("#highlight_duration").val()), 138 | selectedModel: $("#rewrite_extension_model_select").val(), 139 | textRewritePrompt: $("#text_rewrite_prompt").val(), 140 | textShortenPrompt: $("#text_shorten_prompt").val(), 141 | textExpandPrompt: $("#text_expand_prompt").val(), 142 | textCustomPrompt: $("#text_custom_prompt").val(), 143 | useStreaming: $("#use_streaming").is(':checked'), 144 | useDynamicTokens: $("#use_dynamic_tokens").is(':checked'), 145 | dynamicTokenMode: $("#dynamic_token_mode").val(), 146 | rewriteTokens: parseInt($("#rewrite_tokens").val()), 147 | shortenTokens: parseInt($("#shorten_tokens").val()), 148 | expandTokens: parseInt($("#expand_tokens").val()), 149 | customTokens: parseInt($("#custom_tokens").val()), 150 | rewriteTokensAdd: parseInt($("#rewrite_tokens_add").val()), 151 | shortenTokensAdd: parseInt($("#shorten_tokens_add").val()), 152 | expandTokensAdd: parseInt($("#expand_tokens_add").val()), 153 | customTokensAdd: parseInt($("#custom_tokens_add").val()), 154 | rewriteTokensMult: parseFloat($("#rewrite_tokens_mult").val()), 155 | shortenTokensMult: parseFloat($("#shorten_tokens_mult").val()), 156 | expandTokensMult: parseFloat($("#expand_tokens_mult").val()), 157 | customTokensMult: parseFloat($("#custom_tokens_mult").val()), 158 | removePrefix: $("#remove_prefix").val(), 159 | removeSuffix: $("#remove_suffix").val(), 160 | overrideMaxTokens: $("#override_max_tokens").is(':checked'), 161 | showRewrite: $("#show_rewrite").is(':checked'), 162 | showShorten: $("#show_shorten").is(':checked'), 163 | showExpand: $("#show_expand").is(':checked'), 164 | showCustom: $("#show_custom").is(':checked'), 165 | showDelete: $("#show_delete").is(':checked'), 166 | applyRegexOnRewrite: $("#apply_regex_on_rewrite").is(':checked'), // Save new setting 167 | }; 168 | 169 | // Ensure all settings have a value, using defaults if necessary 170 | for (const [key, value] of Object.entries(defaultSettings)) { 171 | if (extension_settings[extensionName][key] === undefined) { 172 | extension_settings[extensionName][key] = value; 173 | } 174 | } 175 | 176 | saveSettingsDebounced(); 177 | } 178 | 179 | // Populate dropdowns 180 | async function populateDropdowns() { 181 | const result = await fetch('/api/settings/get', { 182 | method: 'POST', 183 | headers: getContext().getRequestHeaders(), 184 | body: JSON.stringify({}), 185 | }); 186 | 187 | if (result.ok) { 188 | const data = await result.json(); 189 | const presets = data.openai_setting_names; 190 | const dropdowns = ['rewrite_preset', 'shorten_preset', 'expand_preset', 'custom_preset']; // Added custom_preset 191 | dropdowns.forEach(dropdown => { 192 | const select = $(`#${dropdown}`); 193 | select.empty(); 194 | presets.forEach(preset => { 195 | select.append($('