aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2025-08-14 11:00:14 -0400
committerbobzel <zzzman@gmail.com>2025-08-14 11:00:14 -0400
commit467f1c2543626a50d48c84669cd408571260f147 (patch)
tree2cb3599c4ca95fe2b975deb8dc72283151e1c585
parent5b0b7241e68febc2d0556b6c2e8349411b5c12a0 (diff)
added background removal for images.
-rw-r--r--package-lock.json234
-rw-r--r--package.json6
-rw-r--r--src/client/views/nodes/ImageBox.tsx71
-rw-r--r--src/server/server_Initialization.ts17
-rw-r--r--src/workers/image.worker.ts16
-rw-r--r--tsconfig.worker.json18
-rw-r--r--webpack.worker.config.js30
7 files changed, 377 insertions, 15 deletions
diff --git a/package-lock.json b/package-lock.json
index 9fc7665e1..56e847e0f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -34,6 +34,7 @@
"@fullcalendar/multimonth": "^6.1.17",
"@fullcalendar/react": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.15",
+ "@imgly/background-removal": "^1.7.0",
"@internationalized/date": "^3.5.0",
"@mozilla/readability": "^0.6.0",
"@mui/icons-material": "^6.0.1",
@@ -183,6 +184,7 @@
"nodemailer": "^6.9.7",
"nodemon": "^3.0.2",
"npm": "^11.3.0",
+ "onnxruntime-web": "^1.21.0-dev.20250206-d981b153d3",
"openai": "^4.75.0",
"p-limit": "^6.1.0",
"parse-multipart-data": "^1.5.0",
@@ -277,6 +279,7 @@
"webpack-hot-middleware": "^2.25.4",
"wikijs": "^6.4.1",
"words-to-numbers": "^1.5.1",
+ "worker-loader": "^3.0.8",
"xmlbuilder": "^15.1.1",
"xoauth2": "^1.2.0",
"xregexp": "^5.1.1"
@@ -4786,6 +4789,20 @@
"react": "*"
}
},
+ "node_modules/@imgly/background-removal": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@imgly/background-removal/-/background-removal-1.7.0.tgz",
+ "integrity": "sha512-/1ZryrMYg2ckIvJKoTu5Np50JfYMVffDMlVmppw/BdbN3pBTN7e6stI5/7E/LVh9DDzz6J588s7sWqul3fy5wA==",
+ "license": "SEE LICENSE IN LICENSE.md",
+ "dependencies": {
+ "lodash-es": "^4.17.21",
+ "ndarray": "~1.0.0",
+ "zod": "^3.23.8"
+ },
+ "peerDependencies": {
+ "onnxruntime-web": "1.21.0"
+ }
+ },
"node_modules/@internationalized/date": {
"version": "3.8.2",
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.8.2.tgz",
@@ -6446,6 +6463,70 @@
"url": "https://opencollective.com/popperjs"
}
},
+ "node_modules/@protobufjs/aspromise": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+ "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/base64": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+ "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/codegen": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+ "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/eventemitter": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+ "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/fetch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+ "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.1",
+ "@protobufjs/inquire": "^1.1.0"
+ }
+ },
+ "node_modules/@protobufjs/float": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/inquire": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+ "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/path": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+ "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/pool": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+ "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@protobufjs/utf8": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/@puppeteer/browsers": {
"version": "2.10.6",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz",
@@ -22374,6 +22455,12 @@
"node": ">=16"
}
},
+ "node_modules/flatbuffers": {
+ "version": "25.2.10",
+ "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-25.2.10.tgz",
+ "integrity": "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw==",
+ "license": "Apache-2.0"
+ },
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
@@ -23747,6 +23834,12 @@
"node": ">=14.0.0"
}
},
+ "node_modules/guid-typescript": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/guid-typescript/-/guid-typescript-1.0.9.tgz",
+ "integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ==",
+ "license": "ISC"
+ },
"node_modules/hachure-fill": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz",
@@ -25108,6 +25201,12 @@
"loose-envify": "^1.0.0"
}
},
+ "node_modules/iota-array": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/iota-array/-/iota-array-1.0.0.tgz",
+ "integrity": "sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==",
+ "license": "MIT"
+ },
"node_modules/ip-address": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
@@ -26731,6 +26830,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@@ -28865,6 +28970,16 @@
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"license": "MIT"
},
+ "node_modules/ndarray": {
+ "version": "1.0.19",
+ "resolved": "https://registry.npmjs.org/ndarray/-/ndarray-1.0.19.tgz",
+ "integrity": "sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==",
+ "license": "MIT",
+ "dependencies": {
+ "iota-array": "^1.0.0",
+ "is-buffer": "^1.0.2"
+ }
+ },
"node_modules/needle": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz",
@@ -31973,6 +32088,26 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/onnxruntime-common": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.21.0.tgz",
+ "integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ==",
+ "license": "MIT"
+ },
+ "node_modules/onnxruntime-web": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/onnxruntime-web/-/onnxruntime-web-1.21.0.tgz",
+ "integrity": "sha512-adzOe+7uI7lKz6pQNbAsLMQd2Fq5Jhmoxd8LZjJr8m3KvbFyiYyRxRiC57/XXD+jb18voppjeGAjoZmskXG+7A==",
+ "license": "MIT",
+ "dependencies": {
+ "flatbuffers": "^25.1.24",
+ "guid-typescript": "^1.0.9",
+ "long": "^5.2.3",
+ "onnxruntime-common": "1.21.0",
+ "platform": "^1.3.6",
+ "protobufjs": "^7.2.4"
+ }
+ },
"node_modules/open": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz",
@@ -32907,6 +33042,12 @@
"pathe": "^2.0.3"
}
},
+ "node_modules/platform": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+ "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
+ "license": "MIT"
+ },
"node_modules/please-upgrade-node": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
@@ -33404,6 +33545,30 @@
"prosemirror-transform": "^1.1.0"
}
},
+ "node_modules/protobufjs": {
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz",
+ "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==",
+ "hasInstallScript": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@protobufjs/aspromise": "^1.1.2",
+ "@protobufjs/base64": "^1.1.2",
+ "@protobufjs/codegen": "^2.0.4",
+ "@protobufjs/eventemitter": "^1.1.0",
+ "@protobufjs/fetch": "^1.1.0",
+ "@protobufjs/float": "^1.0.2",
+ "@protobufjs/inquire": "^1.1.0",
+ "@protobufjs/path": "^1.1.2",
+ "@protobufjs/pool": "^1.1.0",
+ "@protobufjs/utf8": "^1.1.0",
+ "@types/node": ">=13.7.0",
+ "long": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
@@ -40394,6 +40559,75 @@
"node": ">=4.0.0"
}
},
+ "node_modules/worker-loader": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
+ "integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
+ "license": "MIT",
+ "dependencies": {
+ "loader-utils": "^2.0.0",
+ "schema-utils": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/worker-loader/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/worker-loader/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/worker-loader/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "license": "MIT"
+ },
+ "node_modules/worker-loader/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
"node_modules/workerpool": {
"version": "9.3.3",
"resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.3.tgz",
diff --git a/package.json b/package.json
index f06e94171..7b88ab5b7 100644
--- a/package.json
+++ b/package.json
@@ -20,12 +20,13 @@
"scripts": {
"start-release": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env RELEASE=true USE_AZURE=false NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
"start-release-debug": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env RELEASE=true USE_AZURE=false NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --inspect -- src/server/index.ts",
- "start": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
+ "start": "npm run build-worker && copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
"debug": "copyfiles node_modules/pdfjs-dist/build/pdf.worker.* src/server/public/files && cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
"monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
"build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
"test": "mocha -r ts-node/register test/**/*.ts",
"tsc": "tsc -t es5",
+ "build-worker": "webpack --config webpack.worker.config.js",
"storybook": "npm -w @dash/components run storybook",
"build-storybook": "npm -w @dash/components run build-storybook",
"lint:css": "stylelint '**/*.{css,scss}'"
@@ -119,6 +120,7 @@
"@fullcalendar/multimonth": "^6.1.17",
"@fullcalendar/react": "^6.1.17",
"@fullcalendar/timegrid": "^6.1.15",
+ "@imgly/background-removal": "^1.7.0",
"@internationalized/date": "^3.5.0",
"@mozilla/readability": "^0.6.0",
"@mui/icons-material": "^6.0.1",
@@ -268,6 +270,7 @@
"nodemailer": "^6.9.7",
"nodemon": "^3.0.2",
"npm": "^11.3.0",
+ "onnxruntime-web": "^1.21.0-dev.20250206-d981b153d3",
"openai": "^4.75.0",
"p-limit": "^6.1.0",
"parse-multipart-data": "^1.5.0",
@@ -362,6 +365,7 @@
"webpack-hot-middleware": "^2.25.4",
"wikijs": "^6.4.1",
"words-to-numbers": "^1.5.1",
+ "worker-loader": "^3.0.8",
"xmlbuilder": "^15.1.1",
"xoauth2": "^1.2.0",
"xregexp": "^5.1.1"
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 617a09ed5..cc747eb32 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -8,7 +8,7 @@ import { extname } from 'path';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
-import { ClientUtils, imageUrlToBase64, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon, returnTrue } from '../../../ClientUtils';
+import { ClientUtils, DashColor, imageUrlToBase64, returnEmptyString, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
@@ -16,10 +16,11 @@ import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
import { ComputedField } from '../../../fields/ScriptField';
-import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast, ImageCastWithSuffix } from '../../../fields/Types';
+import { Cast, DocCast, ImageCast, ImageCastWithSuffix, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { emptyFunction } from '../../../Utils';
+import { gptImageLabel } from '../../apis/gpt/GPT';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
@@ -45,7 +46,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
-import { gptImageLabel } from '../../apis/gpt/GPT';
const DefaultPath = '/assets/unknown-file-icon-hi.png';
export class ImageEditorData {
@@ -389,7 +389,59 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
return cropping;
};
- docEditorView = action(() => {
+ static _worker?: Worker;
+ static removeImgBackground = (doc: Doc, addDoc: (doc: Doc | Doc[], annotationKey?: string) => boolean, docImgPath: string) => {
+ ImageEditorData.AddDoc = addDoc;
+ ImageEditorData.RootDoc = doc;
+ if (ImageBox._worker) return ImageBox._worker;
+ const worker = new Worker('/image.worker.js', { type: 'module' });
+ worker.onmessage = async (event: MessageEvent) => {
+ const { success, result, error } = event.data;
+ if (success) {
+ const blobToDataURL = (blob: any) => {
+ return new Promise<string | ArrayBuffer | null>((resolve, reject) => {
+ const reader = new FileReader();
+ reader.onload = () => resolve(reader.result);
+ reader.onerror = error => reject(error);
+ reader.readAsDataURL(blob);
+ });
+ };
+ blobToDataURL(result).then(durl => {
+ ClientUtils.convertDataUri(durl as string, doc[Id] + '_noBgd').then(url => {
+ const width = NumCast(doc._width) || 1;
+ const height = NumCast(doc._height);
+ const imageSnapshot = Docs.Create.ImageDocument(url, {
+ _nativeWidth: Doc.NativeWidth(doc),
+ _nativeHeight: Doc.NativeHeight(doc),
+ x: NumCast(doc.x) + width,
+ y: NumCast(doc.y),
+ _width: 150,
+ _height: (height / width) * 150,
+ title: 'bgremoved:' + doc.title,
+ });
+ Doc.SetNativeWidth(imageSnapshot[DocData], Doc.NativeWidth(doc));
+ Doc.SetNativeHeight(imageSnapshot[DocData], Doc.NativeHeight(doc));
+ addDoc?.(imageSnapshot);
+ });
+ });
+ } else {
+ console.error('Error in background removal:', error);
+ }
+ // worker.terminate();
+ };
+ worker.onerror = (e: ErrorEvent) => console.error('Worker failed:', e); // worker.terminate();
+
+ worker.postMessage({ imagePath: docImgPath });
+ return worker;
+ };
+ removeBackground = () => {
+ const field = ImageCast(this.dataDoc[this.fieldKey]);
+ if (field && this._props.addDocument) {
+ ImageBox.removeImgBackground(this.rootDoc, this._props.addDocument, this.choosePath(field.url));
+ }
+ };
+
+ docEditorView = () => {
const field = Cast(this.dataDoc[this.fieldKey], ImageField);
if (field) {
ImageEditorData.Open = true;
@@ -397,7 +449,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
ImageEditorData.AddDoc = this._props.addDocument;
ImageEditorData.RootDoc = this.Document;
}
- });
+ };
@observable _showOutpaintPrompt: boolean = false;
@observable _outpaintPromptInput: string = 'Extend this image naturally with matching content';
@@ -433,7 +485,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
@action
processOutpaintingWithPrompt = async (customPrompt: string) => {
- const field = Cast(this.dataDoc[this.fieldKey], ImageField);
+ const field = ImageCast(this.dataDoc[this.fieldKey]);
if (!field) return;
// Set flag that outpainting is in progress
@@ -618,7 +670,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
);
specificContextMenu = (): void => {
- const field = Cast(this.dataDoc[this.fieldKey], ImageField);
+ const field = ImageCast(this.dataDoc[this.fieldKey]);
if (field) {
const funcs: ContextMenuProps[] = [];
funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
@@ -629,13 +681,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
file: (file => {
const ext = file ? extname(file) : '';
return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
- })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href),
+ })(ImageCast(this.Document[this.fieldKey])?.url.href),
}).then(text => alert(text));
},
icon: 'expand-arrows-alt',
}); // prettier-ignore
funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' });
+ funcs.push({ description: 'Remove Background', event: this.removeBackground, icon: 'pencil-alt' });
this.layoutDoc.ai &&
funcs.push({
description: 'Regenerate AI Image',
@@ -800,7 +853,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
}
@computed get paths() {
- const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); // retrieve the primary image URL that is being rendered from the data doc
+ const field = ImageCast(this.dataDoc[this.fieldKey], new ImageField(StrCast(this.dataDoc[this.fieldKey]))); // retrieve the primary image URL that is being rendered from the data doc
const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
const defaultUrl = new URL(ClientUtils.prepend(DefaultPath));
const altpaths =
diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts
index 5deb66caf..7915938b7 100644
--- a/src/server/server_Initialization.ts
+++ b/src/server/server_Initialization.ts
@@ -1,3 +1,4 @@
+import axios from 'axios';
import * as bodyParser from 'body-parser';
import { blue, yellow } from 'colors';
import * as flash from 'connect-flash';
@@ -6,22 +7,22 @@ import * as express from 'express';
import * as expressFlash from 'express-flash';
import * as session from 'express-session';
import { createServer } from 'https';
+import { JSDOM } from 'jsdom';
import * as passport from 'passport';
import * as webpack from 'webpack';
import * as wdm from 'webpack-dev-middleware';
import * as whm from 'webpack-hot-middleware';
import * as config from '../../webpack.config';
+import * as workerConfig from '../../webpack.worker.config';
import { logPort } from './ActionUtilities';
import RouteManager from './RouteManager';
import RouteSubscriber from './RouteSubscriber';
import { publicDirectory, resolvedPorts } from './SocketData';
+import { setupDynamicToolsAPI } from './api/dynamicTools';
import { SSL } from './apis/google/CredentialsLoader';
import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager';
import { Database } from './database';
import { WebSocket } from './websocket';
-import axios from 'axios';
-import { JSDOM } from 'jsdom';
-import { setupDynamicToolsAPI } from './api/dynamicTools';
/* RouteSetter is a wrapper around the server that prevents the server
from being exposed. */
@@ -126,13 +127,12 @@ function registerCorsProxy(server: express.Express) {
//res.status(400).json({ error: 'Invalid URL format' });
return;
}
-
const isBinary = /\.(gif|png|jpe?g|bmp|webp|ico|pdf|zip|mp3|mp4|wav|ogg)$/i.test(targetUrl as string);
const responseType = isBinary ? 'arraybuffer' : 'text';
const response = await axios.get(targetUrl as string, {
headers: { 'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0' },
- responseType: responseType
+ responseType: responseType,
});
const baseUrl = new URL(targetUrl as string);
@@ -200,8 +200,13 @@ function registerAuthenticationRoutes(server: express.Express) {
export default async function InitializeServer(routeSetter: RouteSetter) {
const isRelease = determineEnvironment();
const app = buildWithMiddleware(express());
+ const workerCompiler = webpack(workerConfig as webpack.Configuration);
const compiler = webpack(config as webpack.Configuration);
+ if (!compiler) throw new Error('Webpack compiler is not defined. Please check your webpack configuration.');
+ if (!workerCompiler) throw new Error('Webpack worker compiler is not defined. Please check your webpack worker configuration.');
+ // print out contents of virtual output filesystem used in development
+ // compiler.outputFileSystem?.readdir?.(config.output.path, (err, files) => (err ? console.error('Error reading virtual output path:', err) : console.log('Files in virtual output path:', files)));
// Default route
app.get('/', (req, res) => {
res.redirect(req.user ? '/home' : '/login'); //res.send('This is the default route.');
@@ -209,6 +214,8 @@ export default async function InitializeServer(routeSetter: RouteSetter) {
// route table managed by express. routes are tested sequentially against each of these map rules. when a match is found, the handler is called to process the request
app.use(wdm(compiler, { publicPath: config.output.publicPath }));
app.use(whm(compiler));
+ app.use(wdm(workerCompiler, { publicPath: workerConfig.output.publicPath }));
+ app.use(whm(workerCompiler));
app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between
app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc)
// app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) }));
diff --git a/src/workers/image.worker.ts b/src/workers/image.worker.ts
new file mode 100644
index 000000000..d069742f3
--- /dev/null
+++ b/src/workers/image.worker.ts
@@ -0,0 +1,16 @@
+import { removeBackground } from '@imgly/background-removal';
+
+self.onmessage = async (event: MessageEvent) => {
+ const { imagePath, doc, addDoc } = event.data;
+
+ try {
+ // Perform the background removal
+ const result = await removeBackground(imagePath);
+
+ // Send the result back to the main thread
+ self.postMessage({ success: true, result, doc, addDoc });
+ } catch (error) {
+ // Send the error back to the main thread
+ self.postMessage({ success: false, error: (error as any).message });
+ }
+};
diff --git a/tsconfig.worker.json b/tsconfig.worker.json
new file mode 100644
index 000000000..f58377c2e
--- /dev/null
+++ b/tsconfig.worker.json
@@ -0,0 +1,18 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist/worker",
+ "noEmit": false,
+ "jsx": "react-jsx", // or "react" if you're not using the new JSX transform
+ "module": "ESNext",
+ "target": "ESNext",
+ "lib": ["DOM", "ESNext"],
+ "sourceMap": true,
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true
+ },
+ "include": ["src/workers/**/*", "src/utils/**/*"],
+ "exclude": ["node_modules", "src/components/**/*.stories.tsx", "src/components/**/*.test.tsx", "src/server", "static"]
+}
diff --git a/webpack.worker.config.js b/webpack.worker.config.js
new file mode 100644
index 000000000..f2a2da199
--- /dev/null
+++ b/webpack.worker.config.js
@@ -0,0 +1,30 @@
+// webpack.worker.config.js
+// eslint-disable-next-line @typescript-eslint/no-require-imports
+const path = require('path');
+
+module.exports = {
+ mode: 'development',
+ entry: './src/workers/image.worker.ts', // 👈 Adjust path to your worker file
+ output: {
+ filename: 'image.worker.js',
+ publicPath: '/',
+ path: path.resolve(__dirname, 'build'),
+ },
+ experiments: {
+ outputModule: true,
+ },
+ resolve: {
+ extensions: ['.ts', '.js'],
+ },
+ module: {
+ rules: [
+ {
+ test: /\.ts$/,
+ loader: 'ts-loader',
+ options: {
+ configFile: 'tsconfig.worker.json',
+ },
+ },
+ ],
+ },
+};