mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2024-12-12 09:26:33 +01:00
382 lines
15 KiB
JavaScript
382 lines
15 KiB
JavaScript
import { event_types, eventSource, saveSettingsDebounced } from '../../../script.js';
|
|
import { deleteAttachment, getDataBankAttachments, getDataBankAttachmentsForSource, getFileAttachment, uploadFileAttachmentToServer } from '../../chats.js';
|
|
import { extension_settings, renderExtensionTemplateAsync } from '../../extensions.js';
|
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
|
import { SlashCommandClosure } from '../../slash-commands/SlashCommandClosure.js';
|
|
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
|
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
|
import { SlashCommandExecutor } from '../../slash-commands/SlashCommandExecutor.js';
|
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
|
|
|
/**
|
|
* List of attachment sources
|
|
* @type {string[]}
|
|
*/
|
|
const TYPES = ['global', 'character', 'chat'];
|
|
const FIELDS = ['name', 'url'];
|
|
|
|
/**
|
|
* Get attachments from the data bank. Includes disabled attachments.
|
|
* @param {string} [source] Source for the attachments
|
|
* @returns {import('../../chats').FileAttachment[]} List of attachments
|
|
*/
|
|
function getAttachments(source) {
|
|
if (!source || !TYPES.includes(source)) {
|
|
return getDataBankAttachments(true);
|
|
}
|
|
|
|
return getDataBankAttachmentsForSource(source, true);
|
|
}
|
|
|
|
/**
|
|
* Get attachment by a single name or URL.
|
|
* @param {import('../../chats').FileAttachment[]} attachments List of attachments
|
|
* @param {string} value Name or URL of the attachment
|
|
* @returns {import('../../chats').FileAttachment} Attachment
|
|
*/
|
|
function getAttachmentByField(attachments, value) {
|
|
const match = (a) => String(a).trim().toLowerCase() === String(value).trim().toLowerCase();
|
|
const fullMatchByURL = attachments.find(it => match(it.url));
|
|
const fullMatchByName = attachments.find(it => match(it.name));
|
|
return fullMatchByURL || fullMatchByName;
|
|
}
|
|
|
|
/**
|
|
* Get attachment by multiple fields.
|
|
* @param {import('../../chats').FileAttachment[]} attachments List of attachments
|
|
* @param {string[]} values Name and URL of the attachment to search for
|
|
* @returns
|
|
*/
|
|
function getAttachmentByFields(attachments, values) {
|
|
for (const value of values) {
|
|
const attachment = getAttachmentByField(attachments, value);
|
|
if (attachment) {
|
|
return attachment;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Callback for listing attachments in the data bank.
|
|
* @param {object} args Named arguments
|
|
* @returns {string} JSON string of the list of attachments
|
|
*/
|
|
function listDataBankAttachments(args) {
|
|
const attachments = getAttachments(args?.source);
|
|
const field = args?.field;
|
|
return JSON.stringify(attachments.map(a => FIELDS.includes(field) ? a[field] : a.url));
|
|
}
|
|
|
|
/**
|
|
* Callback for getting text from an attachment in the data bank.
|
|
* @param {object} args Named arguments
|
|
* @param {string} value Name or URL of the attachment
|
|
* @returns {Promise<string>} Content of the attachment
|
|
*/
|
|
async function getDataBankText(args, value) {
|
|
if (!value) {
|
|
toastr.warning('No attachment name or URL provided.');
|
|
return;
|
|
}
|
|
|
|
const attachments = getAttachments(args?.source);
|
|
const attachment = getAttachmentByField(attachments, value);
|
|
|
|
if (!attachment) {
|
|
toastr.warning('Attachment not found.');
|
|
return;
|
|
}
|
|
|
|
const content = await getFileAttachment(attachment.url);
|
|
return content;
|
|
}
|
|
|
|
/**
|
|
* Callback for adding an attachment to the data bank.
|
|
* @param {object} args Named arguments
|
|
* @param {string} value Content of the attachment
|
|
* @returns {Promise<string>} URL of the attachment
|
|
*/
|
|
async function uploadDataBankAttachment(args, value) {
|
|
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
|
const name = args?.name || new Date().toLocaleString();
|
|
const file = new File([value], name, { type: 'text/plain' });
|
|
const url = await uploadFileAttachmentToServer(file, source);
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* Callback for updating an attachment in the data bank.
|
|
* @param {object} args Named arguments
|
|
* @param {string} value Content of the attachment
|
|
* @returns {Promise<string>} URL of the attachment
|
|
*/
|
|
async function updateDataBankAttachment(args, value) {
|
|
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
|
const attachments = getAttachments(source);
|
|
const attachment = getAttachmentByFields(attachments, [args?.url, args?.name]);
|
|
|
|
if (!attachment) {
|
|
toastr.warning('Attachment not found.');
|
|
return '';
|
|
}
|
|
|
|
await deleteAttachment(attachment, source, () => { }, false);
|
|
const file = new File([value], attachment.name, { type: 'text/plain' });
|
|
const url = await uploadFileAttachmentToServer(file, source);
|
|
return url;
|
|
}
|
|
|
|
/**
|
|
* Callback for deleting an attachment from the data bank.
|
|
* @param {object} args Named arguments
|
|
* @param {string} value Name or URL of the attachment
|
|
* @returns {Promise<string>} Empty string
|
|
*/
|
|
async function deleteDataBankAttachment(args, value) {
|
|
const source = args?.source && TYPES.includes(args.source) ? args.source : 'chat';
|
|
const attachments = getAttachments(source);
|
|
const attachment = getAttachmentByField(attachments, value);
|
|
|
|
if (!attachment) {
|
|
toastr.warning('Attachment not found.');
|
|
return '';
|
|
}
|
|
|
|
await deleteAttachment(attachment, source, () => { }, false);
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Callback for disabling an attachment in the data bank.
|
|
* @param {object} args Named arguments
|
|
* @param {string} value Name or URL of the attachment
|
|
* @returns {Promise<string>} Empty string
|
|
*/
|
|
async function disableDataBankAttachment(args, value) {
|
|
const attachments = getAttachments(args?.source);
|
|
const attachment = getAttachmentByField(attachments, value);
|
|
|
|
if (!attachment) {
|
|
toastr.warning('Attachment not found.');
|
|
return '';
|
|
}
|
|
|
|
if (extension_settings.disabled_attachments.includes(attachment.url)) {
|
|
return '';
|
|
}
|
|
|
|
extension_settings.disabled_attachments.push(attachment.url);
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Callback for enabling an attachment in the data bank.
|
|
* @param {object} args Named arguments
|
|
* @param {string} value Name or URL of the attachment
|
|
* @returns {Promise<string>} Empty string
|
|
*/
|
|
async function enableDataBankAttachment(args, value) {
|
|
const attachments = getAttachments(args?.source);
|
|
const attachment = getAttachmentByField(attachments, value);
|
|
|
|
if (!attachment) {
|
|
toastr.warning('Attachment not found.');
|
|
return '';
|
|
}
|
|
|
|
const index = extension_settings.disabled_attachments.indexOf(attachment.url);
|
|
if (index === -1) {
|
|
return '';
|
|
}
|
|
|
|
extension_settings.disabled_attachments.splice(index, 1);
|
|
return '';
|
|
}
|
|
|
|
function cleanUpAttachments() {
|
|
let shouldSaveSettings = false;
|
|
if (extension_settings.character_attachments) {
|
|
Object.values(extension_settings.character_attachments).flat().filter(a => a.text).forEach(a => {
|
|
shouldSaveSettings = true;
|
|
delete a.text;
|
|
});
|
|
}
|
|
if (Array.isArray(extension_settings.attachments)) {
|
|
extension_settings.attachments.filter(a => a.text).forEach(a => {
|
|
shouldSaveSettings = true;
|
|
delete a.text;
|
|
});
|
|
}
|
|
if (shouldSaveSettings) {
|
|
saveSettingsDebounced();
|
|
}
|
|
}
|
|
|
|
jQuery(async () => {
|
|
eventSource.on(event_types.APP_READY, cleanUpAttachments);
|
|
const manageButton = await renderExtensionTemplateAsync('attachments', 'manage-button', {});
|
|
const attachButton = await renderExtensionTemplateAsync('attachments', 'attach-button', {});
|
|
$('#data_bank_wand_container').append(manageButton);
|
|
$('#attach_file_wand_container').append(attachButton);
|
|
|
|
/** A collection of local enum providers for this context of data bank */
|
|
const localEnumProviders = {
|
|
/**
|
|
* All attachments in the data bank based on the source argument. If not provided, defaults to 'chat'.
|
|
* @param {'name' | 'url'} returnField - Whether the enum should return the 'name' field or the 'url'
|
|
* @param {'chat' | 'character' | 'global' | ''} fallbackSource - The source to use if the source argument is not provided. Empty string to use all sources.
|
|
* */
|
|
attachments: (returnField = 'name', fallbackSource = 'chat') => (/** @type {SlashCommandExecutor} */ executor) => {
|
|
const source = executor.namedArgumentList.find(it => it.name == 'source')?.value ?? fallbackSource;
|
|
if (source instanceof SlashCommandClosure) throw new Error('Argument \'source\' does not support closures');
|
|
const attachments = getAttachments(source);
|
|
|
|
return attachments.map(attachment => new SlashCommandEnumValue(
|
|
returnField === 'name' ? attachment.name : attachment.url,
|
|
`${enumIcons.getStateIcon(!extension_settings.disabled_attachments.includes(attachment.url))} [${source}] ${returnField === 'url' ? attachment.name : attachment.url}`,
|
|
enumTypes.enum, enumIcons.file));
|
|
},
|
|
};
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db',
|
|
callback: () => {
|
|
document.getElementById('manageAttachments')?.click();
|
|
return '';
|
|
},
|
|
aliases: ['databank', 'data-bank'],
|
|
helpString: 'Open the data bank',
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-list',
|
|
callback: listDataBankAttachments,
|
|
aliases: ['databank-list', 'data-bank-list'],
|
|
helpString: 'List attachments in the Data Bank as a JSON-serialized array. Optionally, provide the source of the attachments and the field to list by.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source of the attachments.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
|
new SlashCommandNamedArgument('field', 'The field to list by.', ARGUMENT_TYPE.STRING, false, false, 'url', FIELDS),
|
|
],
|
|
returns: ARGUMENT_TYPE.LIST,
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-get',
|
|
callback: getDataBankText,
|
|
aliases: ['databank-get', 'data-bank-get'],
|
|
helpString: 'Get attachment text from the Data Bank. Either provide the name or URL of the attachment. Optionally, provide the source of the attachment.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
|
],
|
|
unnamedArgumentList: [
|
|
SlashCommandArgument.fromProps({
|
|
description: 'The name or URL of the attachment.',
|
|
typeList: [ARGUMENT_TYPE.STRING],
|
|
isRequired: true,
|
|
acceptsMultiple: false,
|
|
enumProvider: localEnumProviders.attachments('name', ''),
|
|
}),
|
|
],
|
|
returns: ARGUMENT_TYPE.STRING,
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-add',
|
|
callback: uploadDataBankAttachment,
|
|
aliases: ['databank-add', 'data-bank-add'],
|
|
helpString: 'Add an attachment to the Data Bank. If name is not provided, it will be generated automatically. Returns the URL of the attachment.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
|
new SlashCommandNamedArgument('name', 'The name of the attachment.', ARGUMENT_TYPE.STRING, false, false),
|
|
],
|
|
unnamedArgumentList: [
|
|
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
|
],
|
|
returns: ARGUMENT_TYPE.STRING,
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-update',
|
|
callback: updateDataBankAttachment,
|
|
aliases: ['databank-update', 'data-bank-update'],
|
|
helpString: 'Update an attachment in the Data Bank, preserving its name. Returns a new URL of the attachment.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source for the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
|
SlashCommandNamedArgument.fromProps({
|
|
name: 'name',
|
|
description: 'The name of the attachment.',
|
|
typeList: [ARGUMENT_TYPE.STRING],
|
|
enumProvider: localEnumProviders.attachments('name'),
|
|
}),
|
|
SlashCommandNamedArgument.fromProps({
|
|
name: 'url',
|
|
description: 'The URL of the attachment.',
|
|
typeList: [ARGUMENT_TYPE.STRING],
|
|
enumProvider: localEnumProviders.attachments('url'),
|
|
}),
|
|
],
|
|
unnamedArgumentList: [
|
|
new SlashCommandArgument('The content of the file attachment.', ARGUMENT_TYPE.STRING, true, false),
|
|
],
|
|
returns: ARGUMENT_TYPE.STRING,
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-disable',
|
|
callback: disableDataBankAttachment,
|
|
aliases: ['databank-disable', 'data-bank-disable'],
|
|
helpString: 'Disable an attachment in the Data Bank by its name or URL. Optionally, provide the source of the attachment.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
|
],
|
|
unnamedArgumentList: [
|
|
SlashCommandArgument.fromProps({
|
|
description: 'The name or URL of the attachment.',
|
|
typeList: [ARGUMENT_TYPE.STRING],
|
|
isRequired: true,
|
|
enumProvider: localEnumProviders.attachments('name', ''),
|
|
}),
|
|
],
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-enable',
|
|
callback: enableDataBankAttachment,
|
|
aliases: ['databank-enable', 'data-bank-enable'],
|
|
helpString: 'Enable an attachment in the Data Bank by its name or URL. Optionally, provide the source of the attachment.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, '', TYPES),
|
|
],
|
|
unnamedArgumentList: [
|
|
SlashCommandArgument.fromProps({
|
|
description: 'The name or URL of the attachment.',
|
|
typeList: [ARGUMENT_TYPE.STRING],
|
|
isRequired: true,
|
|
enumProvider: localEnumProviders.attachments('name', ''),
|
|
}),
|
|
],
|
|
}));
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
name: 'db-delete',
|
|
callback: deleteDataBankAttachment,
|
|
aliases: ['databank-delete', 'data-bank-delete'],
|
|
helpString: 'Delete an attachment from the Data Bank.',
|
|
namedArgumentList: [
|
|
new SlashCommandNamedArgument('source', 'The source of the attachment.', ARGUMENT_TYPE.STRING, false, false, 'chat', TYPES),
|
|
],
|
|
unnamedArgumentList: [
|
|
SlashCommandArgument.fromProps({
|
|
description: 'The name or URL of the attachment.',
|
|
typeList: [ARGUMENT_TYPE.STRING],
|
|
isRequired: true,
|
|
enumProvider: localEnumProviders.attachments(),
|
|
}),
|
|
],
|
|
}));
|
|
});
|