Compare commits

..

53 Commits

Author SHA1 Message Date
Cohee
2d152d2705 Update docker-publish.yml 2024-02-11 15:38:41 +02:00
Cohee
f183f55c74 Update Dockerfile 2024-02-11 15:36:20 +02:00
Cohee
91f31e746e Merge pull request #1810 from SillyTavern/staging
Staging
2024-02-10 21:24:42 +02:00
Cohee
97716ea9ca #1681 Remove pkg builds 2024-02-10 21:19:49 +02:00
Cohee
6b669bbc22 #1809 Add gitkeep to user 2024-02-10 21:15:59 +02:00
Cohee
18f84979f2 Use SSE streaming for MakerSuite 2024-02-10 02:43:50 +02:00
Cohee
867c42cb6d Add cyrillic special quotes to tts option 2024-02-09 18:33:01 +02:00
Cohee
d8d4732614 Add koboldcpp param aliases 2024-02-09 15:32:40 +02:00
Cohee
0f04508f30 Merge pull request #1806 from bdashore3/staging
Fixes for text completions
2024-02-09 10:14:52 +02:00
kingbri
530455979f Textgen: Add tabby to logprobs
Tabby now supports logprob returns. Add support to the backend gate.

Signed-off-by: kingbri <bdashore3@proton.me>
2024-02-08 20:59:54 -05:00
kingbri
f372b2ac16 Textgen: Fix dynatemp defaults
The default min and max temp values should be 1

Signed-off-by: kingbri <bdashore3@proton.me>
2024-02-08 20:57:40 -05:00
Cohee
03ad72b6c7 Merge pull request #1802 from Technologicat/modelname
{{model}} substitution to get name of current LLM
2024-02-08 11:36:02 +02:00
Juha Jeronen
a49d0f1050 use getGeneratingModel 2024-02-08 11:13:54 +02:00
Cohee
c557ade9b4 Merge pull request #1803 from lucyknada/staging 2024-02-08 03:40:19 +02:00
lucy
f5d5a75ef5 [fix/smoothing] align numerical input max with slider max 2024-02-08 02:03:49 +01:00
Cohee
44a3bb8ec7 #1777 Add await argument to /trigger command 2024-02-08 00:20:36 +02:00
Cohee
d7f86a7f6b Merge pull request #1800 from EX3-0/patch-1
Update openai.js added /proxy command.
2024-02-08 00:05:53 +02:00
Cohee
90231680a9 Remove extra space 2024-02-08 00:05:23 +02:00
Cohee
dfc1719c3f Use fuzzy name matching 2024-02-08 00:04:48 +02:00
Cohee
80d9b08cc3 Merge pull request #1801 from Technologicat/getchatname
Add /getchatname command
2024-02-07 23:58:27 +02:00
Cohee
04372848c8 Fix for undefined chats 2024-02-07 23:58:05 +02:00
Juha Jeronen
2dcb490e43 add {{model}} substitution macro to get name of current LLM
This is useful in the character card for an AI assistant, see #1774.

Tested with the Textgen backend, but should work with others too.

Horde will show only "Connected", and Novel will show the tier,
but Kobold and Textgen will show the model name.

If not connected, on any backend, will show "no_connection".
2024-02-07 23:29:32 +02:00
Juha Jeronen
c1a5b50aae improve description for consistency 2024-02-07 23:12:21 +02:00
Juha Jeronen
5183fb40a2 refactor to improve proposed implementation of /getchatname 2024-02-07 23:09:51 +02:00
Juha Jeronen
5d1f3b13ea add /getchatname slash command to get name of current chat file
Example:

/getchatname | /echo {{pipe}}
2024-02-07 22:51:41 +02:00
EX3-0
b2eb361028 Update openai.js added /proxy command.
Added "proxy" slash command to openai.js to change between proxy presets in ST script.
2024-02-07 13:52:48 -05:00
Cohee
58c3d3eb7f Use ooba API key in multimodal request 2024-02-07 19:31:38 +02:00
Cohee
c3129da879 [skip ci] Lint fix 2024-02-07 19:28:34 +02:00
Cohee
b244a1c301 Mark textgen API key as optional 2024-02-07 19:26:34 +02:00
Cohee
8ecab19966 Merge pull request #1798 from oobabooga/staging
Add API key field for text-generation-webui
2024-02-07 19:24:42 +02:00
Cohee
2923d1454f Merge pull request #1799 from Technologicat/fix-delchat
fix /delchat for characters with lots of chat files
2024-02-07 19:22:22 +02:00
Juha Jeronen
f0cffb3dd9 fix /delchat for characters with lots of chat files 2024-02-07 15:20:37 +02:00
oobabooga
b95cddec1c Remove debug statement 2024-02-06 20:03:52 -08:00
oobabooga
21fb143718 Add API key 2024-02-06 20:00:16 -08:00
Cohee
318235e13e Merge pull request #1795 from SillyTavern/staging
Staging
2024-02-06 17:46:34 +02:00
Cohee
2815990589 Force personas sort before returning to caller 2024-02-05 10:58:35 +02:00
Cohee
b158a86c25 Firefox copium for expression images 2024-02-05 02:21:20 +02:00
Cohee
f12aeeed90 Firefox copium for persona images 2024-02-05 02:18:44 +02:00
Cohee
41f53f4162 Merge pull request #1790 from Technologicat/talkinghead-fixes-feb2024
Talkinghead fixes feb2024
2024-02-05 01:33:39 +02:00
Cohee
d192c5ae7f Merge pull request #1791 from anon998/add-logprobs-to-custom-openai
Add logprobs support for custom OpenAI APIs
2024-02-05 01:31:06 +02:00
anon
634c9aad3b add logsprobs support for custom OpenAI APIs 2024-02-04 23:11:45 +00:00
Juha Jeronen
ad48d6666a fix bug: when switching talkinghead off, set character expression 2024-02-05 00:47:14 +02:00
Juha Jeronen
2a39db799a auto-pause Talkinghead when ST tab is hidden to save GPU resources 2024-02-05 00:46:44 +02:00
Juha Jeronen
91c4de6605 add /th (alias /talkinghead) to toggle Talkinghead on/off 2024-02-05 00:46:23 +02:00
Juha Jeronen
5ad2a0d064 refresh talkinghead char on expression zip upload 2024-02-05 00:45:50 +02:00
Juha Jeronen
3b526ce207 remove some useless comments 2024-02-05 00:45:37 +02:00
Juha Jeronen
169b1c2c63 talkinghead check: always check also whether the module is enabled 2024-02-05 00:45:20 +02:00
Juha Jeronen
9e8f3e0def one more debug message 2024-02-05 00:44:46 +02:00
Juha Jeronen
eb634d597f add comment on TTS 2024-02-05 00:44:39 +02:00
Juha Jeronen
24b315a149 comments
The tech is "Talkinghead" (capital T), the Extras module is
"talkinghead" (lowercase t).
2024-02-05 00:44:26 +02:00
Juha Jeronen
83e264db9e add some debug messages 2024-02-05 00:40:03 +02:00
Cohee
08e3fc60c4 Merge branch 'staging' into release 2024-02-04 21:37:12 +02:00
Cohee
b7921f1edd #1630 Fix regex depth application 2024-02-04 21:34:17 +02:00
25 changed files with 250 additions and 1212 deletions

View File

@@ -1,37 +0,0 @@
name: Build and Publish Release (Release)
on:
push:
branches:
- release
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build and package with pkg
run: |
npm install -g pkg
npm run pkg
- name: Upload binaries to release
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-release
name: Continuous Release (Release)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -1,37 +0,0 @@
name: Build and Publish Release (Staging)
on:
push:
branches:
- staging
jobs:
build_and_publish:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: 18
- name: Install dependencies
run: npm ci
- name: Build and package with pkg
run: |
npm install -g pkg
npm run pkg
- name: Upload binaries to release
uses: softprops/action-gh-release@v1
with:
files: dist/*
tag_name: ci-staging
name: Continuous Release (Staging)
prerelease: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -4,8 +4,8 @@ name: Create Docker Image on Release
on:
release:
# Only runs on full releases not pre releases
types: [released]
# Allow pre-releases
types: [published]
env:
# This should allow creation of docker images even in forked repositories

View File

@@ -34,7 +34,8 @@ RUN \
rm -f "config.yaml" "public/settings.json" || true && \
ln -s "./config/config.yaml" "config.yaml" || true && \
ln -s "../config/settings.json" "public/settings.json" || true && \
mkdir "config" || true
mkdir "config" || true && \
mkdir -p "public/user" || true
# Cleanup unnecessary files
RUN \

1008
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -55,11 +55,10 @@
"type": "git",
"url": "https://github.com/SillyTavern/SillyTavern.git"
},
"version": "1.11.3",
"version": "1.11.4",
"scripts": {
"start": "node server.js",
"start-multi": "node server.js --disableCsrf",
"pkg": "pkg --compress Gzip --no-bytecode --public .",
"postinstall": "node post-install.js",
"lint": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js",
"lint-fix": "eslint \"src/**/*.js\" \"public/**/*.js\" ./*.js --fix"
@@ -72,24 +71,8 @@
"no-var": "off"
},
"main": "server.js",
"pkg": {
"targets": [
"node18-linux-x64",
"node18-macos-x64",
"node18-windows-x64"
],
"assets": [
"node_modules/**/*"
],
"outputPath": "dist",
"scripts": [
"server.js"
]
},
"devDependencies": {
"eslint": "^8.55.0",
"jquery": "^3.6.4",
"pkg": "^5.8.1",
"pkg-fetch": "^3.5.2"
"jquery": "^3.6.4"
}
}

View File

@@ -1277,7 +1277,7 @@
<div data-newbie-hidden data-tg-type="ooba, koboldcpp, aphrodite, tabby" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0">
<small data-i18n="Smoothing Factor">Smoothing Factor</small>
<input class="neo-range-slider" type="range" id="smoothing_factor_textgenerationwebui" name="volume" min="0" max="10" step="0.01" />
<input class="neo-range-input" type="number" min="0" max="5" step="0.01" data-for="smoothing_factor_textgenerationwebui" id="smoothing_factor_counter_textgenerationwebui">
<input class="neo-range-input" type="number" min="0" max="10" step="0.01" data-for="smoothing_factor_textgenerationwebui" id="smoothing_factor_counter_textgenerationwebui">
</div>
<!--
<div data-tg-type="aphrodite" class="alignitemscenter flex-container flexFlowColumn flexBasis48p flexGrow flexShrink gap0" data-i18n="Responses">
@@ -1916,6 +1916,15 @@
Make sure you run it with <code>--api</code> flag
</span>
</div>
<h4 data-i18n="API key (optional)">API key (optional)</h4>
<div class="flex-container">
<input id="api_key_ooba" name="api_key_ooba" class="text_pole flex1 wide100p" maxlength="500" size="35" type="text" autocomplete="off">
<div title="Clear your API key" data-i18n="[title]Clear your API key" class="menu_button fa-solid fa-circle-xmark clear-api-key" data-key="api_key_ooba">
</div>
</div>
<div data-for="api_key_ooba" class="neutral_warning" data-i18n="For privacy reasons, your API key will be hidden after you reload the page.">
For privacy reasons, your API key will be hidden after you reload the page.
</div>
<div class="flex1">
<h4 data-i18n="Server url">Server URL</h4>
<small data-i18n="Example: http://127.0.0.1:5000 ">Example: http://127.0.0.1:5000</small>

View File

@@ -2179,6 +2179,7 @@ function substituteParams(content, _name1, _name2, _original, _group, _replaceCh
environment.user = _name1 ?? name1;
environment.char = _name2 ?? name2;
environment.group = environment.charIfNotGroup = _group ?? name2;
environment.model = getGeneratingModel();
return evaluateMacros(content, environment);
}
@@ -5420,17 +5421,21 @@ export async function getUserAvatars(doRender = true, openPageAt = '') {
if (response.ok) {
const allEntities = await response.json();
if (!Array.isArray(allEntities)) {
return [];
}
allEntities.sort((a, b) => {
const aName = String(power_user.personas[a] || a);
const bName = String(power_user.personas[b] || b);
return power_user.persona_sort_order === 'asc' ? aName.localeCompare(bName) : bName.localeCompare(aName);
});
if (!doRender) {
return allEntities;
}
const entities = personasFilter.applyFilters(allEntities);
entities.sort((a, b) => {
const aName = String(power_user.personas[a]);
const bName = String(power_user.personas[b]);
return power_user.persona_sort_order === 'asc' ? aName.localeCompare(bName) : bName.localeCompare(aName);
});
const storageKey = 'Personas_PerPage';
const listId = '#user_avatar_block';
const perPage = Number(localStorage.getItem(storageKey)) || 5;
@@ -5490,6 +5495,7 @@ function highlightSelectedAvatar() {
* @returns {JQuery<HTMLElement>} Avatar block
*/
function getUserAvatarBlock(name) {
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const template = $('#user_avatar_template .avatar-container').clone();
const personaName = power_user.personas[name];
const personaDescription = power_user.persona_descriptions[name]?.description;
@@ -5498,7 +5504,11 @@ function getUserAvatarBlock(name) {
template.attr('imgfile', name);
template.find('.avatar').attr('imgfile', name).attr('title', name);
template.toggleClass('default_persona', name === power_user.default_persona);
template.find('img').attr('src', getUserAvatar(name));
let avatarUrl = getUserAvatar(name);
if (isFirefox) {
avatarUrl += '?t=' + Date.now();
}
template.find('img').attr('src', avatarUrl);
$('#user_avatar_block').append(template);
return template;
}
@@ -6080,6 +6090,21 @@ export async function getPastCharacterChats(characterId = null) {
return data;
}
/**
* Helper for `displayPastChats`, to make the same info consistently available for other functions
*/
function getCurrentChatDetails() {
if (!characters[this_chid] && !selected_group) {
return { sessionName: '', group: null, characterName: '', avatarImgURL: '' };
}
const group = selected_group ? groups.find(x => x.id === selected_group) : null;
const currentChat = selected_group ? group?.chat_id : characters[this_chid]['chat'];
const displayName = selected_group ? group?.name : characters[this_chid].name;
const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']);
return { sessionName: currentChat, group: group, characterName: displayName, avatarImgURL: avatarImg };
}
/**
* Displays the past chats for a character or a group based on the selected context.
* The function first fetches the chats, processes them, and then displays them in
@@ -6090,7 +6115,6 @@ export async function displayPastChats() {
$('#select_chat_div').empty();
$('#select_chat_search').val('').off('input');
const group = selected_group ? groups.find(x => x.id === selected_group) : null;
const data = await (selected_group ? getGroupPastChats(selected_group) : getPastCharacterChats());
if (!data) {
@@ -6098,10 +6122,14 @@ export async function displayPastChats() {
return;
}
const currentChat = selected_group ? group?.chat_id : characters[this_chid]['chat'];
const displayName = selected_group ? group?.name : characters[this_chid].name;
const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['avatar']);
const chatDetails = getCurrentChatDetails();
const group = chatDetails.group;
const currentChat = chatDetails.sessionName;
const displayName = chatDetails.characterName;
const avatarImg = chatDetails.avatarImgURL;
const rawChats = await getChatsFromFiles(data, selected_group);
// Sort by last message date descending
data.sort((a, b) => sortMoments(timestampToMoment(a.last_mes), timestampToMoment(b.last_mes)));
console.log(data);
@@ -7808,15 +7836,18 @@ async function doImpersonate() {
}
async function doDeleteChat() {
$('#option_select_chat').trigger('click', { fromSlashCommand: true });
await delay(100);
await displayPastChats();
let currentChatDeleteButton = $('.select_chat_block[highlight=\'true\']').parent().find('.PastChat_cross');
$(currentChatDeleteButton).trigger('click', { fromSlashCommand: true });
$(currentChatDeleteButton).trigger('click');
await delay(1);
$('#dialogue_popup_ok').trigger('click');
//200 delay needed let the past chat view reshow first
await delay(200);
$('#select_chat_cross').trigger('click');
$('#dialogue_popup_ok').trigger('click', { fromSlashCommand: true });
}
/**
* /getchatname` slash command
*/
async function doGetChatName() {
return getCurrentChatDetails().sessionName;
}
const isPwaMode = window.navigator.standalone;
@@ -7970,6 +8001,7 @@ jQuery(async function () {
registerSlashCommand('api', connectAPISlash, [], `<span class="monospace">(${Object.keys(CONNECT_API_MAP).join(', ')})</span> connect to an API`, true, true);
registerSlashCommand('impersonate', doImpersonate, ['imp'], ' calls an impersonation response', true, true);
registerSlashCommand('delchat', doDeleteChat, [], ' deletes the current chat', true, true);
registerSlashCommand('getchatname', doGetChatName, [], ' returns the name of the current chat file into the pipe', false, true);
registerSlashCommand('closechat', doCloseChat, [], ' closes the current chat', true, true);
registerSlashCommand('panels', doTogglePanels, ['togglepanels'], ' toggle UI panels on/off', true, true);
registerSlashCommand('forcesave', doForceSave, [], ' forces a save of the current chat and settings', true, true);
@@ -8199,7 +8231,8 @@ jQuery(async function () {
$('#character_popup').css('display', 'none');
});
$('#dialogue_popup_ok').click(async function (e) {
$('#dialogue_popup_ok').click(async function (e, customData) {
const fromSlashCommand = customData?.fromSlashCommand || false;
dialogueCloseStop = false;
$('#shadow_popup').transition({
opacity: 0,
@@ -8229,14 +8262,16 @@ jQuery(async function () {
await delChat(chat_file_for_del);
}
//open the history view again after 2seconds (delay to avoid edge cases for deleting last chat)
//hide option popup menu
setTimeout(function () {
$('#option_select_chat').click();
$('#options').hide();
if (fromSlashCommand) { // When called from `/delchat` command, don't re-open the history view.
$('#options').hide(); // hide option popup menu
hideLoader();
}, 2000);
} else { // Open the history view again after 2 seconds (delay to avoid edge cases for deleting last chat).
setTimeout(function () {
$('#option_select_chat').click();
$('#options').hide(); // hide option popup menu
hideLoader();
}, 2000);
}
}
if (popup_type == 'del_ch') {
const deleteChats = !!$('#del_char_checkbox').prop('checked');
@@ -8532,6 +8567,11 @@ jQuery(async function () {
await writeSecret(SECRET_KEYS.TOGETHERAI, togetherKey);
}
const oobaKey = String($('#api_key_ooba').val()).trim();
if (oobaKey.length) {
await writeSecret(SECRET_KEYS.OOBA, oobaKey);
}
validateTextGenUrl();
startStatusLoading();
main_api = 'textgenerationwebui';

View File

@@ -1132,7 +1132,7 @@ export function initRossMods() {
.not('#right-nav-panel')
.not('#floatingPrompt')
.not('#cfgConfig')
.not("#logprobsViewer")
.not('#logprobsViewer')
.is(':visible')) {
let visibleDrawerContent = $('.drawer-content:visible')
.not('#WorldInfo')
@@ -1140,7 +1140,7 @@ export function initRossMods() {
.not('#right-nav-panel')
.not('#floatingPrompt')
.not('#cfgConfig')
.not("#logprobsViewer");
.not('#logprobsViewer');
$(visibleDrawerContent).parent().find('.drawer-icon').trigger('click');
return;
}

View File

@@ -58,6 +58,17 @@ function isTalkingHeadEnabled() {
return extension_settings.expressions.talkinghead && !extension_settings.expressions.local;
}
/**
* Toggles Talkinghead mode on/off.
*
* Implements the `/th` slash command, which is meant to be bound to a Quick Reply button
* as a quick way to switch Talkinghead on or off (e.g. to conserve GPU resources when AFK
* for a long time).
*/
function toggleTalkingHeadCommand(_) {
setTalkingHeadState(!extension_settings.expressions.talkinghead);
}
function isVisualNovelMode() {
return Boolean(!isMobile() && power_user.waifuMode && getContext().groupId);
}
@@ -389,13 +400,14 @@ function onExpressionsShowDefaultInput() {
}
/**
* Stops animating a talkinghead.
* Stops animating Talkinghead.
*/
async function unloadTalkingHead() {
if (!modules.includes('talkinghead')) {
console.debug('talkinghead module is disabled');
return;
}
console.debug('expressions: Stopping Talkinghead');
try {
const url = new URL(getApiUrl());
@@ -418,6 +430,7 @@ async function loadTalkingHead() {
console.debug('talkinghead module is disabled');
return;
}
console.debug('expressions: Starting Talkinghead');
const spriteFolderName = getSpriteFolderName();
@@ -528,8 +541,7 @@ function handleImageChange() {
return;
}
if (isTalkingHeadEnabled()) {
// Method get IP of endpoint
if (isTalkingHeadEnabled() && modules.includes('talkinghead')) {
const talkingheadResultFeedSrc = `${getApiUrl()}/api/talkinghead/result_feed`;
$('#expression-holder').css({ display: '' });
if (imgElement.src !== talkingheadResultFeedSrc) {
@@ -545,20 +557,26 @@ function handleImageChange() {
}
})
.catch(error => {
console.error(error); // Log the error if necessary
console.error(error);
});
}
}
} else {
imgElement.src = ''; //remove incase char doesnt have expressions
setExpression(getContext().name2, FALLBACK_EXPRESSION, true);
imgElement.src = ''; // remove in case char doesn't have expressions
// When switching Talkinghead off, force-set the character to the last known expression, if any.
// This preserves the same expression Talkinghead had at the moment it was switched off.
const charName = getContext().name2;
const last = lastExpression[charName];
const targetExpression = last ? last : FALLBACK_EXPRESSION;
setExpression(charName, targetExpression, true);
}
}
async function moduleWorker() {
const context = getContext();
// Hide and disable talkinghead while in local mode
// Hide and disable Talkinghead while in local mode
$('#image_type_block').toggle(!extension_settings.expressions.local);
if (extension_settings.expressions.local && extension_settings.expressions.talkinghead) {
@@ -691,7 +709,7 @@ async function moduleWorker() {
}
/**
* Starts/stops talkinghead talking animation.
* Starts/stops Talkinghead talking animation.
*
* Talking starts only when all the following conditions are met:
* - The LLM is currently streaming its output.
@@ -700,10 +718,13 @@ async function moduleWorker() {
*
* In all other cases, talking stops.
*
* A talkinghead API call is made only when the talking state changes.
* A Talkinghead API call is made only when the talking state changes.
*
* Note that also the TTS system, if enabled, starts/stops the Talkinghead talking animation.
* See `talkingAnimation` in `SillyTavern/public/scripts/extensions/tts/index.js`.
*/
async function updateTalkingState() {
// Don't bother if talkinghead is disabled or not loaded.
// Don't bother if Talkinghead is disabled or not loaded.
if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) {
return;
}
@@ -727,7 +748,7 @@ async function updateTalkingState() {
newTalkingState = false;
}
try {
// Call the talkinghead API only if the talking state changed.
// Call the Talkinghead API only if the talking state changed.
if (newTalkingState !== lastTalkingState) {
console.debug(`updateTalkingState: calling ${url.pathname}`);
await doExtrasFetch(url);
@@ -787,6 +808,7 @@ function getSpriteFolderName(characterMessage = null, characterName = null) {
}
function setTalkingHeadState(newState) {
console.debug(`expressions: New talkinghead state: ${newState}`);
extension_settings.expressions.talkinghead = newState; // Store setting
saveSettingsDebounced();
@@ -871,12 +893,12 @@ async function setSpriteSlashCommand(_, spriteId) {
spriteId = spriteId.trim().toLowerCase();
// In talkinghead mode, don't check for the existence of the sprite
// In Talkinghead mode, don't check for the existence of the sprite
// (emotion names are the same as for sprites, but it only needs "talkinghead.png").
const currentLastMessage = getLastCharacterMessage();
const spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
let label = spriteId;
if (!isTalkingHeadEnabled()) {
if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) {
await validateImages(spriteFolderName);
// Fuzzy search for sprite
@@ -1051,6 +1073,8 @@ function drawSpritesList(character, labels, sprites) {
* @returns {string} Rendered list item template
*/
function getListItem(item, imageSrc, textClass, isCustom) {
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
imageSrc = isFirefox ? `${imageSrc}?t=${Date.now()}` : imageSrc;
return renderExtensionTemplate(MODULE_NAME, 'list-item', { item, imageSrc, textClass, isCustom });
}
@@ -1144,7 +1168,7 @@ async function getExpressionsList() {
}
async function setExpression(character, expression, force) {
if (!isTalkingHeadEnabled()) {
if (!isTalkingHeadEnabled() || !modules.includes('talkinghead')) {
console.debug('entered setExpressions');
await validateImages(character);
const img = $('img.expression');
@@ -1255,8 +1279,8 @@ async function setExpression(character, expression, force) {
document.getElementById('expression-holder').style.display = '';
} else {
// Set the talkinghead emotion to the specified expression
// TODO: For now, talkinghead emote only supported when VN mode is off; see also updateVisualNovelMode.
// Set the Talkinghead emotion to the specified expression
// TODO: For now, Talkinghead emote only supported when VN mode is off; see also updateVisualNovelMode.
try {
let result = await isTalkingHeadAvailable();
if (result) {
@@ -1409,8 +1433,8 @@ async function onClickExpressionUpload(event) {
// Reset the input
e.target.form.reset();
// In talkinghead mode, when a new talkinghead image is uploaded, refresh the live char.
if (isTalkingHeadEnabled() && id === 'talkinghead') {
// In Talkinghead mode, when a new talkinghead image is uploaded, refresh the live char.
if (id === 'talkinghead' && isTalkingHeadEnabled() && modules.includes('talkinghead')) {
await loadTalkingHead();
}
};
@@ -1520,6 +1544,11 @@ async function onClickExpressionUploadPackButton() {
// Reset the input
e.target.form.reset();
// In Talkinghead mode, refresh the live char.
if (isTalkingHeadEnabled() && modules.includes('talkinghead')) {
await loadTalkingHead();
}
};
$('#expression_upload_pack')
@@ -1657,6 +1686,34 @@ async function fetchImagesNoCache() {
$('#expression_custom_remove').on('click', onClickExpressionRemoveCustom);
}
// Pause Talkinghead to save resources when the ST tab is not visible or the window is minimized.
// We currently do this via loading/unloading. Could be improved by adding new pause/unpause endpoints to Extras.
document.addEventListener('visibilitychange', function (event) {
let pageIsVisible;
if (document.hidden) {
console.debug('expressions: SillyTavern is now hidden');
pageIsVisible = false;
} else {
console.debug('expressions: SillyTavern is now visible');
pageIsVisible = true;
}
if (isTalkingHeadEnabled() && modules.includes('talkinghead')) {
isTalkingHeadAvailable().then(result => {
if (result) {
if (pageIsVisible) {
loadTalkingHead();
} else {
unloadTalkingHead();
}
handleImageChange(); // Change image as needed
} else {
//console.log("talkinghead does not exist.");
}
});
}
});
addExpressionImage();
addVisualNovelMode();
addSettings();
@@ -1664,7 +1721,7 @@ async function fetchImagesNoCache() {
const updateFunction = wrapper.update.bind(wrapper);
setInterval(updateFunction, UPDATE_INTERVAL);
moduleWorker();
// For setting the talkinghead talking animation on/off quickly enough for realtime use, we need another timer on a shorter schedule.
// For setting the Talkinghead talking animation on/off quickly enough for realtime use, we need another timer on a shorter schedule.
const wrapperTalkingState = new ModuleWorkerWrapper(updateTalkingState);
const updateTalkingStateFunction = wrapperTalkingState.update.bind(wrapperTalkingState);
setInterval(updateTalkingStateFunction, TALKINGCHECK_UPDATE_INTERVAL);
@@ -1701,4 +1758,5 @@ async function fetchImagesNoCache() {
registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], '<span class="monospace">(spriteId)</span> force sets the sprite for the current character', true, true);
registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], '<span class="monospace">(optional folder)</span> sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true);
registerSlashCommand('lastsprite', (_, value) => lastExpression[value.trim()] ?? '', [], '<span class="monospace">(charName)</span> Returns the last set sprite / expression for the named character.', true, true);
registerSlashCommand('th', toggleTalkingHeadCommand, ['talkinghead'], ' Character Expressions: toggles <i>Image Type - talkinghead (extras)</i> on/off.');
})();

View File

@@ -73,12 +73,12 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown,
) {
// Check if the depth is within the min/max depth
if (typeof depth === 'number' && depth >= 0) {
if (!isNaN(script.minDepth) && script.minDepth >= 0 && depth < script.minDepth) {
if (!isNaN(script.minDepth) && script.minDepth !== null && script.minDepth >= 0 && depth < script.minDepth) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because depth ${depth} is less than minDepth ${script.minDepth}`);
return;
}
if (!isNaN(script.maxDepth) && script.maxDepth >= 0 && depth > script.maxDepth) {
if (!isNaN(script.maxDepth) && script.maxDepth !== null && script.maxDepth >= 0 && depth > script.maxDepth) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because depth ${depth} is greater than maxDepth ${script.maxDepth}`);
return;
}

View File

@@ -536,7 +536,7 @@ async function processTtsQueue() {
}
if (extension_settings.tts.narrate_quoted_only) {
const special_quotes = /[“”]/g; // Extend this regex to include other special quotes
const special_quotes = /[“”«»]/g; // Extend this regex to include other special quotes
text = text.replace(special_quotes, '"');
const matches = text.match(/".*?"/g); // Matches text inside double quotes, non-greedily
const partJoiner = (ttsProvider?.separator || ' ... ');

View File

@@ -674,7 +674,7 @@ export function parseNovelAILogprobs(data) {
// them with a logprob of -Infinity (0% probability)
const notInAfter = befores
.filter(([id]) => !afters.some(([aid]) => aid === id))
.map(([id]) => [id, -Infinity])
.map(([id]) => [id, -Infinity]);
const merged = afters.concat(notInAfter);
// Add the chosen token to `merged` if it's not already there. This can

View File

@@ -31,6 +31,7 @@ import {
this_chid,
} from '../script.js';
import { groups, selected_group } from './group-chats.js';
import { registerSlashCommand } from './slash-commands.js';
import {
chatCompletionDefaultPrompts,
@@ -1617,7 +1618,7 @@ async function sendOpenAIRequest(type, messages, signal) {
}
// Add logprobs request (currently OpenAI only, max 5 on their side)
if (useLogprobs && isOAI) {
if (useLogprobs && (isOAI || isCustom)) {
generate_data['logprobs'] = 5;
}
@@ -1691,24 +1692,17 @@ async function sendOpenAIRequest(type, messages, signal) {
throw new Error(`Got response status ${response.status}`);
}
if (stream) {
let reader;
let isSSEStream = oai_settings.chat_completion_source !== chat_completion_sources.MAKERSUITE;
if (isSSEStream) {
const eventStream = new EventSourceStream();
response.body.pipeThrough(eventStream);
reader = eventStream.readable.getReader();
} else {
reader = response.body.getReader();
}
const eventStream = new EventSourceStream();
response.body.pipeThrough(eventStream);
const reader = eventStream.readable.getReader();
return async function* streamData() {
let text = '';
let utf8Decoder = new TextDecoder();
const swipes = [];
while (true) {
const { done, value } = await reader.read();
if (done) return;
const rawData = isSSEStream ? value.data : utf8Decoder.decode(value, { stream: true });
if (isSSEStream && rawData === '[DONE]') return;
const rawData = value.data;
if (rawData === '[DONE]') return;
tryParseStreamingError(response, rawData);
const parsed = JSON.parse(rawData);
@@ -1749,7 +1743,7 @@ function getStreamingReply(data) {
if (oai_settings.chat_completion_source == chat_completion_sources.CLAUDE) {
return data?.completion || '';
} else if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) {
return data?.candidates[0].content.parts[0].text || '';
return data?.candidates?.[0]?.content?.parts?.[0]?.text || '';
} else {
return data.choices[0]?.delta?.content || data.choices[0]?.message?.content || data.choices[0]?.text || '';
}
@@ -1768,6 +1762,7 @@ function parseChatCompletionLogprobs(data) {
switch (oai_settings.chat_completion_source) {
case chat_completion_sources.OPENAI:
case chat_completion_sources.CUSTOM:
if (!data.choices?.length) {
return null;
}
@@ -3933,6 +3928,28 @@ $('#delete_proxy').on('click', async function () {
}
});
function runProxyCallback(_, value) {
if (!value) {
toastr.warning('Proxy preset name is required');
return '';
}
const proxyNames = proxies.map(preset => preset.name);
const fuse = new Fuse(proxyNames);
const result = fuse.search(value);
if (result.length === 0) {
toastr.warning(`Proxy preset "${value}" not found`);
return '';
}
const foundName = result[0].item;
$('#openai_proxy_preset').val(foundName).trigger('change');
return foundName;
}
registerSlashCommand('proxy', runProxyCallback, [], '<span class="monospace">(name)</span> sets a proxy preset by name');
$(document).ready(async function () {
$('#test_api_button').on('click', testApiConnection);

View File

@@ -17,6 +17,7 @@ export const SECRET_KEYS = {
MISTRALAI: 'api_key_mistralai',
TOGETHERAI: 'api_key_togetherai',
CUSTOM: 'api_key_custom',
OOBA: 'api_key_ooba',
};
const INPUT_MAP = {
@@ -35,6 +36,7 @@ const INPUT_MAP = {
[SECRET_KEYS.MISTRALAI]: '#api_key_mistralai',
[SECRET_KEYS.CUSTOM]: '#api_key_custom',
[SECRET_KEYS.TOGETHERAI]: '#api_key_togetherai',
[SECRET_KEYS.OOBA]: '#api_key_ooba',
};
async function clearSecret() {

View File

@@ -154,7 +154,7 @@ parser.addCommand('sysgen', generateSystemMessage, [], '<span class="monospace">
parser.addCommand('ask', askCharacter, [], '<span class="monospace">(prompt)</span> asks a specified character card a prompt', true, true);
parser.addCommand('delname', deleteMessagesByNameCallback, ['cancel'], '<span class="monospace">(name)</span> deletes all messages attributed to a specified name', true, true);
parser.addCommand('send', sendUserMessageCallback, [], '<span class="monospace">(text)</span> adds a user message to the chat log without triggering a generation', true, true);
parser.addCommand('trigger', triggerGenerationCallback, [], ' triggers a message generation. If in group, can trigger a message for the specified group member index or name.', true, true);
parser.addCommand('trigger', triggerGenerationCallback, [], ' <span class="monospace">await=true/false</span> triggers a message generation. If in group, can trigger a message for the specified group member index or name. If <code>await=true</code> named argument passed, the command will await for the triggered generation before continuing.', true, true);
parser.addCommand('hide', hideMessageCallback, [], '<span class="monospace">(message index or range)</span> hides a chat message from the prompt', true, true);
parser.addCommand('unhide', unhideMessageCallback, [], '<span class="monospace">(message index or range)</span> unhides a message from the prompt', true, true);
parser.addCommand('disable', disableGroupMemberCallback, [], '<span class="monospace">(member index or name)</span> disables a group member from being drafted for replies', true, true);
@@ -1029,8 +1029,9 @@ async function addGroupMemberCallback(_, arg) {
return character.name;
}
async function triggerGenerationCallback(_, arg) {
setTimeout(async () => {
async function triggerGenerationCallback(args, value) {
const shouldAwait = isTrueBoolean(args?.await);
const outerPromise = new Promise((outerResolve) => setTimeout(async () => {
try {
await waitUntilCondition(() => !is_send_press && !is_group_generating, 10000, 100);
} catch {
@@ -1044,16 +1045,21 @@ async function triggerGenerationCallback(_, arg) {
let chid = undefined;
if (selected_group && arg) {
chid = findGroupMemberId(arg);
if (selected_group && value) {
chid = findGroupMemberId(value);
if (chid === undefined) {
console.warn(`WARN: No group member found for argument ${arg}`);
console.warn(`WARN: No group member found for argument ${value}`);
}
}
setTimeout(() => Generate('normal', { force_chid: chid }), 100);
}, 1);
outerResolve(new Promise(innerResolve => setTimeout(() => innerResolve(Generate('normal', { force_chid: chid })), 100)));
}, 1));
if (shouldAwait) {
const innerPromise = await outerPromise;
await innerPromise;
}
return '';
}

View File

@@ -716,6 +716,7 @@ function parseTextgenLogprobs(token, logprobs) {
}
switch (settings.type) {
case TABBY:
case APHRODITE:
case OOBA: {
/** @type {Record<string, number>[]} */
@@ -807,6 +808,8 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'temperature': settings.dynatemp ? (settings.min_temp + settings.max_temp) / 2 : settings.temp,
'top_p': settings.top_p,
'typical_p': settings.typical_p,
'typical': settings.typical_p,
'sampler_seed': settings.seed,
'min_p': settings.min_p,
'repetition_penalty': settings.rep_pen,
'frequency_penalty': settings.freq_pen,
@@ -819,8 +822,8 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
'early_stopping': settings.early_stopping,
'add_bos_token': settings.add_bos_token,
'dynamic_temperature': settings.dynatemp,
'dynatemp_low': settings.dynatemp ? settings.min_temp : 0,
'dynatemp_high': settings.dynatemp ? settings.max_temp : 0,
'dynatemp_low': settings.dynatemp ? settings.min_temp : 1,
'dynatemp_high': settings.dynatemp ? settings.max_temp : 1,
'dynatemp_range': settings.dynatemp ? (settings.max_temp - settings.min_temp) / 2 : 0,
'dynatemp_exponent': settings.dynatemp ? settings.dynatemp_exponent : 1,
'smoothing_factor': settings.smoothing_factor,

0
public/user/.gitkeep Normal file
View File

View File

@@ -37,6 +37,14 @@ function getTabbyHeaders() {
}) : {};
}
function getOobaHeaders() {
const apiKey = readSecret(SECRET_KEYS.OOBA);
return apiKey ? ({
'Authorization': `Bearer ${apiKey}`,
}) : {};
}
function getOverrideHeaders(urlHost) {
const requestOverrides = getConfigValue('requestOverrides', []);
const overrideHeaders = requestOverrides?.find((e) => e.hosts?.includes(urlHost))?.headers;
@@ -69,6 +77,9 @@ function setAdditionalHeaders(request, args, server) {
case TEXTGEN_TYPES.TOGETHERAI:
headers = getTogetherAIHeaders();
break;
case TEXTGEN_TYPES.OOBA:
headers = getOobaHeaders();
break;
default:
headers = server ? getOverrideHeaders((new URL(server))?.host) : {};
break;

View File

@@ -1,5 +1,6 @@
const DIRECTORIES = {
worlds: 'public/worlds/',
user: 'public/user',
avatars: 'public/User Avatars',
images: 'public/img/',
userImages: 'public/user/images/',

View File

@@ -267,7 +267,7 @@ async function sendMakerSuiteRequest(request, response) {
? (stream ? 'streamGenerateContent' : 'generateContent')
: (isText ? 'generateText' : 'generateMessage');
const generateResponse = await fetch(`https://generativelanguage.googleapis.com/${apiVersion}/models/${model}:${responseType}?key=${apiKey}`, {
const generateResponse = await fetch(`https://generativelanguage.googleapis.com/${apiVersion}/models/${model}:${responseType}?key=${apiKey}${stream ? '&alt=sse' : ''}`, {
body: JSON.stringify(body),
method: 'POST',
headers: {
@@ -279,36 +279,8 @@ async function sendMakerSuiteRequest(request, response) {
// have to do this because of their busted ass streaming endpoint
if (stream) {
try {
let partialData = '';
generateResponse.body.on('data', (data) => {
const chunk = data.toString();
if (chunk.startsWith(',') || chunk.endsWith(',') || chunk.startsWith('[') || chunk.endsWith(']')) {
partialData = chunk.slice(1);
} else {
partialData += chunk;
}
while (true) {
let json;
try {
json = JSON.parse(partialData);
} catch (e) {
break;
}
response.write(JSON.stringify(json));
partialData = '';
}
});
request.socket.on('close', function () {
if (generateResponse.body instanceof Readable) generateResponse.body.destroy();
response.end();
});
generateResponse.body.on('end', () => {
console.log('Streaming request finished');
response.end();
});
// Pipe remote SSE stream to Express response
forwardFetchResponse(generateResponse, response);
} catch (error) {
console.log('Error forwarding streaming response:', error);
if (!response.headersSent) {
@@ -719,7 +691,7 @@ router.post('/generate', jsonParser, function (request, response) {
// Adjust logprobs params for Chat Completions API, which expects { top_logprobs: number; logprobs: boolean; }
if (!isTextCompletion && bodyParams.logprobs > 0) {
bodyParams.top_logprobs = bodyParams.logprobs;
bodyParams.logprobs = true
bodyParams.logprobs = true;
}
if (getConfigValue('openai.randomizeUserId', false)) {
@@ -751,7 +723,16 @@ router.post('/generate', jsonParser, function (request, response) {
apiUrl = request.body.custom_url;
apiKey = readSecret(SECRET_KEYS.CUSTOM);
headers = {};
bodyParams = {};
bodyParams = {
logprobs: request.body.logprobs,
};
// Adjust logprobs params for Chat Completions API, which expects { top_logprobs: number; logprobs: boolean; }
if (!isTextCompletion && bodyParams.logprobs > 0) {
bodyParams.top_logprobs = bodyParams.logprobs;
bodyParams.logprobs = true;
}
mergeObjectWithYaml(bodyParams, request.body.custom_include_body);
mergeObjectWithYaml(headers, request.body.custom_include_headers);
} else {

View File

@@ -216,7 +216,7 @@ router.post('/generate', jsonParser, async function (req, res) {
}
const data = await response.json();
console.log("NovelAI Output", data?.output);
console.log('NovelAI Output', data?.output);
return res.send(data);
}
} catch (error) {

View File

@@ -33,6 +33,7 @@ router.post('/caption-image', jsonParser, async (request, response) => {
}
if (request.body.api === 'ooba') {
key = readSecret(SECRET_KEYS.OOBA);
bodyParams.temperature = 0.1;
}

View File

@@ -29,6 +29,7 @@ const SECRET_KEYS = {
TOGETHERAI: 'api_key_togetherai',
MISTRALAI: 'api_key_mistralai',
CUSTOM: 'api_key_custom',
OOBA: 'api_key_ooba',
};
/**

View File

@@ -172,7 +172,7 @@ function getSourceSettings(source, request) {
const sourceSettings = {
extrasUrl: extrasUrl,
extrasKey: extrasKey
extrasKey: extrasKey,
};
return sourceSettings;
}