Refactor and JSDoc extensions.js

126616d5390d751cffe048fd776854025a1a885f

Cohee <18619528+Cohee1207@users.noreply.github.com>

4 files changed, +86 -69Ignore whitespace
.dockerignore+1 -0
@@ -12,3 +12,4 @@ access.log
12/data12/data
13/cache13/cache
14.DS_Store14.DS_Store
15/public/scripts/extensions/third-party
.npmignore+1 -0
@@ -11,3 +11,4 @@ access.log
11.github11.github
12.vscode12.vscode
13.git13.git
14/public/scripts/extensions/third-party
public/css/extensions-panel.css+0 -5
@@ -116,11 +116,6 @@ input.extension_missing[type="checkbox"] {
116 opacity: 0.5;116 opacity: 0.5;
117}117}
118118
119#extensions_list .disabled {
120 text-decoration: line-through;
121 color: lightgray;
122}
123
124.update-button {119.update-button {
125 margin-right: 10px;120 margin-right: 10px;
126 display: inline-flex;121 display: inline-flex;
public/scripts/extensions.js+84 -64
@@ -8,19 +8,16 @@ import { isSubsetOf, setValueByPath } from './utils.js';
8import { getContext } from './st-context.js';8import { getContext } from './st-context.js';
9import { isAdmin } from './user.js';9import { isAdmin } from './user.js';
10import { t } from './i18n.js';10import { t } from './i18n.js';
11import { debounce_timeout } from './constants.js';
12
11export {13export {
12 getContext,14 getContext,
13 getApiUrl,15 getApiUrl,
14 loadExtensionSettings,
15 runGenerationInterceptors,
16 doExtrasFetch,
17 modules,
18 extension_settings,
19 ModuleWorkerWrapper,
20};16};
2117
22/** @type {string[]} */18/** @type {string[]} */
23export let extensionNames = [];19export let extensionNames = [];
20
24/**21/**
25 * Holds the type of each extension.22 * Holds the type of each extension.
26 * Don't use this directly, use getExtensionType instead!23 * Don't use this directly, use getExtensionType instead!
@@ -28,13 +25,35 @@ export let extensionNames = [];
28 */25 */
29export let extensionTypes = {};26export let extensionTypes = {};
3027
28/**
29 * A list of active modules provided by the Extras API.
30 * @type {string[]}
31 */
32export let modules = [];
33
34/**
35 * A set of active extensions.
36 * @type {Set<string>}
37 */
38let activeExtensions = new Set();
39
40const getApiUrl = () => extension_settings.apiUrl;
41let connectedToApi = false;
42
43/**
44 * Holds manifest data for each extension.
45 * @type {Record<string, object>}
46 */
31let manifests = {};47let manifests = {};
32const defaultUrl = 'http://localhost:5100';
3348
34let saveMetadataTimeout = null;49/**
50 * Default URL for the Extras API.
51 */
52const defaultUrl = 'http://localhost:5100';
3553
36let requiresReload = false;54let requiresReload = false;
37let stateChanged = false;55let stateChanged = false;
56let saveMetadataTimeout = null;
3857
39export function saveMetadataDebounced() {58export function saveMetadataDebounced() {
40 const context = getContext();59 const context = getContext();
@@ -59,9 +78,9 @@ export function saveMetadataDebounced() {
59 }78 }
6079
61 console.debug('Saving metadata...');80 console.debug('Saving metadata...');
62 newContext.saveMetadata();81 await newContext.saveMetadata();
63 console.debug('Saved metadata...');82 console.debug('Saved metadata...');
64 }, 1000);83 }, debounce_timeout.relaxed);
65}84}
6685
67/**86/**
@@ -91,7 +110,7 @@ export function renderExtensionTemplateAsync(extensionName, templateId, template
91}110}
92111
93// Disables parallel updates112// Disables parallel updates
94class ModuleWorkerWrapper {113export class ModuleWorkerWrapper {
95 constructor(callback) {114 constructor(callback) {
96 this.isBusy = false;115 this.isBusy = false;
97 this.callback = callback;116 this.callback = callback;
@@ -115,7 +134,7 @@ class ModuleWorkerWrapper {
115 }134 }
116}135}
117136
118const extension_settings = {137export const extension_settings = {
119 apiUrl: defaultUrl,138 apiUrl: defaultUrl,
120 apiKey: '',139 apiKey: '',
121 autoConnect: false,140 autoConnect: false,
@@ -180,12 +199,6 @@ const extension_settings = {
180 disabled_attachments: [],199 disabled_attachments: [],
181};200};
182201
183let modules = [];
184let activeExtensions = new Set();
185
186const getApiUrl = () => extension_settings.apiUrl;
187let connectedToApi = false;
188
189function showHideExtensionsMenu() {202function showHideExtensionsMenu() {
190 // Get the number of menu items that are not hidden203 // Get the number of menu items that are not hidden
191 const hasMenuItems = $('#extensionsMenu').children().filter((_, child) => $(child).css('display') !== 'none').length > 0;204 const hasMenuItems = $('#extensionsMenu').children().filter((_, child) => $(child).css('display') !== 'none').length > 0;
@@ -212,7 +225,13 @@ function getExtensionType(externalId) {
212 return id ? extensionTypes[id] : '';225 return id ? extensionTypes[id] : '';
213}226}
214227
215async function doExtrasFetch(endpoint, args) {228/**
229 * Performs a fetch of the Extras API.
230 * @param {string|URL} endpoint Extras API endpoint
231 * @param {RequestInit} args Request arguments
232 * @returns {Promise<Response>} Response from the fetch
233 */
234export async function doExtrasFetch(endpoint, args = {}) {
216 if (!args) {235 if (!args) {
217 args = {};236 args = {};
218 }237 }
@@ -231,8 +250,7 @@ async function doExtrasFetch(endpoint, args) {
231 });250 });
232 }251 }
233252
234 const response = await fetch(endpoint, args);253 return await fetch(endpoint, args);
235 return response;
236}254}
237255
238/**256/**
@@ -267,6 +285,11 @@ function onEnableExtensionClick() {
267 enableExtension(name, false);285 enableExtension(name, false);
268}286}
269287
288/**
289 * Enables an extension by name.
290 * @param {string} name Extension name
291 * @param {boolean} [reload=true] If true, reload the page after enabling the extension
292 */
270export async function enableExtension(name, reload = true) {293export async function enableExtension(name, reload = true) {
271 extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);294 extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
272 stateChanged = true;295 stateChanged = true;
@@ -278,6 +301,11 @@ export async function enableExtension(name, reload = true) {
278 }301 }
279}302}
280303
304/**
305 * Disables an extension by name.
306 * @param {string} name Extension name
307 * @param {boolean} [reload=true] If true, reload the page after disabling the extension
308 */
281export async function disableExtension(name, reload = true) {309export async function disableExtension(name, reload = true) {
282 extension_settings.disabledExtensions.push(name);310 extension_settings.disabledExtensions.push(name);
283 stateChanged = true;311 stateChanged = true;
@@ -289,6 +317,11 @@ export async function disableExtension(name, reload = true) {
289 }317 }
290}318}
291319
320/**
321 * Loads manifest.json files for extensions.
322 * @param {string[]} names Array of extension names
323 * @returns {Promise<Record<string, object>>} Object with extension names as keys and their manifests as values
324 */
292async function getManifests(names) {325async function getManifests(names) {
293 const obj = {};326 const obj = {};
294 const promises = [];327 const promises = [];
@@ -316,6 +349,10 @@ async function getManifests(names) {
316 return obj;349 return obj;
317}350}
318351
352/**
353 * Tries to activate all available extensions that are not already active.
354 * @returns {Promise<void>}
355 */
319async function activateExtensions() {356async function activateExtensions() {
320 const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);357 const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
321 const promises = [];358 const promises = [];
@@ -323,36 +360,25 @@ async function activateExtensions() {
323 for (let entry of extensions) {360 for (let entry of extensions) {
324 const name = entry[0];361 const name = entry[0];
325 const manifest = entry[1];362 const manifest = entry[1];
326 const elementExists = document.getElementById(name) !== null;
327363
328 if (elementExists || activeExtensions.has(name)) {364 if (activeExtensions.has(name)) {
329 continue;365 continue;
330 }366 }
331367
332 // all required modules are active (offline extensions require none)368 const meetsModuleRequirements = !Array.isArray(manifest.requires) || isSubsetOf(modules, manifest.requires);
333 if (isSubsetOf(modules, manifest.requires)) {369 const isDisabled = extension_settings.disabledExtensions.includes(name);
334 try {
335 const isDisabled = extension_settings.disabledExtensions.includes(name);
336 const li = document.createElement('li');
337
338 if (!isDisabled) {
339 const promise = Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]);
340 await promise
341 .then(() => activeExtensions.add(name))
342 .catch(err => console.log('Could not activate extension: ' + name, err));
343 promises.push(promise);
344 }
345 else {
346 li.classList.add('disabled');
347 }
348
349 li.id = name;
350 li.innerText = manifest.display_name;
351370
352 $('#extensions_list').append(li);371 if (meetsModuleRequirements && !isDisabled) {
372 try {
373 console.debug('Activating extension', name);
374 const promise = Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]);
375 await promise
376 .then(() => activeExtensions.add(name))
377 .catch(err => console.log('Could not activate extension', name, err));
378 promises.push(promise);
353 }379 }
354 catch (error) {380 catch (error) {
355 console.error(`Could not activate extension: ${name}`);381 console.error('Could not activate extension', name);
356 console.error(error);382 console.error(error);
357 }383 }
358 }384 }
@@ -362,8 +388,8 @@ async function activateExtensions() {
362}388}
363389
364async function connectClickHandler() {390async function connectClickHandler() {
365 const baseUrl = $('#extensions_url').val();391 const baseUrl = String($('#extensions_url').val());
366 extension_settings.apiUrl = String(baseUrl);392 extension_settings.apiUrl = baseUrl;
367 const testApiKey = $('#extensions_api_key').val();393 const testApiKey = $('#extensions_api_key').val();
368 extension_settings.apiKey = String(testApiKey);394 extension_settings.apiKey = String(testApiKey);
369 saveSettingsDebounced();395 saveSettingsDebounced();
@@ -423,21 +449,11 @@ function notifyUpdatesInputHandler() {
423 }449 }
424}450}
425451
426/* $(document).on('click', function (e) {452/**
427 const target = $(e.target);453 * Connects to the Extras API.
428 if (target.is(dropdown)) return;454 * @param {string} baseUrl Extras API base URL
429 if (target.is(button) && dropdown.is(':hidden')) {455 * @returns {Promise<void>}
430 dropdown.toggle(200);456 */
431 popper.update();
432 }
433 if (target !== dropdown &&
434 target !== button &&
435 dropdown.is(":visible")) {
436 dropdown.hide(200);
437 }
438 });
439} */
440
441async function connectToApi(baseUrl) {457async function connectToApi(baseUrl) {
442 if (!baseUrl) {458 if (!baseUrl) {
443 return;459 return;
@@ -453,7 +469,7 @@ async function connectToApi(baseUrl) {
453 const data = await getExtensionsResult.json();469 const data = await getExtensionsResult.json();
454 modules = data.modules;470 modules = data.modules;
455 await activateExtensions();471 await activateExtensions();
456 eventSource.emit(event_types.EXTRAS_CONNECTED, modules);472 await eventSource.emit(event_types.EXTRAS_CONNECTED, modules);
457 }473 }
458474
459 updateStatus(getExtensionsResult.ok);475 updateStatus(getExtensionsResult.ok);
@@ -463,6 +479,10 @@ async function connectToApi(baseUrl) {
463 }479 }
464}480}
465481
482/**
483 * Updates the status of Extras API connection.
484 * @param {boolean} success Whether the connection was successful
485 */
466function updateStatus(success) {486function updateStatus(success) {
467 connectedToApi = success;487 connectedToApi = success;
468 const _text = success ? t`Connected to API` : t`Could not connect to API`;488 const _text = success ? t`Connected to API` : t`Could not connect to API`;
@@ -977,7 +997,7 @@ export async function installExtension(url, global) {
977 * @param {boolean} versionChanged Is this a version change?997 * @param {boolean} versionChanged Is this a version change?
978 * @param {boolean} enableAutoUpdate Enable auto-update998 * @param {boolean} enableAutoUpdate Enable auto-update
979 */999 */
980async function loadExtensionSettings(settings, versionChanged, enableAutoUpdate) {1000export async function loadExtensionSettings(settings, versionChanged, enableAutoUpdate) {
981 if (settings.extension_settings) {1001 if (settings.extension_settings) {
982 Object.assign(extension_settings, settings.extension_settings);1002 Object.assign(extension_settings, settings.extension_settings);
983 }1003 }
@@ -1149,7 +1169,7 @@ async function autoUpdateExtensions(forceAll) {
1149 * @param {number} contextSize Context size1169 * @param {number} contextSize Context size
1150 * @returns {Promise<boolean>} True if generation should be aborted1170 * @returns {Promise<boolean>} True if generation should be aborted
1151 */1171 */
1152async function runGenerationInterceptors(chat, contextSize) {1172export async function runGenerationInterceptors(chat, contextSize) {
1153 let aborted = false;1173 let aborted = false;
1154 let exitImmediately = false;1174 let exitImmediately = false;
11551175