Merge branch 'staging' into pygimport

This commit is contained in:
Cohee
2024-02-17 02:23:18 +02:00
5 changed files with 88 additions and 32 deletions

View File

@@ -3,8 +3,9 @@ TODO:
*/ */
//const DEBUG_TONY_SAMA_FORK_MODE = true //const DEBUG_TONY_SAMA_FORK_MODE = true
import { getRequestHeaders, callPopup } from '../../../script.js'; import { getRequestHeaders, callPopup, processDroppedFiles } from '../../../script.js';
import { deleteExtension, extensionNames, installExtension, renderExtensionTemplate } from '../../extensions.js'; import { deleteExtension, extensionNames, getContext, installExtension, renderExtensionTemplate } from '../../extensions.js';
import { executeSlashCommands } from '../../slash-commands.js';
import { getStringHash, isValidUrl } from '../../utils.js'; import { getStringHash, isValidUrl } from '../../utils.js';
export { MODULE_NAME }; export { MODULE_NAME };
@@ -61,8 +62,8 @@ function downloadAssetsList(url) {
for (const i in availableAssets[assetType]) { for (const i in availableAssets[assetType]) {
const asset = availableAssets[assetType][i]; const asset = availableAssets[assetType][i];
const elemId = `assets_install_${assetType}_${i}`; const elemId = `assets_install_${assetType}_${i}`;
let element = $('<button />', { id: elemId, type: 'button', class: 'asset-download-button menu_button' }); let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
const label = $('<i class="fa-fw fa-solid fa-download fa-xl"></i>'); const label = $('<i class="fa-fw fa-solid fa-download fa-lg"></i>');
element.append(label); element.append(label);
//if (DEBUG_TONY_SAMA_FORK_MODE) //if (DEBUG_TONY_SAMA_FORK_MODE)
@@ -90,6 +91,11 @@ function downloadAssetsList(url) {
}; };
const assetDelete = async function () { const assetDelete = async function () {
if (assetType === 'character') {
toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
await executeSlashCommands(`/go ${asset['id']}`);
return;
}
element.off('click'); element.off('click');
await deleteAsset(assetType, asset['id']); await deleteAsset(assetType, asset['id']);
label.removeClass('fa-check'); label.removeClass('fa-check');
@@ -126,20 +132,27 @@ function downloadAssetsList(url) {
const displayName = DOMPurify.sanitize(asset['name'] || asset['id']); const displayName = DOMPurify.sanitize(asset['name'] || asset['id']);
const description = DOMPurify.sanitize(asset['description'] || ''); const description = DOMPurify.sanitize(asset['description'] || '');
const url = isValidUrl(asset['url']) ? asset['url'] : ''; const url = isValidUrl(asset['url']) ? asset['url'] : '';
const previewIcon = assetType == 'extension' ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple'; const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
$('<i></i>') const assetBlock = $('<i></i>')
.append(element) .append(element)
.append(`<div class="flex-container flexFlowColumn"> .append(`<div class="flex-container flexFlowColumn flexNoGap">
<span class="flex-container alignitemscenter"> <span class="asset-name flex-container alignitemscenter">
<b>${displayName}</b> <b>${displayName}</b>
<a class="asset_preview" href="${url}" target="_blank" title="Preview in browser"> <a class="asset_preview" href="${url}" target="_blank" title="Preview in browser">
<i class="fa-solid fa-sm ${previewIcon}"></i> <i class="fa-solid fa-sm ${previewIcon}"></i>
</a> </a>
</span> </span>
<span>${description}</span> <small class="asset-description">
</div>`) ${description}
.appendTo(assetTypeMenu); </small>
</div>`);
if (assetType === 'character') {
assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['url']}" alt="${displayName}"></div>`);
}
assetTypeMenu.append(assetBlock);
} }
assetTypeMenu.appendTo('#assets_menu'); assetTypeMenu.appendTo('#assets_menu');
assetTypeMenu.on('click', 'a.asset_preview', previewAsset); assetTypeMenu.on('click', 'a.asset_preview', previewAsset);
@@ -186,6 +199,10 @@ function isAssetInstalled(assetType, filename) {
assetList = extensionNames.filter(x => x.startsWith(thirdPartyMarker)).map(x => x.replace(thirdPartyMarker, '')); assetList = extensionNames.filter(x => x.startsWith(thirdPartyMarker)).map(x => x.replace(thirdPartyMarker, ''));
} }
if (assetType == 'character') {
assetList = getContext().characters.map(x => x.avatar);
}
for (const i of assetList) { for (const i of assetList) {
//console.debug(DEBUG_PREFIX,i,filename) //console.debug(DEBUG_PREFIX,i,filename)
if (i.includes(filename)) if (i.includes(filename))
@@ -215,6 +232,13 @@ async function installAsset(url, assetType, filename) {
}); });
if (result.ok) { if (result.ok) {
console.debug(DEBUG_PREFIX, 'Download success.'); console.debug(DEBUG_PREFIX, 'Download success.');
if (category === 'character') {
console.debug(DEBUG_PREFIX, 'Importing character ', filename);
const blob = await result.blob();
const file = new File([blob], filename, { type: blob.type });
await processDroppedFiles([file]);
console.debug(DEBUG_PREFIX, 'Character downloaded.');
}
} }
} }
catch (err) { catch (err) {

View File

@@ -27,17 +27,14 @@
color: inherit; color: inherit;
} }
.assets-list-div i { .assets-list-div > i {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: left; justify-content: left;
padding: 5px; padding: 5px;
font-style: normal; font-style: normal;
} gap: 5px;
.assets-list-div i span {
margin-left: 10px;
} }
.assets-list-div i span:first-of-type { .assets-list-div i span:first-of-type {
@@ -46,12 +43,11 @@
.asset-download-button { .asset-download-button {
position: relative; position: relative;
width: 50px;
padding: 8px 16px;
border: none; border: none;
outline: none; outline: none;
border-radius: 2px; border-radius: 2px;
cursor: pointer; cursor: pointer;
filter: none !important;
} }
.asset-download-button:active { .asset-download-button:active {
@@ -85,6 +81,21 @@
animation: asset-download-button-loading-spinner 1s ease infinite; animation: asset-download-button-loading-spinner 1s ease infinite;
} }
.asset-name .avatar {
--imgSize: 30px !important;
flex: unset;
width: var(--imgSize);
height: var(--imgSize);
}
.asset-name .avatar img {
width: var(--imgSize);
height: var(--imgSize);
border-radius: 50%;
object-fit: cover;
object-position: center center;
}
@keyframes asset-download-button-loading-spinner { @keyframes asset-download-button-loading-spinner {
from { from {
transform: rotate(0turn); transform: rotate(0turn);

View File

@@ -80,14 +80,23 @@ class SlashCommandParser {
const excludedFromRegex = ['sendas']; const excludedFromRegex = ['sendas'];
const firstSpace = text.indexOf(' '); const firstSpace = text.indexOf(' ');
const command = firstSpace !== -1 ? text.substring(1, firstSpace) : text.substring(1); const command = firstSpace !== -1 ? text.substring(1, firstSpace) : text.substring(1);
const args = firstSpace !== -1 ? text.substring(firstSpace + 1) : ''; let args = firstSpace !== -1 ? text.substring(firstSpace + 1) : '';
const argObj = {}; const argObj = {};
let unnamedArg; let unnamedArg;
if (args.length > 0) { if (args.length > 0) {
let match;
// Match unnamed argument
const unnamedArgPattern = /(?:\w+=(?:"(?:\\.|[^"\\])*"|\S+)\s*)*(.*)/s;
match = unnamedArgPattern.exec(args);
if (match !== null && match[1].length > 0) {
args = args.slice(0, -match[1].length);
unnamedArg = match[1].trim();
}
// Match named arguments // Match named arguments
const namedArgPattern = /(\w+)=("(?:\\.|[^"\\])*"|\S+)/g; const namedArgPattern = /(\w+)=("(?:\\.|[^"\\])*"|\S+)/g;
let match;
while ((match = namedArgPattern.exec(args)) !== null) { while ((match = namedArgPattern.exec(args)) !== null) {
const key = match[1]; const key = match[1];
const value = match[2]; const value = match[2];
@@ -95,13 +104,6 @@ class SlashCommandParser {
argObj[key] = value.replace(/(^")|("$)/g, ''); argObj[key] = value.replace(/(^")|("$)/g, '');
} }
// Match unnamed argument
const unnamedArgPattern = /(?:\w+=(?:"(?:\\.|[^"\\])*"|\S+)\s*)*(.*)/s;
match = unnamedArgPattern.exec(args);
if (match !== null) {
unnamedArg = match[1].trim();
}
// Excluded commands format in their own function // Excluded commands format in their own function
if (!excludedFromRegex.includes(command)) { if (!excludedFromRegex.includes(command)) {
unnamedArg = getRegexedString( unnamedArg = getRegexedString(
@@ -1119,6 +1121,12 @@ function findCharacterIndex(name) {
(a, b) => a.includes(b), (a, b) => a.includes(b),
]; ];
const exactAvatarMatch = characters.findIndex(x => x.avatar === name);
if (exactAvatarMatch !== -1) {
return exactAvatarMatch;
}
for (const matchType of matchTypes) { for (const matchType of matchTypes) {
const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase())); const index = characters.findIndex(x => matchType(x.name.toLowerCase(), name.toLowerCase()));
if (index !== -1) { if (index !== -1) {

View File

@@ -8,7 +8,7 @@ const { DIRECTORIES, UNSAFE_EXTENSIONS } = require('../constants');
const { jsonParser } = require('../express-common'); const { jsonParser } = require('../express-common');
const { clientRelativePath } = require('../util'); const { clientRelativePath } = require('../util');
const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm']; const VALID_CATEGORIES = ['bgm', 'ambient', 'blip', 'live2d', 'vrm', 'character'];
/** /**
* Validates the input filename for the asset. * Validates the input filename for the asset.
@@ -199,6 +199,13 @@ router.post('/download', jsonParser, async (request, response) => {
const fileStream = fs.createWriteStream(destination, { flags: 'wx' }); const fileStream = fs.createWriteStream(destination, { flags: 'wx' });
await finished(res.body.pipe(fileStream)); await finished(res.body.pipe(fileStream));
if (category === 'character') {
response.sendFile(temp_path, { root: process.cwd() }, () => {
fs.rmSync(temp_path);
});
return;
}
// Move into asset place // Move into asset place
console.debug('Download finished, moving file from', temp_path, 'to', file_path); console.debug('Download finished, moving file from', temp_path, 'to', file_path);
fs.renameSync(temp_path, file_path); fs.renameSync(temp_path, file_path);

View File

@@ -513,8 +513,11 @@ router.post('/status', jsonParser, async function (request, response_getstatus_o
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
api_url = 'https://openrouter.ai/api/v1'; api_url = 'https://openrouter.ai/api/v1';
api_key_openai = readSecret(SECRET_KEYS.OPENROUTER); api_key_openai = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs // OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
headers = { 'HTTP-Referer': request.headers.referer }; headers = {
'HTTP-Referer': 'https://sillytavern.app',
'X-Title': 'SillyTavern',
};
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.MISTRALAI) {
api_url = new URL(request.body.reverse_proxy || API_MISTRAL).toString(); api_url = new URL(request.body.reverse_proxy || API_MISTRAL).toString();
api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI); api_key_openai = request.body.reverse_proxy ? request.body.proxy_password : readSecret(SECRET_KEYS.MISTRALAI);
@@ -700,8 +703,11 @@ router.post('/generate', jsonParser, function (request, response) {
} else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) { } else if (request.body.chat_completion_source === CHAT_COMPLETION_SOURCES.OPENROUTER) {
apiUrl = 'https://openrouter.ai/api/v1'; apiUrl = 'https://openrouter.ai/api/v1';
apiKey = readSecret(SECRET_KEYS.OPENROUTER); apiKey = readSecret(SECRET_KEYS.OPENROUTER);
// OpenRouter needs to pass the referer: https://openrouter.ai/docs // OpenRouter needs to pass the Referer and X-Title: https://openrouter.ai/docs#requests
headers = { 'HTTP-Referer': request.headers.referer }; headers = {
'HTTP-Referer': 'https://sillytavern.app',
'X-Title': 'SillyTavern',
};
bodyParams = { 'transforms': ['middle-out'] }; bodyParams = { 'transforms': ['middle-out'] };
if (request.body.min_p !== undefined) { if (request.body.min_p !== undefined) {