Merge branch 'staging' into image-swipes

e2a2e3869e80649e6e0d478f86bafb721f78bd18

Cohee <18619528+Cohee1207@users.noreply.github.com>

11 files changed, +53 -12Showing whitespace changes
CONTRIBUTING.md+5 -0
@@ -34,3 +34,8 @@
34 - What did you do to achieve this?34 - What did you do to achieve this?
35 - How would a reviewer test the change?35 - How would a reviewer test the change?
366. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.366. Mind the license. Your contributions will be licensed under the GNU Affero General Public License. If you don't know what that implies, consult your lawyer.
37
38## Further reading
39
401. [How to write UI extensions](https://docs.sillytavern.app/for-contributors/writing-extensions/)
412. [How to write server plugins](https://docs.sillytavern.app/for-contributors/server-plugins)
package-lock.json+4 -5
@@ -1391,12 +1391,11 @@
1391 "license": "MIT"1391 "license": "MIT"
1392 },1392 },
1393 "node_modules/axios": {1393 "node_modules/axios": {
1394 "version": "1.6.1",1394 "version": "1.7.4",
1395 "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.1.tgz",1395 "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
1396 "integrity": "sha512-vfBmhDpKafglh0EldBEbVuoe7DyAavGSLWhuSm5ZSEKQnHhBf0xAAwybbNH1IkrJNGnS/VG4I5yxig1pCEXE4g==",1396 "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
1397 "license": "MIT",
1398 "dependencies": {1397 "dependencies": {
1399 "follow-redirects": "^1.15.0",1398 "follow-redirects": "^1.15.6",
1400 "form-data": "^4.0.0",1399 "form-data": "^4.0.0",
1401 "proxy-from-env": "^1.1.0"1400 "proxy-from-env": "^1.1.0"
1402 }1401 }
public/index.html+2 -1
@@ -383,7 +383,7 @@
383 Max Response Length (tokens)383 Max Response Length (tokens)
384 </div>384 </div>
385 <div class="wide100p">385 <div class="wide100p">
386 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="50" max="8000">386 <input type="number" id="openai_max_tokens" name="openai_max_tokens" class="text_pole" min="1" max="16384">
387 </div>387 </div>
388 </div>388 </div>
389 <div class="range-block" data-source="openai,custom">389 <div class="range-block" data-source="openai,custom">
@@ -2573,6 +2573,7 @@
2573 <option value="gpt-4o">gpt-4o</option>2573 <option value="gpt-4o">gpt-4o</option>
2574 <option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option>2574 <option value="gpt-4o-2024-08-06">gpt-4o-2024-08-06</option>
2575 <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>2575 <option value="gpt-4o-2024-05-13">gpt-4o-2024-05-13</option>
2576 <option value="chatgpt-4o-latest">chatgpt-4o-latest</option>
2576 </optgroup>2577 </optgroup>
2577 <optgroup label="gpt-4o-mini">2578 <optgroup label="gpt-4o-mini">
2578 <option value="gpt-4o-mini">gpt-4o-mini</option>2579 <option value="gpt-4o-mini">gpt-4o-mini</option>
public/scripts/extensions/quick-reply/api/QuickReplyApi.js+6 -0
@@ -204,6 +204,7 @@ export class QuickReplyApi {
204 * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message204 * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
205 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded205 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
206 * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected206 * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
207 * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created
207 * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated208 * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
208 * @returns {QuickReply} the new quick reply209 * @returns {QuickReply} the new quick reply
209 */210 */
@@ -218,6 +219,7 @@ export class QuickReplyApi {
218 executeOnAi,219 executeOnAi,
219 executeOnChatChange,220 executeOnChatChange,
220 executeOnGroupMemberDraft,221 executeOnGroupMemberDraft,
222 executeOnNewChat,
221 automationId,223 automationId,
222 } = {}) {224 } = {}) {
223 const set = this.getSetByName(setName);225 const set = this.getSetByName(setName);
@@ -236,6 +238,7 @@ export class QuickReplyApi {
236 qr.executeOnAi = executeOnAi ?? false;238 qr.executeOnAi = executeOnAi ?? false;
237 qr.executeOnChatChange = executeOnChatChange ?? false;239 qr.executeOnChatChange = executeOnChatChange ?? false;
238 qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;240 qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? false;
241 qr.executeOnNewChat = executeOnNewChat ?? false;
239 qr.automationId = automationId ?? '';242 qr.automationId = automationId ?? '';
240 qr.onUpdate();243 qr.onUpdate();
241 return qr;244 return qr;
@@ -258,6 +261,7 @@ export class QuickReplyApi {
258 * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message261 * @param {boolean} [props.executeOnAi] whether to execute the quick reply after the AI has sent a message
259 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded262 * @param {boolean} [props.executeOnChatChange] whether to execute the quick reply when a new chat is loaded
260 * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected263 * @param {boolean} [props.executeOnGroupMemberDraft] whether to execute the quick reply when a group member is selected
264 * @param {boolean} [props.executeOnNewChat] whether to execute the quick reply when a new chat is created
261 * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated265 * @param {string} [props.automationId] when not empty, the quick reply will be executed when the WI with the given automation ID is activated
262 * @returns {QuickReply} the altered quick reply266 * @returns {QuickReply} the altered quick reply
263 */267 */
@@ -273,6 +277,7 @@ export class QuickReplyApi {
273 executeOnAi,277 executeOnAi,
274 executeOnChatChange,278 executeOnChatChange,
275 executeOnGroupMemberDraft,279 executeOnGroupMemberDraft,
280 executeOnNewChat,
276 automationId,281 automationId,
277 } = {}) {282 } = {}) {
278 const qr = this.getQrByLabel(setName, label);283 const qr = this.getQrByLabel(setName, label);
@@ -290,6 +295,7 @@ export class QuickReplyApi {
290 qr.executeOnAi = executeOnAi ?? qr.executeOnAi;295 qr.executeOnAi = executeOnAi ?? qr.executeOnAi;
291 qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;296 qr.executeOnChatChange = executeOnChatChange ?? qr.executeOnChatChange;
292 qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;297 qr.executeOnGroupMemberDraft = executeOnGroupMemberDraft ?? qr.executeOnGroupMemberDraft;
298 qr.executeOnNewChat = executeOnNewChat ?? qr.executeOnNewChat;
293 qr.automationId = automationId ?? qr.automationId;299 qr.automationId = automationId ?? qr.automationId;
294 qr.onUpdate();300 qr.onUpdate();
295 return qr;301 return qr;
public/scripts/extensions/quick-reply/html/qrEditor.html+4 -0
@@ -109,6 +109,10 @@
109 <span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>109 <span><i class="fa-solid fa-fw fa-message"></i><span data-i18n="Execute on chat change">Execute on chat change</span></span>
110 </label>110 </label>
111 <label class="checkbox_label">111 <label class="checkbox_label">
112 <input type="checkbox" id="qr--executeOnNewChat">
113 <span><i class="fa-solid fa-fw fa-comments"></i><span data-i18n="Execute on new chat">Execute on new chat</span></span>
114 </label>
115 <label class="checkbox_label">
112 <input type="checkbox" id="qr--executeOnGroupMemberDraft">116 <input type="checkbox" id="qr--executeOnGroupMemberDraft">
113 <span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>117 <span><i class="fa-solid fa-fw fa-people-group"></i><span data-i18n="Execute on group member draft">Execute on group member draft</span></span>
114 </label>118 </label>
public/scripts/extensions/quick-reply/index.js+6 -0
@@ -105,6 +105,7 @@ const loadSets = async () => {
105 qr.executeOnAi = slot.autoExecute_botMessage ?? false;105 qr.executeOnAi = slot.autoExecute_botMessage ?? false;
106 qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;106 qr.executeOnChatChange = slot.autoExecute_chatLoad ?? false;
107 qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;107 qr.executeOnGroupMemberDraft = slot.autoExecute_groupMemberDraft ?? false;
108 qr.executeOnNewChat = slot.autoExecute_newChat ?? false;
108 qr.automationId = slot.automationId ?? '';109 qr.automationId = slot.automationId ?? '';
109 qr.contextList = (slot.contextMenu ?? []).map(it=>({110 qr.contextList = (slot.contextMenu ?? []).map(it=>({
110 set: it.preset,111 set: it.preset,
@@ -260,3 +261,8 @@ const onWIActivation = async (entries) => {
260 await autoExec.handleWIActivation(entries);261 await autoExec.handleWIActivation(entries);
261};262};
262eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args));263eventSource.on(event_types.WORLD_INFO_ACTIVATED, (...args) => executeIfReadyElseQueue(onWIActivation, args));
264
265const onNewChat = async () => {
266 await autoExec.handleNewChat();
267};
268eventSource.on(event_types.CHAT_CREATED, (...args) => executeIfReadyElseQueue(onNewChat, args));
public/scripts/extensions/quick-reply/src/AutoExecuteHandler.js+9 -0
@@ -83,6 +83,15 @@ export class AutoExecuteHandler {
83 await this.performAutoExecute(qrList);83 await this.performAutoExecute(qrList);
84 }84 }
8585
86 async handleNewChat() {
87 if (!this.checkExecute()) return;
88 const qrList = [
89 ...this.settings.config.setList.map(link=>link.set.qrList.filter(qr=>qr.executeOnNewChat)).flat(),
90 ...(this.settings.chatConfig?.setList?.map(link=>link.set.qrList.filter(qr=>qr.executeOnNewChat))?.flat() ?? []),
91 ];
92 await this.performAutoExecute(qrList);
93 }
94
86 /**95 /**
87 * @param {any[]} entries Set of activated entries96 * @param {any[]} entries Set of activated entries
88 */97 */
public/scripts/extensions/quick-reply/src/QuickReply.js+8 -0
@@ -44,6 +44,7 @@ export class QuickReply {
44 /**@type {boolean}*/ executeOnAi = false;44 /**@type {boolean}*/ executeOnAi = false;
45 /**@type {boolean}*/ executeOnChatChange = false;45 /**@type {boolean}*/ executeOnChatChange = false;
46 /**@type {boolean}*/ executeOnGroupMemberDraft = false;46 /**@type {boolean}*/ executeOnGroupMemberDraft = false;
47 /**@type {boolean}*/ executeOnNewChat = false;
47 /**@type {string}*/ automationId = '';48 /**@type {string}*/ automationId = '';
4849
49 /**@type {function}*/ onExecute;50 /**@type {function}*/ onExecute;
@@ -1061,6 +1062,13 @@ export class QuickReply {
1061 this.updateContext();1062 this.updateContext();
1062 });1063 });
1063 /**@type {HTMLInputElement}*/1064 /**@type {HTMLInputElement}*/
1065 const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat');
1066 executeOnNewChat.checked = this.executeOnNewChat;
1067 executeOnNewChat.addEventListener('click', ()=>{
1068 this.executeOnNewChat = executeOnNewChat.checked;
1069 this.updateContext();
1070 });
1071 /**@type {HTMLInputElement}*/
1064 const automationId = dom.querySelector('#qr--automationId');1072 const automationId = dom.querySelector('#qr--automationId');
1065 automationId.value = this.automationId;1073 automationId.value = this.automationId;
1066 automationId.addEventListener('input', () => {1074 automationId.addEventListener('input', () => {
public/scripts/openai.js+2 -2
@@ -3921,7 +3921,7 @@ function getMaxContextOpenAI(value) {
3921 if (oai_settings.max_context_unlocked) {3921 if (oai_settings.max_context_unlocked) {
3922 return unlocked_max;3922 return unlocked_max;
3923 }3923 }
3924 else if (value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {3924 else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
3925 return max_128k;3925 return max_128k;
3926 }3926 }
3927 else if (value.includes('gpt-3.5-turbo-1106')) {3927 else if (value.includes('gpt-3.5-turbo-1106')) {
@@ -4768,7 +4768,7 @@ export function isImageInliningSupported() {
47684768
4769 switch (oai_settings.chat_completion_source) {4769 switch (oai_settings.chat_completion_source) {
4770 case chat_completion_sources.OPENAI:4770 case chat_completion_sources.OPENAI:
4771 return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));4771 return visionSupportedModels.some(model => oai_settings.openai_model.includes(model) && !oai_settings.openai_model.includes('chatgpt-4o-latest') && !oai_settings.openai_model.includes('gpt-4-turbo-preview'));
4772 case chat_completion_sources.MAKERSUITE:4772 case chat_completion_sources.MAKERSUITE:
4773 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));4773 return visionSupportedModels.some(model => oai_settings.google_model.includes(model));
4774 case chat_completion_sources.CLAUDE:4774 case chat_completion_sources.CLAUDE:
src/endpoints/tokenizers.js+4 -0
@@ -268,6 +268,10 @@ function getTokenizerModel(requestModel) {
268 return 'gpt-4o';268 return 'gpt-4o';
269 }269 }
270270
271 if (requestModel.includes('chatgpt-4o-latest')) {
272 return 'gpt-4o';
273 }
274
271 if (requestModel.includes('gpt-4-32k')) {275 if (requestModel.includes('gpt-4-32k')) {
272 return 'gpt-4-32k';276 return 'gpt-4-32k';
273 }277 }
tests/package-lock.json+3 -4
@@ -1653,10 +1653,9 @@
1653 "license": "MIT"1653 "license": "MIT"
1654 },1654 },
1655 "node_modules/axios": {1655 "node_modules/axios": {
1656 "version": "1.7.2",1656 "version": "1.7.4",
1657 "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",1657 "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
1658 "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",1658 "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
1659 "license": "MIT",
1660 "dependencies": {1659 "dependencies": {
1661 "follow-redirects": "^1.15.6",1660 "follow-redirects": "^1.15.6",
1662 "form-data": "^4.0.0",1661 "form-data": "^4.0.0",