Merge branch 'staging' of http://github.com/cohee1207/SillyTavern into staging

This commit is contained in:
Cohee1207 2023-07-30 16:20:05 +03:00
commit 1b7973ec13
13 changed files with 240 additions and 32 deletions

View File

@ -291,7 +291,6 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
* RossAscends' additions: AGPL v3
* Portions of CncAnon's TavernAITurbo mod: Unknown license
* kingbri's various commits and suggestions (https://github.com/bdashore3)
* BlipRanger's miscellaneous UI & extension modifications (https://github.com/BlipRanger)
* Waifu mode inspired by the work of PepperTaco (https://github.com/peppertaco/Tavern/)
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
* Thanks oobabooga for compiling presets for TextGen
@ -303,4 +302,4 @@ SillyTavern 会将 API 密钥保存在目录中的 `secrets.json` 文件内。
* Thanks paniphons for providing a FAQ document
* 10K Discord Users Celebratory Background by @kallmeflocc
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse
* Korean translation by @doloroushyeonse

2
.github/readme.md vendored
View File

@ -293,7 +293,6 @@ GNU Affero General Public License for more details.**
* RossAscends' additions: AGPL v3
* Portions of CncAnon's TavernAITurbo mod: Unknown license
* kingbri's various commits and suggestions (<https://github.com/bdashore3>)
* BlipRanger's miscellaneous UI & extension modifications (<https://github.com/BlipRanger>)
* Waifu mode inspired by the work of PepperTaco (<https://github.com/peppertaco/Tavern/>)
* Thanks Pygmalion University for being awesome testers and suggesting cool features!
* Thanks oobabooga for compiling presets for TextGen
@ -306,3 +305,4 @@ GNU Affero General Public License for more details.**
* 10K Discord Users Celebratory Background by @kallmeflocc
* Default content (characters and lore books) provided by @OtisAlejandro, @RossAscends and @kallmeflocc
* Korean translation by @doloroushyeonse
* k_euler_a support for Horde by <https://github.com/Teashrock>

1
.gitignore vendored
View File

@ -26,4 +26,5 @@ secrets.json
/dist
/backups/
public/movingUI/
public/QuickReplies/
content.log

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "sillytavern",
"version": "1.9.2",
"version": "1.9.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sillytavern",
"version": "1.9.2",
"version": "1.9.3",
"license": "AGPL-3.0",
"dependencies": {
"@dqbd/tiktoken": "^1.0.2",

View File

@ -51,7 +51,7 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.9.2",
"version": "1.9.3",
"scripts": {
"start": "node server.js",
"pkg": "pkg --compress Gzip --no-bytecode --public ."

View File

@ -0,0 +1,23 @@
{
"name": "Default",
"quickReplyEnabled": true,
"quickReplySlots": [
{
"mes": "/?",
"label": "HELP",
"enabled": true
},
{
"mes": "/newchat",
"label": "New Chat",
"enabled": true
},
{
"mes": "/bgcol",
"label": "Match UI to Background",
"enabled": true
}
],
"numberOfSlots": 3,
"selectedPreset": "Default"
}

View File

@ -683,6 +683,14 @@
</div>
</div>
</div>
<div class="range-block" data-source="claude">
<div class="range-block-title" data-i18n="Assistant Prefill">
Assistant Prefill
</div>
<div class="wide100p">
<input type="text" id="claude_assistant_prefill" class="text_pole" placeholder="Start Claude's answer with...">
</div>
</div>
</div>
<hr>
</div>

View File

@ -737,6 +737,9 @@ let token;
var PromptArrayItemForRawPromptDisplay;
export let active_character = ""
export let active_group = ""
export function getRequestHeaders() {
return {
"Content-Type": "application/json",
@ -786,6 +789,14 @@ function checkOnlineStatus() {
}
}
export function setActiveCharacter(character) {
active_character = character;
}
export function setActiveGroup(group) {
active_group = group;
}
async function getStatus() {
if (is_get_status) {
if (main_api == "koboldhorde") {
@ -5009,6 +5020,10 @@ async function getSettings(type) {
highlightSelectedAvatar();
setPersonaDescription();
//Load the active character and group
active_character = settings.active_character;
active_group = settings.active_group;
//Load the API server URL from settings
api_server = settings.api_server;
$("#api_url_text").val(api_server);
@ -5049,6 +5064,8 @@ async function saveSettings(type) {
data: JSON.stringify({
firstRun: firstRun,
username: name1,
active_character: active_character,
active_group: active_group,
api_server: api_server,
api_server_textgenerationwebui: api_server_textgenerationwebui,
preset_settings: preset_settings,

View File

@ -13,6 +13,10 @@ import {
menu_type,
max_context,
saveSettingsDebounced,
active_group,
active_character,
setActiveGroup,
setActiveCharacter,
} from "../script.js";
import {
@ -330,11 +334,21 @@ export function RA_CountCharTokens() {
characterStatsHandler(characters, this_chid);
});
}
//Auto Load Last Charcter -- (fires when active_character is defined and auto_load_chat is true)
/**
* Auto load chat with the last active character or group.
* Fires when active_character is defined and auto_load_chat is true.
* The function first tries to find a character with a specific ID from the global settings.
* If it doesn't exist, it tries to find a group with a specific grid from the global settings.
* If the character list hadn't been loaded yet, it calls itself again after 100ms delay.
* The character or group is selected (clicked) if it is found.
*/
async function RA_autoloadchat() {
if (document.getElementById('CharID0') !== null) {
var charToAutoLoad = document.getElementById('CharID' + LoadLocal('ActiveChar'));
let groupToAutoLoad = document.querySelector(`.group_select[grid="${LoadLocal('ActiveGroup')}"]`);
// active character is the name, we should look it up in the character list and get the id
let active_character_id = Object.keys(characters).find(key => characters[key].avatar === active_character);
var charToAutoLoad = document.getElementById('CharID' + active_character_id);
let groupToAutoLoad = document.querySelector(`.group_select[grid="${active_group}"]`);
if (charToAutoLoad != null) {
$(charToAutoLoad).click();
}
@ -342,7 +356,7 @@ async function RA_autoloadchat() {
$(groupToAutoLoad).click();
}
// if the charcter list hadn't been loaded yet, try again.
// if the character list hadn't been loaded yet, try again.
} else { setTimeout(RA_autoloadchat, 100); }
}
@ -903,16 +917,22 @@ $("document").ready(function () {
$("#rm_button_characters").click(function () { SaveLocal('SelectedNavTab', 'rm_button_characters'); });
// when a char is selected from the list, save them as the auto-load character for next page load
// when a char is selected from the list, save their name as the auto-load character for next page load
$(document).on("click", ".character_select", function () {
SaveLocal('ActiveChar', $(this).attr('chid'));
SaveLocal('ActiveGroup', null);
setActiveCharacter($(this).find('.avatar').attr('title'));
setActiveGroup(null);
saveSettingsDebounced();
});
$(document).on("click", ".group_select", function () {
SaveLocal('ActiveChar', null);
SaveLocal('ActiveGroup', $(this).data('id'));
setActiveCharacter(null);
setActiveGroup($(this).data('id'));
saveSettingsDebounced();
});
//this makes the chat input text area resize vertically to match the text size (limited by CSS at 50% window height)
$('#send_textarea').on('input', function () {
this.style.height = '40px';

View File

@ -1,19 +1,48 @@
import { saveSettingsDebounced } from "../../../script.js";
import { saveSettingsDebounced, callPopup, getRequestHeaders } from "../../../script.js";
import { getContext, extension_settings } from "../../extensions.js";
import { initScrollHeight, resetScrollHeight } from "../../utils.js";
export { MODULE_NAME };
const MODULE_NAME = 'quick-reply';
const UPDATE_INTERVAL = 1000;
let presets = [];
let selected_preset = '';
const defaultSettings = {
quickReplyEnabled: false,
quickReplyEnabled: true,
numberOfSlots: 5,
quickReplySlots: [],
}
async function loadSettings() {
//method from worldinfo
async function updateQuickReplyPresetList() {
var result = await fetch("/getsettings", {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({}),
});
if (result.ok) {
var data = await result.json();
presets = data.quickReplyPresets?.length ? data.quickReplyPresets : [];
console.log(presets)
$("#quickReplyPresets").find('option[value!=""]').remove();
if (presets !== undefined) {
presets.forEach((item, i) => {
$("#quickReplyPresets").append(`<option value='${item.name}'${selected_preset.includes(item.name) ? ' selected' : ''}>${item.name}</option>`);
});
}
}
}
async function loadSettings(type) {
if (type === 'init') {
await updateQuickReplyPresetList()
}
if (Object.keys(extension_settings.quickReply).length === 0) {
Object.assign(extension_settings.quickReply, defaultSettings);
}
@ -111,6 +140,51 @@ async function moduleWorker() {
if (extension_settings.quickReply.quickReplyEnabled === true) {
$('#quickReplyBar').toggle(getContext().onlineStatus !== 'no_connection');
}
if (extension_settings.quickReply.selectedPreset) {
selected_preset = extension_settings.quickReply.selectedPreset;
}
}
async function saveQuickReplyPreset() {
const name = await callPopup('Enter a name for the Quick Reply Preset:', 'input');
if (!name) {
return;
}
const quickReplyPreset = {
name: name,
quickReplyEnabled: extension_settings.quickReply.quickReplyEnabled,
quickReplySlots: extension_settings.quickReply.quickReplySlots,
numberOfSlots: extension_settings.quickReply.numberOfSlots,
selectedPreset: name
}
const response = await fetch('/savequickreply', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(quickReplyPreset)
});
if (response.ok) {
const quickReplyPresetIndex = presets.findIndex(x => x.name == name);
if (quickReplyPresetIndex == -1) {
presets.push(quickReplyPreset);
const option = document.createElement('option');
option.selected = true;
option.value = name;
option.innerText = name;
$('#quickReplyPresets').append(option);
}
else {
presets[quickReplyPresetIndex] = quickReplyPreset;
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
}
saveSettingsDebounced();
} else {
toastr.warning('Failed to save Quick Reply Preset.')
}
}
async function onQuickReplyNumberOfSlotsInput() {
@ -178,6 +252,27 @@ function generateQuickReplyElements() {
});
}
async function applyQuickReplyPreset(name) {
const quickReplyPreset = presets.find(x => x.name == name);
if (!quickReplyPreset) {
console.log(`error, QR preset '${name}' not found`)
return;
}
extension_settings.quickReply = quickReplyPreset;
extension_settings.quickReply.selectedPreset = name;
saveSettingsDebounced()
loadSettings('init')
addQuickReplyBar();
moduleWorker();
$(`#quickReplyPresets option[value="${name}"]`).attr('selected', true);
console.debug('QR Preset applied: ' + name);
//loadMovingUIState()
}
jQuery(async () => {
moduleWorker();
@ -190,11 +285,18 @@ jQuery(async () => {
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
</div>
<div class="inline-drawer-content">
<label class="checkbox_label marginBot10">
<input id="quickReplyEnabled" type="checkbox" />
Enable Quick Replies
</label>
<label for="quickReplyNumberOfSlots">Number of slots:</label>
<div class="flex-container ">
<label class="checkbox_label marginBot10 wide100p flexnowrap">
<input id="quickReplyEnabled" type="checkbox" />
Enable Quick Replies
</label>
<div class="flex-container flexnowrap wide100p">
<select id="quickReplyPresets" name="quickreply-preset">
</select>
<i id="quickReplyPresetSaveButton" class="fa-solid fa-save"></i>
</div>
<label for="quickReplyNumberOfSlots">Number of slots:</label>
</div>
<div class="flex-container flexGap5 flexnowrap">
<input id="quickReplyNumberOfSlots" class="text_pole" type="number" min="1" max="100" value="" />
<div class="menu_button menu_button_icon" id="quickReplyNumberOfSlotsApply">
@ -212,8 +314,17 @@ jQuery(async () => {
$('#quickReplyEnabled').on('input', onQuickReplyEnabledInput);
$('#quickReplyNumberOfSlotsApply').on('click', onQuickReplyNumberOfSlotsInput);
$("#quickReplyPresetSaveButton").on('click', saveQuickReplyPreset);
await loadSettings();
$("#quickReplyPresets").on('change', async function () {
const quickReplyPresetSelected = $(this).find(':selected').val();
extension_settings.quickReplyPreset = quickReplyPresetSelected;
applyQuickReplyPreset(quickReplyPresetSelected);
saveSettingsDebounced();
});
await loadSettings('init');
addQuickReplyBar();
});

View File

@ -90,13 +90,6 @@ function loadNovelSettings(settings) {
nai_settings.cfg_scale = settings.cfg_scale;
nai_settings.streaming_novel = !!settings.streaming_novel;
loadNovelSettingsUi(nai_settings);
// reload the preset to migrate any new settings
for (const key of Object.keys(nai_settings)) {
if (typeof nai_settings[key] === 'number' && Number.isNaN(nai_settings[key])) {
$("#settings_perset_novel").trigger("change");
}
}
}
const phraseRepPenStrings = [
@ -109,7 +102,7 @@ const phraseRepPenStrings = [
]
function getPhraseRepPenString(phraseRepPenCounter) {
if (phraseRepPenCounter < 1 || phraseRepPenCounter > F5) {
if (phraseRepPenCounter < 1 || phraseRepPenCounter > 5) {
return null;
} else {
return phraseRepPenStrings[phraseRepPenCounter];

View File

@ -143,6 +143,7 @@ const default_settings = {
api_url_scale: '',
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
};
const oai_settings = {
@ -180,6 +181,7 @@ const oai_settings = {
api_url_scale: '',
show_external_models: false,
proxy_password: '',
assistant_prefill: '',
};
let openai_setting_names;
@ -775,6 +777,7 @@ async function sendOpenAIRequest(type, openai_msgs_tosend, signal) {
if (isClaude) {
generate_data['use_claude'] = true;
generate_data['top_k'] = parseFloat(oai_settings.top_k_openai);
generate_data['assistant_prefill'] = substituteParams(oai_settings.assistant_prefill);
}
if (isOpenRouter) {
@ -1109,6 +1112,7 @@ function loadOpenAISettings(data, settings) {
oai_settings.api_url_scale = settings.api_url_scale ?? default_settings.api_url_scale;
oai_settings.show_external_models = settings.show_external_models ?? default_settings.show_external_models;
oai_settings.proxy_password = settings.proxy_password ?? default_settings.proxy_password;
oai_settings.assistant_prefill = settings.assistant_prefill ?? default_settings.assistant_prefill;
if (settings.nsfw_toggle !== undefined) oai_settings.nsfw_toggle = !!settings.nsfw_toggle;
if (settings.keep_example_dialogue !== undefined) oai_settings.keep_example_dialogue = !!settings.keep_example_dialogue;
@ -1121,6 +1125,7 @@ function loadOpenAISettings(data, settings) {
$('#stream_toggle').prop('checked', oai_settings.stream_openai);
$('#api_url_scale').val(oai_settings.api_url_scale);
$('#openai_proxy_password').val(oai_settings.proxy_password);
$('#claude_assistant_prefill').val(oai_settings.assistant_prefill);
$('#model_openai_select').val(oai_settings.openai_model);
$(`#model_openai_select option[value="${oai_settings.openai_model}"`).attr('selected', true);
@ -1323,6 +1328,7 @@ async function saveOpenAIPreset(name, settings) {
stream_openai: settings.stream_openai,
api_url_scale: settings.api_url_scale,
show_external_models: settings.show_external_models,
assistant_prefill: settings.assistant_prefill,
};
const savePresetSettings = await fetch(`/savepreset_openai?name=${name}`, {
@ -1656,6 +1662,7 @@ function onSettingsPresetChange() {
api_url_scale: ['#api_url_scale', 'api_url_scale', false],
show_external_models: ['#openai_show_external_models', 'show_external_models', true],
proxy_password: ['#openai_proxy_password', 'proxy_password', false],
assistant_prefill: ['#claude_assistant_prefill', 'assistant_prefill', false],
};
for (const [key, [selector, setting, isCheckbox]] of Object.entries(settingsToUpdate)) {
@ -2206,6 +2213,11 @@ $(document).ready(function () {
saveSettingsDebounced();
});
$('#claude_assistant_prefill').on('input', function () {
oai_settings.assistant_prefill = $(this).val();
saveSettingsDebounced();
});
$("#api_button_openai").on("click", onConnectButtonClick);
$("#openai_reverse_proxy").on("input", onReverseProxyInput);
$("#model_openai_select").on("change", onModelChange);

View File

@ -290,6 +290,7 @@ const directories = {
instruct: 'public/instruct',
context: 'public/context',
backups: 'backups/',
quickreplies: 'public/QuickReplies'
};
// CSRF Protection //
@ -1600,6 +1601,8 @@ app.post('/getsettings', jsonParser, (request, response) => {
const themes = readAndParseFromDirectory(directories.themes);
const movingUIPresets = readAndParseFromDirectory(directories.movingUI);
const quickReplyPresets = readAndParseFromDirectory(directories.quickreplies);
const instruct = readAndParseFromDirectory(directories.instruct);
const context = readAndParseFromDirectory(directories.context);
@ -1616,6 +1619,7 @@ app.post('/getsettings', jsonParser, (request, response) => {
textgenerationwebui_preset_names,
themes,
movingUIPresets,
quickReplyPresets,
instruct,
context,
enable_extensions: enableExtensions,
@ -1672,6 +1676,17 @@ app.post('/savemovingui', jsonParser, (request, response) => {
return response.sendStatus(200);
});
app.post('/savequickreply', jsonParser, (request, response) => {
if (!request.body || !request.body.name) {
return response.sendStatus(400);
}
const filename = path.join(directories.quickreplies, sanitize(request.body.name) + '.json');
fs.writeFileSync(filename, JSON.stringify(request.body, null, 4), 'utf8');
return response.sendStatus(200);
});
function convertWorldInfoToCharacterBook(name, entries) {
const result = { entries: [], name };
@ -3077,7 +3092,12 @@ async function sendClaudeRequest(request, response) {
controller.abort();
});
const requestPrompt = convertClaudePrompt(request.body.messages, true, true);
let requestPrompt = convertClaudePrompt(request.body.messages, true, true);
if (request.body.assistant_prefill) {
requestPrompt += request.body.assistant_prefill;
}
console.log('Claude request:', requestPrompt);
const generateResponse = await fetch(api_url + '/complete', {
@ -3260,6 +3280,10 @@ app.post("/generate_openai", jsonParser, function (request, response_generate_op
message = 'API key disabled or exhausted';
console.log(message);
break;
case 451:
message = error?.response?.data?.error?.message || 'Unavailable for legal reasons';
console.log(message);
break;
}
const quota_error = error?.response?.status === 429 && error?.response?.data?.error?.type === 'insufficient_quota';