Merge branch 'staging' into docker-readme

This commit is contained in:
Cohee 2024-05-21 20:40:57 +03:00
commit aae95f70c4
13 changed files with 141 additions and 58 deletions

28
.github/readme.md vendored
View File

@ -200,6 +200,33 @@ We have a comprehensive guide on installing SillyTavern [here](http://docs.silly
5. Start the install launcher with: `chmod +x install.sh && ./install.sh` and choose what you wanna install
6. After installation start the launcher with: `chmod +x launcher.sh && ./launcher.sh`
## Installing via Docker
These instructions assume you have installed Docker, are able to access your command line for the installation of containers, and familiar with their general operation.
### Using the GitHub Container Registry (easiest)
You will need two mandatory directory mappings and a port mapping to allow SillyTavern to function. In the command, replace your selections in the following places:
#### Container Variables
##### Volume Mappings
- [config] - The directory where SillyTavern configuration files will be stored on your host machine
- [data] - The directory where SillyTavern user data (including characters) will be stored on your host machine
- [plugins] - (optional) The directory where SillyTavern server plugins will be stored on your host machine
##### Port Mappings
- [PublicPort] - The port to expose the traffic on. This is mandatory, as you will be accessing the instance from outside of its virtual machine container. DO NOT expose this to the internet without implementing a separate service for security.
##### Additional Settings
- [TimeZone] - The timezone your instance should use. This is useful for making logs match your local time for easier troubleshooting. Use your TZ Identifier. (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
- [DockerNet] - The docker network that the container should be created with a connection to. If you don't know what it is, see the [official Docker documentation](https://docs.docker.com/reference/cli/docker/network/).
- [version] - On the right-hand side of this GitHub page, you'll see "Packages". Select the "sillytavern" package and you'll see the image versions. The image tag "latest" will keep you up-to-date with the current release. You can also utilize "staging" and "release" tags that point to the nightly images of the respective branches, but this may not be appropriate, if you are utilizing extensions that could be broken, and may need time to update.
#### Install command
1. Open your Command Line
2. Run the following command `docker create --name='sillytavern' --net='[DockerNet]' -e TZ="[TimeZone]" -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' 'ghcr.io/sillytavern/sillytavern:[version]' `
> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
## 📱 Mobile - Installing via termux
> \[!NOTE]
@ -351,6 +378,7 @@ GNU Affero General Public License for more details.**
* Korean translation by @doloroushyeonse
* k_euler_a support for Horde by <https://github.com/Teashrock>
* Chinese translation by [@XXpE3](https://github.com/XXpE3), 中文 ISSUES 可以联系 @XXpE3
* Docker guide by [@mrguymiah](https://github.com/mrguymiah)
<!-- LINK GROUP -->
[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square

15
package-lock.json generated
View File

@ -25,7 +25,6 @@
"express": "^4.19.2",
"form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1",
"gpt3-tokenizer": "^1.1.5",
"he": "^1.2.0",
"helmet": "^7.1.0",
"ip-matching": "^2.1.2",
@ -1213,10 +1212,6 @@
"version": "1.1.1",
"license": "MIT"
},
"node_modules/array-keyed-map": {
"version": "2.1.3",
"license": "ISC"
},
"node_modules/async": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
@ -2738,16 +2733,6 @@
"version": "1.1.4",
"license": "MIT"
},
"node_modules/gpt3-tokenizer": {
"version": "1.1.5",
"license": "MIT",
"dependencies": {
"array-keyed-map": "^2.1.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",

View File

@ -15,7 +15,6 @@
"express": "^4.19.2",
"form-data": "^4.0.0",
"google-translate-api-browser": "^3.0.1",
"gpt3-tokenizer": "^1.1.5",
"he": "^1.2.0",
"helmet": "^7.1.0",
"ip-matching": "^2.1.2",

View File

@ -1684,7 +1684,7 @@
</div>
</div>
<div class="range-block" data-source="openai,openrouter,makersuite,claude,custom">
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand marginBot10">
<label for="openai_image_inlining" class="checkbox_label flexWrap widthFreeExpand">
<input id="openai_image_inlining" type="checkbox" />
<span data-i18n="Send inline images">Send inline images</span>
<div id="image_inlining_hint" class="flexBasis100p toggle-description justifyLeft">
@ -1693,7 +1693,7 @@
<code><i class="fa-solid fa-wand-magic-sparkles"></i></code> <span data-i18n="image_inlining_hint_3">menu to attach an image file to the chat.</span>
</div>
</label>
<div class="flex-container flexFlowColumn wide100p textAlignCenter">
<div class="flex-container flexFlowColumn wide100p textAlignCenter marginTop10" data-source="openai,custom">
<label for="openai_inline_image_quality">
Inline Image Quality
</label>
@ -1729,8 +1729,8 @@
</span>
</label>
<div class="toggle-description justifyLeft marginBot5">
<span data-i18n="Merges all system messages up until the first message with a non system role, and sends them through google's system_instruction field instead of with the rest of the prompt contents.">
Merges all system messages up until the first message with a non system role, and sends them through google's system_instruction field instead of with the rest of the prompt contents.
<span data-i18n="Merges all system messages up until the first message with a non-system role, and sends them in a system_instruction field.">
Merges all system messages up until the first message with a non-system role, and sends them in a <code>system_instruction</code> field.
</span>
</div>
</div>

View File

@ -469,7 +469,6 @@ let settingsReady = false;
let currentVersion = '0.0.0';
let displayVersion = 'SillyTavern';
export const default_ch_mes = 'Hello';
let generatedPromptCache = '';
let generation_started = new Date();
/** @type {import('scripts/char-data.js').v1CharData[]} */
@ -684,6 +683,7 @@ export function reloadMarkdownProcessor(render_formulas = false) {
tables: true,
parseImgDimensions: true,
simpleLineBreaks: true,
strikethrough: true,
extensions: [
showdownKatex(
{
@ -703,6 +703,7 @@ export function reloadMarkdownProcessor(render_formulas = false) {
tables: true,
underline: true,
simpleLineBreaks: true,
strikethrough: true,
extensions: [markdownUnderscoreExt()],
});
}
@ -5785,8 +5786,10 @@ async function getChatResult() {
name2 = characters[this_chid].name;
if (chat.length === 0) {
const message = getFirstMessage();
chat.push(message);
await saveChatConditional();
if (message.mes) {
chat.push(message);
await saveChatConditional();
}
}
await loadItemizedPrompts(getCurrentChatId());
await printMessages();
@ -5802,7 +5805,7 @@ async function getChatResult() {
}
function getFirstMessage() {
const firstMes = characters[this_chid].first_mes || default_ch_mes;
const firstMes = characters[this_chid].first_mes || '';
const alternateGreetings = characters[this_chid]?.data?.alternate_greetings;
const message = {
@ -5816,10 +5819,17 @@ function getFirstMessage() {
if (Array.isArray(alternateGreetings) && alternateGreetings.length > 0) {
const swipes = [message.mes, ...(alternateGreetings.map(greeting => getRegexedString(greeting, regex_placement.AI_OUTPUT)))];
if (!message.mes) {
swipes.shift();
message.mes = swipes[0];
}
message['swipe_id'] = 0;
message['swipes'] = swipes;
message['swipe_info'] = [];
}
return message;
}
@ -6381,9 +6391,9 @@ async function messageEditDone(div) {
appendMediaToMessage(mes, div.closest('.mes'));
addCopyToCodeBlocks(div.closest('.mes'));
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
this_edit_mes_id = undefined;
await saveChatConditional();
await eventSource.emit(event_types.MESSAGE_UPDATED, this_edit_mes_id);
}
/**
@ -7400,8 +7410,8 @@ function openAlternateGreetings() {
template.find('.add_alternate_greeting').on('click', function () {
const array = getArray();
const index = array.length;
array.push(default_ch_mes);
addAlternateGreeting(template, default_ch_mes, index, getArray);
array.push('');
addAlternateGreeting(template, '', index, getArray);
updateAlternateGreetingsHintVisibility(template);
});
@ -7572,15 +7582,20 @@ async function createOrEditCharacter(e) {
eventSource.emit(event_types.CHARACTER_EDITED, { detail: { id: this_chid, character: characters[this_chid] } });
// Recreate the chat if it hasn't been used at least once (i.e. with continue).
if (chat.length === 1 && !selected_group && !chat_metadata['tainted']) {
const firstMessage = getFirstMessage();
chat[0] = firstMessage;
const message = getFirstMessage();
const shouldRegenerateMessage =
message.mes &&
!selected_group &&
!chat_metadata['tainted'] &&
(chat.length === 0 || (chat.length === 1 && !chat[0].is_user && !chat[0].is_system));
const chat_id = (chat.length - 1);
await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);
if (shouldRegenerateMessage) {
chat.splice(0, chat.length, message);
const messageId = (chat.length - 1);
await eventSource.emit(event_types.MESSAGE_RECEIVED, messageId);
await clearChat();
await printMessages();
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, chat_id);
await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, messageId);
await saveChatConditional();
}
},

View File

@ -566,6 +566,7 @@ export function dragElement(elmnt) {
containerAspectRatio = null;
imageAspectRatio = null;
$(window).off('mouseup');
});
}

View File

@ -31,13 +31,6 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
import { resolveVariable } from '../../variables.js';
export { MODULE_NAME };
// Wraps a string into monospace font-face span
const m = x => `<span class="monospace">${x}</span>`;
// Joins an array of strings with ' / '
const j = a => a.join(' / ');
// Wraps a string into paragraph block
const p = a => `<p>${a}</p>`;
const MODULE_NAME = 'sd';
const UPDATE_INTERVAL = 1000;
// This is a 1x1 transparent PNG
@ -151,11 +144,6 @@ const promptTemplates = {
[generationMode.USER_MULTIMODAL]: 'Provide an exhaustive comma-separated list of tags describing the appearance of the character on this image in great detail. Start with "full body portrait".',
};
const helpString = [
`${m('[quiet=false/true] (argument)')} requests to generate an image and posts it to chat (unless quiet=true argument is specified). Supported arguments: ${m(j(Object.values(triggerWords).flat()))}.`,
'Anything else would trigger a "free mode" to make generate whatever you prompted. Example: \'/imagine apple tree\' would generate a picture of an apple tree. Returns a link to the generated image.',
].join(' ');
const defaultPrefix = 'best quality, absurdres, aesthetic,';
const defaultNegative = 'lowres, bad anatomy, bad hands, text, error, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry';
@ -1897,7 +1885,7 @@ function processReply(str) {
str = str.replaceAll('“', '');
str = str.replaceAll('.', ',');
str = str.replaceAll('\n', ', ');
str = str.replace(/[^a-zA-Z0-9,:()\-']+/g, ' '); // Replace everything except alphanumeric characters and commas with spaces
str = str.replace(/[^a-zA-Z0-9,:_(){}[\]\-']+/g, ' ');
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
str = str.trim();
@ -2849,18 +2837,20 @@ async function onComfyDeleteWorkflowClick() {
*/
async function sendMessage(prompt, image, generationType, additionalNegativePrefix) {
const context = getContext();
const messageText = `[${context.name2} sends a picture that contains: ${prompt}]`;
const name = context.groupId ? systemUserName : context.name2;
const messageText = `[${name} sends a picture that contains: ${prompt}]`;
const message = {
name: context.groupId ? systemUserName : context.name2,
name: name,
is_user: false,
is_system: true,
send_date: getMessageTimeStamp(),
mes: context.groupId ? p(messageText) : messageText,
mes: messageText,
extra: {
image: image,
title: prompt,
generationType: generationType,
negative: additionalNegativePrefix,
inline_image: false,
},
};
context.chat.push(message);

View File

@ -646,7 +646,7 @@ jQuery(() => {
eventSource.makeFirst(event_types.USER_MESSAGE_RENDERED, handleOutgoingMessage);
eventSource.on(event_types.MESSAGE_SWIPED, handleIncomingMessage);
eventSource.on(event_types.IMPERSONATE_READY, handleImpersonateReady);
eventSource.on(event_types.MESSAGE_EDITED, handleMessageEdit);
eventSource.on(event_types.MESSAGE_UPDATED, handleMessageEdit);
document.body.classList.add('translate');
});

View File

@ -40,7 +40,6 @@ import {
online_status,
talkativeness_default,
selectRightMenuWithAnimation,
default_ch_mes,
deleteLastMessage,
showSwipeButtons,
hideSwipeButtons,
@ -204,6 +203,12 @@ export async function getGroupChat(groupId, reload = false) {
}
const mes = await getFirstCharacterMessage(character);
// No first message
if (!(mes?.mes)) {
continue;
}
chat.push(mes);
await eventSource.emit(event_types.MESSAGE_RECEIVED, (chat.length - 1));
addOneMessage(mes);
@ -452,7 +457,7 @@ async function getFirstCharacterMessage(character) {
mes['extra'] = { 'gen_id': Date.now() * Math.random() * 1000000 };
mes['mes'] = messageText
? substituteParams(messageText.trim(), name1, character.name)
: default_ch_mes;
: '';
mes['force_avatar'] =
character.avatar != 'none'
? getThumbnailUrl('avatar', character.avatar)

View File

@ -2996,13 +2996,20 @@ $(document).ready(() => {
});
const reportZoomLevelDebounced = debounce(() => {
const zoomLevel = Number(window.devicePixelRatio).toFixed(2);
const zoomLevel = Number(window.devicePixelRatio).toFixed(2) || 1;
const winWidth = window.innerWidth;
const winHeight = window.innerHeight;
console.debug(`Zoom: ${zoomLevel}, X:${winWidth}, Y:${winHeight}`);
const originalWidth = winWidth * zoomLevel;
const originalHeight = winHeight * zoomLevel;
console.debug(`Zoom: ${zoomLevel}, X:${winWidth}, Y:${winHeight}, original: ${originalWidth}x${originalHeight} `);
return zoomLevel;
});
var coreTruthWinWidth = window.innerWidth;
var coreTruthWinHeight = window.innerHeight;
$(window).on('resize', async () => {
console.log(`Window resize: ${coreTruthWinWidth}x${coreTruthWinHeight} -> ${window.innerWidth}x${window.innerHeight}`)
adjustAutocompleteDebounced();
setHotswapsDebounced();
@ -3012,9 +3019,54 @@ $(document).ready(() => {
reportZoomLevelDebounced();
//attempt to scale movingUI elements naturally across window resizing/zooms
//this will still break if the zoom level causes mobile styles to come into play.
const scaleY = Number(window.innerHeight / coreTruthWinHeight).toFixed(4);
const scaleX = Number(window.innerWidth / coreTruthWinWidth).toFixed(4);
if (Object.keys(power_user.movingUIState).length > 0) {
resetMovablePanels('resize');
for (var elmntName of Object.keys(power_user.movingUIState)) {
var elmntState = power_user.movingUIState[elmntName];
var oldHeight = elmntState.height;
var oldWidth = elmntState.width;
var oldLeft = elmntState.left;
var oldTop = elmntState.top;
var oldBottom = elmntState.bottom;
var oldRight = elmntState.right;
var newHeight, newWidth, newTop, newBottom, newLeft, newRight;
newHeight = Number(oldHeight * scaleY).toFixed(0);
newWidth = Number(oldWidth * scaleX).toFixed(0);
newLeft = Number(oldLeft * scaleX).toFixed(0);
newTop = Number(oldTop * scaleY).toFixed(0);
newBottom = Number(oldBottom * scaleY).toFixed(0);
newRight = Number(oldRight * scaleX).toFixed(0);
try {
var elmnt = $('#' + $.escapeSelector(elmntName));
if (elmnt.length) {
console.log(`scaling ${elmntName} by ${scaleX}x${scaleY} to ${newWidth}x${newHeight}`);
elmnt.css('height', newHeight);
elmnt.css('width', newWidth);
elmnt.css('inset', `${newTop}px ${newRight}px ${newBottom}px ${newLeft}px`);
power_user.movingUIState[elmntName].height = newHeight;
power_user.movingUIState[elmntName].width = newWidth;
power_user.movingUIState[elmntName].top = newTop;
power_user.movingUIState[elmntName].bottom = newBottom;
power_user.movingUIState[elmntName].left = newLeft;
power_user.movingUIState[elmntName].right = newRight;
} else {
console.log(`skipping ${elmntName} because it doesn't exist in the DOM`);
}
} catch (err) {
console.log(`error occurred while processing ${elmntName}: ${err}`);
}
}
} else {
console.log('aborting MUI reset', Object.keys(power_user.movingUIState).length)
}
saveSettingsDebounced();
coreTruthWinWidth = window.innerWidth;
coreTruthWinHeight = window.innerHeight;
});
// Settings that go to settings.json

View File

@ -4,6 +4,7 @@ Text formatting commands:
<li><tt>**text**</tt> - displays as <b>bold</b></li>
<li><tt>***text***</tt> - displays as <b><i>bold italics</i></b></li>
<li><tt>__text__</tt> - displays as an <u>underline</u></li>
<li><tt>~~text~~</tt> - displays as a <del>strikethough</del></li>
<li><tt>[text](url)</tt> - displays as a <a href="#">hyperlink</a></li>
<li><tt>![text](url)</tt> - displays as an image</li>
<li><tt>```text```</tt> - displays as a code block (new lines allowed between the backticks)</li>

View File

@ -7,4 +7,5 @@
<li><span data-i18n="char_import_4">Pygmalion.chat Character (Direct Link or UUID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>a7ca95a1-0c88-4e23-91b3-149db1e78ab9</tt></li>
<li><span data-i18n="char_import_5">AICharacterCard.com Character (Direct Link or ID)</span><br><span data-i18n="char_import_example">Example:</span> <tt>AICC/aicharcards/the-game-master</tt></li>
<li><span data-i18n="char_import_6">Direct PNG Link (refer to</span> <code>config.yaml</code><span data-i18n="char_import_7"> for allowed hosts)</span><br><span data-i18n="char_import_example">Example:</span> <tt>https://files.catbox.moe/notarealfile.png</tt></li>
<ul>
<li><span data-i18n="char_import_7">RisuRealm Character (Direct Link)<br><span data-i18n="char_import_example">Example:</span> <tt>https://realm.risuai.net/character/3ca54c71-6efe-46a2-b9d0-4f62df23d712</tt></li>
<ul>

View File

@ -263,8 +263,14 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '
const PNG_PIXEL = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const visionSupportedModels = [
'gemini-1.0-pro-vision-latest',
'gemini-1.5-flash-latest',
'gemini-1.5-pro-latest',
'gemini-1.0-pro-vision-latest',
'gemini-pro-vision',
];
const dummyRequiredModels = [
'gemini-1.0-pro-vision-latest',
'gemini-pro-vision',
];
@ -343,7 +349,7 @@ function convertGooglePrompt(messages, model, useSysPrompt = false, charName = '
});
// pro 1.5 doesn't require a dummy image to be attached, other vision models do
if (isMultimodal && model !== 'gemini-1.5-pro-latest' && !hasImage) {
if (isMultimodal && dummyRequiredModels.includes(model) && !hasImage) {
contents[0].parts.push({
inlineData: {
mimeType: 'image/png',