Compare commits
5 Commits
f947c1304a
...
4665db62f4
Author | SHA1 | Date |
---|---|---|
Cohee | 4665db62f4 | |
Cohee | ab5b497562 | |
Cohee | 5a614b5173 | |
Cohee | 8546490bcc | |
Cohee | 3dcea41c4e |
|
@ -41,6 +41,7 @@ import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
|
||||||
* @property {string} url File URL
|
* @property {string} url File URL
|
||||||
* @property {number} size File size
|
* @property {number} size File size
|
||||||
* @property {string} name File name
|
* @property {string} name File name
|
||||||
|
* @property {number} created Timestamp
|
||||||
* @property {string} [text] File text
|
* @property {string} [text] File text
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -168,6 +169,7 @@ export async function populateFileAttachment(message, inputId = 'file_form_input
|
||||||
url: fileUrl,
|
url: fileUrl,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
|
created: Date.now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +385,7 @@ export async function appendFileContent(message, messageText) {
|
||||||
const fileText = message.extra.file.text || (await getFileAttachment(message.extra.file.url));
|
const fileText = message.extra.file.text || (await getFileAttachment(message.extra.file.url));
|
||||||
|
|
||||||
if (fileText) {
|
if (fileText) {
|
||||||
const fileWrapped = `\`\`\`\n${fileText}\n\`\`\`\n\n`;
|
const fileWrapped = `${fileText}\n\n`;
|
||||||
message.extra.fileLength = fileWrapped.length;
|
message.extra.fileLength = fileWrapped.length;
|
||||||
messageText = fileWrapped + messageText;
|
messageText = fileWrapped + messageText;
|
||||||
}
|
}
|
||||||
|
@ -618,11 +620,38 @@ async function deleteAttachment(attachment, source, callback) {
|
||||||
*/
|
*/
|
||||||
async function openAttachmentManager() {
|
async function openAttachmentManager() {
|
||||||
/**
|
/**
|
||||||
*
|
* Renders a list of attachments.
|
||||||
* @param {FileAttachment[]} attachments List of attachments
|
* @param {FileAttachment[]} attachments List of attachments
|
||||||
* @param {string} source Source of the attachments
|
* @param {string} source Source of the attachments
|
||||||
*/
|
*/
|
||||||
async function renderList(attachments, source) {
|
async function renderList(attachments, source) {
|
||||||
|
/**
|
||||||
|
* Sorts attachments by sortField and sortOrder.
|
||||||
|
* @param {FileAttachment} a First attachment
|
||||||
|
* @param {FileAttachment} b Second attachment
|
||||||
|
* @returns {number} Sort order
|
||||||
|
*/
|
||||||
|
function sortFn(a, b) {
|
||||||
|
const sortValueA = a[sortField];
|
||||||
|
const sortValueB = b[sortField];
|
||||||
|
if (typeof sortValueA === 'string' && typeof sortValueB === 'string') {
|
||||||
|
return sortValueA.localeCompare(sortValueB) * (sortOrder === 'asc' ? 1 : -1);
|
||||||
|
}
|
||||||
|
return (sortValueA - sortValueB) * (sortOrder === 'asc' ? 1 : -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters attachments by name.
|
||||||
|
* @param {FileAttachment} a Attachment
|
||||||
|
* @returns {boolean} True if attachment matches the filter, false otherwise.
|
||||||
|
*/
|
||||||
|
function filterFn(a) {
|
||||||
|
if (!filterString) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.name.toLowerCase().includes(filterString.toLowerCase());
|
||||||
|
}
|
||||||
const sources = {
|
const sources = {
|
||||||
[ATTACHMENT_SOURCE.GLOBAL]: '.globalAttachmentsList',
|
[ATTACHMENT_SOURCE.GLOBAL]: '.globalAttachmentsList',
|
||||||
[ATTACHMENT_SOURCE.CHARACTER]: '.characterAttachmentsList',
|
[ATTACHMENT_SOURCE.CHARACTER]: '.characterAttachmentsList',
|
||||||
|
@ -630,16 +659,37 @@ async function openAttachmentManager() {
|
||||||
};
|
};
|
||||||
|
|
||||||
template.find(sources[source]).empty();
|
template.find(sources[source]).empty();
|
||||||
for (const attachment of attachments) {
|
|
||||||
|
// Sort attachments by sortField and sortOrder, and apply filter
|
||||||
|
const sortedAttachmentList = attachments.slice().filter(filterFn).sort(sortFn);
|
||||||
|
|
||||||
|
for (const attachment of sortedAttachmentList) {
|
||||||
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
|
const attachmentTemplate = template.find('.attachmentListItemTemplate .attachmentListItem').clone();
|
||||||
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
|
attachmentTemplate.find('.attachmentListItemName').text(attachment.name);
|
||||||
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
|
attachmentTemplate.find('.attachmentListItemSize').text(humanFileSize(attachment.size));
|
||||||
|
attachmentTemplate.find('.attachmentListItemCreated').text(new Date(attachment.created).toLocaleString());
|
||||||
attachmentTemplate.find('.viewAttachmentButton').on('click', () => openFilePopup(attachment));
|
attachmentTemplate.find('.viewAttachmentButton').on('click', () => openFilePopup(attachment));
|
||||||
attachmentTemplate.find('.deleteAttachmentButton').on('click', () => deleteAttachment(attachment, source, renderAttachments));
|
attachmentTemplate.find('.deleteAttachmentButton').on('click', () => deleteAttachment(attachment, source, renderAttachments));
|
||||||
template.find(sources[source]).append(attachmentTemplate);
|
template.find(sources[source]).append(attachmentTemplate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders buttons for the attachment manager.
|
||||||
|
* @param {string} source Source of the buttons
|
||||||
|
*/
|
||||||
|
function renderButtons(source) {
|
||||||
|
const sources = {
|
||||||
|
[ATTACHMENT_SOURCE.GLOBAL]: '.globalAttachmentsTitle',
|
||||||
|
[ATTACHMENT_SOURCE.CHARACTER]: '.characterAttachmentsTitle',
|
||||||
|
[ATTACHMENT_SOURCE.CHAT]: '.chatAttachmentsTitle',
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonsList = template.find('.actionButtonsTemplate .actionButtons').clone();
|
||||||
|
buttonsList.find('.menu_button').data('attachment-manager-target', source);
|
||||||
|
template.find(sources[source]).append(buttonsList);
|
||||||
|
}
|
||||||
|
|
||||||
async function renderAttachments() {
|
async function renderAttachments() {
|
||||||
/** @type {FileAttachment[]} */
|
/** @type {FileAttachment[]} */
|
||||||
const globalAttachments = extension_settings.attachments ?? [];
|
const globalAttachments = extension_settings.attachments ?? [];
|
||||||
|
@ -664,8 +714,14 @@ async function openAttachmentManager() {
|
||||||
template.find('.chatAttachmentsName').text(chatName);
|
template.find('.chatAttachmentsName').text(chatName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sortField = localStorage.getItem('DataBank_sortField') || 'created';
|
||||||
|
let sortOrder = localStorage.getItem('DataBank_sortOrder') || 'desc';
|
||||||
|
let filterString = '';
|
||||||
const hasFandomPlugin = await isFandomPluginAvailable();
|
const hasFandomPlugin = await isFandomPluginAvailable();
|
||||||
const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {}));
|
const template = $(await renderExtensionTemplateAsync('attachments', 'manager', {}));
|
||||||
|
renderButtons(ATTACHMENT_SOURCE.GLOBAL);
|
||||||
|
renderButtons(ATTACHMENT_SOURCE.CHARACTER);
|
||||||
|
renderButtons(ATTACHMENT_SOURCE.CHAT);
|
||||||
template.find('.scrapeWebpageButton').on('click', function () {
|
template.find('.scrapeWebpageButton').on('click', function () {
|
||||||
openWebpageScraper(String($(this).data('attachment-manager-target')), renderAttachments);
|
openWebpageScraper(String($(this).data('attachment-manager-target')), renderAttachments);
|
||||||
});
|
});
|
||||||
|
@ -675,6 +731,21 @@ async function openAttachmentManager() {
|
||||||
template.find('.uploadFileButton').on('click', function () {
|
template.find('.uploadFileButton').on('click', function () {
|
||||||
openFileUploader(String($(this).data('attachment-manager-target')), renderAttachments);
|
openFileUploader(String($(this).data('attachment-manager-target')), renderAttachments);
|
||||||
});
|
});
|
||||||
|
template.find('.attachmentSearch').on('input', function () {
|
||||||
|
filterString = String($(this).val());
|
||||||
|
renderAttachments();
|
||||||
|
});
|
||||||
|
template.find('.attachmentSort').on('change', function () {
|
||||||
|
if (!(this instanceof HTMLSelectElement) || this.selectedOptions.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sortField = this.selectedOptions[0].dataset.sortField;
|
||||||
|
sortOrder = this.selectedOptions[0].dataset.sortOrder;
|
||||||
|
localStorage.setItem('DataBank_sortField', sortField);
|
||||||
|
localStorage.setItem('DataBank_sortOrder', sortOrder);
|
||||||
|
renderAttachments();
|
||||||
|
});
|
||||||
await renderAttachments();
|
await renderAttachments();
|
||||||
callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
|
callGenericPopup(template, POPUP_TYPE.TEXT, '', { wide: true, large: true, okButton: 'Close' });
|
||||||
}
|
}
|
||||||
|
@ -734,8 +805,85 @@ async function getTitleFromHtmlBlob(blob) {
|
||||||
* @param {function} callback Callback function
|
* @param {function} callback Callback function
|
||||||
*/
|
*/
|
||||||
async function openFandomScraper(target, callback) {
|
async function openFandomScraper(target, callback) {
|
||||||
toastr.info('Not implemented yet', target);
|
if (!await isFandomPluginAvailable()) {
|
||||||
|
toastr.error('Fandom scraper plugin is not available');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fandom = '';
|
||||||
|
let filter = '';
|
||||||
|
let output = 'single';
|
||||||
|
|
||||||
|
const template = $(await renderExtensionTemplateAsync('attachments', 'fandom-scrape', {}));
|
||||||
|
template.find('input[name="fandomScrapeInput"]').on('input', function () {
|
||||||
|
fandom = String($(this).val());
|
||||||
|
});
|
||||||
|
template.find('input[name="fandomScrapeFilter"]').on('input', function () {
|
||||||
|
filter = String($(this).val());
|
||||||
|
});
|
||||||
|
template.find('input[name="fandomScrapeOutput"]').on('input', function () {
|
||||||
|
output = String($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
const confirm = await callGenericPopup(template, POPUP_TYPE.CONFIRM, '', { wide: false, large: false });
|
||||||
|
|
||||||
|
if (confirm !== POPUP_RESULT.AFFIRMATIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fandom) {
|
||||||
|
toastr.error('Fandom name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetch('/api/plugins/fandom/scrape', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: getRequestHeaders(),
|
||||||
|
body: JSON.stringify({ fandom, filter }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.ok) {
|
||||||
|
const error = await result.text();
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get domain name part if it's a URL
|
||||||
|
try {
|
||||||
|
const url = new URL(fandom);
|
||||||
|
const fandomId = url.hostname.split('.')[0] || fandom;
|
||||||
|
fandom = fandomId;
|
||||||
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await result.json();
|
||||||
|
let numberOfAttachments;
|
||||||
|
|
||||||
|
if (output === 'multi') {
|
||||||
|
numberOfAttachments = data.length;
|
||||||
|
for (const attachment of data) {
|
||||||
|
const file = new File([String(attachment.content).trim()], `${String(attachment.title).trim()}.txt`, { type: 'text/plain' });
|
||||||
|
await uploadFileAttachmentToServer(file, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'single') {
|
||||||
|
numberOfAttachments = 1;
|
||||||
|
const combinedContent = data.map((a) => String(a.title).trim() + '\n\n' + String(a.content).trim()).join('\n\n\n\n');
|
||||||
|
const file = new File([combinedContent], `${fandom}.txt`, { type: 'text/plain' });
|
||||||
|
await uploadFileAttachmentToServer(file, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numberOfAttachments) {
|
||||||
|
toastr.success(`Scraped ${numberOfAttachments} attachments from ${fandom}`);
|
||||||
|
}
|
||||||
|
|
||||||
callback();
|
callback();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fandom scraping failed', error);
|
||||||
|
toastr.error('Check browser console for details.', 'Fandom scraping failed');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -791,6 +939,7 @@ async function uploadFileAttachmentToServer(file, target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data);
|
const fileUrl = await uploadFileAttachment(uniqueFileName, base64Data);
|
||||||
|
const convertedSize = Math.round(base64Data.length * 0.75);
|
||||||
|
|
||||||
if (!fileUrl) {
|
if (!fileUrl) {
|
||||||
return;
|
return;
|
||||||
|
@ -798,8 +947,9 @@ async function uploadFileAttachmentToServer(file, target) {
|
||||||
|
|
||||||
const attachment = {
|
const attachment = {
|
||||||
url: fileUrl,
|
url: fileUrl,
|
||||||
size: file.size,
|
size: convertedSize,
|
||||||
name: file.name,
|
name: file.name,
|
||||||
|
created: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ensureAttachmentsExist();
|
ensureAttachmentsExist();
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
<div>
|
||||||
|
<div class="flex-container flexFlowColumn">
|
||||||
|
<label for="fandomScrapeInput" data-i18n="Enter a URL or the ID of a Fandom wiki page to scrape:">
|
||||||
|
Enter a URL or the ID of a Fandom wiki page to scrape:
|
||||||
|
</label>
|
||||||
|
<small>
|
||||||
|
<span data-i18n=Examples:">Examples:</span>
|
||||||
|
<code>https://harrypotter.fandom.com/</code>
|
||||||
|
<span data-i18n="or">or</span>
|
||||||
|
<code>harrypotter</code>
|
||||||
|
</small>
|
||||||
|
<input type="text" id="fandomScrapeInput" name="fandomScrapeInput" class="text_pole" placeholder="">
|
||||||
|
</div>
|
||||||
|
<div class="flex-container flexFlowColumn">
|
||||||
|
<label for="fandomScrapeFilter">
|
||||||
|
Optional regex to pick the content by its title:
|
||||||
|
</label>
|
||||||
|
<small>
|
||||||
|
<span data-i18n="Example:">Example:</span>
|
||||||
|
<code>/(Azkaban|Weasley)/gi</code>
|
||||||
|
</small>
|
||||||
|
<input type="text" id="fandomScrapeFilter" name="fandomScrapeFilter" class="text_pole" placeholder="">
|
||||||
|
</div>
|
||||||
|
<div class="flex-container flexFlowColumn">
|
||||||
|
<label>
|
||||||
|
Output format:
|
||||||
|
</label>
|
||||||
|
<label class="checkbox_label justifyLeft" for="fandomScrapeOutputSingle">
|
||||||
|
<input id="fandomScrapeOutputSingle" type="radio" name="fandomScrapeOutput" value="single" checked>
|
||||||
|
<div class="flex-container flexFlowColumn flexNoGap">
|
||||||
|
<span data-i18n="Single file">
|
||||||
|
Single file
|
||||||
|
</span>
|
||||||
|
<small data-i18n="All articles will be concatenated into a single file.">
|
||||||
|
All articles will be concatenated into a single file.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="checkbox_label justifyLeft" for="fandomScrapeOutputMulti">
|
||||||
|
<input id="fandomScrapeOutputMulti" type="radio" name="fandomScrapeOutput" value="multi">
|
||||||
|
<div class="flex-container flexFlowColumn flexNoGap">
|
||||||
|
<span data-i18n="File per article">
|
||||||
|
File per article
|
||||||
|
</span>
|
||||||
|
<small data-i18n="Each article will be saved as a separate file.">
|
||||||
|
Not recommended. Each article will be saved as a separate file.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="wide100p paddingTopBot5">
|
<div class="wide100p padding5">
|
||||||
<h2 class="marginBot5">
|
<h2 class="marginBot5">
|
||||||
<span data-i18n="Data Bank">
|
<span data-i18n="Data Bank">
|
||||||
Data Bank
|
Data Bank
|
||||||
|
@ -10,31 +10,35 @@
|
||||||
<div data-i18n="Supported file types: Plain Text, PDF, Markdown, HTML." class="marginTopBot5">
|
<div data-i18n="Supported file types: Plain Text, PDF, Markdown, HTML." class="marginTopBot5">
|
||||||
Supported file types: Plain Text, PDF, Markdown, HTML.
|
Supported file types: Plain Text, PDF, Markdown, HTML.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-container marginTopBot5">
|
||||||
|
<input type="search" id="attachmentSearch" class="attachmentSearch text_pole margin0 flex1" placeholder="Search...">
|
||||||
|
<select id="attachmentSort" class="attachmentSort text_pole margin0 flex1">
|
||||||
|
<option data-sort-field="created" data-sort-order="desc" data-i18n="Date (Newest First)">
|
||||||
|
Date (Newest First)
|
||||||
|
</option>
|
||||||
|
<option data-sort-field="created" data-sort-order="asc" data-i18n="Date (Oldest First)">
|
||||||
|
Date (Oldest First)
|
||||||
|
</option>
|
||||||
|
<option data-sort-field="name" data-sort-order="asc" data-i18n="Name (A-Z)">
|
||||||
|
Name (A-Z)
|
||||||
|
</option>
|
||||||
|
<option data-sort-field="name" data-sort-order="desc" data-i18n="Name (Z-A)">
|
||||||
|
Name (Z-A)
|
||||||
|
</option>
|
||||||
|
<option data-sort-field="size" data-sort-order="asc" data-i18n="Size (Smallest First)">
|
||||||
|
Size (Smallest First)
|
||||||
|
</option>
|
||||||
|
<option data-sort-field="size" data-sort-order="desc" data-i18n="Size (Largest First)">
|
||||||
|
Size (Largest First)
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</div>
|
||||||
<div class="justifyLeft globalAttachmentsBlock marginBot10">
|
<div class="justifyLeft globalAttachmentsBlock marginBot10">
|
||||||
<h3 class="margin0 title_restorable">
|
<h3 class="globalAttachmentsTitle margin0 title_restorable">
|
||||||
<span data-i18n="Global Attachments">
|
<span data-i18n="Global Attachments">
|
||||||
Global Attachments
|
Global Attachments
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-container flexGap10">
|
|
||||||
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="global" title="Download a page from the web.">
|
|
||||||
<i class="fa-fw fa-solid fa-globe"></i>
|
|
||||||
<span data-i18n="From Web">
|
|
||||||
From Web
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="global" title="Download a page from the Fandom wiki.">
|
|
||||||
<i class="fa-fw fa-solid fa-fan"></i>
|
|
||||||
<span data-i18n="From Fandom">
|
|
||||||
From Fandom
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="global" title="Upload a file from your computer.">
|
|
||||||
<i class="fa-fw fa-solid fa-upload"></i>
|
|
||||||
<span data-i18n="From File">
|
|
||||||
From File
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</h3>
|
</h3>
|
||||||
<small data-i18n="These files are available for all characters in all chats.">
|
<small data-i18n="These files are available for all characters in all chats.">
|
||||||
These files are available for all characters in all chats.
|
These files are available for all characters in all chats.
|
||||||
|
@ -43,30 +47,10 @@
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<div class="justifyLeft characterAttachmentsBlock marginBot10">
|
<div class="justifyLeft characterAttachmentsBlock marginBot10">
|
||||||
<h3 class="margin0 title_restorable">
|
<h3 class="characterAttachmentsTitle margin0 title_restorable">
|
||||||
<span data-i18n="Character Attachments">
|
<span data-i18n="Character Attachments">
|
||||||
Character Attachments
|
Character Attachments
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-container flexGap10">
|
|
||||||
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="character" title="Download a page from the web.">
|
|
||||||
<i class="fa-fw fa-solid fa-globe"></i>
|
|
||||||
<span data-i18n="From Web">
|
|
||||||
From Web
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="character" title="Download a page from the Fandom wiki.">
|
|
||||||
<i class="fa-fw fa-solid fa-fan"></i>
|
|
||||||
<span data-i18n="From Fandom">
|
|
||||||
From Fandom
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="character" title="Upload a file from your computer.">
|
|
||||||
<i class="fa-fw fa-solid fa-upload"></i>
|
|
||||||
<span data-i18n="From File">
|
|
||||||
From File
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex-container flexFlowColumn">
|
<div class="flex-container flexFlowColumn">
|
||||||
<strong><small class="characterAttachmentsName"></small></strong>
|
<strong><small class="characterAttachmentsName"></small></strong>
|
||||||
|
@ -78,30 +62,10 @@
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<div class="justifyLeft chatAttachmentsBlock marginBot10">
|
<div class="justifyLeft chatAttachmentsBlock marginBot10">
|
||||||
<h3 class="margin0 title_restorable">
|
<h3 class="chatAttachmentsTitle margin0 title_restorable">
|
||||||
<span data-i18n="Chat Attachments">
|
<span data-i18n="Chat Attachments">
|
||||||
Chat Attachments
|
Chat Attachments
|
||||||
</span>
|
</span>
|
||||||
<div class="flex-container flexGap10">
|
|
||||||
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="chat" title="Download a page from the web.">
|
|
||||||
<i class="fa-fw fa-solid fa-globe"></i>
|
|
||||||
<span data-i18n="From Web">
|
|
||||||
From Web
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="chat" title="Download a page from the Fandom wiki.">
|
|
||||||
<i class="fa-fw fa-solid fa-fan"></i>
|
|
||||||
<span data-i18n="From Fandom">
|
|
||||||
From Fandom
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="chat" title="Upload a file from your computer.">
|
|
||||||
<i class="fa-fw fa-solid fa-upload"></i>
|
|
||||||
<span data-i18n="From File">
|
|
||||||
From File
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</h3>
|
</h3>
|
||||||
<div class="flex-container flexFlowColumn">
|
<div class="flex-container flexFlowColumn">
|
||||||
<strong><small class="chatAttachmentsName"></small></strong>
|
<strong><small class="chatAttachmentsName"></small></strong>
|
||||||
|
@ -116,9 +80,33 @@
|
||||||
<div class="attachmentListItem flex-container alignItemsCenter flexGap10">
|
<div class="attachmentListItem flex-container alignItemsCenter flexGap10">
|
||||||
<div class="attachmentFileIcon fa-solid fa-file-alt"></div>
|
<div class="attachmentFileIcon fa-solid fa-file-alt"></div>
|
||||||
<div class="attachmentListItemName flex1"></div>
|
<div class="attachmentListItemName flex1"></div>
|
||||||
|
<small class="attachmentListItemCreated"></small>
|
||||||
<small class="attachmentListItemSize"></small>
|
<small class="attachmentListItemSize"></small>
|
||||||
<div class="viewAttachmentButton right_menu_button fa-solid fa-magnifying-glass" title="View attachment content"></div>
|
<div class="viewAttachmentButton right_menu_button fa-solid fa-magnifying-glass" title="View attachment content"></div>
|
||||||
<div class="deleteAttachmentButton right_menu_button fa-solid fa-trash" title="Delete attachment"></div>
|
<div class="deleteAttachmentButton right_menu_button fa-solid fa-trash" title="Delete attachment"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="actionButtonsTemplate template_element">
|
||||||
|
<div class="actionButtons flex-container flexGap10">
|
||||||
|
<div class="scrapeWebpageButton menu_button_icon menu_button" data-attachment-manager-target="" title="Download a page from the web.">
|
||||||
|
<i class="fa-fw fa-solid fa-globe"></i>
|
||||||
|
<span data-i18n="From Web">
|
||||||
|
From Web
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="scrapeFandomButton menu_button_icon menu_button" data-attachment-manager-target="" title="Download a page from the Fandom wiki.">
|
||||||
|
<i class="fa-fw fa-solid fa-fire"></i>
|
||||||
|
<span data-i18n="From Fandom">
|
||||||
|
From Fandom
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="uploadFileButton menu_button_icon menu_button" data-attachment-manager-target="" title="Upload a file from your computer.">
|
||||||
|
<i class="fa-fw fa-solid fa-upload"></i>
|
||||||
|
<span data-i18n="From File">
|
||||||
|
From File
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,3 +18,12 @@
|
||||||
.attachmentListItem {
|
.attachmentListItem {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachmentListItemSize {
|
||||||
|
min-width: 4em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachmentListItemCreated {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
|
@ -222,8 +222,7 @@ async function processFiles(chat) {
|
||||||
|
|
||||||
// Trim file inserted by the script
|
// Trim file inserted by the script
|
||||||
const fileText = String(message.mes)
|
const fileText = String(message.mes)
|
||||||
.substring(0, message.extra.fileLength).trim()
|
.substring(0, message.extra.fileLength).trim();
|
||||||
.replace(/^```/, '').replace(/```$/, '').trim();
|
|
||||||
|
|
||||||
// Convert kilobytes to string length
|
// Convert kilobytes to string length
|
||||||
const thresholdLength = settings.size_threshold * 1024;
|
const thresholdLength = settings.size_threshold * 1024;
|
||||||
|
@ -247,8 +246,7 @@ async function processFiles(chat) {
|
||||||
const queryText = getQueryText(chat);
|
const queryText = getQueryText(chat);
|
||||||
const fileChunks = await retrieveFileChunks(queryText, collectionId);
|
const fileChunks = await retrieveFileChunks(queryText, collectionId);
|
||||||
|
|
||||||
// Wrap it back in a code block
|
message.mes = `${fileChunks}\n\n${message.mes}`;
|
||||||
message.mes = `\`\`\`\n${fileChunks}\n\`\`\`\n\n${message.mes}`;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Vectors: Failed to retrieve files', error);
|
console.error('Vectors: Failed to retrieve files', error);
|
||||||
|
|
|
@ -177,9 +177,10 @@ function displayError(message) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirects the user to the home page.
|
* Redirects the user to the home page.
|
||||||
|
* Preserves the query string.
|
||||||
*/
|
*/
|
||||||
function redirectToHome() {
|
function redirectToHome() {
|
||||||
window.location.href = '/';
|
window.location.href = '/' + window.location.search;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1594,6 +1594,11 @@ async function sendAltScaleRequest(messages, logit_bias, signal, type) {
|
||||||
signal: signal,
|
signal: signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
tryParseStreamingError(response, await response.text());
|
||||||
|
throw new Error('Scale response does not indicate success.');
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data.output;
|
return data.output;
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,7 +248,9 @@ if (!disableCsrf) {
|
||||||
// Host index page
|
// Host index page
|
||||||
app.get('/', (request, response) => {
|
app.get('/', (request, response) => {
|
||||||
if (userModule.shouldRedirectToLogin(request)) {
|
if (userModule.shouldRedirectToLogin(request)) {
|
||||||
return response.redirect('/login');
|
const query = request.url.split('?')[1];
|
||||||
|
const redirectUrl = query ? `/login?${query}` : '/login';
|
||||||
|
return response.redirect(redirectUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') });
|
return response.sendFile('index.html', { root: path.join(process.cwd(), 'public') });
|
||||||
|
|
|
@ -7,16 +7,18 @@ const { readSecret, SECRET_KEYS } = require('../secrets');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/generate', jsonParser, function (request, response) {
|
router.post('/generate', jsonParser, async function (request, response) {
|
||||||
if (!request.body) return response.sendStatus(400);
|
if (!request.body) return response.sendStatus(400);
|
||||||
|
|
||||||
fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', {
|
try {
|
||||||
method: 'POST',
|
const cookie = readSecret(request.user.directories, SECRET_KEYS.SCALE_COOKIE);
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
if (!cookie) {
|
||||||
'cookie': `_jwt=${readSecret(request.user.directories, SECRET_KEYS.SCALE_COOKIE)}`,
|
console.log('No Scale cookie found');
|
||||||
},
|
return response.sendStatus(400);
|
||||||
body: JSON.stringify({
|
}
|
||||||
|
|
||||||
|
const body = {
|
||||||
json: {
|
json: {
|
||||||
variant: {
|
variant: {
|
||||||
name: 'New Variant',
|
name: 'New Variant',
|
||||||
|
@ -59,18 +61,41 @@ router.post('/generate', jsonParser, function (request, response) {
|
||||||
'modelParameters.logprobs': ['undefined'],
|
'modelParameters.logprobs': ['undefined'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}),
|
};
|
||||||
})
|
|
||||||
.then(res => res.json())
|
console.log('Scale request:', body);
|
||||||
.then(data => {
|
|
||||||
console.log(data.result.data.json.outputs[0]);
|
const result = await fetch('https://dashboard.scale.com/spellbook/api/trpc/v2.variant.run', {
|
||||||
return response.send({ output: data.result.data.json.outputs[0] });
|
method: 'POST',
|
||||||
})
|
headers: {
|
||||||
.catch((error) => {
|
'Content-Type': 'application/json',
|
||||||
console.error('Error:', error);
|
'cookie': `_jwt=${cookie}`,
|
||||||
return response.send({ error: true });
|
},
|
||||||
|
timeout: 0,
|
||||||
|
body: JSON.stringify(body),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!result.ok) {
|
||||||
|
const text = await result.text();
|
||||||
|
console.log('Scale request failed', result.statusText, text);
|
||||||
|
return response.status(500).send({ error: { message: result.statusText } });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await result.json();
|
||||||
|
const output = data?.result?.data?.json?.outputs?.[0] || '';
|
||||||
|
|
||||||
|
console.log('Scale response:', data);
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
console.warn('Scale response is empty');
|
||||||
|
return response.sendStatus(500).send({ error: { message: 'Empty response' } });
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json({ output });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return response.sendStatus(500);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = { router };
|
module.exports = { router };
|
||||||
|
|
|
@ -326,7 +326,7 @@ function toAvatarKey(handle) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the user storage. Currently a no-op.
|
* Initializes the user storage.
|
||||||
* @param {string} dataRoot The root directory for user data
|
* @param {string} dataRoot The root directory for user data
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
|
@ -655,6 +655,10 @@ async function createBackupArchive(handle, response) {
|
||||||
archive.finalize();
|
archive.finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if any admin users are not password protected. If so, logs a warning.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
async function checkAccountsProtection() {
|
async function checkAccountsProtection() {
|
||||||
if (!ENABLE_ACCOUNTS) {
|
if (!ENABLE_ACCOUNTS) {
|
||||||
return;
|
return;
|
||||||
|
|
Loading…
Reference in New Issue