diff --git a/.gitignore b/.gitignore index 386ac2b..b10a13f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,11 @@ main.js # Exclude sourcemaps *.map -# obsidian +# obsidian data data.json # Exclude macOS Finder (System Explorer) View States .DS_Store + +# Exclude test +*.txt \ No newline at end of file diff --git a/manifest.json b/manifest.json index 3e9120d..386cc5b 100644 --- a/manifest.json +++ b/manifest.json @@ -1,10 +1,10 @@ { "id": "auto-classifier", "name": "Auto Classifier", - "version": "1.2.0", + "version": "1.3.0", "minAppVersion": "1.1.1", - "description": "This plugin automatically classify tag from your notes using ChatGPT API. It analyze your note (It can be title, frontmatter, content or selected area) and automatically insert tag where you set.", - "author": "Hyeonseo Nam", + "description": "This plugin automatically classify tag from your notes using ChatGPT API or Jina Classifier. It analyze your note (It can be title, frontmatter, content or selected area) and automatically insert tag where you set.", + "author": "Hyeonseo Nam, Thiago Moraes", "authorUrl": "https://github.com/HyeonseoNam/auto-classifier", "isDesktopOnly": false } diff --git a/package-lock.json b/package-lock.json index 45e40a9..a58cf29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "auto-classifier", - "version": "1.0.0", + "version": "1.1.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "auto-classifier", - "version": "1.0.0", + "version": "1.1.2", "license": "MIT", "devDependencies": { "@types/node": "^16.11.6", @@ -15,6 +15,7 @@ "builtin-modules": "3.3.0", "esbuild": "0.17.3", "obsidian": "latest", + "openai": "4.76.1", "tslib": "2.4.0", "typescript": "4.7.4" } @@ -212,6 +213,16 @@ "integrity": "sha512-wvzClDGQXOCVNU4APPopC2KtMYukaF1MN/W3xAmslx22Z4/IF1/izDMekuyoUlwfnDHYCIZGaj7jMwnJKBTxKw==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/tern": { "version": "0.23.4", "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", @@ -405,6 +416,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", @@ -428,6 +451,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "dev": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -487,6 +522,12 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -529,6 +570,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -576,6 +630,18 @@ "dev": true, "peer": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -622,6 +688,15 @@ "dev": true, "peer": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -647,6 +722,65 @@ "node": ">=6.0.0" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.17.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz", @@ -909,6 +1043,15 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1030,6 +1173,41 @@ "dev": true, "peer": true }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "dev": true + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "dev": true, + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1037,12 +1215,58 @@ "dev": true, "peer": true }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1113,6 +1337,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", @@ -1130,6 +1366,54 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "dev": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1318,6 +1602,15 @@ "node": ">=10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1340,6 +1633,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1375,6 +1689,46 @@ "dev": true, "peer": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/obsidian": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.1.1.tgz", @@ -1399,6 +1753,41 @@ "wrappy": "1" } }, + "node_modules/openai": { + "version": "4.76.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.76.1.tgz", + "integrity": "sha512-ci63/WFEMd6QjjEVeH0pV7hnFS6CCqhgJydSti4Aak/8uo2SpgzKjteUDaY+OkwziVj11mi6j+0mRUIiGKUzWw==", + "dev": true, + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.115", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.115.tgz", + "integrity": "sha512-kNrFiTgG4a9JAn1LMQeLOv3MvXIPokzXziohMrMsvpYgLpdEt/mMiVYc4sGKtDfyxM5gIDF4VgrPRyCw4fHOYg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -1736,6 +2125,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, "node_modules/tslib": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", @@ -1802,6 +2197,12 @@ "node": ">=4.2.0" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1819,6 +2220,31 @@ "dev": true, "peer": true }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 4ecbfaa..3231580 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,6 @@ "obsidian": "latest", "tslib": "2.4.0", "typescript": "4.7.4", - "openai": "4.76.0" + "openai": "4.76.1" } } diff --git a/src/jina-api.ts b/src/jina-api.ts new file mode 100644 index 0000000..7a237aa --- /dev/null +++ b/src/jina-api.ts @@ -0,0 +1,86 @@ +import { requestUrl } from 'obsidian'; + +export interface JinaAPIRequest { + model: string; + input: Array<{ text: string }>; + labels: string[]; +} + +export interface JinaAPIResponsePrediction { + label: string; + score: number; +} + +export interface JinaAPIResponseData { + object: string; + index: number; + prediction: string; + score: number; + predictions: JinaAPIResponsePrediction[]; +} + +export interface JinaAPIResponse { + usage: { + total_tokens: number; + }; + data: JinaAPIResponseData[]; +} + +export class JinaAI { + static async callAPI( + apiKey: string, + baseURL: string, + model: string, + inputText: string[], // Changed from string to string[] to allow multiple inputs if needed by the plugin logic later + labels: string[] + ): Promise { + const apiUrl = `${baseURL}/classify`; // Assuming baseURL does not contain /classify + const requestBody: JinaAPIRequest = { + model: model, + input: inputText.map(text => ({ text })), + labels: labels, + }; + + console.log('Jina AI Request:', { url: apiUrl, body: requestBody }); + + try { + const response = await requestUrl({ + url: apiUrl, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${apiKey}`, + }, + body: JSON.stringify(requestBody), + throw: false, // To handle errors manually + }); + + console.log('Jina AI Response Status:', response.status); + console.log('Jina AI Response Body:', response.text); // Log raw text for debugging + + if (response.status !== 200) { + let errorDetails = response.text; + try { + const jsonError = JSON.parse(response.text); + errorDetails = jsonError.detail || JSON.stringify(jsonError); + } catch (e) { + // Keep errorDetails as text if not JSON + } + const errorMessage = `Jina AI API Error: ${response.status} - ${errorDetails}`; + console.error(errorMessage); + throw new Error(errorMessage); + } + + const responseData: JinaAPIResponse = response.json; + console.log('Jina AI Parsed Response Data:', responseData); + return responseData; + + } catch (error) { + console.error('Error calling Jina AI API:', error); + if (error instanceof Error) { + throw error; + } + throw new Error('An unknown error occurred while calling Jina AI API.'); + } + } +} diff --git a/src/main.ts b/src/main.ts index e3f9347..a340b68 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,13 +1,21 @@ import { Plugin, Notice } from "obsidian"; -import { AutoClassifierSettingTab, AutoClassifierSettings, DEFAULT_SETTINGS, OutLocation, OutType} from "src/settings"; +import { + AutoClassifierSettingTab, + AutoClassifierSettings, + DEFAULT_SETTINGS, + OutLocation, + OutType, + ClassifierEngine, // Added ClassifierEngine +} from "src/settings"; import { ViewManager } from "src/view-manager"; -import { ChatGPT } from 'src/api'; +import { ChatGPT } from "src/api"; +import { JinaAI, JinaAPIResponse } from "src/jina-api"; // Added JinaAI and JinaAPIResponse enum InputType { SelectedArea, Title, FrontMatter, - Content + Content, } export default class AutoClassifierPlugin extends Plugin { @@ -19,32 +27,32 @@ export default class AutoClassifierPlugin extends Plugin { // Commands this.addCommand({ - id: 'classify-tag-selected', - name: 'Classify tag from Selected Area', + id: "classify-tag-selected", + name: "Classify tag from Selected Area", callback: async () => { await this.runClassifyTag(InputType.SelectedArea); - } + }, }); this.addCommand({ - id: 'classify-tag-title', - name: 'Classify tag from Note Title', + id: "classify-tag-title", + name: "Classify tag from Note Title", callback: async () => { await this.runClassifyTag(InputType.Title); - } + }, }); this.addCommand({ - id: 'classify-tag-frontmatter', - name: 'Classify tag from FrontMatter', + id: "classify-tag-frontmatter", + name: "Classify tag from FrontMatter", callback: async () => { await this.runClassifyTag(InputType.FrontMatter); - } + }, }); this.addCommand({ - id: 'classify-tag-content', - name: 'Classify tag from Note Content', + id: "classify-tag-content", + name: "Classify tag from Note Content", callback: async () => { await this.runClassifyTag(InputType.Content); - } + }, }); this.addSettingTab(new AutoClassifierSettingTab(this.app, this)); @@ -57,7 +65,26 @@ export default class AutoClassifierPlugin extends Plugin { await this.saveData(this.settings); } - async onunload() { + async onunload() {} + + // create loading spin in the Notice message + createLoadingNotice(text: string, number = 10000): Notice { + const notice = new Notice("", number); + const loadingContainer = document.createElement("div"); + loadingContainer.addClass("loading-container"); + + const loadingIcon = document.createElement("div"); + loadingIcon.addClass("loading-icon"); + const loadingText = document.createElement("span"); + loadingText.textContent = text; + //@ts-ignore + notice.noticeEl.empty(); + loadingContainer.appendChild(loadingIcon); + loadingContainer.appendChild(loadingText); + //@ts-ignore + notice.noticeEl.appendChild(loadingContainer); + + return notice; } async runClassifyTag(inputType: InputType) { @@ -73,31 +100,35 @@ export default class AutoClassifierPlugin extends Plugin { // Main Classification async classifyTag(inputType: InputType) { const commandOption = this.settings.commandOption; + const currentEngine = this.settings.classifierEngine; + // ------- [API Key check] ------- - if (!this.settings.apiKey) { - new Notice(`⛔ ${this.manifest.name}: You shuld input your API Key`); - return null + if (currentEngine === ClassifierEngine.ChatGPT && !this.settings.apiKey) { + new Notice(`⛔ ${this.manifest.name}: ChatGPT API Key is missing.`); + return null; } + if (currentEngine === ClassifierEngine.JinaAI && !this.settings.jinaApiKey) { + new Notice(`⛔ ${this.manifest.name}: Jina AI API Key is missing.`); + return null; + } + // ------- [Input] ------- const refs = this.settings.commandOption.refs; // reference check if (this.settings.commandOption.useRef && (!refs || refs.length == 0)) { new Notice(`⛔ ${this.manifest.name}: no reference tags`); - return null + return null; } - // Set Input - let input: string | null = ''; + // Set Input + let input: string | null = ""; if (inputType == InputType.SelectedArea) { input = await this.viewManager.getSelection(); - } - else if (inputType == InputType.Title) { + } else if (inputType == InputType.Title) { input = await this.viewManager.getTitle(); - } - else if (inputType == InputType.FrontMatter) { + } else if (inputType == InputType.FrontMatter) { input = await this.viewManager.getFrontMatter(); - } - else if (inputType == InputType.Content) { + } else if (inputType == InputType.Content) { input = await this.viewManager.getContent(); } @@ -107,86 +138,135 @@ export default class AutoClassifierPlugin extends Plugin { return null; } - // Replace {{input}}, {{reference}} - let user_prompt = this.settings.commandOption.prmpt_template; - user_prompt = user_prompt.replace('{{input}}', input); - user_prompt = user_prompt.replace('{{reference}}', refs.join(',')); + // Replace {{input}}, {{reference}} for ChatGPT + let user_prompt = ""; + let system_role = ""; - const system_role = this.settings.commandOption.prmpt_template; + if (currentEngine === ClassifierEngine.ChatGPT) { + user_prompt = this.settings.commandOption.prmpt_template; + user_prompt = user_prompt.replace("{{input}}", input); + user_prompt = user_prompt.replace("{{reference}}", refs.join(",")); + system_role = this.settings.commandOption.chat_role; // Corrected from prmpt_template + } // ------- [API Processing] ------- - // Call API - const responseRaw = await ChatGPT.callAPI( - system_role, - user_prompt, - this.settings.apiKey, - this.settings.commandOption.model, - this.settings.commandOption.max_tokens, - undefined, - undefined, - undefined, - undefined, - this.settings.baseURL, - ); - const jsonRegex = /reliability[\s\S]*?:\s*([\d.]+)[\s\S]*?output[\s\S]*?:\s*"([^"^}]+)/; - const match = responseRaw.match(jsonRegex); - let resOutput; - let resReliabity; - if (match && match.length > 1) { - resOutput = match[2]; - resReliabity = parseFloat(match[1]); - } else { - new Notice(`⛔ ${this.manifest.name}: output format error (output: ${responseRaw})`); - return null; - } - + try { + let outputs: string[] = []; - // Avoid row reliability - if (resReliabity <= 0.2) { - new Notice(`⛔ ${this.manifest.name}: response has row reliability (${resReliabity})`); - return null; - } + if (currentEngine === ClassifierEngine.ChatGPT) { + const responseRaw = await ChatGPT.callAPI( + system_role, + user_prompt, + this.settings.apiKey, + this.settings.commandOption.model, + this.settings.commandOption.max_tokens, + undefined, + undefined, + undefined, + undefined, + this.settings.baseURL, + ); + // Parse ChatGPT response + try { + const response = JSON.parse(responseRaw.replace(/^```json\n/, "").replace(/\n```$/, "")); + const resReliability = response.reliability; + const resOutputs = response.outputs; - // ------- [Add Tag] ------- - // Output Type 1. [Tag Case] + Output Type 2. [Wikilink Case] - if (commandOption.outType == OutType.Tag || commandOption.outType == OutType.Wikilink) { - if (commandOption.outLocation == OutLocation.Cursor) { - this.viewManager.insertAtCursor(resOutput, commandOption.overwrite, commandOption.outType, commandOption.outPrefix, commandOption.outSuffix); - } - else if (commandOption.outLocation == OutLocation.ContentTop) { - this.viewManager.insertAtContentTop(resOutput, commandOption.outType, commandOption.outPrefix, commandOption.outSuffix); + if (!Array.isArray(resOutputs)) { + new Notice(`⛔ ${this.manifest.name}: ChatGPT output format error (expected array)`); + return null; + } + if (resReliability <= 0.2 && commandOption.useRef) { // Reliability check only if using references + new Notice( + `⛔ ${this.manifest.name}: ChatGPT response has low reliability (${resReliability})`, + ); + return null; + } + outputs = resOutputs; + } catch (error) { + new Notice(`⛔ ${this.manifest.name}: ChatGPT JSON parsing error - ${error}`); + console.error("ChatGPT JSON parsing error:", error, "Raw response:", responseRaw); + return null; + } + } else if (currentEngine === ClassifierEngine.JinaAI) { + const jinaResponse: JinaAPIResponse = await JinaAI.callAPI( + this.settings.jinaApiKey, + this.settings.jinaBaseURL, + this.settings.commandOption.model || 'jina-embeddings-v3', // Ensure model is passed + [input], // Jina expects an array of texts + refs + ); + // Extract labels from Jina AI response + // Assuming we take the top prediction for the first input text. + // If multiple inputs were sent, this logic would need to iterate jinaResponse.data + if (jinaResponse.data && jinaResponse.data.length > 0) { + // Sort predictions by score and take the top ones + const sortedPredictions = jinaResponse.data[0].predictions.sort((a, b) => b.score - a.score); + outputs = sortedPredictions.map(p => p.label); + } else { + new Notice(`⛔ ${this.manifest.name}: Jina AI returned no data.`); + return null; + } } - } - // Output Type 3. [Frontmatter Case] - else if (commandOption.outType == OutType.FrontMatter) { - this.viewManager.insertAtFrontMatter(commandOption.key, resOutput, commandOption.overwrite, commandOption.outPrefix, commandOption.outSuffix); - } - // Output Type 4. [Title] - else if (commandOption.outType == OutType.Title) { - this.viewManager.insertAtTitle(resOutput, commandOption.overwrite, commandOption.outPrefix, commandOption.outSuffix); - } - new Notice(`✅ ${this.manifest.name}: classified to ${resOutput}`); - } - // create loading spin in the Notice message - createLoadingNotice(text: string, number = 10000): Notice { - const notice = new Notice('', number); - const loadingContainer = document.createElement('div'); - loadingContainer.addClass('loading-container'); + // Limit number of suggestions + const limitedOutputs = outputs.slice(0, this.settings.commandOption.max_suggestions); - const loadingIcon = document.createElement('div'); - loadingIcon.addClass('loading-icon'); - const loadingText = document.createElement('span'); - loadingText.textContent = text; - //@ts-ignore - notice.noticeEl.empty(); - loadingContainer.appendChild(loadingIcon); - loadingContainer.appendChild(loadingText); - //@ts-ignore - notice.noticeEl.appendChild(loadingContainer); + if (limitedOutputs.length === 0) { + new Notice(`⛔ ${this.manifest.name}: No tags were classified.`); + return null; + } - return notice; + // ------- [Add Tags] ------- + for (const resOutput of limitedOutputs) { + // Output Type 1. [Tag Case] + Output Type 2. [Wikilink Case] + if ( + commandOption.outType == OutType.Tag || + commandOption.outType == OutType.Wikilink + ) { + if (commandOption.outLocation == OutLocation.Cursor) { + this.viewManager.insertAtCursor( + resOutput, + commandOption.overwrite, + commandOption.outType, + commandOption.outPrefix, + commandOption.outSuffix, + ); + } else if (commandOption.outLocation == OutLocation.ContentTop) { + this.viewManager.insertAtContentTop( + resOutput, + commandOption.outType, + commandOption.outPrefix, + commandOption.outSuffix, + ); + } + } + // Output Type 3. [Frontmatter Case] + else if (commandOption.outType == OutType.FrontMatter) { + this.viewManager.insertAtFrontMatter( + commandOption.key, + resOutput, + commandOption.overwrite, + commandOption.outPrefix, + commandOption.outSuffix, + ); + } + // Output Type 4. [Title] + else if (commandOption.outType == OutType.Title) { + this.viewManager.insertAtTitle( + resOutput, + commandOption.overwrite, + commandOption.outPrefix, + commandOption.outSuffix, + ); + } + } + new Notice(`✅ ${this.manifest.name}: classified with ${limitedOutputs.length} tags using ${ClassifierEngine[currentEngine]}.`); + + } catch (error: any) { + new Notice(`⛔ ${this.manifest.name} API Error: ${error.message || error}`); + console.error(`${ClassifierEngine[currentEngine]} API Error:`, error); + return null; + } } } - - diff --git a/src/settings.ts b/src/settings.ts index f491390..7b2ed2a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,8 +1,14 @@ import { App, Notice, PluginSettingTab, Setting } from "obsidian"; import { ChatGPT } from 'src/api'; +import { JinaAI } from 'src/jina-api'; // Added JinaAI import import type AutoClassifierPlugin from "src/main"; import { DEFAULT_CHAT_ROLE, DEFAULT_PROMPT_TEMPLATE, DEFAULT_PROMPT_TEMPLATE_WO_REF } from 'src/template' +export enum ClassifierEngine { // Added ClassifierEngine enum + ChatGPT, + JinaAI, +} + export enum ReferenceType { All, Filter, @@ -49,6 +55,7 @@ export interface CommandOption { prmpt_template: string; model: string; max_tokens: number; + max_suggestions: number; } @@ -56,6 +63,9 @@ export class AutoClassifierSettings { apiKey: string; apiKeyCreatedAt: Date | null; baseURL: string; + classifierEngine: ClassifierEngine; // Added classifierEngine + jinaApiKey: string; // Added jinaApiKey + jinaBaseURL: string; // Added jinaBaseURL commandOption: CommandOption; } @@ -63,6 +73,9 @@ export const DEFAULT_SETTINGS: AutoClassifierSettings = { apiKey: '', apiKeyCreatedAt: null, baseURL: 'https://api.openai.com/v1', + classifierEngine: ClassifierEngine.ChatGPT, // Default to ChatGPT + jinaApiKey: 'jina_e24cf807496a48d9be7ea660bd652a37x2ZJYPjVDlgMU69KQvKSepRDbTgM', // Default Jina API Key + jinaBaseURL: 'https://api.jina.ai/v1', // Default Jina Base URL commandOption: { useRef: true, refs: [], @@ -82,6 +95,7 @@ export const DEFAULT_SETTINGS: AutoClassifierSettings = { prmpt_template: DEFAULT_PROMPT_TEMPLATE, model: "gpt-3.5-turbo", max_tokens: 150, + max_suggestions: 3, }, }; @@ -119,79 +133,166 @@ export class AutoClassifierSettingTab extends PluginSettingTab { // ------- [API Setting] ------- - // API Key input containerEl.createEl('h1', { text: 'API Setting' }); - new Setting(containerEl) - .setName('API Base URL') - .setDesc('Optional: Set a different base URL for API calls (e.g. for proxies)') - .addText((text) => - text - .setPlaceholder('https://api.openai.com/v1') - .setValue(this.plugin.settings.baseURL) - .onChange((value) => { - this.plugin.settings.baseURL = value; - this.plugin.saveSettings(); - }) - ); + // Classifier Engine Dropdown new Setting(containerEl) - .setName('Custom Model') - .setDesc("ID of the model to use. See https://platform.openai.com/docs/models") - .addText((text) => - text - .setPlaceholder('gpt-3.5-turbo') - .setValue(commandOption.model) + .setName('Classifier Engine') + .setDesc('Select the classification engine to use.') + .addDropdown((dropdown) => { + dropdown + .addOption(String(ClassifierEngine.ChatGPT), "ChatGPT") + .addOption(String(ClassifierEngine.JinaAI), "JinaAI") + .setValue(String(this.plugin.settings.classifierEngine)) .onChange(async (value) => { - commandOption.model = value; + this.plugin.settings.classifierEngine = parseInt(value) as ClassifierEngine; await this.plugin.saveSettings(); - }) - ); + this.display(); // Re-render settings to show/hide relevant fields + }); + }); - const apiKeySetting = new Setting(containerEl) - .setName('ChatGPT API Key') - .setDesc('') - .addText((text) => - text - .setPlaceholder('API key') - .setValue(this.plugin.settings.apiKey) - .onChange((value) => { - this.plugin.settings.apiKey = value; - this.plugin.saveSettings(); - }) - ) - // API Key Description & Message - apiKeySetting.descEl.createSpan({text: 'Enter your ChatGPT API key. If you don\'t have one yet, you can create it at '}); - apiKeySetting.descEl.createEl('a', {href: 'https://platform.openai.com/account/api-keys', text: 'here'}) - const apiTestMessageEl = document.createElement('div'); - apiKeySetting.descEl.appendChild(apiTestMessageEl); + // Conditional API Settings + if (this.plugin.settings.classifierEngine === ClassifierEngine.ChatGPT) { + new Setting(containerEl) + .setName('ChatGPT API Base URL') + .setDesc('Optional: Set a different base URL for ChatGPT API calls (e.g. for proxies)') + .addText((text) => + text + .setPlaceholder('https://api.openai.com/v1') + .setValue(this.plugin.settings.baseURL) + .onChange((value) => { + this.plugin.settings.baseURL = value; + this.plugin.saveSettings(); + }) + ); - //API Key default message - if (this.plugin.settings.apiKey && this.plugin.settings.apiKeyCreatedAt) { - apiTestMessageEl.setText(`This key was tested at ${this.plugin.settings.apiKeyCreatedAt.toString()}`); - apiTestMessageEl.style.color = 'var(--success-color)'; + new Setting(containerEl) + .setName('ChatGPT Model') + .setDesc("ID of the ChatGPT model to use. See https://platform.openai.com/docs/models") + .addText((text) => + text + .setPlaceholder('gpt-3.5-turbo') + .setValue(commandOption.model) // Assuming commandOption.model is for ChatGPT + .onChange(async (value) => { + commandOption.model = value; + await this.plugin.saveSettings(); + }) + ); + + const apiKeySetting = new Setting(containerEl) + .setName('ChatGPT API Key') + .setDesc('') + .addText((text) => + text + .setPlaceholder('API key') + .setValue(this.plugin.settings.apiKey) + .onChange((value) => { + this.plugin.settings.apiKey = value; + this.plugin.saveSettings(); + }) + ) + apiKeySetting.descEl.createSpan({ text: 'Enter your ChatGPT API key. If you don\'t have one yet, you can create it at ' }); + apiKeySetting.descEl.createEl('a', { href: 'https://platform.openai.com/account/api-keys', text: 'here' }) + const apiTestMessageEl = document.createElement('div'); + apiKeySetting.descEl.appendChild(apiTestMessageEl); + + if (this.plugin.settings.apiKey && this.plugin.settings.apiKeyCreatedAt) { + apiTestMessageEl.setText(`This key was tested at ${this.plugin.settings.apiKeyCreatedAt.toString()}`); + apiTestMessageEl.style.color = 'var(--success-color)'; + } + + apiKeySetting.addButton((cb) => { + cb.setButtonText('Test API call') + .setCta() + .onClick(async () => { + apiTestMessageEl.setText('Testing API call...'); + apiTestMessageEl.style.color = 'var(--text-normal)'; + try { + await ChatGPT.callAPI('', 'test', this.plugin.settings.apiKey, commandOption.model, undefined, undefined, undefined, undefined, undefined, this.plugin.settings.baseURL); + apiTestMessageEl.setText('Success! API working.'); + apiTestMessageEl.style.color = 'var(--success-color)'; + this.plugin.settings.apiKeyCreatedAt = new Date(); + } catch (error) { + apiTestMessageEl.setText('Error: API is not working. Check console for details.'); + apiTestMessageEl.style.color = 'var(--warning-color)'; + this.plugin.settings.apiKeyCreatedAt = null; + console.error("ChatGPT API Test Error:", error); + } + }); + }); + } else if (this.plugin.settings.classifierEngine === ClassifierEngine.JinaAI) { + new Setting(containerEl) + .setName('Jina AI API Base URL') + .setDesc('Optional: Set a different base URL for Jina AI API calls.') + .addText((text) => + text + .setPlaceholder('https://api.jina.ai/v1') + .setValue(this.plugin.settings.jinaBaseURL) + .onChange((value) => { + this.plugin.settings.jinaBaseURL = value; + this.plugin.saveSettings(); + }) + ); + + // Note: commandOption.model is currently shared. If Jina needs a separate model field, + // it should be added to AutoClassifierSettings and handled here. + // For now, we'll reuse commandOption.model but default it appropriately. + new Setting(containerEl) + .setName('Jina AI Model') + .setDesc("ID of the Jina AI model to use (e.g., jina-embeddings-v3).") + .addText((text) => + text + .setPlaceholder('jina-embeddings-v3') + .setValue(commandOption.model) // Reuse existing model field, ensure it's set to a Jina default if empty or on switch + .onChange(async (value) => { + commandOption.model = value; + await this.plugin.saveSettings(); + }) + ); + + const jinaApiKeySetting = new Setting(containerEl) + .setName('Jina AI API Key') + .setDesc('') + .addText((text) => + text + .setPlaceholder('Jina API key') + .setValue(this.plugin.settings.jinaApiKey) + .onChange((value) => { + this.plugin.settings.jinaApiKey = value; + this.plugin.saveSettings(); + }) + ); + jinaApiKeySetting.descEl.createSpan({ text: 'Enter your Jina AI API key.' }); + const jinaApiTestMessageEl = document.createElement('div'); + jinaApiKeySetting.descEl.appendChild(jinaApiTestMessageEl); + + // Jina API Key test button + jinaApiKeySetting.addButton((cb) => { + cb.setButtonText('Test Jina API') + .setCta() + .onClick(async () => { + jinaApiTestMessageEl.setText('Testing Jina API call...'); + jinaApiTestMessageEl.style.color = 'var(--text-normal)'; + try { + // Use a simple test case + await JinaAI.callAPI( + this.plugin.settings.jinaApiKey, + this.plugin.settings.jinaBaseURL, + commandOption.model || 'jina-embeddings-v3', // Fallback to default if model is not set + ['This is a test sentence.'], + ['positive', 'negative', 'neutral'] + ); + jinaApiTestMessageEl.setText('Success! Jina AI API working.'); + jinaApiTestMessageEl.style.color = 'var(--success-color)'; + } catch (error: any) { + jinaApiTestMessageEl.setText(`Error: Jina AI API is not working. ${error.message}`); + jinaApiTestMessageEl.style.color = 'var(--warning-color)'; + console.error("Jina AI API Test Error:", error); + } + }); + }); } - // API Key test button - apiKeySetting.addButton((cb) => { - cb.setButtonText('Test API call') - .setCta() - .onClick(async () => { - this.plugin.settings.apiKeyCreatedAt - apiTestMessageEl.setText('Testing api call...'); - apiTestMessageEl.style.color = 'var(--text-normal)'; - try { - await ChatGPT.callAPI('', 'test', this.plugin.settings.apiKey, this.plugin.settings.commandOption.model, undefined, undefined, undefined, undefined, undefined, this.plugin.settings.baseURL); - apiTestMessageEl.setText('Success! API working.'); - apiTestMessageEl.style.color = 'var(--success-color)'; - this.plugin.settings.apiKeyCreatedAt = new Date(); - } catch (error) { - apiTestMessageEl.setText('Error: API is not working.'); - apiTestMessageEl.style.color = 'var(--warning-color)'; - this.plugin.settings.apiKeyCreatedAt = null; - } - }); - }); - // ------- [Tag Reference Setting] ------- containerEl.createEl('h1', { text: 'Tag Reference Setting' }); @@ -422,112 +523,133 @@ export class AutoClassifierSettingTab extends PluginSettingTab { // ------- [Advanced Setting] ------- - // Toggle custom rule containerEl.createEl('h1', { text: 'Advanced Setting' }); + new Setting(containerEl) - .setName('Use Custom Request Template') - .addToggle((toggle) => - toggle - .setValue(commandOption.useCustomCommand) + .setName('Maximum Tag Suggestions') + .setDesc("Maximum number of tags to suggest (1-10)") + .addText((text) => + text + .setPlaceholder('3') + .setValue(String(commandOption.max_suggestions)) .onChange(async (value) => { - commandOption.useCustomCommand = value; - await this.plugin.saveSettings(); - this.display(); - }), + const num = parseInt(value); + if (num >= 1 && num <= 10) { + commandOption.max_suggestions = num; + await this.plugin.saveSettings(); + } + }) ); - // Custom template textarea - if (commandOption.useCustomCommand) { - - // Different default template depanding on useRef - if (commandOption.useRef) { - if(commandOption.prmpt_template == DEFAULT_PROMPT_TEMPLATE_WO_REF) commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE; - } else { - if(commandOption.prmpt_template == DEFAULT_PROMPT_TEMPLATE) commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE_WO_REF; - } - - const customPromptTemplateEl = new Setting(containerEl) - .setName('Custom Prompt Template') - .setDesc('') - .setClass('setting-item-child') - .setClass('block-control-item') - .setClass('height20-text-area') - .addTextArea((text) => - text - .setPlaceholder('Write custom prompt template.') - .setValue(commandOption.prmpt_template) - .onChange(async (value) => { - commandOption.prmpt_template = value; - await this.plugin.saveSettings(); - }) - ) - .addExtraButton(cb => { - cb - .setIcon('reset') - .setTooltip('Restore to default') - .onClick(async () => { - // Different default template depanding on useRef - if (commandOption.useRef) commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE; - else commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE_WO_REF; - - await this.plugin.saveSettings(); - this.display(); - }) - }); - customPromptTemplateEl.descEl.createSpan({text: 'This plugin is based on the ChatGPT answer.'}); - customPromptTemplateEl.descEl.createEl('br'); - customPromptTemplateEl.descEl.createSpan({text: 'You can use your own template when making a request to ChatGPT.'}); - customPromptTemplateEl.descEl.createEl('br'); - customPromptTemplateEl.descEl.createEl('br'); - customPromptTemplateEl.descEl.createSpan({text: 'Variables:'}); - customPromptTemplateEl.descEl.createEl('br'); - customPromptTemplateEl.descEl.createSpan({text: '- {{input}}: The text to classify will be inserted here.'}); - customPromptTemplateEl.descEl.createEl('br'); - customPromptTemplateEl.descEl.createSpan({text: '- {{reference}}: The reference tags will be inserted here.'}); - customPromptTemplateEl.descEl.createEl('br'); - - const customChatRoleEl = new Setting(containerEl) - .setName('Custom Chat Role') - .setDesc('') - .setClass('setting-item-child') - .setClass('block-control-item') - .setClass('height10-text-area') - .addTextArea((text) => - text - .setPlaceholder('Write custom chat role for gpt system.') - .setValue(commandOption.chat_role) - .onChange(async (value) => { - commandOption.chat_role = value; - await this.plugin.saveSettings(); - }) - ) - .addExtraButton(cb => { - cb - .setIcon('reset') - .setTooltip('Restore to default') - .onClick(async () => { - commandOption.chat_role = DEFAULT_CHAT_ROLE; - await this.plugin.saveSettings(); - this.display(); - }) - }); - customChatRoleEl.descEl.createSpan({text: 'Define custom role to ChatGPT system.'}); - - + // Conditional Advanced Settings for ChatGPT + if (this.plugin.settings.classifierEngine === ClassifierEngine.ChatGPT) { + // Toggle custom rule new Setting(containerEl) - .setName('Custom Max Tokens') - .setDesc("The maximum number of tokens that can be generated in the completion.") - .setClass('setting-item-child') - .addText((text) => - text - .setPlaceholder('150') - .setValue(String(commandOption.max_tokens)) + .setName('Use Custom Request Template (ChatGPT)') + .addToggle((toggle) => + toggle + .setValue(commandOption.useCustomCommand) .onChange(async (value) => { - commandOption.max_tokens = parseInt(value); + commandOption.useCustomCommand = value; await this.plugin.saveSettings(); - }) + this.display(); + }), ); + + // Custom template textarea + if (commandOption.useCustomCommand) { + + // Different default template depanding on useRef + if (commandOption.useRef) { + if(commandOption.prmpt_template == DEFAULT_PROMPT_TEMPLATE_WO_REF) commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE; + } else { + if(commandOption.prmpt_template == DEFAULT_PROMPT_TEMPLATE) commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE_WO_REF; + } + + const customPromptTemplateEl = new Setting(containerEl) + .setName('Custom Prompt Template (ChatGPT)') + .setDesc('') + .setClass('setting-item-child') + .setClass('block-control-item') + .setClass('height20-text-area') + .addTextArea((text) => + text + .setPlaceholder('Write custom prompt template.') + .setValue(commandOption.prmpt_template) + .onChange(async (value) => { + commandOption.prmpt_template = value; + await this.plugin.saveSettings(); + }) + ) + .addExtraButton(cb => { + cb + .setIcon('reset') + .setTooltip('Restore to default') + .onClick(async () => { + // Different default template depanding on useRef + if (commandOption.useRef) commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE; + else commandOption.prmpt_template = DEFAULT_PROMPT_TEMPLATE_WO_REF; + + await this.plugin.saveSettings(); + this.display(); + }) + }); + customPromptTemplateEl.descEl.createSpan({text: 'This plugin is based on the ChatGPT answer.'}); + customPromptTemplateEl.descEl.createEl('br'); + customPromptTemplateEl.descEl.createSpan({text: 'You can use your own template when making a request to ChatGPT.'}); + customPromptTemplateEl.descEl.createEl('br'); + customPromptTemplateEl.descEl.createEl('br'); + customPromptTemplateEl.descEl.createSpan({text: 'Variables:'}); + customPromptTemplateEl.descEl.createEl('br'); + customPromptTemplateEl.descEl.createSpan({text: '- {{input}}: The text to classify will be inserted here.'}); + customPromptTemplateEl.descEl.createEl('br'); + customPromptTemplateEl.descEl.createSpan({text: '- {{reference}}: The reference tags will be inserted here.'}); + customPromptTemplateEl.descEl.createEl('br'); + + const customChatRoleEl = new Setting(containerEl) + .setName('Custom Chat Role (ChatGPT)') + .setDesc('') + .setClass('setting-item-child') + .setClass('block-control-item') + .setClass('height10-text-area') + .addTextArea((text) => + text + .setPlaceholder('Write custom chat role for gpt system.') + .setValue(commandOption.chat_role) + .onChange(async (value) => { + commandOption.chat_role = value; + await this.plugin.saveSettings(); + }) + ) + .addExtraButton(cb => { + cb + .setIcon('reset') + .setTooltip('Restore to default') + .onClick(async () => { + commandOption.chat_role = DEFAULT_CHAT_ROLE; + await this.plugin.saveSettings(); + this.display(); + }) + }); + customChatRoleEl.descEl.createSpan({text: 'Define custom role to ChatGPT system.'}); + + + new Setting(containerEl) + .setName('Custom Max Tokens (ChatGPT)') + .setDesc("The maximum number of tokens that can be generated in the completion.") + .setClass('setting-item-child') + .addText((text) => + text + .setPlaceholder('150') + .setValue(String(commandOption.max_tokens)) + .onChange(async (value) => { + commandOption.max_tokens = parseInt(value); + await this.plugin.saveSettings(); + }) + ); + } } + // For JinaAI, these custom prompt/role/token settings are hidden as they are not applicable. } diff --git a/src/template.ts b/src/template.ts index 67cf742..e304b58 100644 --- a/src/template.ts +++ b/src/template.ts @@ -3,9 +3,10 @@ export const DEFAULT_PROMPT_TEMPLATE = `Classify this content: """ {{input}} """ -Answer format is JSON {reliability:0~1, output:selected_category}. -Even if you are not sure, qualify the reliability and select one. -Output must be one of these: +Answer format is JSON {reliability:0~1, outputs:[tag1,tag2,...]}. +Even if you are unsure, qualify the reliability and select the best matches. +Respond only with valid JSON. Do not write an introduction or summary. +Output tags must be from these options: {{reference}} `; @@ -16,5 +17,5 @@ export const DEFAULT_PROMPT_TEMPLATE_WO_REF = `Classify this content: """ Answer format is JSON {reliability:0~1, output:selected_category}. Even if you are not sure, qualify the reliability and recommend a proper category. - -`; \ No newline at end of file +Respond only with valid JSON. Do not write an introduction or summary. +`;