aboutsummaryrefslogtreecommitdiff
path: root/src/FaunerieAI.ts
diff options
context:
space:
mode:
authorRaindropsSys <raindrops@equestria.dev>2024-06-29 20:59:16 +0200
committerRaindropsSys <raindrops@equestria.dev>2024-06-29 20:59:16 +0200
commit914bcbb474f6f186c212b2da0d9d864b5e75d8e4 (patch)
treeb77a3e341f87e7a9c4ad9e1f9d4928f5b2495329 /src/FaunerieAI.ts
parent1c94bd658c2469f9ca9f465db82e71b6f7d2bfe8 (diff)
downloadfaunerie-914bcbb474f6f186c212b2da0d9d864b5e75d8e4.tar.gz
faunerie-914bcbb474f6f186c212b2da0d9d864b5e75d8e4.tar.bz2
faunerie-914bcbb474f6f186c212b2da0d9d864b5e75d8e4.zip
Rename to Faunerie
Diffstat (limited to 'src/FaunerieAI.ts')
-rwxr-xr-xsrc/FaunerieAI.ts224
1 files changed, 224 insertions, 0 deletions
diff --git a/src/FaunerieAI.ts b/src/FaunerieAI.ts
new file mode 100755
index 0000000..aa93cbb
--- /dev/null
+++ b/src/FaunerieAI.ts
@@ -0,0 +1,224 @@
+import {FaunerieApp} from "./FaunerieApp";
+import {ChildProcess} from "node:child_process";
+import {FaunerieImageType} from "libfaunerie";
+import * as fs from "node:fs";
+import * as cp from "node:child_process";
+
+export class FaunerieAI {
+ instance: FaunerieApp;
+ aiProcess: ChildProcess;
+ aiStarting: boolean;
+
+ constructor(instance: FaunerieApp) {
+ this.instance = instance;
+ }
+
+ get aiRunning() {
+ return (!!this.aiProcess && this.aiProcess.exitCode === null) || this.aiStarting;
+ }
+
+ findPythonExecutable() {
+ try {
+ if (process.platform !== "darwin") throw new Error("Not running on macOS");
+ cp.execFileSync("/usr/local/bin/python3.11", ["--version"], {cwd: __dirname + "/../ai"}).toString().trim();
+ return "/usr/local/bin/python3.11";
+ } catch (e) {
+ try {
+ cp.execFileSync("py", ["--version"], {cwd: __dirname + "/../ai"}).toString().trim();
+ return "py";
+ } catch (e) {
+ try {
+ cp.execFileSync("python3.11", ["--version"], {cwd: __dirname + "/../ai"}).toString().trim();
+ return "python3.11";
+ } catch (e) {
+ try {
+ cp.execFileSync("python3", ["--version"], {cwd: __dirname + "/../ai"}).toString().trim();
+ return "python3";
+ } catch (e) {
+ cp.execFileSync("python", ["--version"], {cwd: __dirname + "/../ai"}).toString().trim();
+ return "python";
+ }
+ }
+ }
+ }
+ }
+
+ getPythonVersion() {
+ return cp.execFileSync(this.findPythonExecutable(), ["--version"], {cwd: __dirname + "/../ai"}).toString().trim();
+ }
+
+ updateDependencies() {
+ return new Promise<void>((res) => {
+ this.aiProcess = cp.execFile(this.findPythonExecutable(), ["-m", "pip", "install", "-U", "torch", "ultralytics", "Pillow", "requests", "pandas", "opencv-python", "flask", "gitpython", "setuptools>=65.5.1"], {cwd: __dirname + "/../ai"});
+
+ this.aiProcess.stdout.on('data', (d) => {
+ console.debug(d.toString());
+ });
+
+ this.aiProcess.stderr.on('data', (d) => {
+ console.debug(d.toString());
+ });
+
+ this.aiProcess.on('exit', () => {
+ res();
+ });
+ });
+ }
+
+ triggerEngineStart() {
+ this.aiProcess = cp.execFile(this.findPythonExecutable(), ["server.py"], {cwd: __dirname + "/../ai"});
+
+ this.aiProcess.stdout.on('data', (d) => {
+ console.debug(d.toString());
+ });
+
+ this.aiProcess.stderr.on('data', (d) => {
+ console.debug(d.toString());
+ });
+
+ this.aiProcess.on('exit', () => {
+ this.aiProcess = null;
+ });
+ }
+
+ waitForEngine() {
+ return new Promise<void>((res) => {
+ let aiInterval = setInterval(async () => {
+ try {
+ if ("data" in (await (await fetch("http://127.0.0.1:25091/status")).json())) {
+ clearInterval(aiInterval);
+ this.aiStarting = false;
+ res();
+ }
+ } catch (e) {
+ }
+ }, 1000);
+ })
+ }
+
+ validatePythonVersion() {
+ let pyVersion: string = this.getPythonVersion();
+
+ if (!pyVersion) {
+ console.log("Unable to find a Python executable");
+ throw new Error("Python not found");
+ } else if (!pyVersion.startsWith("Python 3.11.") && pyVersion !== "Python 3.11") {
+ console.log("Invalid Python version: " + pyVersion);
+ throw new Error("Python not found");
+ }
+ }
+
+ async startAI() {
+ if (this.aiRunning) return;
+
+ this.aiStarting = true;
+ this.validatePythonVersion();
+ await this.updateDependencies();
+ this.triggerEngineStart();
+ await this.waitForEngine();
+ }
+
+ async getClasses() {
+ let _dataStore = this.instance.dataStore;
+ let protectedDecode = (b: Buffer) => {
+ return require('zlib').inflateRawSync(b);
+ }
+
+ await this.startAI();
+ let url = _dataStore.database.frontend.getImageFile(_dataStore.currentImage, FaunerieImageType.ViewURL);
+ let data: any;
+
+ if (url.startsWith("blob:") || url.startsWith("pbip:")) {
+ fs.writeFileSync(_dataStore.appData + "/.temp", protectedDecode(fs.readFileSync(_dataStore.database.frontend.getImageFile(_dataStore.currentImage, FaunerieImageType.ViewFile))));
+ url = "file://" + (_dataStore.appData + "/.temp").replaceAll("\\", "/");
+ }
+
+ if (_dataStore.currentImage.tags.includes("safe")) {
+ data = await (await fetch("http://127.0.0.1:25091/safe?url=" + encodeURIComponent(url.replace("file://", "")))).json();
+ } else {
+ data = await (await fetch("http://127.0.0.1:25091/explicit?url=" + encodeURIComponent(url.replace("file://", "")))).json();
+ }
+
+ if (fs.existsSync(_dataStore.appData + "/.temp")) fs.unlinkSync(_dataStore.appData + "/.temp");
+ return data;
+ }
+
+ unload() {
+ if (this.instance.dataStore.appData && fs.existsSync(this.instance.dataStore.appData + "/.temp")) fs.unlinkSync(this.instance.dataStore.appData + "/.temp");
+
+ if (this.aiProcess) {
+ console.log("Engine did not stop before quitting, forcefully killing it");
+ this.aiProcess.kill("SIGKILL");
+ }
+ }
+
+ makeClassesHTML(c: any) {
+ let _dataStore = this.instance.dataStore;
+
+ window.onresize = () => {
+ document.getElementById("preview-zones").style.top = document.getElementById("preview-content-inner").offsetTop + "px";
+ document.getElementById("preview-zones").style.left = document.getElementById("preview-content-inner").offsetLeft + "px";
+ document.getElementById("preview-zones").style.width = document.getElementById("preview-content-inner").clientWidth + "px";
+ document.getElementById("preview-zones").style.height = document.getElementById("preview-content-inner").clientHeight + "px";
+ }
+
+ window.onresize(null);
+ _dataStore.currentImageClasses = c.data || [];
+
+ let properClasses = _dataStore.currentImageClasses.filter(i => i.confidence > 0.5).map(i => {
+ return {
+ name: i.name,
+ top: (i.ymin / _dataStore.currentImage.height) * 100,
+ left: (i.xmin / _dataStore.currentImage.width) * 100,
+ width: ((i.xmax - i.xmin) / _dataStore.currentImage.width) * 100,
+ height: ((i.ymax - i.ymin) / _dataStore.currentImage.height) * 100
+ }
+ });
+ document.getElementById("preview-zones").innerHTML = properClasses.map((i, j) => `
+ <div onmouseenter="instance.display.highlightZone(${j}, true);" onmouseleave="instance.display.highlightZone(${j}, false);" class="preview-zone" id="preview-zone-${j}" style="opacity: 0; transition: opacity 100ms; border-radius: 1%; background-color: rgba(255, 255, 255, .25); border: 1px solid rgba(255, 255, 255, .5); width: ${i.width}%; height: ${i.height}%; top: ${i.top}%; left: ${i.left}%; position: absolute;"></div>
+ `).join("");
+
+ this.displayClassesList(properClasses);
+ }
+
+ displayClassesList(properClasses: any) {
+ if (properClasses.length === 0) {
+ document.getElementById("preview-parts-loader").style.display = "none";
+ document.getElementById("preview-parts-none").style.display = "";
+ document.getElementById("preview-parts-unsupported").style.display = "none";
+ document.getElementById("preview-parts-list").style.display = "none";
+ } else {
+ document.getElementById("preview-parts-loader").style.display = "none";
+ document.getElementById("preview-parts-none").style.display = "none";
+ document.getElementById("preview-parts-unsupported").style.display = "none";
+ document.getElementById("preview-parts-list").style.display = "";
+ document.getElementById("preview-parts-list").innerHTML = properClasses.map((i, j) => `
+ <a onmouseenter="instance.display.highlightZone(${j}, true);" onmouseleave="instance.display.highlightZone(${j}, false);" id="preview-tag-zone-${j}" class='preview-tag preview-tag-zone' href='#'>${i.name}</a>
+ `).join("");
+ }
+ }
+
+ displayClasses(id: string) {
+ let _dataStore = this.instance.dataStore;
+
+ if (_dataStore.currentImage.mime_type.startsWith("image/")) {
+ if (_dataStore.currentImage.tags.includes("screencap") || !_dataStore.currentImage.tags.includes("safe")) {
+ this.instance.ai.getClasses().then((c: any) => {
+ if (_dataStore.currentImage.id === parseInt(id)) {
+ this.makeClassesHTML(c);
+ }
+ });
+ } else {
+ document.getElementById("preview-parts-loader").style.display = "none";
+ document.getElementById("preview-parts-none").style.display = "";
+ document.getElementById("preview-parts-unsupported").style.display = "none";
+ document.getElementById("preview-parts-list").style.display = "none";
+ }
+ } else {
+ document.getElementById("preview-parts-loader").style.display = "none";
+ document.getElementById("preview-parts-none").style.display = "none";
+ document.getElementById("preview-parts-unsupported").style.display = "";
+ document.getElementById("preview-parts-list").style.display = "none";
+ }
+ }
+}