mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge remote-tracking branch 'upstream/staging' into staging
This commit is contained in:
@ -121,7 +121,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Add the custom checkbox */
|
/* Add the custom checkbox */
|
||||||
.select2-results__option:before {
|
.select2-results__option::before {
|
||||||
content: '';
|
content: '';
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -141,11 +141,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Add the custom checkbox checkmark */
|
/* Add the custom checkbox checkmark */
|
||||||
.select2-results__option--selected.select2-results__option:before {
|
.select2-results__option--selected.select2-results__option::before {
|
||||||
content: '\2713';
|
content: '\2713';
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--SmartThemeBodyColor);
|
||||||
background-color: var(--SmartThemeBlurTintColor);
|
background-color: var(--SmartThemeBlurTintColor);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-results__option.select2-results__message {
|
||||||
|
background-color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select2-results__option.select2-results__message::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@ -3847,7 +3847,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="rm_character_import" class="right_menu" style="display: none;">
|
<div id="rm_character_import" class="right_menu" style="display: none;">
|
||||||
<form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
<form id="form_import" action="javascript:void(null);" method="post" enctype="multipart/form-data">
|
||||||
<input multiple type="file" id="character_import_file" accept=".json, image/png" name="avatar">
|
<input multiple type="file" id="character_import_file" accept=".json, image/png, .yaml, .yml" name="avatar">
|
||||||
<input id="character_import_file_type" name="file_type" class="text_pole" maxlength="999" size="2" value="" autocomplete="off">
|
<input id="character_import_file_type" name="file_type" class="text_pole" maxlength="999" size="2" value="" autocomplete="off">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
1332
public/script.js
1332
public/script.js
File diff suppressed because it is too large
Load Diff
@ -47,8 +47,6 @@ export function saveMetadataDebounced() {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const extensionsHandlebars = Handlebars.create();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an ability for extensions to render HTML templates.
|
* Provides an ability for extensions to render HTML templates.
|
||||||
* Templates sanitation and localization is forced.
|
* Templates sanitation and localization is forced.
|
||||||
@ -61,40 +59,6 @@ export function renderExtensionTemplate(extensionName, templateId, templateData
|
|||||||
return renderTemplate(`scripts/extensions/${extensionName}/${templateId}.html`, templateData, sanitize, localize, true);
|
return renderTemplate(`scripts/extensions/${extensionName}/${templateId}.html`, templateData, sanitize, localize, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a Handlebars helper for use in extensions.
|
|
||||||
* @param {string} name Handlebars helper name
|
|
||||||
* @param {function} helper Handlebars helper function
|
|
||||||
*/
|
|
||||||
export function registerExtensionHelper(name, helper) {
|
|
||||||
extensionsHandlebars.registerHelper(name, helper);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies handlebars extension helpers to a message.
|
|
||||||
* @param {number} messageId Message index in the chat.
|
|
||||||
*/
|
|
||||||
export function processExtensionHelpers(messageId) {
|
|
||||||
const context = getContext();
|
|
||||||
const message = context.chat[messageId];
|
|
||||||
|
|
||||||
if (!message?.mes || typeof message.mes !== 'string') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't waste time if there are no mustaches
|
|
||||||
if (!substituteParams(message.mes).includes('{{')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const template = extensionsHandlebars.compile(substituteParams(message.mes), { noEscape: true });
|
|
||||||
message.mes = template({});
|
|
||||||
} catch {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disables parallel updates
|
// Disables parallel updates
|
||||||
class ModuleWorkerWrapper {
|
class ModuleWorkerWrapper {
|
||||||
constructor(callback) {
|
constructor(callback) {
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from '../../../script.js';
|
} from '../../../script.js';
|
||||||
import { extension_settings, getContext } from '../../extensions.js';
|
import { extension_settings, getContext } from '../../extensions.js';
|
||||||
import { secret_state, writeSecret } from '../../secrets.js';
|
import { secret_state, writeSecret } from '../../secrets.js';
|
||||||
|
import { splitRecursive } from '../../utils.js';
|
||||||
|
|
||||||
export const autoModeOptions = {
|
export const autoModeOptions = {
|
||||||
NONE: 'none',
|
NONE: 'none',
|
||||||
@ -315,6 +316,28 @@ async function translateProviderBing(text, lang) {
|
|||||||
throw new Error(response.statusText);
|
throw new Error(response.statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits text into chunks and translates each chunk separately
|
||||||
|
* @param {string} text Text to translate
|
||||||
|
* @param {string} lang Target language code
|
||||||
|
* @param {(text: string, lang: string) => Promise<string>} translateFn Function to translate a single chunk (must return a Promise)
|
||||||
|
* @param {number} chunkSize Maximum chunk size
|
||||||
|
* @returns {Promise<string>} Translated text
|
||||||
|
*/
|
||||||
|
async function chunkedTranslate(text, lang, translateFn, chunkSize = 5000) {
|
||||||
|
if (text.length <= chunkSize) {
|
||||||
|
return await translateFn(text, lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
const chunks = splitRecursive(text, chunkSize);
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
result += await translateFn(chunk, lang);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translates text using the selected translation provider
|
* Translates text using the selected translation provider
|
||||||
* @param {string} text Text to translate
|
* @param {string} text Text to translate
|
||||||
@ -331,15 +354,15 @@ async function translate(text, lang) {
|
|||||||
case 'libre':
|
case 'libre':
|
||||||
return await translateProviderLibre(text, lang);
|
return await translateProviderLibre(text, lang);
|
||||||
case 'google':
|
case 'google':
|
||||||
return await translateProviderGoogle(text, lang);
|
return await chunkedTranslate(text, lang, translateProviderGoogle, 5000);
|
||||||
case 'deepl':
|
case 'deepl':
|
||||||
return await translateProviderDeepl(text, lang);
|
return await translateProviderDeepl(text, lang);
|
||||||
case 'deeplx':
|
case 'deeplx':
|
||||||
return await translateProviderDeepLX(text, lang);
|
return await chunkedTranslate(text, lang, translateProviderDeepLX, 1500);
|
||||||
case 'oneringtranslator':
|
case 'oneringtranslator':
|
||||||
return await translateProviderOneRing(text, lang);
|
return await translateProviderOneRing(text, lang);
|
||||||
case 'bing':
|
case 'bing':
|
||||||
return await translateProviderBing(text, lang);
|
return await chunkedTranslate(text, lang, translateProviderBing, 1000);
|
||||||
default:
|
default:
|
||||||
console.error('Unknown translation provider', extension_settings.translate.provider);
|
console.error('Unknown translation provider', extension_settings.translate.provider);
|
||||||
return text;
|
return text;
|
||||||
|
@ -21,7 +21,6 @@ import {
|
|||||||
MAX_INJECTION_DEPTH,
|
MAX_INJECTION_DEPTH,
|
||||||
name1,
|
name1,
|
||||||
name2,
|
name2,
|
||||||
replaceBiasMarkup,
|
|
||||||
replaceItemizedPromptText,
|
replaceItemizedPromptText,
|
||||||
resultCheckStatus,
|
resultCheckStatus,
|
||||||
saveSettingsDebounced,
|
saveSettingsDebounced,
|
||||||
@ -443,8 +442,6 @@ function setOpenAIMessages(chat) {
|
|||||||
content = `${chat[j].name}: ${content}`;
|
content = `${chat[j].name}: ${content}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content = replaceBiasMarkup(content);
|
|
||||||
|
|
||||||
// remove caret return (waste of tokens)
|
// remove caret return (waste of tokens)
|
||||||
content = content.replace(/\r/gm, '');
|
content = content.replace(/\r/gm, '');
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ import {
|
|||||||
main_api,
|
main_api,
|
||||||
name1,
|
name1,
|
||||||
reloadCurrentChat,
|
reloadCurrentChat,
|
||||||
replaceBiasMarkup,
|
removeMacros,
|
||||||
saveChatConditional,
|
saveChatConditional,
|
||||||
sendMessageAsUser,
|
sendMessageAsUser,
|
||||||
sendSystemMessage,
|
sendSystemMessage,
|
||||||
@ -1260,7 +1260,7 @@ export async function sendMessageAs(args, text) {
|
|||||||
|
|
||||||
// Messages that do nothing but set bias will be hidden from the context
|
// Messages that do nothing but set bias will be hidden from the context
|
||||||
const bias = extractMessageBias(mesText);
|
const bias = extractMessageBias(mesText);
|
||||||
const isSystem = replaceBiasMarkup(mesText).trim().length === 0;
|
const isSystem = bias && !removeMacros(mesText).length;
|
||||||
|
|
||||||
const character = characters.find(x => x.name === name);
|
const character = characters.find(x => x.name === name);
|
||||||
let force_avatar, original_avatar;
|
let force_avatar, original_avatar;
|
||||||
@ -1313,7 +1313,7 @@ export async function sendNarratorMessage(args, text) {
|
|||||||
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
|
const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
|
||||||
// Messages that do nothing but set bias will be hidden from the context
|
// Messages that do nothing but set bias will be hidden from the context
|
||||||
const bias = extractMessageBias(text);
|
const bias = extractMessageBias(text);
|
||||||
const isSystem = replaceBiasMarkup(text).trim().length === 0;
|
const isSystem = bias && !removeMacros(text).length;
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -637,6 +637,9 @@ hr {
|
|||||||
order: 2;
|
order: 2;
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
place-self: center;
|
place-self: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.3s;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
#options_button {
|
#options_button {
|
||||||
|
@ -5,7 +5,7 @@ const Readable = require('stream').Readable;
|
|||||||
|
|
||||||
const { jsonParser } = require('../../express-common');
|
const { jsonParser } = require('../../express-common');
|
||||||
const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS } = require('../../constants');
|
const { TEXTGEN_TYPES, TOGETHERAI_KEYS, OLLAMA_KEYS } = require('../../constants');
|
||||||
const { forwardFetchResponse } = require('../../util');
|
const { forwardFetchResponse, trimV1 } = require('../../util');
|
||||||
const { setAdditionalHeaders } = require('../../additional-headers');
|
const { setAdditionalHeaders } = require('../../additional-headers');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
@ -57,6 +57,26 @@ async function parseOllamaStream(jsonStream, request, response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abort KoboldCpp generation request.
|
||||||
|
* @param {string} url Server base URL
|
||||||
|
* @returns {Promise<void>} Promise resolving when we are done
|
||||||
|
*/
|
||||||
|
async function abortKoboldCppRequest(url) {
|
||||||
|
try {
|
||||||
|
console.log('Aborting Kobold generation...');
|
||||||
|
const abortResponse = await fetch(`${url}/api/extra/abort`, {
|
||||||
|
method: 'POST',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!abortResponse.ok) {
|
||||||
|
console.log('Error sending abort request to Kobold:', abortResponse.status, abortResponse.statusText);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//************** Ooba/OpenAI text completions API
|
//************** Ooba/OpenAI text completions API
|
||||||
router.post('/status', jsonParser, async function (request, response) {
|
router.post('/status', jsonParser, async function (request, response) {
|
||||||
if (!request.body) return response.sendStatus(400);
|
if (!request.body) return response.sendStatus(400);
|
||||||
@ -67,9 +87,7 @@ router.post('/status', jsonParser, async function (request, response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Trying to connect to API:', request.body);
|
console.log('Trying to connect to API:', request.body);
|
||||||
|
const baseUrl = trimV1(request.body.api_server);
|
||||||
// Convert to string + remove trailing slash + /v1 suffix
|
|
||||||
const baseUrl = String(request.body.api_server).replace(/\/$/, '').replace(/\/v1$/, '');
|
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
@ -195,12 +213,15 @@ router.post('/generate', jsonParser, async function (request, response) {
|
|||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
request.socket.removeAllListeners('close');
|
request.socket.removeAllListeners('close');
|
||||||
request.socket.on('close', function () {
|
request.socket.on('close', async function () {
|
||||||
|
if (request.body.api_type === TEXTGEN_TYPES.KOBOLDCPP && !response.writableEnded) {
|
||||||
|
await abortKoboldCppRequest(trimV1(baseUrl));
|
||||||
|
}
|
||||||
|
|
||||||
controller.abort();
|
controller.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Convert to string + remove trailing slash + /v1 suffix
|
let url = trimV1(baseUrl);
|
||||||
let url = String(baseUrl).replace(/\/$/, '').replace(/\/v1$/, '');
|
|
||||||
|
|
||||||
if (request.body.legacy_api) {
|
if (request.body.legacy_api) {
|
||||||
url += '/v1/generate';
|
url += '/v1/generate';
|
||||||
@ -337,8 +358,7 @@ ollama.post('/caption-image', jsonParser, async function (request, response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('Ollama caption request:', request.body);
|
console.log('Ollama caption request:', request.body);
|
||||||
// Convert to string + remove trailing slash + /v1 suffix
|
const baseUrl = trimV1(request.body.server_url);
|
||||||
const baseUrl = String(request.body.server_url).replace(/\/$/, '').replace(/\/v1$/, '');
|
|
||||||
|
|
||||||
const fetchResponse = await fetch(`${baseUrl}/api/generate`, {
|
const fetchResponse = await fetch(`${baseUrl}/api/generate`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -383,8 +403,7 @@ llamacpp.post('/caption-image', jsonParser, async function (request, response) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('LlamaCpp caption request:', request.body);
|
console.log('LlamaCpp caption request:', request.body);
|
||||||
// Convert to string + remove trailing slash + /v1 suffix
|
const baseUrl = trimV1(request.body.server_url);
|
||||||
const baseUrl = String(request.body.server_url).replace(/\/$/, '').replace(/\/v1$/, '');
|
|
||||||
|
|
||||||
const fetchResponse = await fetch(`${baseUrl}/completion`, {
|
const fetchResponse = await fetch(`${baseUrl}/completion`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -4,6 +4,7 @@ const readline = require('readline');
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const sanitize = require('sanitize-filename');
|
const sanitize = require('sanitize-filename');
|
||||||
const writeFileAtomicSync = require('write-file-atomic').sync;
|
const writeFileAtomicSync = require('write-file-atomic').sync;
|
||||||
|
const yaml = require('yaml');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
const encode = require('png-chunks-encode');
|
const encode = require('png-chunks-encode');
|
||||||
@ -19,6 +20,7 @@ const characterCardParser = require('../character-card-parser.js');
|
|||||||
const { readWorldInfoFile } = require('./worldinfo');
|
const { readWorldInfoFile } = require('./worldinfo');
|
||||||
const { invalidateThumbnail } = require('./thumbnails');
|
const { invalidateThumbnail } = require('./thumbnails');
|
||||||
const { importRisuSprites } = require('./sprites');
|
const { importRisuSprites } = require('./sprites');
|
||||||
|
const defaultAvatarPath = './public/img/ai4.png';
|
||||||
|
|
||||||
let characters = {};
|
let characters = {};
|
||||||
|
|
||||||
@ -394,6 +396,36 @@ function convertWorldInfoToCharacterBook(name, entries) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a character from a YAML file.
|
||||||
|
* @param {string} uploadPath Path to the uploaded file
|
||||||
|
* @param {import('express').Response} response Express response object
|
||||||
|
*/
|
||||||
|
function importFromYaml(uploadPath, response) {
|
||||||
|
const fileText = fs.readFileSync(uploadPath, 'utf8');
|
||||||
|
fs.rmSync(uploadPath);
|
||||||
|
const yamlData = yaml.parse(fileText);
|
||||||
|
console.log('importing from yaml');
|
||||||
|
yamlData.name = sanitize(yamlData.name);
|
||||||
|
const fileName = getPngName(yamlData.name);
|
||||||
|
let char = convertToV2({
|
||||||
|
'name': yamlData.name,
|
||||||
|
'description': yamlData.context ?? '',
|
||||||
|
'first_mes': yamlData.greeting ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'chat': `${yamlData.name} - ${humanizedISO8601DateTime()}`,
|
||||||
|
'personality': '',
|
||||||
|
'creatorcomment': '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'mes_example': '',
|
||||||
|
'scenario': '',
|
||||||
|
'talkativeness': 0.5,
|
||||||
|
'creator': '',
|
||||||
|
'tags': '',
|
||||||
|
});
|
||||||
|
charaWrite(defaultAvatarPath, JSON.stringify(char), fileName, response, { file_name: fileName });
|
||||||
|
}
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/create', urlencodedParser, async function (request, response) {
|
router.post('/create', urlencodedParser, async function (request, response) {
|
||||||
@ -760,144 +792,147 @@ function getPngName(file) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.post('/import', urlencodedParser, async function (request, response) {
|
router.post('/import', urlencodedParser, async function (request, response) {
|
||||||
|
if (!request.body || !request.file) return response.sendStatus(400);
|
||||||
if (!request.body || request.file === undefined) return response.sendStatus(400);
|
|
||||||
|
|
||||||
let png_name = '';
|
let png_name = '';
|
||||||
let filedata = request.file;
|
let filedata = request.file;
|
||||||
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
let uploadPath = path.join(UPLOADS_PATH, filedata.filename);
|
||||||
var format = request.body.file_type;
|
let format = request.body.file_type;
|
||||||
const defaultAvatarPath = './public/img/ai4.png';
|
|
||||||
//console.log(format);
|
|
||||||
if (filedata) {
|
|
||||||
if (format == 'json') {
|
|
||||||
fs.readFile(uploadPath, 'utf8', async (err, data) => {
|
|
||||||
fs.unlinkSync(uploadPath);
|
|
||||||
|
|
||||||
if (err) {
|
if (format == 'yaml' || format == 'yml') {
|
||||||
console.log(err);
|
try {
|
||||||
response.send({ error: true });
|
importFromYaml(uploadPath, response);
|
||||||
}
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
response.send({ error: true });
|
||||||
|
}
|
||||||
|
} else if (format == 'json') {
|
||||||
|
fs.readFile(uploadPath, 'utf8', async (err, data) => {
|
||||||
|
fs.unlinkSync(uploadPath);
|
||||||
|
|
||||||
let jsonData = JSON.parse(data);
|
if (err) {
|
||||||
|
|
||||||
if (jsonData.spec !== undefined) {
|
|
||||||
console.log('importing from v2 json');
|
|
||||||
importRisuSprites(jsonData);
|
|
||||||
unsetFavFlag(jsonData);
|
|
||||||
jsonData = readFromV2(jsonData);
|
|
||||||
jsonData['create_date'] = humanizedISO8601DateTime();
|
|
||||||
png_name = getPngName(jsonData.data?.name || jsonData.name);
|
|
||||||
let char = JSON.stringify(jsonData);
|
|
||||||
charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name });
|
|
||||||
} else if (jsonData.name !== undefined) {
|
|
||||||
console.log('importing from v1 json');
|
|
||||||
jsonData.name = sanitize(jsonData.name);
|
|
||||||
if (jsonData.creator_notes) {
|
|
||||||
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
|
||||||
}
|
|
||||||
png_name = getPngName(jsonData.name);
|
|
||||||
let char = {
|
|
||||||
'name': jsonData.name,
|
|
||||||
'description': jsonData.description ?? '',
|
|
||||||
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
|
||||||
'personality': jsonData.personality ?? '',
|
|
||||||
'first_mes': jsonData.first_mes ?? '',
|
|
||||||
'avatar': 'none',
|
|
||||||
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
|
||||||
'mes_example': jsonData.mes_example ?? '',
|
|
||||||
'scenario': jsonData.scenario ?? '',
|
|
||||||
'create_date': humanizedISO8601DateTime(),
|
|
||||||
'talkativeness': jsonData.talkativeness ?? 0.5,
|
|
||||||
'creator': jsonData.creator ?? '',
|
|
||||||
'tags': jsonData.tags ?? '',
|
|
||||||
};
|
|
||||||
char = convertToV2(char);
|
|
||||||
let charJSON = JSON.stringify(char);
|
|
||||||
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
|
||||||
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
|
|
||||||
console.log('importing from gradio json');
|
|
||||||
jsonData.char_name = sanitize(jsonData.char_name);
|
|
||||||
if (jsonData.creator_notes) {
|
|
||||||
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
|
||||||
}
|
|
||||||
png_name = getPngName(jsonData.char_name);
|
|
||||||
let char = {
|
|
||||||
'name': jsonData.char_name,
|
|
||||||
'description': jsonData.char_persona ?? '',
|
|
||||||
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
|
||||||
'personality': '',
|
|
||||||
'first_mes': jsonData.char_greeting ?? '',
|
|
||||||
'avatar': 'none',
|
|
||||||
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
|
||||||
'mes_example': jsonData.example_dialogue ?? '',
|
|
||||||
'scenario': jsonData.world_scenario ?? '',
|
|
||||||
'create_date': humanizedISO8601DateTime(),
|
|
||||||
'talkativeness': jsonData.talkativeness ?? 0.5,
|
|
||||||
'creator': jsonData.creator ?? '',
|
|
||||||
'tags': jsonData.tags ?? '',
|
|
||||||
};
|
|
||||||
char = convertToV2(char);
|
|
||||||
let charJSON = JSON.stringify(char);
|
|
||||||
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
|
||||||
} else {
|
|
||||||
console.log('Incorrect character format .json');
|
|
||||||
response.send({ error: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
var img_data = await charaRead(uploadPath, format);
|
|
||||||
if (img_data === undefined) throw new Error('Failed to read character data');
|
|
||||||
|
|
||||||
let jsonData = JSON.parse(img_data);
|
|
||||||
|
|
||||||
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
|
||||||
png_name = getPngName(jsonData.name);
|
|
||||||
|
|
||||||
if (jsonData.spec !== undefined) {
|
|
||||||
console.log('Found a v2 character file.');
|
|
||||||
importRisuSprites(jsonData);
|
|
||||||
unsetFavFlag(jsonData);
|
|
||||||
jsonData = readFromV2(jsonData);
|
|
||||||
jsonData['create_date'] = humanizedISO8601DateTime();
|
|
||||||
const char = JSON.stringify(jsonData);
|
|
||||||
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
|
||||||
fs.unlinkSync(uploadPath);
|
|
||||||
} else if (jsonData.name !== undefined) {
|
|
||||||
console.log('Found a v1 character file.');
|
|
||||||
|
|
||||||
if (jsonData.creator_notes) {
|
|
||||||
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
let char = {
|
|
||||||
'name': jsonData.name,
|
|
||||||
'description': jsonData.description ?? '',
|
|
||||||
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
|
||||||
'personality': jsonData.personality ?? '',
|
|
||||||
'first_mes': jsonData.first_mes ?? '',
|
|
||||||
'avatar': 'none',
|
|
||||||
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
|
||||||
'mes_example': jsonData.mes_example ?? '',
|
|
||||||
'scenario': jsonData.scenario ?? '',
|
|
||||||
'create_date': humanizedISO8601DateTime(),
|
|
||||||
'talkativeness': jsonData.talkativeness ?? 0.5,
|
|
||||||
'creator': jsonData.creator ?? '',
|
|
||||||
'tags': jsonData.tags ?? '',
|
|
||||||
};
|
|
||||||
char = convertToV2(char);
|
|
||||||
const charJSON = JSON.stringify(char);
|
|
||||||
await charaWrite(uploadPath, charJSON, png_name, response, { file_name: png_name });
|
|
||||||
fs.unlinkSync(uploadPath);
|
|
||||||
} else {
|
|
||||||
console.log('Unknown character card format');
|
|
||||||
response.send({ error: true });
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
console.log(err);
|
||||||
response.send({ error: true });
|
response.send({ error: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let jsonData = JSON.parse(data);
|
||||||
|
|
||||||
|
if (jsonData.spec !== undefined) {
|
||||||
|
console.log('importing from v2 json');
|
||||||
|
importRisuSprites(jsonData);
|
||||||
|
unsetFavFlag(jsonData);
|
||||||
|
jsonData = readFromV2(jsonData);
|
||||||
|
jsonData['create_date'] = humanizedISO8601DateTime();
|
||||||
|
png_name = getPngName(jsonData.data?.name || jsonData.name);
|
||||||
|
let char = JSON.stringify(jsonData);
|
||||||
|
charaWrite(defaultAvatarPath, char, png_name, response, { file_name: png_name });
|
||||||
|
} else if (jsonData.name !== undefined) {
|
||||||
|
console.log('importing from v1 json');
|
||||||
|
jsonData.name = sanitize(jsonData.name);
|
||||||
|
if (jsonData.creator_notes) {
|
||||||
|
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
||||||
|
}
|
||||||
|
png_name = getPngName(jsonData.name);
|
||||||
|
let char = {
|
||||||
|
'name': jsonData.name,
|
||||||
|
'description': jsonData.description ?? '',
|
||||||
|
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||||
|
'personality': jsonData.personality ?? '',
|
||||||
|
'first_mes': jsonData.first_mes ?? '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
||||||
|
'mes_example': jsonData.mes_example ?? '',
|
||||||
|
'scenario': jsonData.scenario ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'talkativeness': jsonData.talkativeness ?? 0.5,
|
||||||
|
'creator': jsonData.creator ?? '',
|
||||||
|
'tags': jsonData.tags ?? '',
|
||||||
|
};
|
||||||
|
char = convertToV2(char);
|
||||||
|
let charJSON = JSON.stringify(char);
|
||||||
|
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
||||||
|
} else if (jsonData.char_name !== undefined) {//json Pygmalion notepad
|
||||||
|
console.log('importing from gradio json');
|
||||||
|
jsonData.char_name = sanitize(jsonData.char_name);
|
||||||
|
if (jsonData.creator_notes) {
|
||||||
|
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
||||||
|
}
|
||||||
|
png_name = getPngName(jsonData.char_name);
|
||||||
|
let char = {
|
||||||
|
'name': jsonData.char_name,
|
||||||
|
'description': jsonData.char_persona ?? '',
|
||||||
|
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||||
|
'personality': '',
|
||||||
|
'first_mes': jsonData.char_greeting ?? '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
||||||
|
'mes_example': jsonData.example_dialogue ?? '',
|
||||||
|
'scenario': jsonData.world_scenario ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'talkativeness': jsonData.talkativeness ?? 0.5,
|
||||||
|
'creator': jsonData.creator ?? '',
|
||||||
|
'tags': jsonData.tags ?? '',
|
||||||
|
};
|
||||||
|
char = convertToV2(char);
|
||||||
|
let charJSON = JSON.stringify(char);
|
||||||
|
charaWrite(defaultAvatarPath, charJSON, png_name, response, { file_name: png_name });
|
||||||
|
} else {
|
||||||
|
console.log('Incorrect character format .json');
|
||||||
|
response.send({ error: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
var img_data = await charaRead(uploadPath, format);
|
||||||
|
if (img_data === undefined) throw new Error('Failed to read character data');
|
||||||
|
|
||||||
|
let jsonData = JSON.parse(img_data);
|
||||||
|
|
||||||
|
jsonData.name = sanitize(jsonData.data?.name || jsonData.name);
|
||||||
|
png_name = getPngName(jsonData.name);
|
||||||
|
|
||||||
|
if (jsonData.spec !== undefined) {
|
||||||
|
console.log('Found a v2 character file.');
|
||||||
|
importRisuSprites(jsonData);
|
||||||
|
unsetFavFlag(jsonData);
|
||||||
|
jsonData = readFromV2(jsonData);
|
||||||
|
jsonData['create_date'] = humanizedISO8601DateTime();
|
||||||
|
const char = JSON.stringify(jsonData);
|
||||||
|
await charaWrite(uploadPath, char, png_name, response, { file_name: png_name });
|
||||||
|
fs.unlinkSync(uploadPath);
|
||||||
|
} else if (jsonData.name !== undefined) {
|
||||||
|
console.log('Found a v1 character file.');
|
||||||
|
|
||||||
|
if (jsonData.creator_notes) {
|
||||||
|
jsonData.creator_notes = jsonData.creator_notes.replace('Creator\'s notes go here.', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
let char = {
|
||||||
|
'name': jsonData.name,
|
||||||
|
'description': jsonData.description ?? '',
|
||||||
|
'creatorcomment': jsonData.creatorcomment ?? jsonData.creator_notes ?? '',
|
||||||
|
'personality': jsonData.personality ?? '',
|
||||||
|
'first_mes': jsonData.first_mes ?? '',
|
||||||
|
'avatar': 'none',
|
||||||
|
'chat': jsonData.name + ' - ' + humanizedISO8601DateTime(),
|
||||||
|
'mes_example': jsonData.mes_example ?? '',
|
||||||
|
'scenario': jsonData.scenario ?? '',
|
||||||
|
'create_date': humanizedISO8601DateTime(),
|
||||||
|
'talkativeness': jsonData.talkativeness ?? 0.5,
|
||||||
|
'creator': jsonData.creator ?? '',
|
||||||
|
'tags': jsonData.tags ?? '',
|
||||||
|
};
|
||||||
|
char = convertToV2(char);
|
||||||
|
const charJSON = JSON.stringify(char);
|
||||||
|
await charaWrite(uploadPath, charJSON, png_name, response, { file_name: png_name });
|
||||||
|
fs.unlinkSync(uploadPath);
|
||||||
|
} else {
|
||||||
|
console.log('Unknown character card format');
|
||||||
|
response.send({ error: true });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
response.send({ error: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -36,20 +36,12 @@ function sanitizeHordeImagePrompt(prompt) {
|
|||||||
prompt = prompt.replace(/\b(boy)\b/gmi, 'man');
|
prompt = prompt.replace(/\b(boy)\b/gmi, 'man');
|
||||||
prompt = prompt.replace(/\b(girls)\b/gmi, 'women');
|
prompt = prompt.replace(/\b(girls)\b/gmi, 'women');
|
||||||
prompt = prompt.replace(/\b(boys)\b/gmi, 'men');
|
prompt = prompt.replace(/\b(boys)\b/gmi, 'men');
|
||||||
|
|
||||||
//always remove these high risk words from prompt, as they add little value to image gen while increasing the risk the prompt gets flagged
|
//always remove these high risk words from prompt, as they add little value to image gen while increasing the risk the prompt gets flagged
|
||||||
prompt = prompt.replace(/\b(under.age|under.aged|underage|underaged|loli|pedo|pedophile|(\w+).year.old|(\w+).years.old|minor|prepubescent|minors|shota)\b/gmi, '');
|
prompt = prompt.replace(/\b(under.age|under.aged|underage|underaged|loli|pedo|pedophile|(\w+).year.old|(\w+).years.old|minor|prepubescent|minors|shota)\b/gmi, '');
|
||||||
|
//replace risky subject nouns with person
|
||||||
//if nsfw is detected, do not remove it but apply additional precautions
|
prompt = prompt.replace(/\b(youngster|infant|baby|toddler|child|teen|kid|kiddie|kiddo|teenager|student|preteen|pre.teen)\b/gmi, 'person');
|
||||||
let isNsfw = prompt.match(/\b(cock|ahegao|hentai|uncensored|lewd|cocks|deepthroat|deepthroating|dick|dicks|cumshot|lesbian|fuck|fucked|fucking|sperm|naked|nipples|tits|boobs|breasts|boob|breast|topless|ass|butt|fingering|masturbate|masturbating|bitch|blowjob|pussy|piss|asshole|dildo|dildos|vibrator|erection|foreskin|handjob|nude|penis|porn|vibrator|virgin|vagina|vulva|threesome|orgy|bdsm|hickey|condom|testicles|anal|bareback|bukkake|creampie|stripper|strap-on|missionary|clitoris|clit|clitty|cowgirl|fleshlight|sex|buttplug|milf|oral|sucking|bondage|orgasm|scissoring|railed|slut|sluts|slutty|cumming|cunt|faggot|sissy|anal|anus|cum|semen|scat|nsfw|xxx|explicit|erotic|horny|aroused|jizz|moan|rape|raped|raping|throbbing|humping)\b/gmi);
|
//remove risky adjectives and related words
|
||||||
|
prompt = prompt.replace(/\b(young|younger|youthful|youth|small|smaller|smallest|girly|boyish|lil|tiny|teenaged|lit[tl]le|school.aged|school|highschool|kindergarten|teens|children|kids)\b/gmi, '');
|
||||||
if (isNsfw) {
|
|
||||||
//replace risky subject nouns with person
|
|
||||||
prompt = prompt.replace(/\b(youngster|infant|baby|toddler|child|teen|kid|kiddie|kiddo|teenager|student|preteen|pre.teen)\b/gmi, 'person');
|
|
||||||
|
|
||||||
//remove risky adjectives and related words
|
|
||||||
prompt = prompt.replace(/\b(young|younger|youthful|youth|small|smaller|smallest|girly|boyish|lil|tiny|teenaged|lit[tl]le|school.aged|school|highschool|kindergarten|teens|children|kids)\b/gmi, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return prompt;
|
return prompt;
|
||||||
}
|
}
|
||||||
|
10
src/util.js
10
src/util.js
@ -458,6 +458,15 @@ function excludeKeysByYaml(obj, yamlString) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes trailing slash and /v1 from a string.
|
||||||
|
* @param {string} str Input string
|
||||||
|
* @returns {string} Trimmed string
|
||||||
|
*/
|
||||||
|
function trimV1(str) {
|
||||||
|
return String(str ?? '').replace(/\/$/, '').replace(/\/v1$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getConfig,
|
getConfig,
|
||||||
getConfigValue,
|
getConfigValue,
|
||||||
@ -481,4 +490,5 @@ module.exports = {
|
|||||||
getHexString,
|
getHexString,
|
||||||
mergeObjectWithYaml,
|
mergeObjectWithYaml,
|
||||||
excludeKeysByYaml,
|
excludeKeysByYaml,
|
||||||
|
trimV1,
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user