Merge pull request #1810 from SillyTavern/staging

Staging
This commit is contained in:
Cohee 2024-02-10 21:24:42 +02:00 committed by GitHub
commit 91f31e746e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 140 additions and 1180 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 }}

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);
}
@ -6089,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
@ -6099,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) {
@ -6107,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);
@ -7817,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;
@ -7979,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);
@ -8208,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,
@ -8238,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');
@ -8541,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

@ -1688,7 +1688,7 @@ async function fetchImagesNoCache() {
// 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) {
document.addEventListener('visibilitychange', function (event) {
let pageIsVisible;
if (document.hidden) {
console.debug('expressions: SillyTavern is now hidden');

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,
@ -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 || '';
}
@ -3934,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)) {

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;
}