Refactor and JSDoc extensions.js

126616d5390d751cffe048fd776854025a1a885f

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

4 files changed, +81 -64Showing whitespace changes
.dockerignore+1 -0
@@ -12,3 +12,4 @@ access.log
1212/data
1313/cache
1414.DS_Store
15+/public/scripts/extensions/third-party
.npmignore+1 -0
@@ -11,3 +11,4 @@ access.log
1111.github
1212.vscode
1313.git
14+/public/scripts/extensions/third-party
public/css/extensions-panel.css+0 -5
@@ -116,11 +116,6 @@ input.extension_missing[type="checkbox"] {
116116 opacity: 0.5;
117117}
118118
119-#extensions_list .disabled {
120- text-decoration: line-through;
121- color: lightgray;
122-}
123-
124119.update-button {
125120 margin-right: 10px;
126121 display: inline-flex;
public/scripts/extensions.js+79 -59
@@ -8,19 +8,16 @@ import { isSubsetOf, setValueByPath } from './utils.js';
88import { getContext } from './st-context.js';
99import { isAdmin } from './user.js';
1010import { t } from './i18n.js';
11+import { debounce_timeout } from './constants.js';
12+
1113export {
1214 getContext,
1315 getApiUrl,
14- loadExtensionSettings,
15- runGenerationInterceptors,
16- doExtrasFetch,
17- modules,
18- extension_settings,
19- ModuleWorkerWrapper,
2016};
2117
2218/** @type {string[]} */
2319export let extensionNames = [];
20+
2421/**
2522 * Holds the type of each extension.
2623 * Don't use this directly, use getExtensionType instead!
@@ -28,13 +25,35 @@ export let extensionNames = [];
2825 */
2926export let extensionTypes = {};
3027
28+/**
29+ * A list of active modules provided by the Extras API.
30+ * @type {string[]}
31+ */
32+export let modules = [];
33+
34+/**
35+ * A set of active extensions.
36+ * @type {Set<string>}
37+ */
38+let activeExtensions = new Set();
39+
40+const getApiUrl = () => extension_settings.apiUrl;
41+let connectedToApi = false;
42+
43+/**
44+ * Holds manifest data for each extension.
45+ * @type {Record<string, object>}
46+ */
3147let manifests = {};
32-const defaultUrl = 'http://localhost:5100';
3348
34-let saveMetadataTimeout = null;
49+/**
50+ * Default URL for the Extras API.
51+ */
52+const defaultUrl = 'http://localhost:5100';
3553
3654let requiresReload = false;
3755let stateChanged = false;
56+let saveMetadataTimeout = null;
3857
3958export function saveMetadataDebounced() {
4059 const context = getContext();
@@ -59,9 +78,9 @@ export function saveMetadataDebounced() {
5978 }
6079
6180 console.debug('Saving metadata...');
6281 await newContext.saveMetadata();
6382 console.debug('Saved metadata...');
6483 }, 1000debounce_timeout.relaxed);
6584}
6685
6786/**
@@ -91,7 +110,7 @@ export function renderExtensionTemplateAsync(extensionName, templateId, template
91110}
92111
93112// Disables parallel updates
94113export class ModuleWorkerWrapper {
95114 constructor(callback) {
96115 this.isBusy = false;
97116 this.callback = callback;
@@ -115,7 +134,7 @@ class ModuleWorkerWrapper {
115134 }
116135}
117136
118137export const extension_settings = {
119138 apiUrl: defaultUrl,
120139 apiKey: '',
121140 autoConnect: false,
@@ -180,12 +199,6 @@ const extension_settings = {
180199 disabled_attachments: [],
181200};
182201
183-let modules = [];
184-let activeExtensions = new Set();
185-
186-const getApiUrl = () => extension_settings.apiUrl;
187-let connectedToApi = false;
188-
189202function showHideExtensionsMenu() {
190203 // Get the number of menu items that are not hidden
191204 const hasMenuItems = $('#extensionsMenu').children().filter((_, child) => $(child).css('display') !== 'none').length > 0;
@@ -212,7 +225,13 @@ function getExtensionType(externalId) {
212225 return id ? extensionTypes[id] : '';
213226}
214227
215-async 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+ */
234+export async function doExtrasFetch(endpoint, args = {}) {
216235 if (!args) {
217236 args = {};
218237 }
@@ -231,8 +250,7 @@ async function doExtrasFetch(endpoint, args) {
231250 });
232251 }
233252
234253 const response =return await fetch(endpoint, args);
235- return response;
236254}
237255
238256/**
@@ -267,6 +285,11 @@ function onEnableExtensionClick() {
267285 enableExtension(name, false);
268286}
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+ */
270293export async function enableExtension(name, reload = true) {
271294 extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
272295 stateChanged = true;
@@ -278,6 +301,11 @@ export async function enableExtension(name, reload = true) {
278301 }
279302}
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+ */
281309export async function disableExtension(name, reload = true) {
282310 extension_settings.disabledExtensions.push(name);
283311 stateChanged = true;
@@ -289,6 +317,11 @@ export async function disableExtension(name, reload = true) {
289317 }
290318}
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+ */
292325async function getManifests(names) {
293326 const obj = {};
294327 const promises = [];
@@ -316,6 +349,10 @@ async function getManifests(names) {
316349 return obj;
317350}
318351
352+/**
353+ * Tries to activate all available extensions that are not already active.
354+ * @returns {Promise<void>}
355+ */
319356async function activateExtensions() {
320357 const extensions = Object.entries(manifests).sort((a, b) => a[1].loading_order - b[1].loading_order);
321358 const promises = [];
@@ -323,36 +360,25 @@ async function activateExtensions() {
323360 for (let entry of extensions) {
324361 const name = entry[0];
325362 const manifest = entry[1];
326- const elementExists = document.getElementById(name) !== null;
327363
328364 if (elementExists || activeExtensions.has(name)) {
329365 continue;
330366 }
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)) {
334- try {
335369 const isDisabled = extension_settings.disabledExtensions.includes(name);
336- const li = document.createElement('li');
337370
338371 if (meetsModuleRequirements && !isDisabled) {
372+ try {
373+ console.debug('Activating extension', name);
339374 const promise = Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]);
340375 await promise
341376 .then(() => activeExtensions.add(name))
342377 .catch(err => console.log('Could not activate extension: ' +, name, err));
343378 promises.push(promise);
344379 }
345- else {
346- li.classList.add('disabled');
347- }
348-
349- li.id = name;
350- li.innerText = manifest.display_name;
351-
352- $('#extensions_list').append(li);
353- }
354380 catch (error) {
355381 console.error(`'Could not activate extension:', ${name}`);
356382 console.error(error);
357383 }
358384 }
@@ -362,8 +388,8 @@ async function activateExtensions() {
362388}
363389
364390async function connectClickHandler() {
365391 const baseUrl = String($('#extensions_url').val());
366392 extension_settings.apiUrl = String(baseUrl);
367393 const testApiKey = $('#extensions_api_key').val();
368394 extension_settings.apiKey = String(testApiKey);
369395 saveSettingsDebounced();
@@ -423,21 +449,11 @@ function notifyUpdatesInputHandler() {
423449 }
424450}
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-
441457async function connectToApi(baseUrl) {
442458 if (!baseUrl) {
443459 return;
@@ -453,7 +469,7 @@ async function connectToApi(baseUrl) {
453469 const data = await getExtensionsResult.json();
454470 modules = data.modules;
455471 await activateExtensions();
456472 await eventSource.emit(event_types.EXTRAS_CONNECTED, modules);
457473 }
458474
459475 updateStatus(getExtensionsResult.ok);
@@ -463,6 +479,10 @@ async function connectToApi(baseUrl) {
463479 }
464480}
465481
482+/**
483+ * Updates the status of Extras API connection.
484+ * @param {boolean} success Whether the connection was successful
485+ */
466486function updateStatus(success) {
467487 connectedToApi = success;
468488 const _text = success ? t`Connected to API` : t`Could not connect to API`;
@@ -977,7 +997,7 @@ export async function installExtension(url, global) {
977997 * @param {boolean} versionChanged Is this a version change?
978998 * @param {boolean} enableAutoUpdate Enable auto-update
979999 */
9801000export async function loadExtensionSettings(settings, versionChanged, enableAutoUpdate) {
9811001 if (settings.extension_settings) {
9821002 Object.assign(extension_settings, settings.extension_settings);
9831003 }
@@ -1149,7 +1169,7 @@ async function autoUpdateExtensions(forceAll) {
11491169 * @param {number} contextSize Context size
11501170 * @returns {Promise<boolean>} True if generation should be aborted
11511171 */
11521172export async function runGenerationInterceptors(chat, contextSize) {
11531173 let aborted = false;
11541174 let exitImmediately = false;
11551175