mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Exorcised base64 image stuff
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ public/backgrounds/
|
|||||||
public/groups/
|
public/groups/
|
||||||
public/group chats/
|
public/group chats/
|
||||||
public/worlds/
|
public/worlds/
|
||||||
|
public/user/
|
||||||
public/css/bg_load.css
|
public/css/bg_load.css
|
||||||
public/themes/
|
public/themes/
|
||||||
public/OpenAI Settings/
|
public/OpenAI Settings/
|
||||||
|
@ -5629,7 +5629,14 @@ export async function getChatsFromFiles(data, isGroupChat) {
|
|||||||
return chat_dict;
|
return chat_dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches the metadata of all past chats related to a specific character based on its avatar URL.
|
||||||
|
* The function sends a POST request to the server to retrieve all chats for the character. It then
|
||||||
|
* processes the received data, sorts it by the file name, and returns the sorted data.
|
||||||
|
*
|
||||||
|
* @returns {Array} - An array containing metadata of all past chats of the character, sorted
|
||||||
|
* in descending order by file name. Returns `undefined` if the fetch request is unsuccessful.
|
||||||
|
*/
|
||||||
async function getPastCharacterChats() {
|
async function getPastCharacterChats() {
|
||||||
const response = await fetch("/getallchatsofcharacter", {
|
const response = await fetch("/getallchatsofcharacter", {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -537,6 +537,38 @@ function processReply(str) {
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveBase64AsFile(base64Data, characterName, ext) {
|
||||||
|
// Construct the full data URL
|
||||||
|
const format = ext; // Extract the file extension (jpg, png, webp)
|
||||||
|
const dataURL = `data:image/${format};base64,${base64Data}`;
|
||||||
|
|
||||||
|
// Prepare the request body
|
||||||
|
const requestBody = {
|
||||||
|
image: dataURL,
|
||||||
|
ch_name: characterName
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the data URL to your backend using fetch
|
||||||
|
const response = await fetch('/uploadimage', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(requestBody),
|
||||||
|
headers: {
|
||||||
|
...getRequestHeaders(),
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// If the response is successful, get the saved image path from the server's response
|
||||||
|
if (response.ok) {
|
||||||
|
const responseData = await response.json();
|
||||||
|
return responseData.path;
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
throw new Error(errorData.error || 'Failed to upload the image to the server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function getRawLastMessage() {
|
function getRawLastMessage() {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const lastMessage = context.chat.slice(-1)[0].mes,
|
const lastMessage = context.chat.slice(-1)[0].mes,
|
||||||
@ -565,6 +597,10 @@ async function generatePicture(_, trigger, message, callback) {
|
|||||||
const quiet_prompt = getQuietPrompt(generationType, trigger);
|
const quiet_prompt = getQuietPrompt(generationType, trigger);
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
|
|
||||||
|
// if context.characterId is not null, then we get context.characters[context.characterId].avatar, else we get groupId and context.groups[groupId].id
|
||||||
|
// sadly, groups is not an array, but is a dict with keys being index numbers, so we have to filter it
|
||||||
|
const characterName = context.characterId ? context.characters[context.characterId].name : context.groups[Object.keys(context.groups).filter(x => context.groups[x].id === context.groupId)[0]].id.toString();
|
||||||
|
|
||||||
const prevSDHeight = extension_settings.sd.height;
|
const prevSDHeight = extension_settings.sd.height;
|
||||||
const prevSDWidth = extension_settings.sd.width;
|
const prevSDWidth = extension_settings.sd.width;
|
||||||
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
|
const aspectRatio = extension_settings.sd.width / extension_settings.sd.height;
|
||||||
@ -580,8 +616,10 @@ async function generatePicture(_, trigger, message, callback) {
|
|||||||
// Round to nearest multiple of 64
|
// Round to nearest multiple of 64
|
||||||
extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64;
|
extension_settings.sd.width = Math.round(extension_settings.sd.height * 1.8 / 64) * 64;
|
||||||
const callbackOriginal = callback;
|
const callbackOriginal = callback;
|
||||||
callback = function (prompt, base64Image) {
|
callback = async function (prompt, base64Image) {
|
||||||
|
const imagePath = base64Image;
|
||||||
const imgUrl = `url(${base64Image})`;
|
const imgUrl = `url(${base64Image})`;
|
||||||
|
|
||||||
if ('forceSetBackground' in window) {
|
if ('forceSetBackground' in window) {
|
||||||
forceSetBackground(imgUrl);
|
forceSetBackground(imgUrl);
|
||||||
} else {
|
} else {
|
||||||
@ -590,9 +628,9 @@ async function generatePicture(_, trigger, message, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof callbackOriginal === 'function') {
|
if (typeof callbackOriginal === 'function') {
|
||||||
callbackOriginal(prompt, base64Image);
|
callbackOriginal(prompt, imagePath);
|
||||||
} else {
|
} else {
|
||||||
sendMessage(prompt, base64Image);
|
sendMessage(prompt, imagePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -604,7 +642,7 @@ async function generatePicture(_, trigger, message, callback) {
|
|||||||
context.deactivateSendButtons();
|
context.deactivateSendButtons();
|
||||||
hideSwipeButtons();
|
hideSwipeButtons();
|
||||||
|
|
||||||
await sendGenerationRequest(generationType, prompt, callback);
|
await sendGenerationRequest(generationType, prompt, characterName, callback);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.trace(err);
|
console.trace(err);
|
||||||
throw new Error('SD prompt text generation failed.')
|
throw new Error('SD prompt text generation failed.')
|
||||||
@ -644,19 +682,19 @@ async function generatePrompt(quiet_prompt) {
|
|||||||
return processReply(reply);
|
return processReply(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendGenerationRequest(generationType, prompt, callback) {
|
async function sendGenerationRequest(generationType, prompt, characterName=null, callback) {
|
||||||
const prefix = generationType !== generationMode.BACKGROUND
|
const prefix = generationType !== generationMode.BACKGROUND
|
||||||
? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix())
|
? combinePrefixes(extension_settings.sd.prompt_prefix, getCharacterPrefix())
|
||||||
: extension_settings.sd.prompt_prefix;
|
: extension_settings.sd.prompt_prefix;
|
||||||
|
|
||||||
if (extension_settings.sd.horde) {
|
if (extension_settings.sd.horde) {
|
||||||
await generateHordeImage(prompt, prefix, callback);
|
await generateHordeImage(prompt, prefix, characterName, callback);
|
||||||
} else {
|
} else {
|
||||||
await generateExtrasImage(prompt, prefix, callback);
|
await generateExtrasImage(prompt, prefix, characterName, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateExtrasImage(prompt, prefix, callback) {
|
async function generateExtrasImage(prompt, prefix, characterName, callback) {
|
||||||
console.debug(extension_settings.sd);
|
console.debug(extension_settings.sd);
|
||||||
const url = new URL(getApiUrl());
|
const url = new URL(getApiUrl());
|
||||||
url.pathname = '/api/image';
|
url.pathname = '/api/image';
|
||||||
@ -680,14 +718,14 @@ async function generateExtrasImage(prompt, prefix, callback) {
|
|||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
const data = await result.json();
|
const data = await result.json();
|
||||||
const base64Image = `data:image/jpeg;base64,${data.image}`;
|
const base64Image = await saveBase64AsFile(data.image, characterName, "jpg");
|
||||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||||
} else {
|
} else {
|
||||||
callPopup('Image generation has failed. Please try again.', 'text');
|
callPopup('Image generation has failed. Please try again.', 'text');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateHordeImage(prompt, prefix, callback) {
|
async function generateHordeImage(prompt, prefix, characterName, callback) {
|
||||||
const result = await fetch('/horde_generateimage', {
|
const result = await fetch('/horde_generateimage', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getRequestHeaders(),
|
headers: getRequestHeaders(),
|
||||||
@ -709,7 +747,8 @@ async function generateHordeImage(prompt, prefix, callback) {
|
|||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
const data = await result.text();
|
const data = await result.text();
|
||||||
const base64Image = `data:image/webp;base64,${data}`;
|
|
||||||
|
const base64Image = await saveBase64AsFile(data, characterName, "webp");
|
||||||
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
callback ? callback(prompt, base64Image) : sendMessage(prompt, base64Image);
|
||||||
} else {
|
} else {
|
||||||
toastr.error('Image generation has failed. Please try again.');
|
toastr.error('Image generation has failed. Please try again.');
|
||||||
@ -842,7 +881,7 @@ async function sdMessageButton(e) {
|
|||||||
message.extra.title = prompt;
|
message.extra.title = prompt;
|
||||||
|
|
||||||
console.log('Regenerating an image, using existing prompt:', prompt);
|
console.log('Regenerating an image, using existing prompt:', prompt);
|
||||||
await sendGenerationRequest(generationMode.FREE, prompt, saveGeneratedImage);
|
await sendGenerationRequest(generationMode.FREE, prompt, characterName, saveGeneratedImage);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log("doing /sd raw last");
|
console.log("doing /sd raw last");
|
||||||
|
49
server.js
49
server.js
@ -296,6 +296,8 @@ const baseRequestArgs = { headers: { "Content-Type": "application/json" } };
|
|||||||
const directories = {
|
const directories = {
|
||||||
worlds: 'public/worlds/',
|
worlds: 'public/worlds/',
|
||||||
avatars: 'public/User Avatars',
|
avatars: 'public/User Avatars',
|
||||||
|
images: 'public/img/',
|
||||||
|
userImages: 'public/user/images/',
|
||||||
groups: 'public/groups/',
|
groups: 'public/groups/',
|
||||||
groupChats: 'public/group chats',
|
groupChats: 'public/group chats',
|
||||||
chats: 'public/chats/',
|
chats: 'public/chats/',
|
||||||
@ -2585,6 +2587,53 @@ app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function ensureDirectoryExistence(filePath) {
|
||||||
|
const dirname = path.dirname(filePath);
|
||||||
|
if (fs.existsSync(dirname)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
ensureDirectoryExistence(dirname);
|
||||||
|
fs.mkdirSync(dirname);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post('/uploadimage', jsonParser, async (request, response) => {
|
||||||
|
// Check for image data
|
||||||
|
if (!request.body || !request.body.image) {
|
||||||
|
return response.status(400).send({ error: "No image data provided" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracting the base64 data and the image format
|
||||||
|
const match = request.body.image.match(/^data:image\/(png|jpg|webp);base64,(.+)$/);
|
||||||
|
if (!match) {
|
||||||
|
return response.status(400).send({ error: "Invalid image format" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, format, base64Data] = match;
|
||||||
|
|
||||||
|
// Constructing filename and path
|
||||||
|
const filename = `${Date.now()}.${format}`;
|
||||||
|
// if character is defined, save to a sub folder for that character
|
||||||
|
let pathToNewFile = path.join(directories.userImages, filename);
|
||||||
|
if(request.body.ch_name){
|
||||||
|
pathToNewFile = path.join(directories.userImages, request.body.ch_name, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ensureDirectoryExistence(pathToNewFile);
|
||||||
|
const imageBuffer = Buffer.from(base64Data, 'base64');
|
||||||
|
await fs.promises.writeFile(pathToNewFile, imageBuffer);
|
||||||
|
// send the path to the image, relative to the client folder, which means removing the first folder from the path which is 'public'
|
||||||
|
pathToNewFile = pathToNewFile.split(path.sep).slice(1).join(path.sep);
|
||||||
|
response.send({ path:pathToNewFile });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
response.status(500).send({ error: "Failed to save the image" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
app.post('/getgroups', jsonParser, (_, response) => {
|
app.post('/getgroups', jsonParser, (_, response) => {
|
||||||
const groups = [];
|
const groups = [];
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user