diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..1fe0a72
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,20 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:prettier/recommended"
+ ],
+ "plugins": ["prettier"],
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "rules": {
+ "prettier/prettier": "error",
+ "no-unused-vars": "warn",
+ "no-console": "off"
+ }
+}
diff --git a/.gitignore b/.gitignore
index 06362ca..3d2fec6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
dev/train/trainData/mnist/mnistTraindata_large.js
dev/train/trainData/mnist/mnist_train.csv
+node_modules
+dist
+build
+*.min.js
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..904225f
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
new file mode 100644
index 0000000..ea0f3cf
--- /dev/null
+++ b/.idea/prettier.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..bae74ff
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "printWidth": 100,
+ "tabWidth": 4,
+ "trailingComma": "all",
+ "singleQuote": true,
+ "semi": true
+}
\ No newline at end of file
diff --git a/dev/train/Trainer.js b/dev/train/Trainer.js
index 3594d8f..0434c50 100644
--- a/dev/train/Trainer.js
+++ b/dev/train/Trainer.js
@@ -1,27 +1,44 @@
-import { NeuralNetworkBase } from "../../src/core/NeuralNetworkBase.js";
+import { NeuralNetworkBase } from '../../src/core/NeuralNetworkBase.ts';
// matrix operations
-import {matrixMultiply, transposeMatrix} from "../../src/core/ops/matrixOps.js";
+import { matrixMultiply, transposeMatrix } from '../../src/core/ops/matrixOps.ts';
export class Trainer extends NeuralNetworkBase {
- train(inputs, targets){
+ train(inputs, targets) {
// CNN operations
const { finalOutputs, hiddenOutputs } = this.feedForward(inputs);
// calculate errors
- const output_errors = targets.map((v, idx) => v - finalOutputs[idx]).map(v => [v]); // 출력 계층의 오차를 목표값 - 출력값으로 지정
+ const output_errors = targets.map((v, idx) => v - finalOutputs[idx]).map((v) => [v]); // 출력 계층의 오차를 목표값 - 출력값으로 지정
const hidden_errors = matrixMultiply(transposeMatrix(this.W_hiddenToOutput), output_errors); // 은닉 계층의 오차를 은닉-> 출력 계층의 가중치값과(W_hiddenToOutput.T) 출력 계층의 오차들을 재조합하여 계산
// activation function derivative
- const activationDerivative_HtO = finalOutputs.map(v => 1.0 - v).map((v, idx) => v * finalOutputs[idx]); // hidden to output derivative
- const outputGradient = activationDerivative_HtO.map((v, idx) => v * output_errors[idx]).map(v => [v]);
- const W_HtO_Update = matrixMultiply(outputGradient, transposeMatrix(hiddenOutputs)).map(array => array.map(v => v * this.learningRate)); // 오차값을 이용해 은닉 계층과 출력 계층간의 가중치 업데이트
+ const activationDerivative_HtO = finalOutputs
+ .map((v) => 1.0 - v)
+ .map((v, idx) => v * finalOutputs[idx]); // hidden to output derivative
+ const outputGradient = activationDerivative_HtO
+ .map((v, idx) => v * output_errors[idx])
+ .map((v) => [v]);
+ const W_HtO_Update = matrixMultiply(outputGradient, transposeMatrix(hiddenOutputs)).map(
+ (array) => array.map((v) => v * this.learningRate),
+ ); // 오차값을 이용해 은닉 계층과 출력 계층간의 가중치 업데이트
- const activationDerivative_ItH = hiddenOutputs.map(v => 1.0 - v).map((v, idx) => v * hiddenOutputs[idx]);
- const hiddenGradient = activationDerivative_ItH.map((v, idx) => v * hidden_errors[idx]).map(v => [v]);
- const W_ItH_update = matrixMultiply(hiddenGradient, transposeMatrix(inputs.map(v => [v]))).map(array => array.map(v => v * this.learningRate));
+ const activationDerivative_ItH = hiddenOutputs
+ .map((v) => 1.0 - v)
+ .map((v, idx) => v * hiddenOutputs[idx]);
+ const hiddenGradient = activationDerivative_ItH
+ .map((v, idx) => v * hidden_errors[idx])
+ .map((v) => [v]);
+ const W_ItH_update = matrixMultiply(
+ hiddenGradient,
+ transposeMatrix(inputs.map((v) => [v])),
+ ).map((array) => array.map((v) => v * this.learningRate));
// update weights
- this.W_hiddenToOutput = this.W_hiddenToOutput.map((row, i)=> row.map((v, j) => v + W_HtO_Update[i][j]));
- this.W_inputToHidden = this.W_inputToHidden.map((row, i)=> row.map((v, j) => v + W_ItH_update[i][j]));
+ this.W_hiddenToOutput = this.W_hiddenToOutput.map((row, i) =>
+ row.map((v, j) => v + W_HtO_Update[i][j]),
+ );
+ this.W_inputToHidden = this.W_inputToHidden.map((row, i) =>
+ row.map((v, j) => v + W_ItH_update[i][j]),
+ );
}
-}
\ No newline at end of file
+}
diff --git a/public/index.html b/index.html
similarity index 67%
rename from public/index.html
rename to index.html
index 7df7fa3..3e0130a 100644
--- a/public/index.html
+++ b/index.html
@@ -6,9 +6,8 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
Neural Network Visualization
-
-
-
+
+
@@ -21,9 +20,7 @@
diff --git a/jsconfig.json b/jsconfig.json
index 8ecfcad..1286948 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -1,9 +1,8 @@
{
"compilerOptions": {
- "baseUrl": "src",
+ "baseUrl": "./src",
"paths": {
- "@controller/*": ["controller/*"],
- "@view/*": ["view/*"]
+ "@/*": ["*"]
}
},
"include": ["src"]
diff --git a/package.json b/package.json
index 15fecff..eba51c4 100644
--- a/package.json
+++ b/package.json
@@ -3,9 +3,22 @@
"version": "1.0.0",
"type": "module",
"description": "",
- "main": "public/index.html",
+ "main": "index.html",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint .",
+ "lint:fix": "eslint . --fix",
+ "format": "prettier --write ."
},
- "private": true
-}
+ "private": true,
+ "devDependencies": {
+ "eslint": "^9.32.0",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-prettier": "^5.5.3",
+ "prettier": "3.6.2",
+ "vite": "^7.0.6"
+ }
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..9c498a0
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,1328 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ eslint:
+ specifier: ^9.32.0
+ version: 9.32.0
+ eslint-config-prettier:
+ specifier: ^10.1.8
+ version: 10.1.8(eslint@9.32.0)
+ eslint-plugin-prettier:
+ specifier: ^5.5.3
+ version: 5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2)
+ prettier:
+ specifier: 3.6.2
+ version: 3.6.2
+ vite:
+ specifier: ^7.0.6
+ version: 7.0.6
+
+packages:
+
+ '@esbuild/aix-ppc64@0.25.8':
+ resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.25.8':
+ resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.8':
+ resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.8':
+ resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.25.8':
+ resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.8':
+ resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.25.8':
+ resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.8':
+ resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.25.8':
+ resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.8':
+ resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.8':
+ resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.8':
+ resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.8':
+ resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.8':
+ resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.8':
+ resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.8':
+ resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.8':
+ resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.25.8':
+ resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.8':
+ resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.25.8':
+ resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.8':
+ resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.25.8':
+ resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.25.8':
+ resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.25.8':
+ resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.8':
+ resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.8':
+ resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@eslint-community/eslint-utils@4.7.0':
+ resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+ '@eslint-community/regexpp@4.12.1':
+ resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+ '@eslint/config-array@0.21.0':
+ resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/config-helpers@0.3.0':
+ resolution: {integrity: sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/core@0.15.1':
+ resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/eslintrc@3.3.1':
+ resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/js@9.32.0':
+ resolution: {integrity: sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/object-schema@2.1.6':
+ resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@eslint/plugin-kit@0.3.4':
+ resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ '@humanfs/core@0.19.1':
+ resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanfs/node@0.16.6':
+ resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
+ engines: {node: '>=18.18.0'}
+
+ '@humanwhocodes/module-importer@1.0.1':
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+
+ '@humanwhocodes/retry@0.3.1':
+ resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
+ engines: {node: '>=18.18'}
+
+ '@humanwhocodes/retry@0.4.3':
+ resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
+ engines: {node: '>=18.18'}
+
+ '@pkgr/core@0.2.9':
+ resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
+ '@rollup/rollup-android-arm-eabi@4.46.2':
+ resolution: {integrity: sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==}
+ cpu: [arm]
+ os: [android]
+
+ '@rollup/rollup-android-arm64@4.46.2':
+ resolution: {integrity: sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==}
+ cpu: [arm64]
+ os: [android]
+
+ '@rollup/rollup-darwin-arm64@4.46.2':
+ resolution: {integrity: sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@rollup/rollup-darwin-x64@4.46.2':
+ resolution: {integrity: sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@rollup/rollup-freebsd-arm64@4.46.2':
+ resolution: {integrity: sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@rollup/rollup-freebsd-x64@4.46.2':
+ resolution: {integrity: sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.46.2':
+ resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm-musleabihf@4.46.2':
+ resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
+ cpu: [arm]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-gnu@4.46.2':
+ resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-arm64-musl@4.46.2':
+ resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.46.2':
+ resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
+ cpu: [loong64]
+ os: [linux]
+
+ '@rollup/rollup-linux-ppc64-gnu@4.46.2':
+ resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-gnu@4.46.2':
+ resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-riscv64-musl@4.46.2':
+ resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@rollup/rollup-linux-s390x-gnu@4.46.2':
+ resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
+ cpu: [s390x]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-gnu@4.46.2':
+ resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-linux-x64-musl@4.46.2':
+ resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
+ cpu: [x64]
+ os: [linux]
+
+ '@rollup/rollup-win32-arm64-msvc@4.46.2':
+ resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
+ cpu: [arm64]
+ os: [win32]
+
+ '@rollup/rollup-win32-ia32-msvc@4.46.2':
+ resolution: {integrity: sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==}
+ cpu: [ia32]
+ os: [win32]
+
+ '@rollup/rollup-win32-x64-msvc@4.46.2':
+ resolution: {integrity: sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==}
+ cpu: [x64]
+ os: [win32]
+
+ '@types/estree@1.0.8':
+ resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
+
+ '@types/json-schema@7.0.15':
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+ acorn-jsx@5.3.2:
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+ acorn@8.15.0:
+ resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+
+ ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+ ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+ brace-expansion@1.1.12:
+ resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+ callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+
+ chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+
+ color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+
+ color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+ concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+ cross-spawn@7.0.6:
+ resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+ engines: {node: '>= 8'}
+
+ debug@4.4.1:
+ resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+ esbuild@0.25.8:
+ resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+
+ eslint-config-prettier@10.1.8:
+ resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
+ hasBin: true
+ peerDependencies:
+ eslint: '>=7.0.0'
+
+ eslint-plugin-prettier@5.5.3:
+ resolution: {integrity: sha512-NAdMYww51ehKfDyDhv59/eIItUVzU0Io9H2E8nHNGKEeeqlnci+1gCvrHib6EmZdf6GxF+LCV5K7UC65Ezvw7w==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ '@types/eslint': '>=8.0.0'
+ eslint: '>=8.0.0'
+ eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
+ prettier: '>=3.0.0'
+ peerDependenciesMeta:
+ '@types/eslint':
+ optional: true
+ eslint-config-prettier:
+ optional: true
+
+ eslint-scope@8.4.0:
+ resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+ eslint-visitor-keys@4.2.1:
+ resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ eslint@9.32.0:
+ resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ hasBin: true
+ peerDependencies:
+ jiti: '*'
+ peerDependenciesMeta:
+ jiti:
+ optional: true
+
+ espree@10.4.0:
+ resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
+ engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+
+ esquery@1.6.0:
+ resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
+ engines: {node: '>=0.10'}
+
+ esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+
+ estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+
+ esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-diff@1.3.0:
+ resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
+
+ fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+ fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+ fdir@6.4.6:
+ resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ file-entry-cache@8.0.0:
+ resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
+ engines: {node: '>=16.0.0'}
+
+ find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+
+ flat-cache@4.0.1:
+ resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
+ engines: {node: '>=16'}
+
+ flatted@3.3.3:
+ resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+
+ globals@14.0.0:
+ resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
+ engines: {node: '>=18'}
+
+ has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+
+ ignore@5.3.2:
+ resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+ engines: {node: '>= 4'}
+
+ import-fresh@3.3.1:
+ resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+ engines: {node: '>=6'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+
+ is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+
+ isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+ js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+
+ json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+ json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+ json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+ keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+ levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+
+ locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+
+ lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+ minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nanoid@3.3.11:
+ resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+
+ natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+ optionator@0.9.4:
+ resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+ engines: {node: '>= 0.8.0'}
+
+ p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+
+ p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+
+ parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+
+ path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+
+ path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.3:
+ resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ engines: {node: '>=12'}
+
+ postcss@8.5.6:
+ resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
+ engines: {node: ^10 || ^12 || >=14}
+
+ prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+
+ prettier-linter-helpers@1.0.0:
+ resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
+ engines: {node: '>=6.0.0'}
+
+ prettier@3.6.2:
+ resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
+ engines: {node: '>=14'}
+ hasBin: true
+
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
+ resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+
+ rollup@4.46.2:
+ resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==}
+ engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+ hasBin: true
+
+ shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+
+ shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+
+ supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+
+ synckit@0.11.11:
+ resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
+ tinyglobby@0.2.14:
+ resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
+ engines: {node: '>=12.0.0'}
+
+ type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+
+ uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+ vite@7.0.6:
+ resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': ^20.19.0 || >=22.12.0
+ jiti: '>=1.21.0'
+ less: ^4.0.0
+ lightningcss: ^1.21.0
+ sass: ^1.70.0
+ sass-embedded: ^1.70.0
+ stylus: '>=0.54.8'
+ sugarss: ^5.0.0
+ terser: ^5.16.0
+ tsx: ^4.8.1
+ yaml: ^2.4.2
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ jiti:
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ sass-embedded:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ tsx:
+ optional: true
+ yaml:
+ optional: true
+
+ which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
+ word-wrap@1.2.5:
+ resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+ engines: {node: '>=0.10.0'}
+
+ yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+
+snapshots:
+
+ '@esbuild/aix-ppc64@0.25.8':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/android-arm@0.25.8':
+ optional: true
+
+ '@esbuild/android-x64@0.25.8':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.8':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.8':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.8':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.8':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.8':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.8':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.8':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.8':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.8':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.8':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.8':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.8':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.8':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.8':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.8':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.8':
+ optional: true
+
+ '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0)':
+ dependencies:
+ eslint: 9.32.0
+ eslint-visitor-keys: 3.4.3
+
+ '@eslint-community/regexpp@4.12.1': {}
+
+ '@eslint/config-array@0.21.0':
+ dependencies:
+ '@eslint/object-schema': 2.1.6
+ debug: 4.4.1
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/config-helpers@0.3.0': {}
+
+ '@eslint/core@0.15.1':
+ dependencies:
+ '@types/json-schema': 7.0.15
+
+ '@eslint/eslintrc@3.3.1':
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.4.1
+ espree: 10.4.0
+ globals: 14.0.0
+ ignore: 5.3.2
+ import-fresh: 3.3.1
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@eslint/js@9.32.0': {}
+
+ '@eslint/object-schema@2.1.6': {}
+
+ '@eslint/plugin-kit@0.3.4':
+ dependencies:
+ '@eslint/core': 0.15.1
+ levn: 0.4.1
+
+ '@humanfs/core@0.19.1': {}
+
+ '@humanfs/node@0.16.6':
+ dependencies:
+ '@humanfs/core': 0.19.1
+ '@humanwhocodes/retry': 0.3.1
+
+ '@humanwhocodes/module-importer@1.0.1': {}
+
+ '@humanwhocodes/retry@0.3.1': {}
+
+ '@humanwhocodes/retry@0.4.3': {}
+
+ '@pkgr/core@0.2.9': {}
+
+ '@rollup/rollup-android-arm-eabi@4.46.2':
+ optional: true
+
+ '@rollup/rollup-android-arm64@4.46.2':
+ optional: true
+
+ '@rollup/rollup-darwin-arm64@4.46.2':
+ optional: true
+
+ '@rollup/rollup-darwin-x64@4.46.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-arm64@4.46.2':
+ optional: true
+
+ '@rollup/rollup-freebsd-x64@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-gnueabihf@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm-musleabihf@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-gnu@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-arm64-musl@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-loongarch64-gnu@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-ppc64-gnu@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-gnu@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-riscv64-musl@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-s390x-gnu@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-gnu@4.46.2':
+ optional: true
+
+ '@rollup/rollup-linux-x64-musl@4.46.2':
+ optional: true
+
+ '@rollup/rollup-win32-arm64-msvc@4.46.2':
+ optional: true
+
+ '@rollup/rollup-win32-ia32-msvc@4.46.2':
+ optional: true
+
+ '@rollup/rollup-win32-x64-msvc@4.46.2':
+ optional: true
+
+ '@types/estree@1.0.8': {}
+
+ '@types/json-schema@7.0.15': {}
+
+ acorn-jsx@5.3.2(acorn@8.15.0):
+ dependencies:
+ acorn: 8.15.0
+
+ acorn@8.15.0: {}
+
+ ajv@6.12.6:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+
+ ansi-styles@4.3.0:
+ dependencies:
+ color-convert: 2.0.1
+
+ argparse@2.0.1: {}
+
+ balanced-match@1.0.2: {}
+
+ brace-expansion@1.1.12:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ callsites@3.1.0: {}
+
+ chalk@4.1.2:
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+
+ color-convert@2.0.1:
+ dependencies:
+ color-name: 1.1.4
+
+ color-name@1.1.4: {}
+
+ concat-map@0.0.1: {}
+
+ cross-spawn@7.0.6:
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+
+ debug@4.4.1:
+ dependencies:
+ ms: 2.1.3
+
+ deep-is@0.1.4: {}
+
+ esbuild@0.25.8:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.8
+ '@esbuild/android-arm': 0.25.8
+ '@esbuild/android-arm64': 0.25.8
+ '@esbuild/android-x64': 0.25.8
+ '@esbuild/darwin-arm64': 0.25.8
+ '@esbuild/darwin-x64': 0.25.8
+ '@esbuild/freebsd-arm64': 0.25.8
+ '@esbuild/freebsd-x64': 0.25.8
+ '@esbuild/linux-arm': 0.25.8
+ '@esbuild/linux-arm64': 0.25.8
+ '@esbuild/linux-ia32': 0.25.8
+ '@esbuild/linux-loong64': 0.25.8
+ '@esbuild/linux-mips64el': 0.25.8
+ '@esbuild/linux-ppc64': 0.25.8
+ '@esbuild/linux-riscv64': 0.25.8
+ '@esbuild/linux-s390x': 0.25.8
+ '@esbuild/linux-x64': 0.25.8
+ '@esbuild/netbsd-arm64': 0.25.8
+ '@esbuild/netbsd-x64': 0.25.8
+ '@esbuild/openbsd-arm64': 0.25.8
+ '@esbuild/openbsd-x64': 0.25.8
+ '@esbuild/openharmony-arm64': 0.25.8
+ '@esbuild/sunos-x64': 0.25.8
+ '@esbuild/win32-arm64': 0.25.8
+ '@esbuild/win32-ia32': 0.25.8
+ '@esbuild/win32-x64': 0.25.8
+
+ escape-string-regexp@4.0.0: {}
+
+ eslint-config-prettier@10.1.8(eslint@9.32.0):
+ dependencies:
+ eslint: 9.32.0
+
+ eslint-plugin-prettier@5.5.3(eslint-config-prettier@10.1.8(eslint@9.32.0))(eslint@9.32.0)(prettier@3.6.2):
+ dependencies:
+ eslint: 9.32.0
+ prettier: 3.6.2
+ prettier-linter-helpers: 1.0.0
+ synckit: 0.11.11
+ optionalDependencies:
+ eslint-config-prettier: 10.1.8(eslint@9.32.0)
+
+ eslint-scope@8.4.0:
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+
+ eslint-visitor-keys@3.4.3: {}
+
+ eslint-visitor-keys@4.2.1: {}
+
+ eslint@9.32.0:
+ dependencies:
+ '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0)
+ '@eslint-community/regexpp': 4.12.1
+ '@eslint/config-array': 0.21.0
+ '@eslint/config-helpers': 0.3.0
+ '@eslint/core': 0.15.1
+ '@eslint/eslintrc': 3.3.1
+ '@eslint/js': 9.32.0
+ '@eslint/plugin-kit': 0.3.4
+ '@humanfs/node': 0.16.6
+ '@humanwhocodes/module-importer': 1.0.1
+ '@humanwhocodes/retry': 0.4.3
+ '@types/estree': 1.0.8
+ '@types/json-schema': 7.0.15
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.1
+ escape-string-regexp: 4.0.0
+ eslint-scope: 8.4.0
+ eslint-visitor-keys: 4.2.1
+ espree: 10.4.0
+ esquery: 1.6.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 8.0.0
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ ignore: 5.3.2
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ json-stable-stringify-without-jsonify: 1.0.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.4
+ transitivePeerDependencies:
+ - supports-color
+
+ espree@10.4.0:
+ dependencies:
+ acorn: 8.15.0
+ acorn-jsx: 5.3.2(acorn@8.15.0)
+ eslint-visitor-keys: 4.2.1
+
+ esquery@1.6.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ esrecurse@4.3.0:
+ dependencies:
+ estraverse: 5.3.0
+
+ estraverse@5.3.0: {}
+
+ esutils@2.0.3: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-diff@1.3.0: {}
+
+ fast-json-stable-stringify@2.1.0: {}
+
+ fast-levenshtein@2.0.6: {}
+
+ fdir@6.4.6(picomatch@4.0.3):
+ optionalDependencies:
+ picomatch: 4.0.3
+
+ file-entry-cache@8.0.0:
+ dependencies:
+ flat-cache: 4.0.1
+
+ find-up@5.0.0:
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+
+ flat-cache@4.0.1:
+ dependencies:
+ flatted: 3.3.3
+ keyv: 4.5.4
+
+ flatted@3.3.3: {}
+
+ fsevents@2.3.3:
+ optional: true
+
+ glob-parent@6.0.2:
+ dependencies:
+ is-glob: 4.0.3
+
+ globals@14.0.0: {}
+
+ has-flag@4.0.0: {}
+
+ ignore@5.3.2: {}
+
+ import-fresh@3.3.1:
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+
+ imurmurhash@0.1.4: {}
+
+ is-extglob@2.1.1: {}
+
+ is-glob@4.0.3:
+ dependencies:
+ is-extglob: 2.1.1
+
+ isexe@2.0.0: {}
+
+ js-yaml@4.1.0:
+ dependencies:
+ argparse: 2.0.1
+
+ json-buffer@3.0.1: {}
+
+ json-schema-traverse@0.4.1: {}
+
+ json-stable-stringify-without-jsonify@1.0.1: {}
+
+ keyv@4.5.4:
+ dependencies:
+ json-buffer: 3.0.1
+
+ levn@0.4.1:
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+
+ locate-path@6.0.0:
+ dependencies:
+ p-locate: 5.0.0
+
+ lodash.merge@4.6.2: {}
+
+ minimatch@3.1.2:
+ dependencies:
+ brace-expansion: 1.1.12
+
+ ms@2.1.3: {}
+
+ nanoid@3.3.11: {}
+
+ natural-compare@1.4.0: {}
+
+ optionator@0.9.4:
+ dependencies:
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ word-wrap: 1.2.5
+
+ p-limit@3.1.0:
+ dependencies:
+ yocto-queue: 0.1.0
+
+ p-locate@5.0.0:
+ dependencies:
+ p-limit: 3.1.0
+
+ parent-module@1.0.1:
+ dependencies:
+ callsites: 3.1.0
+
+ path-exists@4.0.0: {}
+
+ path-key@3.1.1: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.3: {}
+
+ postcss@8.5.6:
+ dependencies:
+ nanoid: 3.3.11
+ picocolors: 1.1.1
+ source-map-js: 1.2.1
+
+ prelude-ls@1.2.1: {}
+
+ prettier-linter-helpers@1.0.0:
+ dependencies:
+ fast-diff: 1.3.0
+
+ prettier@3.6.2: {}
+
+ punycode@2.3.1: {}
+
+ resolve-from@4.0.0: {}
+
+ rollup@4.46.2:
+ dependencies:
+ '@types/estree': 1.0.8
+ optionalDependencies:
+ '@rollup/rollup-android-arm-eabi': 4.46.2
+ '@rollup/rollup-android-arm64': 4.46.2
+ '@rollup/rollup-darwin-arm64': 4.46.2
+ '@rollup/rollup-darwin-x64': 4.46.2
+ '@rollup/rollup-freebsd-arm64': 4.46.2
+ '@rollup/rollup-freebsd-x64': 4.46.2
+ '@rollup/rollup-linux-arm-gnueabihf': 4.46.2
+ '@rollup/rollup-linux-arm-musleabihf': 4.46.2
+ '@rollup/rollup-linux-arm64-gnu': 4.46.2
+ '@rollup/rollup-linux-arm64-musl': 4.46.2
+ '@rollup/rollup-linux-loongarch64-gnu': 4.46.2
+ '@rollup/rollup-linux-ppc64-gnu': 4.46.2
+ '@rollup/rollup-linux-riscv64-gnu': 4.46.2
+ '@rollup/rollup-linux-riscv64-musl': 4.46.2
+ '@rollup/rollup-linux-s390x-gnu': 4.46.2
+ '@rollup/rollup-linux-x64-gnu': 4.46.2
+ '@rollup/rollup-linux-x64-musl': 4.46.2
+ '@rollup/rollup-win32-arm64-msvc': 4.46.2
+ '@rollup/rollup-win32-ia32-msvc': 4.46.2
+ '@rollup/rollup-win32-x64-msvc': 4.46.2
+ fsevents: 2.3.3
+
+ shebang-command@2.0.0:
+ dependencies:
+ shebang-regex: 3.0.0
+
+ shebang-regex@3.0.0: {}
+
+ source-map-js@1.2.1: {}
+
+ strip-json-comments@3.1.1: {}
+
+ supports-color@7.2.0:
+ dependencies:
+ has-flag: 4.0.0
+
+ synckit@0.11.11:
+ dependencies:
+ '@pkgr/core': 0.2.9
+
+ tinyglobby@0.2.14:
+ dependencies:
+ fdir: 6.4.6(picomatch@4.0.3)
+ picomatch: 4.0.3
+
+ type-check@0.4.0:
+ dependencies:
+ prelude-ls: 1.2.1
+
+ uri-js@4.4.1:
+ dependencies:
+ punycode: 2.3.1
+
+ vite@7.0.6:
+ dependencies:
+ esbuild: 0.25.8
+ fdir: 6.4.6(picomatch@4.0.3)
+ picomatch: 4.0.3
+ postcss: 8.5.6
+ rollup: 4.46.2
+ tinyglobby: 0.2.14
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ which@2.0.2:
+ dependencies:
+ isexe: 2.0.0
+
+ word-wrap@1.2.5: {}
+
+ yocto-queue@0.1.0: {}
diff --git a/src/controller/AppContoller.js b/src/controller/AppContoller.js
deleted file mode 100644
index 22fd9c7..0000000
--- a/src/controller/AppContoller.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import WeightManager from "../core/WeightManager.js";
-import NeuralNetworkBase from "../core/NeuralNetworkBase.js";
-import QueryProcessController from "./queryPipeline/QueryProcessController.js";
-import UserInputCanvas from "../view/components/userQuery/UserInputCanvas.js";
-import PathTrackingCanvas from "../view/components/userQuery/PathTrackingCanvas.js";
-import AlignCanvas from "../view/components/userQuery/AlignCanvas.js";
-import ResizeCanvas from "../view/components/userQuery/ResizeCanvas.js";
-import { DataStore } from "./DataStore.js";
-
-import eventBus from "./EventBus.js";
-import { DATA_EVENTS, HANDLER_EVENTS } from "./constants/events.js";
-import { NETWORK_CONFIG } from "./constants/networkConfig.js";
-
-class AppController {
- constructor({ dataStore }) {
- this.dataStore = dataStore;
- }
-
- async initialize() {
- const $WM = new WeightManager(NETWORK_CONFIG);
- const $NN = new NeuralNetworkBase(await $WM.getWeights());
- const $DS = new DataStore();
- const $QC = new QueryProcessController({
- userInputCanvas: new UserInputCanvas(),
- trackingCanvas: new PathTrackingCanvas(),
- alignCanvas: new AlignCanvas(),
- resizeCanvas: new ResizeCanvas(),
- $NN: $NN,
- });
- eventBus.emit(HANDLER_EVENTS.APP_READY, this.dataStore);
- eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data) => {console.log("RESULT CHANGED", data)});
- };
-}
-
-export default AppController;
\ No newline at end of file
diff --git a/src/controller/AppController.ts b/src/controller/AppController.ts
new file mode 100644
index 0000000..1d24875
--- /dev/null
+++ b/src/controller/AppController.ts
@@ -0,0 +1,43 @@
+import WeightManager from '@/core/WeightManager.ts';
+import NeuralNetworkBase from '@/core/NeuralNetworkBase.ts';
+import QueryProcessController from './queryPipeline/QueryProcessController.ts';
+import UserInputCanvas from '@/view/components/userQuery/UserInputCanvas.ts';
+import PathTrackingCanvas from '@/view/components/userQuery/PathTrackingCanvas.ts';
+import AlignCanvas from '@/view/components/userQuery/AlignCanvas.ts';
+import ResizeCanvas from '@/view/components/userQuery/ResizeCanvas.ts';
+import DataStore from './DataStore.ts';
+
+import eventBus from './EventBus.ts';
+import { DATA_EVENTS, HANDLER_EVENTS } from './constants/events.ts';
+import { NETWORK_CONFIG } from './constants/networkConfig.ts';
+import DrawingEventHandler from './queryPipeline/DrawingEventHandler.js';
+import { IWeightManager } from '@/core/types/WeightManager.ts';
+import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase.ts';
+import { IDataStore } from './types/DataStore.ts';
+import { Matrix2D } from '@/core/ops/types/OpsType.ts';
+
+class AppController {
+ private $WM: IWeightManager;
+ private $NN: INeuralNetworkBase;
+ private $DS: IDataStore;
+
+ async initialize() {
+ const $WM = new WeightManager(NETWORK_CONFIG);
+ const $NN = new NeuralNetworkBase(await $WM.getWeights());
+ const $DS = new DataStore();
+ new DrawingEventHandler();
+ const $QC = new QueryProcessController({
+ userInputCanvas: new UserInputCanvas(),
+ trackingCanvas: new PathTrackingCanvas(),
+ alignCanvas: new AlignCanvas(),
+ resizeCanvas: new ResizeCanvas(),
+ $NN: $NN,
+ $DS: $DS,
+ });
+ eventBus.on(DATA_EVENTS.RESULT_CHANGED, (data: Matrix2D): void => {
+ console.log('RESULT CHANGED', data);
+ });
+ }
+}
+
+export default AppController;
diff --git a/src/controller/DataStore.ts b/src/controller/DataStore.ts
new file mode 100644
index 0000000..556ab6c
--- /dev/null
+++ b/src/controller/DataStore.ts
@@ -0,0 +1,51 @@
+import eventBus from './EventBus.js';
+import { DATA_EVENTS } from './constants/events.js';
+
+import { nodeState } from './types/DataStore';
+import { Matrix2D } from '@/core/ops/types/OpsType.ts';
+
+class DataStore {
+ private queryInfo: number[] | null = null;
+ private nodeState: nodeState | null = null;
+ private queryResult: Matrix2D | null = null;
+
+ constructor() {
+ this.queryInfo = null;
+ this.nodeState = null;
+ this.queryResult = null;
+ }
+
+ setQueryInfo(queryInfo: number[]): void {
+ if (queryInfo === null) throw new Error('No query info found');
+ this.queryInfo = queryInfo;
+ eventBus.emit(DATA_EVENTS.QUERY_CHANGED, this.queryInfo);
+ }
+ getQueryInfo(): number[] {
+ return this.queryInfo;
+ }
+
+ setNodeState(nodeState: nodeState): void {
+ if (nodeState === null) throw new Error('No node state found');
+ this.nodeState = nodeState;
+ }
+ getNodeState(): nodeState {
+ return this.nodeState;
+ }
+
+ setQueryResult(queryResult: Matrix2D): void {
+ if (queryResult === null) throw new Error('No query result found');
+ this.queryResult = queryResult;
+ eventBus.emit(DATA_EVENTS.RESULT_CHANGED, queryResult);
+ }
+ getQueryResult() {
+ return this.queryResult;
+ }
+
+ reset() {
+ this.queryInfo = null;
+ this.nodeState = null;
+ this.queryResult = null;
+ }
+}
+
+export default DataStore;
diff --git a/src/controller/EventBus.js b/src/controller/EventBus.js
deleted file mode 100644
index e8d182a..0000000
--- a/src/controller/EventBus.js
+++ /dev/null
@@ -1,20 +0,0 @@
-const EventBus = {
- _events: {},
-
- on(eventName, subscriber) {
- if(!this._events[eventName]) this._events[eventName] = [];
- this._events[eventName].push(subscriber);
- },
-
- off(eventName, subscriber) {
- if(!this._events[eventName]) return;
- this._events[eventName] = this._events[eventName].filter(fn => fn !== subscriber);
- },
-
- emit(eventName, payload) {
- if(!this._events[eventName]) return;
- this._events[eventName].forEach(fn => fn(payload));
- }
-}
-
-export default EventBus;
\ No newline at end of file
diff --git a/src/controller/EventBus.ts b/src/controller/EventBus.ts
new file mode 100644
index 0000000..ffe271f
--- /dev/null
+++ b/src/controller/EventBus.ts
@@ -0,0 +1,33 @@
+import { IEventBus } from './types/eventBus';
+import { eventPayloads } from './types/eventBus';
+
+const EventBus: IEventBus = {
+ events: {},
+
+ on(
+ eventName: K,
+ subscriber: (payload: eventPayloads[K]) => any,
+ ): void {
+ if (!this.events[eventName]) this.events[eventName] = [];
+ this.events[eventName].push(subscriber);
+ },
+
+ off(
+ eventName: K,
+ subscriber: (payload: eventPayloads[K]) => any,
+ ): void {
+ if (!this.events[eventName]) return;
+ this.events[eventName] = this.events[eventName].filter(
+ (fn: (payload: eventPayloads[K]) => void): boolean => fn !== subscriber,
+ );
+ },
+
+ emit(eventName: K, payload: eventPayloads[K]): void {
+ if (!this.events[eventName]) return;
+ this.events[eventName].forEach((fn: (payload: eventPayloads[K]) => any): any =>
+ fn(payload),
+ );
+ },
+};
+
+export default EventBus;
diff --git a/src/controller/constants/canvasConfig.js b/src/controller/constants/canvasConfig.ts
similarity index 53%
rename from src/controller/constants/canvasConfig.js
rename to src/controller/constants/canvasConfig.ts
index 23eef1c..dbca9a0 100644
--- a/src/controller/constants/canvasConfig.js
+++ b/src/controller/constants/canvasConfig.ts
@@ -2,4 +2,6 @@ export const CANVAS_CONFIG = Object.freeze({
lineWidth: 20,
lineCap: 'round',
lineJoin: 'round',
-});
\ No newline at end of file
+} as const);
+
+export type CanvasConfig = (typeof CANVAS_CONFIG)[keyof typeof CANVAS_CONFIG];
diff --git a/src/controller/constants/events.js b/src/controller/constants/events.ts
similarity index 52%
rename from src/controller/constants/events.js
rename to src/controller/constants/events.ts
index edebe68..bf0e08c 100644
--- a/src/controller/constants/events.js
+++ b/src/controller/constants/events.ts
@@ -4,18 +4,23 @@ export const DATA_EVENTS = Object.freeze({
NODE_UPDATE: 'node:update',
NODE_CHANGED: 'node:changed',
RESULT_UPDATE: 'result:update',
- RESULT_CHANGED: 'result:changed'
-});
+ RESULT_CHANGED: 'result:changed',
+} as const);
export const HANDLER_EVENTS = Object.freeze({
APP_READY: 'app:ready',
NN_INITIALIZE: 'nn:initialize',
ICH_INITIALIZE: 'ich:initialize',
-})
+} as const);
export const DRAWING_EVENTS = Object.freeze({
- START_DRAW: 'draw: start',
- DRAW: 'draw: drawing',
- END_DRAW: 'draw: end',
+ START_DRAW: 'draw:start',
+ DRAW: 'draw:drawing',
+ END_DRAW: 'draw:end',
+ CLEAR_DRAW: 'draw:clear',
BOUNDINGBOX_UPDATE: 'boundingbox:update',
-})
\ No newline at end of file
+} as const);
+
+export type DataEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS];
+export type HandlerEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS];
+export type DrawingEvent = (typeof DATA_EVENTS)[keyof typeof DATA_EVENTS];
diff --git a/src/controller/constants/networkConfig.js b/src/controller/constants/networkConfig.js
deleted file mode 100644
index 8d6a6c8..0000000
--- a/src/controller/constants/networkConfig.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export const NETWORK_CONFIG = {
- inputNodes: 784,
- hiddenNodes: 200,
- outputNodes: 10,
- learningRate: 0.15,
-}
\ No newline at end of file
diff --git a/src/controller/constants/networkConfig.ts b/src/controller/constants/networkConfig.ts
new file mode 100644
index 0000000..fe022fd
--- /dev/null
+++ b/src/controller/constants/networkConfig.ts
@@ -0,0 +1,8 @@
+import { networkConfig } from './types/networkConfig';
+
+export const NETWORK_CONFIG: networkConfig = {
+ inputNodes: 784,
+ hiddenNodes: 200,
+ outputNodes: 10,
+ learningRate: 0.15,
+};
diff --git a/src/view/components/perceptron/Perceptron.js b/src/controller/constants/types/events.ts
similarity index 100%
rename from src/view/components/perceptron/Perceptron.js
rename to src/controller/constants/types/events.ts
diff --git a/src/controller/constants/types/networkConfig.ts b/src/controller/constants/types/networkConfig.ts
new file mode 100644
index 0000000..f099979
--- /dev/null
+++ b/src/controller/constants/types/networkConfig.ts
@@ -0,0 +1,6 @@
+export interface networkConfig {
+ inputNodes: number;
+ hiddenNodes: number;
+ outputNodes: number;
+ learningRate: number;
+}
diff --git a/src/controller/dataStore.js b/src/controller/dataStore.js
deleted file mode 100644
index 434e342..0000000
--- a/src/controller/dataStore.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import eventBus from "./EventBus.js";
-import { DATA_EVENTS } from "./constants/events.js"
-
-export class DataStore {
- constructor() {
- this._queryInfo = null;
- this._nodeState = {};
- this._queryResult = null;
-
- eventBus.on(DATA_EVENTS.NODE_UPDATE, this.setQueryInfo.bind(this));
- eventBus.on(DATA_EVENTS.RESULT_UPDATE, this.setQueryResult.bind(this));
- }
-
- setQueryInfo(queryInfo) {
- this._queryInfo = queryInfo;
- eventBus.emit(DATA_EVENTS.QUERY_CHANGED, queryInfo)
- }
- getQueryInfo() {
- return this._queryInfo;
- }
-
- setNodeState(nodeState) {
- this._nodeState = nodeState;
- }
- getNodeState() {
- return this._nodeState;
- }
-
- setQueryResult(queryResult) {
- this._queryResult = queryResult;
- eventBus.emit(DATA_EVENTS.QUERY_CHANGED, queryResult);
- }
- getQueryResult() {
- return this._queryResult;
- }
-
- reset() {
- this._queryInfo = null;
- this._nodeState = {};
- this._queryResult = null;
- }
-}
\ No newline at end of file
diff --git a/src/controller/queryPipeline/DrawingEventHandler.js b/src/controller/queryPipeline/DrawingEventHandler.js
deleted file mode 100644
index 05671c2..0000000
--- a/src/controller/queryPipeline/DrawingEventHandler.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import BoundingBox from "../../view/components/canvasUtils/BoundingBox.js";
-import eventBus from "../EventBus.js";
-import { DRAWING_EVENTS } from "../constants/events.js";
-
-class DrawingEventHandler {
- constructor() {
- this.inputCanvas = document.getElementById('userInputCanvas');
- this.registerEvents();
- }
-
- registerEvents() {
- const startDraw = (x, y) => {
- this.isDrawing = true;
- eventBus.emit(DRAWING_EVENTS.START_DRAW, {x, y});
- }
-
- const draw = (x, y) => {
- if (!this.isDrawing) return;
- BoundingBox.update(x, y);
- eventBus.emit(DRAWING_EVENTS.DRAW, {x, y})
- }
-
- const endDraw = () => {
- this.isDrawing = false;
- BoundingBox.log();
- eventBus.emit(DRAWING_EVENTS.END_DRAW);
- }
-
- const getTouchPosition = (e) => {
- e.preventDefault();
- const touch = e.touches[0];
- const rect = this.inputCanvas.getBoundingClientRect();
- return {
- x: touch.clientX - rect.left,
- y: touch.clientY - rect.top,
- }
- }
-
- this.inputCanvas.addEventListener("mousedown", (e) => {
- startDraw(e.offsetX, e.offsetY);
- });
- this.inputCanvas.addEventListener("touchstart", (e) => {
- let {x, y} = getTouchPosition(e);
- startDraw(x, y);
- });
-
- this.inputCanvas.addEventListener("mousemove", (e) => {
- draw(e.offsetX, e.offsetY);
- });
- this.inputCanvas.addEventListener("touchmove", (e) => {
- let {x, y} = getTouchPosition(e);
- draw(x, y);
- });
-
- this.inputCanvas.addEventListener("mouseup", endDraw);
- this.inputCanvas.addEventListener("mouseout", endDraw);
- this.inputCanvas.addEventListener("touchend", (e) => {
- e.preventDefault();
- endDraw();
- });
- }
-}
-
-export default DrawingEventHandler;
diff --git a/src/controller/queryPipeline/DrawingEventHandler.ts b/src/controller/queryPipeline/DrawingEventHandler.ts
new file mode 100644
index 0000000..d64076c
--- /dev/null
+++ b/src/controller/queryPipeline/DrawingEventHandler.ts
@@ -0,0 +1,92 @@
+import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts';
+import eventBus from '../EventBus.ts';
+import { DRAWING_EVENTS } from '../constants/events.ts';
+
+class DrawingEventHandler {
+ private inputCanvas: HTMLCanvasElement | null;
+ private clearButton: HTMLButtonElement | null;
+ private isDrawing: boolean;
+
+ constructor() {
+ this.inputCanvas = document.getElementById('userInputCanvas') as HTMLCanvasElement;
+ this.clearButton = document.getElementById('clear') as HTMLButtonElement;
+ this.isDrawing = false;
+ this.registerEvents();
+ }
+
+ registerEvents(): void {
+ const bindings: [string, EventListener][] = [
+ ['mousedown', this.handleStartDraw],
+ ['touchstart', this.handleStartDraw],
+ ['mousemove', this.handleDraw],
+ ['touchmove', this.handleDraw],
+ ['mouseup', this.endDraw],
+ ['mouseout', this.endDraw],
+ [
+ 'touchend',
+ (e: TouchEvent): void => {
+ e.preventDefault();
+ this.endDraw();
+ },
+ ],
+ ];
+
+ bindings.forEach(([event, handler]: [string, EventListener]): void =>
+ this.inputCanvas.addEventListener(event, handler),
+ );
+
+ this.clearButton.addEventListener('click', () => {
+ eventBus.emit(DRAWING_EVENTS.CLEAR_DRAW, null);
+ });
+ }
+
+ // eventBus로 입력 event 전송
+ startDraw = (x: number, y: number): void => {
+ this.isDrawing = true;
+ eventBus.emit(DRAWING_EVENTS.START_DRAW, { x, y });
+ };
+
+ draw = (x: number, y: number): void => {
+ if (!this.isDrawing) return;
+ BoundingBox.update(x, y);
+ eventBus.emit(DRAWING_EVENTS.DRAW, { x, y });
+ };
+
+ endDraw = (): void => {
+ this.isDrawing = false;
+ BoundingBox.log();
+ eventBus.emit(DRAWING_EVENTS.END_DRAW, null);
+ };
+
+ // 캔버스에서의 터치 위치 반환
+ getTouchPosition = (e: TouchEvent): { x: number; y: number } => {
+ e.preventDefault();
+ const touch = e.touches[0];
+ const rect = this.inputCanvas.getBoundingClientRect();
+ return {
+ x: touch.clientX - rect.left,
+ y: touch.clientY - rect.top,
+ };
+ };
+
+ // 마우스 클릭, 터치 이벤트에 대한 입력 이벤트 발생
+ handleStartDraw = (e: MouseEvent | TouchEvent): void => {
+ if (e instanceof MouseEvent) {
+ this.startDraw(e.offsetX, e.offsetY);
+ } else if (e instanceof TouchEvent) {
+ let { x, y } = this.getTouchPosition(e);
+ this.startDraw(x, y);
+ }
+ };
+
+ handleDraw = (e: MouseEvent | TouchEvent): void => {
+ if (e instanceof MouseEvent) {
+ this.draw(e.offsetX, e.offsetY);
+ } else if (e instanceof TouchEvent) {
+ let { x, y } = this.getTouchPosition(e);
+ this.draw(x, y);
+ }
+ };
+}
+
+export default DrawingEventHandler;
diff --git a/src/controller/queryPipeline/QueryProcessController.js b/src/controller/queryPipeline/QueryProcessController.js
deleted file mode 100644
index 0bc3697..0000000
--- a/src/controller/queryPipeline/QueryProcessController.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import eventBus from "../EventBus.js";
-import { DATA_EVENTS, HANDLER_EVENTS, DRAWING_EVENTS } from "../constants/events.js";
-import DrawingEventHandler from "./DrawingEventHandler.js";
-import {pixelExtractor} from "../queryPipeline/pixelExtractor.js";
-import {throttle} from "../queryPipeline/throttle.js";
-
-
-class QueryProcessController {
- constructor({ userInputCanvas, trackingCanvas, alignCanvas, resizeCanvas, $NN }) {
- const drawingEventHandler = new DrawingEventHandler();
- this.userInputCanvas = userInputCanvas;
- this.trackingCanvas = trackingCanvas;
- this.alignCanvas = alignCanvas;
- this.resizeCanvas = resizeCanvas;
-
- this.$NN = $NN;
- this.drawingEvent();
- }
-
- drawingEvent() {
- const throttleQuery = throttle((inputs) => {
- const result = this.$NN.query(inputs);
- if(result) {
- eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result);
- // Array 중 가장 값이 큰 값의 index number가 추론 결과
- }
- },100);
-
- eventBus.on(DRAWING_EVENTS.START_DRAW, ({x, y}) => {
- this.userInputCanvas.startPath(x, y);
- this.trackingCanvas.startPath(x, y);
- });
- eventBus.on(DRAWING_EVENTS.DRAW, ({x, y}) => {
- this.userInputCanvas.drawPath(x, y);
- this.trackingCanvas.drawPath(x, y);
- this.alignCanvas.updateCanvasScale();
- this.alignCanvas.centralize(this.trackingCanvas.canvas);
- this.resizeCanvas.downScale(this.alignCanvas.canvas);
- throttleQuery(pixelExtractor(this.resizeCanvas));
- });
- eventBus.on(DRAWING_EVENTS.END_DRAW, () => {
- this.userInputCanvas.endPath();
- this.trackingCanvas.endPath();
- });
- }
-}
-
-export default QueryProcessController;
\ No newline at end of file
diff --git a/src/controller/queryPipeline/QueryProcessController.ts b/src/controller/queryPipeline/QueryProcessController.ts
new file mode 100644
index 0000000..eddbb2f
--- /dev/null
+++ b/src/controller/queryPipeline/QueryProcessController.ts
@@ -0,0 +1,85 @@
+import eventBus from '../EventBus.js';
+import { DATA_EVENTS, DRAWING_EVENTS } from '../constants/events.js';
+import { pixelExtractor } from './pixelExtractor.js';
+import { throttle } from './throttle';
+import { IQueryProcessControllerProps } from './types/QueryProcessController.js';
+import {
+ ICanvasBase,
+ IUserInputCanvas,
+ IPathTrackingCanvas,
+ IAlignCanvas,
+ IResizeCanvas,
+} from '@/view/components/userQuery/types/canvas';
+import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase';
+import { IDataStore } from '../types/DataStore';
+import { Matrix2D } from '@/core/ops/types/OpsType.ts';
+import BoundingBox from '@/view/components/canvasUtils/BoundingBox.ts';
+
+class QueryProcessController {
+ private readonly userInputCanvas: ICanvasBase & IUserInputCanvas;
+ private readonly trackingCanvas: ICanvasBase & IPathTrackingCanvas;
+ private readonly alignCanvas: ICanvasBase & IAlignCanvas;
+ private readonly resizeCanvas: ICanvasBase & IResizeCanvas;
+ private readonly $NN: INeuralNetworkBase;
+ private readonly $DS: IDataStore;
+ private readonly queryFrequencyMs: number;
+ //TODO: Canvas clear시 Skeleton 에니메이션 적용하기
+
+ constructor({
+ userInputCanvas,
+ trackingCanvas,
+ alignCanvas,
+ resizeCanvas,
+ $NN,
+ $DS,
+ }: IQueryProcessControllerProps) {
+ this.userInputCanvas = userInputCanvas;
+ this.trackingCanvas = trackingCanvas;
+ this.alignCanvas = alignCanvas;
+ this.resizeCanvas = resizeCanvas;
+
+ this.$NN = $NN;
+ this.$DS = $DS;
+ this.registerDrawingEvent();
+ this.query();
+ this.queryFrequencyMs = 200;
+ }
+
+ registerDrawingEvent(): void {
+ eventBus.on(DRAWING_EVENTS.START_DRAW, ({ x, y }) => {
+ this.userInputCanvas.startPath(x, y);
+ this.trackingCanvas.startPath(x, y);
+ });
+ eventBus.on(DRAWING_EVENTS.DRAW, ({ x, y }) => {
+ this.userInputCanvas.drawPath(x, y);
+ this.trackingCanvas.drawPath(x, y);
+ this.alignCanvas.updateCanvasScale();
+ this.alignCanvas.centralize(this.trackingCanvas.canvas);
+ this.resizeCanvas.downScale(this.alignCanvas.canvas);
+ this.$DS.setQueryInfo(pixelExtractor(this.resizeCanvas));
+ });
+ eventBus.on(DRAWING_EVENTS.END_DRAW, () => {
+ this.userInputCanvas.endPath();
+ this.trackingCanvas.endPath();
+ });
+ eventBus.on(DRAWING_EVENTS.CLEAR_DRAW, () => {
+ this.userInputCanvas.clear();
+ this.trackingCanvas.clear();
+ this.alignCanvas.clear();
+ this.resizeCanvas.clear();
+ BoundingBox.reset();
+ });
+ }
+
+ query() {
+ const throttleQuery = throttle((inputs) => {
+ const result: Matrix2D = this.$NN.query(inputs);
+ if (result) eventBus.emit(DATA_EVENTS.RESULT_CHANGED, result);
+ }, this.queryFrequencyMs);
+
+ eventBus.on(DATA_EVENTS.QUERY_CHANGED, (inputs: number[]) => throttleQuery(inputs));
+ // TODO: type interface 추가하기, 이벤트버스 구조와 쿼리 구조 다시 생각해보기, TS 마이그레이션
+ }
+}
+
+export default QueryProcessController;
diff --git a/src/controller/queryPipeline/pixelExtractor.js b/src/controller/queryPipeline/pixelExtractor.js
deleted file mode 100644
index d4156c7..0000000
--- a/src/controller/queryPipeline/pixelExtractor.js
+++ /dev/null
@@ -1,28 +0,0 @@
-export const pixelExtractor = (path) => {
- const pathToMatrix = (width, height, data) => {
- return Array.from({ length: height }, (_, y) => {
- return Array.from({ length: width }, (_, x) => {
- const i = (y * width + x) * 4;
- const [r, g, b, a] = data.slice(i, i + 4);
- return { r, g, b, a };
- });
- });
- }
-
- const grayscaleMatrix = (matrixRGB) => {
- return matrixRGB.flatMap(pixelObject =>
- pixelObject.map(v => {
- const RGB_sum = v.r + v.g + v.b;
- return Math.round(RGB_sum/3);
- }));
- }
-
- const normalize = (grayscaleMatrix) => {
- return grayscaleMatrix.map(v => (v/255)*0.99+0.01);
- }
-
- const {width, height, data} = path.ctx.getImageData(0, 0, path.canvas.width, path.canvas.height);
- const matrixRGB = pathToMatrix(width, height, data);
- const grayScale = grayscaleMatrix(matrixRGB);
- return normalize(grayScale);
-};
\ No newline at end of file
diff --git a/src/controller/queryPipeline/pixelExtractor.ts b/src/controller/queryPipeline/pixelExtractor.ts
new file mode 100644
index 0000000..3e37ef1
--- /dev/null
+++ b/src/controller/queryPipeline/pixelExtractor.ts
@@ -0,0 +1,42 @@
+import { IResizeCanvas, ICanvasBase } from '@/view/components/userQuery/types/canvas';
+
+export const pixelExtractor = (path: IResizeCanvas & ICanvasBase) => {
+ const pathToMatrix = (width: number, height: number, data: Uint8ClampedArray): object[] => {
+ return Array.from(
+ { length: height },
+ (_: unknown, y: number): { r: number; g: number; b: number; a: number }[] => {
+ return Array.from(
+ { length: width },
+ (_: unknown, x: number): { r: number; g: number; b: number; a: number } => {
+ const i: number = (y * width + x) * 4;
+ const [r, g, b, a] = data.slice(i, i + 4);
+ return { r, g, b, a };
+ },
+ );
+ },
+ );
+ };
+
+ const grayscaleMatrix = (matrixRGB: object[]): number[] => {
+ return matrixRGB.flatMap((pixelObject: object[]) =>
+ pixelObject.map((v: { r: number; g: number; b: number; a: number }): number => {
+ const RGB_sum: number = v.r + v.g + v.b;
+ return Math.round(RGB_sum / 3);
+ }),
+ );
+ };
+
+ const normalize = (grayscaleMatrix: number[]): number[] => {
+ return grayscaleMatrix.map((v) => (v / 255) * 0.99 + 0.01);
+ };
+
+ const { width, height, data } = path.ctx.getImageData(
+ 0,
+ 0,
+ path.canvas.width,
+ path.canvas.height,
+ );
+ const matrixRGB: object[] = pathToMatrix(width, height, data);
+ const grayScale = grayscaleMatrix(matrixRGB);
+ return normalize(grayScale);
+};
diff --git a/src/controller/queryPipeline/throttle.js b/src/controller/queryPipeline/throttle.ts
similarity index 51%
rename from src/controller/queryPipeline/throttle.js
rename to src/controller/queryPipeline/throttle.ts
index 8d25156..bc966df 100644
--- a/src/controller/queryPipeline/throttle.js
+++ b/src/controller/queryPipeline/throttle.ts
@@ -1,6 +1,8 @@
-export const throttle = (fn, delay) => {
+export const throttle = any> (
+ fn: T, delay: number
+): (...args: Parameters) => ReturnType => {
let pause = false;
- return (...args) => {
+ return (...args: Parameters): ReturnType => {
if(!pause) {
const result = fn(...args);
pause = true;
diff --git a/src/controller/queryPipeline/types/DrawingEventHandler.ts b/src/controller/queryPipeline/types/DrawingEventHandler.ts
new file mode 100644
index 0000000..d4a36b5
--- /dev/null
+++ b/src/controller/queryPipeline/types/DrawingEventHandler.ts
@@ -0,0 +1,12 @@
+interface DrawingEventHandlerParams {
+ startDraw(x: number, y: number): void;
+ draw(x: number, y: number): void;
+ endDraw(): void;
+ getTouchPosition(e: TouchEvent): { x: number; y: number };
+
+ addEventListener(listener: EventListener): void;
+}
+
+interface DrawingEventHandlerParams {
+ registerEvents: DrawingEventHandlerParams;
+}
diff --git a/src/controller/queryPipeline/types/QueryProcessController.ts b/src/controller/queryPipeline/types/QueryProcessController.ts
new file mode 100644
index 0000000..e43c0bb
--- /dev/null
+++ b/src/controller/queryPipeline/types/QueryProcessController.ts
@@ -0,0 +1,18 @@
+import {
+ ICanvasBase,
+ IUserInputCanvas,
+ IPathTrackingCanvas,
+ IAlignCanvas,
+ IResizeCanvas,
+} from '@/view/components/userQuery/types/canvas';
+import { INeuralNetworkBase } from '@/core/types/NeuralNetworkBase';
+import { IDataStore } from '@/controller/types/DataStore';
+
+export interface IQueryProcessControllerProps {
+ userInputCanvas: ICanvasBase & IUserInputCanvas;
+ trackingCanvas: ICanvasBase & IPathTrackingCanvas;
+ alignCanvas: ICanvasBase & IAlignCanvas;
+ resizeCanvas: ICanvasBase & IResizeCanvas;
+ $NN: INeuralNetworkBase;
+ $DS: IDataStore;
+}
diff --git a/src/controller/types/DataStore.ts b/src/controller/types/DataStore.ts
new file mode 100644
index 0000000..cff00ae
--- /dev/null
+++ b/src/controller/types/DataStore.ts
@@ -0,0 +1,18 @@
+import { Matrix2D } from '@/core/ops/types/OpsType.ts';
+
+export interface IDataStore {
+ setQueryInfo(queryInfo: number[]): void;
+ getQueryInfo(): number[];
+
+ setNodeState(nodeState: nodeState): void;
+ getNodeState(): nodeState;
+
+ setQueryResult(queryResult: Matrix2D): void;
+ getQueryResult(): Matrix2D;
+}
+
+export interface nodeState {
+ inputNodes: number[];
+ hiddenNodes: number[];
+ outputNodes: number[];
+}
diff --git a/src/controller/types/eventBus.ts b/src/controller/types/eventBus.ts
new file mode 100644
index 0000000..7c302ac
--- /dev/null
+++ b/src/controller/types/eventBus.ts
@@ -0,0 +1,32 @@
+import { Matrix2D } from '@/core/ops/types/OpsType';
+
+export interface eventPayloads {
+ // Data handling event
+ 'query:changed': number[];
+ 'node:changed': { inputNodes: number[]; hiddenNodes: number[]; outputNodes: number[] };
+ 'result:changed': Matrix2D;
+
+ // Canvas handling event
+ 'draw:start': { x: number; y: number };
+ 'draw:drawing': { x: number; y: number };
+ 'draw:end': void;
+ 'draw:clear': void;
+ 'bondingbox:update': { X: number; y: number };
+
+ 'node:update': { hiddenInputs: Matrix2D; hiddenOutputs: Matrix2D; finalOutputs: Matrix2D };
+}
+
+export interface IEventBus {
+ events: object;
+ on(
+ eventName: K,
+ subscriber: (payload: eventPayloads[K]) => void,
+ ): void;
+
+ off(
+ eventName: K,
+ subscriber: (payload: eventPayloads[K]) => void,
+ ): void;
+
+ emit(eventName: K, payload: eventPayloads[K]): void;
+}
diff --git a/src/controller/types/weights.ts b/src/controller/types/weights.ts
new file mode 100644
index 0000000..a149746
--- /dev/null
+++ b/src/controller/types/weights.ts
@@ -0,0 +1,4 @@
+export interface weights {
+ W_inputToHidden: number[][];
+ W_hiddenToOutput: number[][];
+}
diff --git a/src/core/NeuralNetworkBase.js b/src/core/NeuralNetworkBase.js
deleted file mode 100644
index adb4bfa..0000000
--- a/src/core/NeuralNetworkBase.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import activationFunction from "./ops/activationOps.js";
-import { matrixMultiply } from "./ops/matrixOps.js";
-import eventBus from "../controller/EventBus.js";
-import { DATA_EVENTS } from "../controller/constants/events.js";
-import { NETWORK_CONFIG } from "../controller/constants/networkConfig.js";
-
-class NeuralNetworkBase {
- constructor(weights) {
- this.inputNodes = NETWORK_CONFIG.inputNodes;
- this.hiddenNodes = NETWORK_CONFIG.hiddenNodes;
- this.outputNodes = NETWORK_CONFIG.outputNodes;
- this.learningRate = NETWORK_CONFIG.learningRate;
-
- this.W_inputToHidden = weights?.W_inputToHidden ?? null;
- this.W_hiddenToOutput = weights?.W_hiddenToOutput ?? null;
- }
-
- // CNN operations
- feedForward(inputs) {
- console.log("inputs", inputs);
- const hiddenInputs = matrixMultiply(this.W_inputToHidden, inputs.map(v => [v])); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환.
- const hiddenOutputs = activationFunction(hiddenInputs);
- const finalInputs = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs);
- const finalOutputs = activationFunction(finalInputs);
- return { hiddenInputs, hiddenOutputs, finalInputs, finalOutputs };
- }
-
- query(inputs){
- const { hiddenInputs, hiddenOutputs , finalOutputs } = this.feedForward(inputs);
- eventBus.emit(DATA_EVENTS.NODE_UPDATE, {hiddenInputs, hiddenOutputs, finalOutputs});
- return finalOutputs;
- }
-}
-
-export default NeuralNetworkBase;
\ No newline at end of file
diff --git a/src/core/NeuralNetworkBase.ts b/src/core/NeuralNetworkBase.ts
new file mode 100644
index 0000000..3f1cc6e
--- /dev/null
+++ b/src/core/NeuralNetworkBase.ts
@@ -0,0 +1,42 @@
+import activationFunction from './ops/activationOps.js';
+import { matrixMultiply } from './ops/matrixOps.js';
+import eventBus from '@/controller/EventBus.js';
+import { DATA_EVENTS } from '@/controller/constants/events.js';
+
+import { weights } from '@/controller/types/weights';
+import { Matrix2D } from './ops/types/OpsType';
+
+class NeuralNetworkBase {
+ private readonly W_inputToHidden: number[][] | null;
+ private readonly W_hiddenToOutput: number[][] | null;
+
+ constructor(weights: Partial) {
+ // weight를 optional하게 처리하기 위해 Partial 사용
+ this.W_inputToHidden = weights?.W_inputToHidden ?? null;
+ this.W_hiddenToOutput = weights?.W_hiddenToOutput ?? null;
+ }
+
+ // CNN operations
+ feedForward(inputs: number[]): {
+ hiddenInputs: Matrix2D;
+ hiddenOutputs: Matrix2D;
+ finalOutputs: Matrix2D;
+ } {
+ const hiddenInputs: number[][] = matrixMultiply(
+ this.W_inputToHidden,
+ inputs.map((v) => [v]),
+ ); //신경망 출력 결과를 Nx1 형태의 행렬곱으로 변환.
+ const hiddenOutputs: Matrix2D = activationFunction(hiddenInputs);
+ const finalInputs: Matrix2D = matrixMultiply(this.W_hiddenToOutput, hiddenOutputs);
+ const finalOutputs: Matrix2D = activationFunction(finalInputs);
+ return { hiddenInputs, hiddenOutputs, finalOutputs };
+ }
+
+ query(inputs: number[]): Matrix2D {
+ const { hiddenInputs, hiddenOutputs, finalOutputs } = this.feedForward(inputs);
+ eventBus.emit(DATA_EVENTS.NODE_UPDATE, { hiddenInputs, hiddenOutputs, finalOutputs });
+ return finalOutputs;
+ }
+}
+
+export default NeuralNetworkBase;
diff --git a/src/core/WeightManager.js b/src/core/WeightManager.js
deleted file mode 100644
index 40b49ef..0000000
--- a/src/core/WeightManager.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { createRandomWeight } from "./utils/createRandomWeights.js";
-import { loadPretrainedWeights } from "./utils/loadPretrainedWeights.js";
-
-class WeightManager {
- _cache = null;
-
- constructor(networkConfig) {
- this.config = networkConfig;
- this.path = "assets/preTrainedWeights_h200_lr15p.json";
- }
-
- async getWeights() {
- if(this._cache) return this._cache
- try{
- const json = await loadPretrainedWeights(this.path);
- this._cache = {
- W_inputToHidden: json.W_inputToHidden,
- W_hiddenToOutput: json.W_hiddenToOutput,
- };
- }catch(err){
- console.warn(`[WeightManager] Using random weights due to error: ${err.message}`);
- this._cache = {
- W_inputToHidden: createRandomWeight(this.config.hiddenNodes, this.config.inputNodes),
- W_hiddenToOutput: createRandomWeight(this.config.outputNodes, this.config.hiddenNodes),
- };
- }
- console.log("Network weights fetched");
- return this._cache;
- }
-
-}
-
-export default WeightManager;
\ No newline at end of file
diff --git a/src/core/WeightManager.ts b/src/core/WeightManager.ts
new file mode 100644
index 0000000..29aa62d
--- /dev/null
+++ b/src/core/WeightManager.ts
@@ -0,0 +1,43 @@
+import { createRandomWeight } from './utils/createRandomWeights.js';
+import { loadPretrainedWeights } from './utils/loadPretrainedWeights.js';
+import { networkConfig } from '@/controller/constants/types/networkConfig';
+
+import { weights } from '@/controller/types/weights';
+
+class WeightManager {
+ private _cache: null | weights = null;
+ private config: networkConfig;
+ private readonly path: string;
+
+ constructor(networkConfig: networkConfig) {
+ this.config = networkConfig;
+ this.path = 'assets/preTrainedWeights_h200_lr15p.json';
+ }
+
+ async getWeights(): Promise {
+ if (this._cache) return this._cache;
+ try {
+ const json = await loadPretrainedWeights(this.path);
+ this._cache = {
+ W_inputToHidden: json.W_inputToHidden,
+ W_hiddenToOutput: json.W_hiddenToOutput,
+ };
+ } catch (err) {
+ console.warn(`[WeightManager] Using random weights due to error: ${err.message}`);
+ this._cache = {
+ W_inputToHidden: createRandomWeight(
+ this.config.hiddenNodes,
+ this.config.inputNodes,
+ ),
+ W_hiddenToOutput: createRandomWeight(
+ this.config.outputNodes,
+ this.config.hiddenNodes,
+ ),
+ };
+ }
+ console.log('Network weights fetched');
+ return this._cache;
+ }
+}
+
+export default WeightManager;
diff --git a/src/core/ops/activationOps.js b/src/core/ops/activationOps.js
deleted file mode 100644
index 9dfb928..0000000
--- a/src/core/ops/activationOps.js
+++ /dev/null
@@ -1,17 +0,0 @@
-const activationFunction = matrix =>
- matrix.map(array =>
- array.map(v => {
- const value = sigmoid(v)
- return parseFloat(value.toFixed(5)) //활성화 함수를 적용시킨 값을 소숫점 5자리로 반올림
- })
- )
-
-const sigmoid = ($x) => {
- return 1 / (1 + Math.exp(-$x));
-};
-
-const ReLU = ($x) => {
- return Math.max(0, $x);
-}
-
-export default activationFunction;
\ No newline at end of file
diff --git a/src/core/ops/activationOps.ts b/src/core/ops/activationOps.ts
new file mode 100644
index 0000000..3d92e4a
--- /dev/null
+++ b/src/core/ops/activationOps.ts
@@ -0,0 +1,19 @@
+import { Matrix2D } from './types/OpsType.ts';
+
+const activationFunction = (matrix: Matrix2D): Matrix2D =>
+ matrix.map((array: number[]): number[] =>
+ array.map((v: number): number => {
+ const value: number = sigmoid(v);
+ return parseFloat(value.toFixed(5)); //활성화 함수를 적용시킨 값을 소숫점 5자리로 반올림
+ }),
+ );
+
+const sigmoid = ($x: number): number => {
+ return 1 / (1 + Math.exp(-$x));
+};
+
+const ReLU = ($x: number): number => {
+ return Math.max(0, $x);
+};
+
+export default activationFunction;
diff --git a/src/core/ops/matrixOps.js b/src/core/ops/matrixOps.js
deleted file mode 100644
index 41247f3..0000000
--- a/src/core/ops/matrixOps.js
+++ /dev/null
@@ -1,17 +0,0 @@
-export const matrixMultiply = (A, B) => {
- A = Array.isArray(A[0]) ? A : [A];
-
- if (!Array.isArray(A) || !Array.isArray(B)) throw new Error('matrixA, B must be an array');
- else if (A[0].length !== B.length) throw new Error('rows and columns length doesn\'t match');
-
- return A.map((row, i) =>
- B[0].map((_, j) =>
- row.reduce((sum, _, k) => {
- return sum + A[i][k]*B[k][j];
- }, 0)
- )
- )
-}
-
-export const transposeMatrix = matrix =>
- matrix[0].map((_, idx)=> matrix.map(row => row[idx]))
\ No newline at end of file
diff --git a/src/core/ops/matrixOps.ts b/src/core/ops/matrixOps.ts
new file mode 100644
index 0000000..f6eda6e
--- /dev/null
+++ b/src/core/ops/matrixOps.ts
@@ -0,0 +1,25 @@
+import { Matrix2D } from './types/OpsType';
+
+const ensure2DArray = (matrix: number[] | number[][]): number[][] => {
+ return Array.isArray(matrix[0]) ? (matrix as number[][]) : [matrix as unknown as number[]];
+};
+
+export const matrixMultiply = (A: Matrix2D, B: Matrix2D): Matrix2D => {
+ A = ensure2DArray(A);
+
+ if (!Array.isArray(A) || !Array.isArray(B)) throw new Error('matrixA, B must be an array');
+ else if (A[0].length !== B.length) throw new Error("rows and columns length doesn't match");
+
+ return A.map((row: number[], i: number): number[] =>
+ B[0].map((_: unknown, j: number): number =>
+ row.reduce((sum: number, _: unknown, k: number): number => {
+ return sum + A[i][k] * B[k][j];
+ }, 0),
+ ),
+ );
+};
+
+export const transposeMatrix = (matrix: Matrix2D): Matrix2D =>
+ matrix[0].map((_: unknown, idx: number): number[] =>
+ matrix.map((row: number[]): number => row[idx]),
+ );
diff --git a/src/core/ops/types/OpsType.ts b/src/core/ops/types/OpsType.ts
new file mode 100644
index 0000000..5f6ec6d
--- /dev/null
+++ b/src/core/ops/types/OpsType.ts
@@ -0,0 +1 @@
+export type Matrix2D = number[][];
diff --git a/src/core/types/NeuralNetworkBase.ts b/src/core/types/NeuralNetworkBase.ts
new file mode 100644
index 0000000..0c15bab
--- /dev/null
+++ b/src/core/types/NeuralNetworkBase.ts
@@ -0,0 +1,10 @@
+import { Matrix2D } from '@/core/ops/types/OpsType.ts';
+
+export interface INeuralNetworkBase {
+ feedForward(inputs: number[]): {
+ hiddenInputs: Matrix2D;
+ hiddenOutputs: Matrix2D;
+ finalOutputs: Matrix2D;
+ };
+ query(inputs: number[]): Matrix2D;
+}
diff --git a/src/core/types/WeightManager.ts b/src/core/types/WeightManager.ts
new file mode 100644
index 0000000..5fd417f
--- /dev/null
+++ b/src/core/types/WeightManager.ts
@@ -0,0 +1,9 @@
+import { weights } from '@/controller/types/weights.ts';
+import { networkConfig } from '@/controller/constants/types/networkConfig.ts';
+
+export interface IWeightManager {
+ _cache: null | weights;
+ config: networkConfig;
+ path: string;
+ getWeights(): Promise;
+}
diff --git a/src/core/utils/createRandomWeights.js b/src/core/utils/createRandomWeights.js
deleted file mode 100644
index 8ff2a1f..0000000
--- a/src/core/utils/createRandomWeights.js
+++ /dev/null
@@ -1,7 +0,0 @@
-export const createRandomWeight = (col, row) =>
- Array.from({ length: col }, () =>
- Array.from({ length: row }, () => {
- const value = (Math.random() - .5) * 1.99999;
- return value;
- })
- );
\ No newline at end of file
diff --git a/src/core/utils/createRandomWeights.ts b/src/core/utils/createRandomWeights.ts
new file mode 100644
index 0000000..b5d3086
--- /dev/null
+++ b/src/core/utils/createRandomWeights.ts
@@ -0,0 +1,7 @@
+export const createRandomWeight = (col: number, row: number): number[][] =>
+ Array.from({ length: col }, (): number[] =>
+ Array.from({ length: row }, (): number => {
+ const value: number = (Math.random() - 0.5) * 1.99999;
+ return value;
+ }),
+ );
diff --git a/src/core/utils/loadPretrainedWeights.js b/src/core/utils/loadPretrainedWeights.js
deleted file mode 100644
index 9eab7ae..0000000
--- a/src/core/utils/loadPretrainedWeights.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export async function loadPretrainedWeights(path) {
- const response = await fetch(path);
- if(!response.ok) throw new Error(`Failed to load weight datas from ${path}`);
- return await response.json();
-}
\ No newline at end of file
diff --git a/src/core/utils/loadPretrainedWeights.ts b/src/core/utils/loadPretrainedWeights.ts
new file mode 100644
index 0000000..3e89e03
--- /dev/null
+++ b/src/core/utils/loadPretrainedWeights.ts
@@ -0,0 +1,7 @@
+import { weights } from '@/controller/types/weights';
+
+export async function loadPretrainedWeights(path: string): Promise {
+ const response: Response = await fetch(path);
+ if (!response.ok) throw new Error(`Failed to load weight data from ${path}`);
+ return await response.json();
+}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 807ff3c..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import AppController from './controller/AppContoller.js';
-import { DataStore } from "./controller/DataStore.js";
-
-const App = new AppController({DataStore});
-await App.initialize();
-console.log("App initialized!");
\ No newline at end of file
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..70a5ce8
--- /dev/null
+++ b/src/main.ts
@@ -0,0 +1,5 @@
+import AppController from './controller/AppController.js';
+
+const App = new AppController();
+await App.initialize();
+console.log('App initialized!');
diff --git a/src/view/DataPresnter.ts b/src/view/DataPresnter.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/view/ViewHandler.js b/src/view/ViewHandler.ts
similarity index 100%
rename from src/view/ViewHandler.js
rename to src/view/ViewHandler.ts
diff --git a/src/view/components/CanvasComponentBase.js b/src/view/components/CanvasComponentBase.js
index bf544ff..2af8a95 100644
--- a/src/view/components/CanvasComponentBase.js
+++ b/src/view/components/CanvasComponentBase.js
@@ -30,7 +30,6 @@ class CanvasComponentBase {
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
-
}
-export default CanvasComponentBase;
\ No newline at end of file
+export default CanvasComponentBase;
diff --git a/src/view/components/canvasUtils/BoundingBox.js b/src/view/components/canvasUtils/BoundingBox.js
deleted file mode 100644
index 7fe4911..0000000
--- a/src/view/components/canvasUtils/BoundingBox.js
+++ /dev/null
@@ -1,38 +0,0 @@
-class BoundingBox {
- constructor() {
- this.initialCoords = {
- minX: Infinity,
- minY: Infinity,
- maxX: -Infinity,
- maxY: -Infinity,
- }
- this.coordinate = {...this.initialCoords};
- this.originalObject = {};
- }
-
- update(currentX, currentY) {
- this.coordinate = {
- minX: Math.round(Math.min(this.coordinate.minX, currentX)),
- minY: Math.round(Math.min(this.coordinate.minY, currentY)),
- maxX: Math.round(Math.max(this.coordinate.maxX, currentX)),
- maxY: Math.round(Math.max(this.coordinate.maxY, currentY)),
- }
- Object.assign(this.originalObject,
- {
- x: this.coordinate.minX-20,
- y: this.coordinate.minY-20,
- width: this.coordinate.maxX+40 - this.coordinate.minX,
- height: this.coordinate.maxY+40 - this.coordinate.minY,});
- }
-
- log() {
- console.log(this.coordinate);
- console.log(this.originalObject);
- }
-
- reset() {
- this.coordinate = {...this.initialCoords};
- }
-}
-
-export default new BoundingBox;
\ No newline at end of file
diff --git a/src/view/components/canvasUtils/BoundingBox.ts b/src/view/components/canvasUtils/BoundingBox.ts
new file mode 100644
index 0000000..bd7b1b1
--- /dev/null
+++ b/src/view/components/canvasUtils/BoundingBox.ts
@@ -0,0 +1,53 @@
+import { IinitialCoords, originalObject } from './types';
+
+class BoundingBox {
+ private readonly initialCoords: IinitialCoords;
+ private coordinate: IinitialCoords;
+ private originalObject: originalObject;
+
+ constructor() {
+ this.initialCoords = {
+ minX: Infinity,
+ minY: Infinity,
+ maxX: -Infinity,
+ maxY: -Infinity,
+ };
+ this.coordinate = { ...this.initialCoords };
+ this.originalObject = {
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ };
+ }
+
+ getOriginalObject(): Readonly {
+ return { ...this.originalObject };
+ }
+
+ update(currentX: number, currentY: number): void {
+ this.coordinate = {
+ minX: Math.round(Math.min(this.coordinate.minX, currentX)),
+ minY: Math.round(Math.min(this.coordinate.minY, currentY)),
+ maxX: Math.round(Math.max(this.coordinate.maxX, currentX)),
+ maxY: Math.round(Math.max(this.coordinate.maxY, currentY)),
+ };
+ Object.assign(this.originalObject, {
+ x: this.coordinate.minX - 20,
+ y: this.coordinate.minY - 20,
+ width: this.coordinate.maxX + 40 - this.coordinate.minX,
+ height: this.coordinate.maxY + 40 - this.coordinate.minY,
+ });
+ }
+
+ log(): void {
+ console.log(this.coordinate);
+ console.log(this.originalObject);
+ }
+
+ reset(): void {
+ this.coordinate = { ...this.initialCoords };
+ }
+}
+
+export default new BoundingBox();
diff --git a/src/view/components/canvasUtils/types.ts b/src/view/components/canvasUtils/types.ts
new file mode 100644
index 0000000..b9e8646
--- /dev/null
+++ b/src/view/components/canvasUtils/types.ts
@@ -0,0 +1,19 @@
+export interface IinitialCoords {
+ minX: number;
+ minY: number;
+ maxX: number;
+ maxY: number;
+}
+
+export interface originalObject {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+}
+
+interface BoundingBox {
+ update(currentX: number, currentY: number): void;
+ log(): void;
+ reset(): void;
+}
diff --git a/src/view/components/perceptron/Perceptron.ts b/src/view/components/perceptron/Perceptron.ts
new file mode 100644
index 0000000..e69de29
diff --git a/src/view/components/userQuery/AlignCanvas.js b/src/view/components/userQuery/AlignCanvas.js
deleted file mode 100644
index c5ae35e..0000000
--- a/src/view/components/userQuery/AlignCanvas.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import BoundingBox from "../canvasUtils/BoundingBox.js";
-
-class AlignCanvas {
- constructor() {
- // this.canvas = document.createElement('alignCanvas');
- this.canvas = document.getElementById('alignCanvas');
- this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
- this.canvas.style.border = '2px solid red';
-
- const { minX, minY, maxX, maxY } = BoundingBox.coordinate;
- this.setupCanvas();
- }
- setupCanvas() {
- this.ctx.fillStyle = 'rgba(0, 0, 0)';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- this.updateCanvasScale();
- }
- updateCanvasScale() {
- if(BoundingBox.originalObject.width > BoundingBox.originalObject.height){
- this.canvas.width = BoundingBox.originalObject.width*1.3;
- this.canvas.height = BoundingBox.originalObject.width*1.3;
- } else {
- this.canvas.width = BoundingBox.originalObject.height*1.3;
- this.canvas.height = BoundingBox.originalObject.height*1.3;
- }
- }
-
- centralize(path) {
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- const alignStartPosition = {
- x: (this.canvas.width - BoundingBox.originalObject.width) / 2,
- y: (this.canvas.height - BoundingBox.originalObject.height) / 2,
- }
- this.ctx.drawImage(path,
- BoundingBox.originalObject.x, BoundingBox.originalObject.y,
- BoundingBox.originalObject.width, BoundingBox.originalObject.height,
- alignStartPosition.x, alignStartPosition.y,
- BoundingBox.originalObject.width, BoundingBox.originalObject.height
- );
- }
-}
-
-export default AlignCanvas;
\ No newline at end of file
diff --git a/src/view/components/userQuery/AlignCanvas.ts b/src/view/components/userQuery/AlignCanvas.ts
new file mode 100644
index 0000000..691f45c
--- /dev/null
+++ b/src/view/components/userQuery/AlignCanvas.ts
@@ -0,0 +1,53 @@
+import BoundingBox from '../canvasUtils/BoundingBox.ts';
+
+class AlignCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+
+ constructor() {
+ this.canvas = document.createElement('canvas') as HTMLCanvasElement;
+ this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+ this.canvas.style.border = '2px solid red';
+ this.setupCanvas();
+ }
+ setupCanvas(): void {
+ this.ctx.fillStyle = 'rgba(0, 0, 0)';
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ this.updateCanvasScale();
+ }
+ updateCanvasScale(): void {
+ if (BoundingBox.getOriginalObject().width > BoundingBox.getOriginalObject().height) {
+ this.canvas.width = BoundingBox.getOriginalObject().width * 1.3;
+ this.canvas.height = BoundingBox.getOriginalObject().width * 1.3;
+ } else {
+ this.canvas.width = BoundingBox.getOriginalObject().height * 1.3;
+ this.canvas.height = BoundingBox.getOriginalObject().height * 1.3;
+ }
+ }
+
+ centralize(path: HTMLCanvasElement): void {
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ const alignStartPosition = {
+ x: (this.canvas.width - BoundingBox.getOriginalObject().width) / 2,
+ y: (this.canvas.height - BoundingBox.getOriginalObject().height) / 2,
+ };
+ this.ctx.drawImage(
+ path,
+ BoundingBox.getOriginalObject().x,
+ BoundingBox.getOriginalObject().y,
+ BoundingBox.getOriginalObject().width,
+ BoundingBox.getOriginalObject().height,
+ alignStartPosition.x,
+ alignStartPosition.y,
+ BoundingBox.getOriginalObject().width,
+ BoundingBox.getOriginalObject().height,
+ );
+ }
+
+ clear(): void {
+ this.canvas.width = this.canvas.height = 1;
+ this.setupCanvas();
+ }
+}
+
+export default AlignCanvas;
diff --git a/src/view/components/userQuery/PathTrackingCanvas.js b/src/view/components/userQuery/PathTrackingCanvas.ts
similarity index 55%
rename from src/view/components/userQuery/PathTrackingCanvas.js
rename to src/view/components/userQuery/PathTrackingCanvas.ts
index 2f0248e..bd2b4b2 100644
--- a/src/view/components/userQuery/PathTrackingCanvas.js
+++ b/src/view/components/userQuery/PathTrackingCanvas.ts
@@ -1,16 +1,18 @@
-import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js";
+import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts';
class PathTrackingCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+
constructor() {
- // this.strokeCanvas = document.createElement('pathTrackingCanvas');
- this.canvas = document.getElementById('pathTrackingCanvas');
+ this.canvas = document.createElement('canvas') as HTMLCanvasElement;
this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
- this.setupCanvas()
+ this.setupCanvas();
}
- setupCanvas() {
+ setupCanvas(): void {
this.canvas.width = window.innerWidth;
- this.canvas.height = window.innerHeight / 3;
+ this.canvas.height = window.innerHeight / 3;
this.ctx.fillStyle = 'rgba(0, 0, 0)';
this.ctx.strokeStyle = 'rgba(255, 255, 255)';
@@ -22,19 +24,23 @@ class PathTrackingCanvas {
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
- startPath(x, y) {
+ startPath(x: number, y: number): void {
this.ctx.beginPath();
this.ctx.moveTo(x, y);
}
- drawPath(x, y) {
+ drawPath(x: number, y: number): void {
this.ctx.lineTo(x, y);
this.ctx.stroke();
}
- endPath() {
+ endPath(): void {
this.ctx.closePath();
}
+
+ clear(): void {
+ this.setupCanvas();
+ }
}
-export default PathTrackingCanvas;
\ No newline at end of file
+export default PathTrackingCanvas;
diff --git a/src/view/components/userQuery/ResizeCanvas.js b/src/view/components/userQuery/ResizeCanvas.js
deleted file mode 100644
index c424a57..0000000
--- a/src/view/components/userQuery/ResizeCanvas.js
+++ /dev/null
@@ -1,15 +0,0 @@
-class ResizeCanvas {
- constructor() {
- this.canvas = document.getElementById("resizeCanvas");
- this.ctx = this.canvas.getContext("2d", { willReadFrequently: true });
- this.canvas.width = this.canvas.height = 28;
- }
-
- downScale(path) {
- this.ctx.fillStyle = 'rgba(255,255,255)';
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- this.ctx.drawImage(path, 0, 0, path.width, path.height, 0, 0, this.canvas.width, this.canvas.height);
- }
-}
-
-export default ResizeCanvas;
\ No newline at end of file
diff --git a/src/view/components/userQuery/ResizeCanvas.ts b/src/view/components/userQuery/ResizeCanvas.ts
new file mode 100644
index 0000000..96a4275
--- /dev/null
+++ b/src/view/components/userQuery/ResizeCanvas.ts
@@ -0,0 +1,37 @@
+class ResizeCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+
+ constructor() {
+ this.canvas = document.createElement('canvas') as HTMLCanvasElement;
+ this.ctx = this.canvas.getContext('2d', { willReadFrequently: true });
+ this.setupCanvas();
+ }
+
+ setupCanvas(): void {
+ this.canvas.width = this.canvas.height = 28;
+ this.ctx.fillStyle = 'rgba(255,255,255)';
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+
+ downScale(path: HTMLCanvasElement): void {
+ this.ctx.drawImage(
+ path,
+ 0,
+ 0,
+ path.width,
+ path.height,
+ 0,
+ 0,
+ this.canvas.width,
+ this.canvas.height,
+ );
+ }
+
+ clear(): void {
+ this.setupCanvas();
+ // this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+}
+
+export default ResizeCanvas;
diff --git a/src/view/components/userQuery/UserInputCanvas.js b/src/view/components/userQuery/UserInputCanvas.ts
similarity index 72%
rename from src/view/components/userQuery/UserInputCanvas.js
rename to src/view/components/userQuery/UserInputCanvas.ts
index 6693753..564819a 100644
--- a/src/view/components/userQuery/UserInputCanvas.js
+++ b/src/view/components/userQuery/UserInputCanvas.ts
@@ -1,8 +1,12 @@
-import { CANVAS_CONFIG } from "../../../controller/constants/canvasConfig.js";
+import { CANVAS_CONFIG } from '@/controller/constants/canvasConfig.ts';
class UserInputCanvas {
+ public readonly canvas: HTMLCanvasElement | null;
+ public readonly ctx: CanvasRenderingContext2D;
+ private isDrawing: boolean;
+
constructor() {
- this.canvas = document.getElementById('userInputCanvas');
+ this.canvas = document.getElementById('userInputCanvas') as HTMLCanvasElement;
this.ctx = this.canvas.getContext('2d');
this.isDrawing = false;
this.setupCanvas();
@@ -13,11 +17,13 @@ class UserInputCanvas {
this.ctx.lineJoin = CANVAS_CONFIG.lineJoin;
}
- clear = () => {
+ clear = (): void => {
+ this.ctx.fillStyle = 'rgba(40,40,40)';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
- }
+ this.drawGridDots();
+ };
- setupCanvas() {
+ setupCanvas(): void {
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight / 3;
@@ -33,7 +39,7 @@ class UserInputCanvas {
this.ctx.fillStyle = 'rgba(255,255,255,0.3)';
this.ctx.arc(x, y, 1, 0, 2 * Math.PI);
this.ctx.fill();
- }
+ };
for (let x = 5; x < this.canvas.width; x += 12) {
for (let y = 5; y < this.canvas.height; y += 12) {
@@ -42,19 +48,19 @@ class UserInputCanvas {
}
}
- startPath(x, y) {
+ startPath(x: number, y: number): void {
this.ctx.beginPath();
this.ctx.moveTo(x, y);
}
- drawPath(x, y) {
+ drawPath(x: number, y: number): void {
this.ctx.lineTo(x, y);
this.ctx.stroke();
}
- endPath() {
+ endPath(): void {
this.ctx.closePath();
}
}
-export default UserInputCanvas;
\ No newline at end of file
+export default UserInputCanvas;
diff --git a/src/view/components/userQuery/types/canvas.ts b/src/view/components/userQuery/types/canvas.ts
new file mode 100644
index 0000000..7d0936a
--- /dev/null
+++ b/src/view/components/userQuery/types/canvas.ts
@@ -0,0 +1,23 @@
+export interface ICanvasBase {
+ setupCanvas(): void;
+ clear(): void;
+ canvas: HTMLCanvasElement;
+ ctx: CanvasRenderingContext2D;
+}
+
+export interface IUserInputCanvas extends IPathTrackingCanvas {
+ drawGridDots(): void;
+}
+export interface IPathTrackingCanvas {
+ startPath(x: number, y: number): void;
+ drawPath(x: number, y: number): void;
+ endPath(): void;
+}
+export interface IAlignCanvas {
+ updateCanvasScale(): void;
+ centralize(path: HTMLCanvasElement): void;
+}
+
+export interface IResizeCanvas {
+ downScale(path: HTMLCanvasElement): void;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..e5e1ea1
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ES2022",
+ "moduleResolution": "Node",
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": false,
+ "allowJs": true,
+ "checkJs": false,
+ "esModuleInterop": true,
+ "baseUrl": "./src",
+ "paths": {
+ "@/*": ["*"]
+ }
+ },
+ "include": ["src/**/*"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..1f6b4b0
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,17 @@
+import { defineConfig } from 'vite';
+
+export default defineConfig({
+ root: './src',
+ resolve: {
+ alias: [{ find: '@', replacement: path.resolve(__dirname, 'src') }],
+ },
+ publicDir: 'public',
+ build: {
+ outDir: 'dist',
+ target: 'esnext',
+ },
+ server: {
+ port: 5173,
+ open: true,
+ },
+});