220 lines
5.5 KiB
JavaScript
220 lines
5.5 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
|
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
|
|
computeSha256HashAsString:
|
|
"resource://gre/modules/addons/crypto-utils.sys.mjs",
|
|
ModelHub: "chrome://global/content/ml/ModelHub.sys.mjs",
|
|
isAddonEngineId: "chrome://global/content/ml/Utils.sys.mjs",
|
|
engineIdToAddonId: "chrome://global/content/ml/Utils.sys.mjs",
|
|
});
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"LOCAL_MODEL_MANAGEMENT_ENABLED",
|
|
"extensions.htmlaboutaddons.local_model_management",
|
|
false,
|
|
(_pref, _old, val) => ModelHubProvider[val ? "init" : "shutdown"]()
|
|
);
|
|
|
|
const MODELHUB_ADDON_ID_SUFFIX = "@modelhub.mozilla.org";
|
|
const MODELHUB_ADDON_TYPE = "mlmodel";
|
|
|
|
export class ModelHubAddonWrapper {
|
|
#provider;
|
|
id;
|
|
model;
|
|
version;
|
|
totalSize;
|
|
lastUsed;
|
|
updateDate;
|
|
modelIconURL;
|
|
engineIds;
|
|
|
|
constructor(params) {
|
|
this.#provider = params.provider;
|
|
this.id = params.id;
|
|
this.model = params.model;
|
|
this.version = params.version;
|
|
this.totalSize = params.totalSize;
|
|
this.lastUsed = params.lastUsed;
|
|
this.updateDate = params.updateDate;
|
|
this.modelIconURL = params.modelIconURL;
|
|
this.engineIds = params.engineIds ?? [];
|
|
}
|
|
|
|
async uninstall() {
|
|
await this.#provider.modelHub.deleteModels({
|
|
model: this.model,
|
|
revision: this.version,
|
|
deletedBy: "about:addons",
|
|
});
|
|
|
|
await this.#provider.onUninstalled(this);
|
|
}
|
|
|
|
get usedByFirefoxFeatures() {
|
|
return this.engineIds.filter(engineId => !lazy.isAddonEngineId(engineId));
|
|
}
|
|
|
|
get usedByAddonIds() {
|
|
return this.engineIds
|
|
.filter(engineId => lazy.isAddonEngineId(engineId))
|
|
.map(engineId => lazy.engineIdToAddonId(engineId));
|
|
}
|
|
|
|
get name() {
|
|
const parts = this.model.split("/");
|
|
return parts.slice(2).join("/");
|
|
}
|
|
|
|
get modelHomepageURL() {
|
|
// Model card URL for models downloaded from "model-hub.mozilla.org" should point to the corresponding "https://huggingface.co" url.
|
|
return `https://${this.model}/`.replace(
|
|
"https://model-hub.mozilla.org/",
|
|
"https://huggingface.co/"
|
|
);
|
|
}
|
|
|
|
get isActive() {
|
|
return true;
|
|
}
|
|
|
|
get isCompatible() {
|
|
return true;
|
|
}
|
|
|
|
get permissions() {
|
|
return lazy.AddonManager.PERM_CAN_UNINSTALL;
|
|
}
|
|
|
|
get type() {
|
|
return MODELHUB_ADDON_TYPE;
|
|
}
|
|
|
|
// TODO(Bug 1965104): replace extensionGeneric.svg with a new mlmodel-specific fallback icon
|
|
get iconURL() {
|
|
return (
|
|
this.modelIconURL ||
|
|
"chrome://mozapps/skin/extensions/extensionGeneric.svg"
|
|
);
|
|
}
|
|
}
|
|
|
|
export const ModelHubProvider = {
|
|
cache: new Map(),
|
|
modelHub: null,
|
|
|
|
get name() {
|
|
return "ModelHubProvider";
|
|
},
|
|
|
|
async getAddonsByTypes(types) {
|
|
if (!lazy.LOCAL_MODEL_MANAGEMENT_ENABLED) {
|
|
return [];
|
|
}
|
|
|
|
const match = types?.includes?.(MODELHUB_ADDON_TYPE);
|
|
return match
|
|
? await this.refreshAddonCache().then(() =>
|
|
Array.from(this.cache.values())
|
|
)
|
|
: [];
|
|
},
|
|
|
|
async getAddonByID(id) {
|
|
if (id?.endsWith(MODELHUB_ADDON_ID_SUFFIX) && !this.cache.size) {
|
|
await this.refreshAddonCache();
|
|
}
|
|
return this.cache.get(id);
|
|
},
|
|
|
|
observe(_subject, topic, _data) {
|
|
switch (topic) {
|
|
case "browser-delayed-startup-finished":
|
|
Services.obs.removeObserver(this, "browser-delayed-startup-finished");
|
|
this.init();
|
|
break;
|
|
}
|
|
},
|
|
|
|
shutdown() {
|
|
if (this.initialized) {
|
|
lazy.AddonManagerPrivate.unregisterProvider(this);
|
|
this.initialized = false;
|
|
this.clearAddonCache();
|
|
}
|
|
},
|
|
|
|
init() {
|
|
if (!lazy.LOCAL_MODEL_MANAGEMENT_ENABLED || this.initialized) {
|
|
return;
|
|
}
|
|
|
|
this.initialized = true;
|
|
lazy.AddonManagerPrivate.registerProvider(this, [MODELHUB_ADDON_TYPE]);
|
|
this.modelHub = new lazy.ModelHub();
|
|
},
|
|
|
|
async onUninstalled(addon) {
|
|
if (!this.cache.has(addon.id)) {
|
|
return;
|
|
}
|
|
this.cache.delete(addon.id);
|
|
lazy.AddonManagerPrivate.callAddonListeners("onUninstalled", addon);
|
|
},
|
|
|
|
clearAddonCache() {
|
|
this.cache.clear();
|
|
},
|
|
|
|
getWrapperIdForModel(model) {
|
|
return [
|
|
lazy.computeSha256HashAsString(`${model.name}:${model.revision}`),
|
|
MODELHUB_ADDON_ID_SUFFIX,
|
|
].join("");
|
|
},
|
|
|
|
async refreshAddonCache() {
|
|
// Return earlier if the model hub provider was disabled.
|
|
// by the time it was being called.
|
|
if (!lazy.LOCAL_MODEL_MANAGEMENT_ENABLED) {
|
|
return;
|
|
}
|
|
|
|
this.clearAddonCache();
|
|
|
|
const models = await this.modelHub.listModels();
|
|
|
|
for (const model of models) {
|
|
const { metadata } = await this.modelHub.listFiles({
|
|
model: model.name,
|
|
revision: model.revision,
|
|
});
|
|
|
|
const modelIconURL = await this.modelHub.getOwnerIcon(model.name);
|
|
const id = this.getWrapperIdForModel(model);
|
|
|
|
const wrapper = new ModelHubAddonWrapper({
|
|
provider: this,
|
|
id,
|
|
model: model.name,
|
|
version: model.revision,
|
|
totalSize: metadata.totalSize,
|
|
lastUsed: new Date(metadata.lastUsed),
|
|
updateDate: new Date(metadata.updateDate),
|
|
modelIconURL,
|
|
engineIds: metadata.engineIds,
|
|
});
|
|
this.cache.set(wrapper.id, wrapper);
|
|
}
|
|
},
|
|
};
|
|
|
|
Services.obs.addObserver(ModelHubProvider, "browser-delayed-startup-finished");
|