diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..ea4bd58c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,3 @@ +# .husky/pre-commit +prettier $(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g') --write --ignore-unknown +git update-index --again diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..5a1169e6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "lf", + "printWidth": 100, + "tabWidth": 2, + "semi": true, + "singleQuote": false, + "trailingComma": "es5" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3c832d70..eab8fda4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -44,8 +44,12 @@ In the case of Copilot for Obsidian, you will need to: 1. Fork the repo. 2. Create a vault just for development. 3. Clone the forked repo into your vault's `plugins` folder. -4. Run `npm run dev` in your repo to see the effect of your changes. -5. When you are ready to make a pull request, ensure to make your changes in **a branch on your fork**, and then submit a pull request to the **main repo**. +4. Run `npm install` to install all dependencies. +5. Install the recommended VS Code extensions (Prettier and ESLint). +6. Ensure your editor respects the `.editorconfig` and Prettier settings. +7. Run `npm run dev` in your repo to see the effect of your changes. +8. Before committing, run `npm run format` to ensure all files are properly formatted. +9. When you are ready to make a pull request, ensure to make your changes in **a branch on your fork**, and then submit a pull request to the **main repo**. Try to be descriptive in your branch names and pull requests. Happy coding! @@ -55,4 +59,3 @@ Try to be descriptive in your branch names and pull requests. Happy coding! - **Email**: logan@brevilabs.com Thank you for contributing to Copilot for Obsidian! - diff --git a/package-lock.json b/package-lock.json index ad6b75d8..608a32bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,8 +53,10 @@ "electron": "^27.3.2", "esbuild": "0.17.3", "eslint": "^8.57.0", + "husky": "^9.1.5", "jest": "^29.7.0", "jest-environment-jsdom": "^29.5.0", + "lint-staged": "^15.2.9", "obsidian": "latest", "prettier": "^3.3.3", "ts-jest": "^29.1.0", @@ -4347,6 +4349,87 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -4432,6 +4515,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4681,9 +4770,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -5154,6 +5243,18 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/errno": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", @@ -6036,6 +6137,18 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", @@ -6544,6 +6657,21 @@ "ms": "^2.0.0" } }, + "node_modules/husky": { + "version": "9.1.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.5.tgz", + "integrity": "sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/i18next": { "version": "22.4.15", "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.15.tgz", @@ -8970,12 +9098,308 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/lint-staged": { + "version": "15.2.9", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz", + "integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/lint-staged/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lint-staged/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/lint-staged/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/listr2/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -9009,6 +9433,150 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9642,12 +10210,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -9682,6 +10250,18 @@ "node": ">=6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -10469,6 +11049,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -11148,6 +11740,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -11166,6 +11801,12 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -11415,6 +12056,46 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11529,6 +12210,15 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -12499,9 +13189,12 @@ "dev": true }, "node_modules/yaml": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.2.2.tgz", - "integrity": "sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index 9389dc59..5182eaa7 100644 --- a/package.json +++ b/package.json @@ -8,11 +8,25 @@ "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", + "format": "prettier --write .", + "format:check": "prettier --check .", "version": "node version-bump.mjs && git add manifest.json versions.json", - "test": "jest" + "test": "jest", + "prepare": "husky" }, "keywords": [], - "author": "", + "author": "Logan Yang", + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,json,css,md}": [ + "prettier --write", + "git add" + ] + }, "license": "AGPL-3.0", "devDependencies": { "@testing-library/jest-dom": "^5.16.5", @@ -32,8 +46,10 @@ "electron": "^27.3.2", "esbuild": "0.17.3", "eslint": "^8.57.0", + "husky": "^9.1.5", "jest": "^29.7.0", "jest-environment-jsdom": "^29.5.0", + "lint-staged": "^15.2.9", "obsidian": "latest", "prettier": "^3.3.3", "ts-jest": "^29.1.0", diff --git a/src/constants.ts b/src/constants.ts index f31c1fa5..00d90cb3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -52,9 +52,7 @@ export const GOOGLE_MODELS = new Set([ export const ANTHROPIC_MODELS = new Set([ChatModelDisplayNames.CLAUDE]); -export const OPENROUTERAI_MODELS = new Set([ - ChatModelDisplayNames.OPENROUTERAI, -]); +export const OPENROUTERAI_MODELS = new Set([ChatModelDisplayNames.OPENROUTERAI]); export const OLLAMA_MODELS = new Set([ChatModelDisplayNames.OLLAMA]); @@ -198,7 +196,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = { lmStudioBaseUrl: "http://localhost:1234/v1", stream: true, defaultSaveFolder: "copilot-conversations", - indexVaultToVectorStore: VAULT_VECTOR_STORE_STRATEGY.NEVER, + indexVaultToVectorStore: VAULT_VECTOR_STORE_STRATEGY.ON_MODE_SWITCH, qaExclusionPaths: "", chatNoteContextPath: "", chatNoteContextTags: [], diff --git a/src/settings/SettingsPage.tsx b/src/settings/SettingsPage.tsx index 95c17a08..aed66da9 100644 --- a/src/settings/SettingsPage.tsx +++ b/src/settings/SettingsPage.tsx @@ -1,8 +1,9 @@ import CopilotPlugin from "@/main"; import { App, Notice, PluginSettingTab, Setting } from "obsidian"; -import React from 'react'; +import React from "react"; import { createRoot } from "react-dom/client"; import SettingsMain from "./components/SettingsMain"; +import { SettingsProvider } from "./contexts/SettingsContext"; export interface CopilotSettings { openAIApiKey: string; @@ -29,7 +30,7 @@ export interface CopilotSettings { contextTurns: number; userSystemPrompt: string; openAIProxyBaseUrl: string; - useOpenAILocalProxy: boolean + useOpenAILocalProxy: boolean; openAIProxyModelName: string; openAIEmbeddingProxyBaseUrl: string; openAIEmbeddingProxyModelName: string; @@ -47,7 +48,7 @@ export interface CopilotSettings { qaExclusionPaths: string; groqModel: string; groqApiKey: string; - enabledCommands: Record; + enabledCommands: Record; } export class CopilotSettingTab extends PluginSettingTab { @@ -65,31 +66,33 @@ export class CopilotSettingTab extends PluginSettingTab { // Reload the plugin // eslint-disable-next-line @typescript-eslint/no-explicit-any - const app = (this.plugin.app as any); + const app = this.plugin.app as any; await app.plugins.disablePlugin("copilot"); await app.plugins.enablePlugin("copilot"); app.setting.openTabById("copilot").display(); - new Notice('Plugin reloaded successfully.'); + new Notice("Plugin reloaded successfully."); } catch (error) { - new Notice('Failed to reload the plugin. Please reload manually.'); - console.error('Error reloading plugin:', error); + new Notice("Failed to reload the plugin. Please reload manually."); + console.error("Error reloading plugin:", error); } } display(): void { const { containerEl } = this; containerEl.empty(); - containerEl.style.userSelect = 'text'; - const div = containerEl.createDiv("div") + containerEl.style.userSelect = "text"; + const div = containerEl.createDiv("div"); const sections = createRoot(div); sections.render( - + + + ); - const devModeHeader = containerEl.createEl('h1', { text: 'Additional Settings' }); - devModeHeader.style.marginTop = '40px'; + const devModeHeader = containerEl.createEl("h1", { text: "Additional Settings" }); + devModeHeader.style.marginTop = "40px"; new Setting(containerEl) .setName("Enable Encryption") @@ -98,9 +101,8 @@ export class CopilotSettingTab extends PluginSettingTab { frag.appendText("Enable encryption for the API keys."); }) ) - .addToggle(toggle => toggle - .setValue(this.plugin.settings.enableEncryption) - .onChange(async (value) => { + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.enableEncryption).onChange(async (value) => { this.plugin.settings.enableEncryption = value; await this.plugin.saveSettings(); }) @@ -113,9 +115,8 @@ export class CopilotSettingTab extends PluginSettingTab { frag.appendText("Debug mode will log all API requests and prompts to the console."); }) ) - .addToggle(toggle => toggle - .setValue(this.plugin.settings.debug) - .onChange(async (value) => { + .addToggle((toggle) => + toggle.setValue(this.plugin.settings.debug).onChange(async (value) => { this.plugin.settings.debug = value; await this.plugin.saveSettings(); }) diff --git a/src/settings/components/AdvancedSettings.tsx b/src/settings/components/AdvancedSettings.tsx index 41021689..b586dcd8 100644 --- a/src/settings/components/AdvancedSettings.tsx +++ b/src/settings/components/AdvancedSettings.tsx @@ -1,9 +1,5 @@ -import React from 'react'; -import { - TextAreaComponent, - TextComponent, - ToggleComponent, -} from './SettingBlocks'; +import React from "react"; +import { TextAreaComponent, TextComponent, ToggleComponent } from "./SettingBlocks"; interface AdvancedSettingsProps { openAIProxyBaseUrl: string; @@ -40,7 +36,8 @@ const AdvancedSettings: React.FC = ({

Advanced Settings

- OpenAI Proxy settings override the default OpenAI parameters, meaning now your OpenAI models are routed to this provider instead! Clear these fields to use OpenAI again. + OpenAI Proxy settings override the default OpenAI parameters, meaning now your OpenAI models + are routed to this provider instead! Clear these fields to use OpenAI again.
This is in effect only when you pick any OpenAI model in the model dropdown.
diff --git a/src/settings/components/ApiSetting.tsx b/src/settings/components/ApiSetting.tsx index 3313b9d5..826528bc 100644 --- a/src/settings/components/ApiSetting.tsx +++ b/src/settings/components/ApiSetting.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { TextComponent } from './SettingBlocks'; +import React from "react"; +import { TextComponent } from "./SettingBlocks"; const ApiSetting: React.FC<{ title: string; @@ -17,8 +17,8 @@ const ApiSetting: React.FC<{ value={value} // @ts-ignore onChange={setValue} - placeholder={placeholder || ''} - type={type || 'password'} + placeholder={placeholder || ""} + type={type || "password"} />
); diff --git a/src/settings/components/ApiSettings.tsx b/src/settings/components/ApiSettings.tsx index 85df4cc5..aa047749 100644 --- a/src/settings/components/ApiSettings.tsx +++ b/src/settings/components/ApiSettings.tsx @@ -1,7 +1,7 @@ import { DEFAULT_SETTINGS } from "@/constants"; -import React from 'react'; -import ApiSetting from './ApiSetting'; -import Collapsible from './Collapsible'; +import React from "react"; +import ApiSetting from "./ApiSetting"; +import Collapsible from "./Collapsible"; interface ApiSettingsProps { openAIApiKey: string; @@ -77,13 +77,12 @@ const ApiSettings: React.FC = ({

API Settings

-

- All your API keys are stored locally. -

+

All your API keys are stored locally.

Make sure you have access to the model and the correct API key.
- If errors occur, please re-enter the API key, save and reload the plugin to see if it resolves the issue. + If errors occur, please re-enter the API key, save and reload the plugin to see if it + resolves the issue.
@@ -94,8 +93,12 @@ const ApiSettings: React.FC = ({ placeholder="Enter OpenAI API Key" />

- You can find your API key at{' '} - + You can find your API key at{" "} + https://platform.openai.com/api-keys

@@ -116,7 +119,11 @@ const ApiSettings: React.FC = ({
If you are a new user, try - + OpenAI playground to see if you have correct API access first. @@ -132,10 +139,15 @@ const ApiSettings: React.FC = ({ placeholder="Enter Google API Key" />

- If you have Google Cloud, you can get Gemini API key {' '} - + If you have Google Cloud, you can get Gemini API key{" "} + here - . + + .
Your API key is stored locally and is only used to make requests to Google's services.

@@ -167,12 +179,18 @@ const ApiSettings: React.FC = ({ type="text" />

- If you have Anthropic API access, you can get the API key {' '} - + If you have Anthropic API access, you can get the API key{" "} + here - . + + .
- Your API key is stored locally and is only used to make requests to Anthropic's services. + Your API key is stored locally and is only used to make requests to Anthropic's + services.

@@ -194,14 +212,17 @@ const ApiSettings: React.FC = ({ type="text" />

- You can get your OpenRouterAI key {' '} + You can get your OpenRouterAI key{" "} here - . + + .
- Find models + Find models{" "} + here - . + + .

@@ -263,10 +284,11 @@ const ApiSettings: React.FC = ({ type="text" />

- If you have Groq API access, you can get the API key {' '} + If you have Groq API access, you can get the API key{" "} here - . + + .
Your API key is stored locally and is only used to make requests to Groq's services.

@@ -276,4 +298,4 @@ const ApiSettings: React.FC = ({ ); }; -export default ApiSettings; \ No newline at end of file +export default ApiSettings; diff --git a/src/settings/components/Collapsible.tsx b/src/settings/components/Collapsible.tsx index 515eea3e..d893ab17 100644 --- a/src/settings/components/Collapsible.tsx +++ b/src/settings/components/Collapsible.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useState } from 'react'; +import React, { ReactNode, useState } from "react"; interface CollapsibleProps { title: ReactNode; @@ -9,44 +9,60 @@ const Collapsible: React.FC = ({ title, children }) => { const [isOpen, setIsOpen] = useState(false); const titleStyle = { - fontWeight: 'bold', - cursor: 'pointer', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - marginBottom: '10px', - borderRadius: '5px', + fontWeight: "bold", + cursor: "pointer", + display: "flex", + alignItems: "center", + justifyContent: "space-between", + marginBottom: "10px", + borderRadius: "5px", }; const contentStyle = { - padding: '10px 10px', - borderRadius: '8px', - marginTop: '10px', + padding: "10px 10px", + borderRadius: "8px", + marginTop: "10px", }; const ChevronDown = () => ( - + ); const ChevronRight = () => ( - + ); return ( -
+
setIsOpen(!isOpen)}> {title} {isOpen ? : }
- {isOpen && ( -
- {children} -
- )} + {isOpen &&
{children}
}
); }; diff --git a/src/settings/components/CommandToggleSettings.tsx b/src/settings/components/CommandToggleSettings.tsx index 8e4364c7..25da39c8 100644 --- a/src/settings/components/CommandToggleSettings.tsx +++ b/src/settings/components/CommandToggleSettings.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { ToggleComponent } from './SettingBlocks'; +import React, { useState } from "react"; +import { ToggleComponent } from "./SettingBlocks"; interface CommandToggleSettingsProps { enabledCommands: Record; @@ -15,14 +15,14 @@ const CommandToggleSettings: React.FC = ({ const toggleCommand = (commandId: string, enabled: boolean) => { setEnabledCommands({ ...enabledCommands, - [commandId]: { ...enabledCommands[commandId], enabled } + [commandId]: { ...enabledCommands[commandId], enabled }, }); }; return (
-

setIsExpanded(!isExpanded)} style={{ cursor: 'pointer' }}> - Command Settings {isExpanded ? '▼' : '▶'} +

setIsExpanded(!isExpanded)} style={{ cursor: "pointer" }}> + Command Settings {isExpanded ? "▼" : "▶"}

{isExpanded && (
@@ -40,4 +40,4 @@ const CommandToggleSettings: React.FC = ({ ); }; -export default CommandToggleSettings; \ No newline at end of file +export default CommandToggleSettings; diff --git a/src/settings/components/GeneralSettings.tsx b/src/settings/components/GeneralSettings.tsx new file mode 100644 index 00000000..e95c3aa9 --- /dev/null +++ b/src/settings/components/GeneralSettings.tsx @@ -0,0 +1,75 @@ +import { ChatModelDisplayNames } from "@/constants"; +import React from "react"; +import { useSettingsContext } from "../contexts/SettingsContext"; +import CommandToggleSettings from "./CommandToggleSettings"; +import { DropdownComponent, SliderComponent, TextComponent } from "./SettingBlocks"; + +const GeneralSettings: React.FC = () => { + const { settings, updateSettings } = useSettingsContext(); + + return ( +
+

General Settings

+ updateSettings({ defaultModelDisplayName: value })} + /> + updateSettings({ defaultSaveFolder: value })} + /> +
+ Please be mindful of the number of tokens and context conversation turns you set here, as + they will affect the cost of your API requests. +
+ updateSettings({ temperature: value })} + /> + +

+ The maximum number of output tokens to generate. Default is 1000. +

+ + This number plus the length of your prompt (input tokens) must be smaller than the + context window of the model. + + + } + min={0} + max={16000} + step={100} + value={settings.maxTokens} + onChange={(value) => updateSettings({ maxTokens: value })} + /> + updateSettings({ contextTurns: value })} + /> + updateSettings({ enabledCommands: value })} + /> +
+ ); +}; + +export default GeneralSettings; diff --git a/src/settings/components/LocalCopilotSettings.tsx b/src/settings/components/LocalCopilotSettings.tsx index ee023146..e9411792 100644 --- a/src/settings/components/LocalCopilotSettings.tsx +++ b/src/settings/components/LocalCopilotSettings.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { TextComponent } from './SettingBlocks'; +import React from "react"; +import { TextComponent } from "./SettingBlocks"; interface LocalCopilotSettingsProps { lmStudioBaseUrl: string; @@ -20,18 +20,23 @@ const LocalCopilotSettings: React.FC = ({ }) => { return (
-
+

Local Copilot (No Internet Required!)

Please check the doc to set up LM Studio or Ollama server on your device.

- Local models can be limited in capabilities and may not work for some use cases at this time. Keep in mind that it is still in early experimental phase. But some 13B even 7B models are already quite capable! + Local models can be limited in capabilities and may not work for some use cases at this + time. Keep in mind that it is still in early experimental phase. But some 13B even 7B models + are already quite capable!

LM Studio

- To use Local Copilot with LM Studio:
- 1. Start LM Studio server with CORS on. Default port is 1234 but if you change it, you can provide it below.
+ To use Local Copilot with LM Studio: +
+ 1. Start LM Studio server with CORS on. Default port is 1234 but if you change it, you can + provide it below. +
2. Pick LM Studio in the Copilot Chat model selection dropdown to chat with it!

= ({ placeholder="http://localhost:1234/v1" />

Ollama

-

To use Local Copilot with Ollama, pick Ollama in the Copilot Chat model selection dropdown.

+

+ To use Local Copilot with Ollama, pick Ollama in the Copilot Chat model selection dropdown. +

Run the local Ollama server by running this in your terminal:

-

OLLAMA_ORIGINS=app://obsidian.md* ollama serve

+

+ OLLAMA_ORIGINS=app://obsidian.md* ollama serve +

= ({

QA Settings

- Vault QA is in BETA and may not be stable. If you have issues please report in the github repo. + Vault QA is in BETA and may not be stable. If you have issues please report in the github + repo.
-

QA mode relies a local vector index.

+

+ QA mode relies a local vector index. +

Long Note QA vs. Vault QA (BETA)

-

Long Note QA mode uses the Active Note as context. Vault QA (BETA) uses your entire vault as context. Please ask questions as specific as possible, avoid vague questions to get better results.

+

+ Long Note QA mode uses the Active Note as context. Vault QA (BETA) uses your entire vault as + context. Please ask questions as specific as possible, avoid vague questions to get better + results. +

Local Embedding Model

-

Check the local copilot setup guide to setup Ollama's local embedding model (requires Ollama v0.1.26 or above).

+

+ Check the{" "} + + local copilot + {" "} + setup guide to setup Ollama's local embedding model (requires Ollama v0.1.26 or above). +

= ({ If you are using a paid embedding provider, beware of costs for large vaults!

- When you switch to Long Note QA mode, your active note is indexed automatically upon mode switch. + When you switch to Long Note QA mode, your active note is indexed + automatically upon mode switch.
- When you switch to Vault QA mode, your vault is indexed based on the auto-index strategy you select below. + When you switch to Vault QA mode, your vault is indexed{" "} + based on the auto-index strategy you select below.

= ({ />

- NEVER: Notes are never indexed to the vector store unless users run the command Index vault for QA explicitly, or hit the Refresh Index button. -

+ NEVER: Notes are never indexed to the vector store unless users run the + command Index vault for QA explicitly, or hit the Refresh Index button. +
+
ON STARTUP: Vault index is refreshed on plugin load/reload. -

- ON MODE SWITCH (Recommended): Vault index is refreshed when switching to Vault QA mode. -

- By "refreshed", it means the vault index is not rebuilt from scratch but rather updated incrementally with new/modified notes since the last index. If you need a complete rebuild, run the commands "Clear vector store" and "Force re-index for QA" manually. This helps reduce costs when using paid embedding models.

- Beware of the cost if you are using a paid embedding model and have a large vault! You can run Copilot command Count total tokens in your vault and refer to your selected embedding model pricing to estimate indexing costs. +
+
+ ON MODE SWITCH (Recommended): Vault index is refreshed when switching to + Vault QA mode. +
+
+ By "refreshed", it means the vault index is not rebuilt from scratch but rather updated + incrementally with new/modified notes since the last index. If you need a complete rebuild, + run the commands "Clear vector store" and "Force re-index for QA" manually. This helps + reduce costs when using paid embedding models. +
+
+ Beware of the cost if you are using a paid embedding model and have a large vault! You can + run Copilot command Count total tokens in your vault and refer to your selected + embedding model pricing to estimate indexing costs.


= ({ placeholder="Enter Cohere API Key" />

- Get your free Cohere API key{' '} - + Get your free Cohere API key{" "} + here

diff --git a/src/settings/components/SettingBlocks.tsx b/src/settings/components/SettingBlocks.tsx index 7da3d28f..cb5c038b 100644 --- a/src/settings/components/SettingBlocks.tsx +++ b/src/settings/components/SettingBlocks.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; type DropdownComponentProps = { name: string; @@ -40,9 +40,15 @@ type ToggleComponentProps = { description?: string; value: boolean; onChange: (value: boolean) => void; -} +}; -const DropdownComponent: React.FC = ({ name, description, options, value, onChange }) => { +const DropdownComponent: React.FC = ({ + name, + description, + options, + value, + onChange, +}) => { return (
{name}
@@ -62,13 +68,20 @@ const DropdownComponent: React.FC = ({ name, description ); }; -const TextComponent: React.FC = ({ name, description, placeholder, value, type, onChange }) => { +const TextComponent: React.FC = ({ + name, + description, + placeholder, + value, + type, + onChange, +}) => { return (
{name}
{description}
= ({ name, description, placeh ); }; -const TextAreaComponent: React.FC = ({ name, description, placeholder, value, onChange }) => { +const TextAreaComponent: React.FC = ({ + name, + description, + placeholder, + value, + onChange, +}) => { return (
{name}
@@ -93,13 +112,20 @@ const TextAreaComponent: React.FC = ({ name, description ); }; - -const SliderComponent: React.FC = ({ name, description, min, max, step, value, onChange }) => { +const SliderComponent: React.FC = ({ + name, + description, + min, + max, + step, + value, + onChange, +}) => { return (
{name}
{description}
-
+
= ({ name, description, mi value={value} onChange={(e) => onChange(parseFloat(e.target.value))} /> - {value} + + {value} +
); }; -const ToggleComponent: React.FC = ({ name, description, value, onChange }) => { +const ToggleComponent: React.FC = ({ + name, + description, + value, + onChange, +}) => { return (
{name}
{description}
@@ -133,4 +164,3 @@ const ToggleComponent: React.FC = ({ name, description, va }; export { DropdownComponent, SliderComponent, TextAreaComponent, TextComponent, ToggleComponent }; - diff --git a/src/settings/components/SettingsMain.tsx b/src/settings/components/SettingsMain.tsx index f5e4c20f..b4f1f52b 100644 --- a/src/settings/components/SettingsMain.tsx +++ b/src/settings/components/SettingsMain.tsx @@ -1,279 +1,88 @@ import CopilotPlugin from "@/main"; -import { Notice } from 'obsidian'; -import React, { Fragment, useState } from 'react'; -import { ChatModelDisplayNames, DEFAULT_SETTINGS, DISPLAY_NAME_TO_MODEL } from '../../constants'; -import AdvancedSettings from './AdvancedSettings'; -import ApiSettings from './ApiSettings'; -import CommandToggleSettings from './CommandToggleSettings'; -import LocalCopilotSettings from './LocalCopilotSettings'; -import QASettings from './QASettings'; -import { DropdownComponent, SliderComponent, TextComponent } from './SettingBlocks'; +import React from "react"; +import { useSettingsContext } from "../contexts/SettingsContext"; +import AdvancedSettings from "./AdvancedSettings"; +import ApiSettings from "./ApiSettings"; +import GeneralSettings from "./GeneralSettings"; +import LocalCopilotSettings from "./LocalCopilotSettings"; +import QASettings from "./QASettings"; -interface SettingsMainProps { - plugin: CopilotPlugin; - reloadPlugin: () => Promise; -} - -export default function SettingsMain({ plugin, reloadPlugin }: SettingsMainProps) { - const [defaultModelDisplayName, setDefaultModelDisplayName] = useState(plugin.settings.defaultModelDisplayName); - const [defaultSaveFolder, setDefaultSaveFolder] = useState(plugin.settings.defaultSaveFolder); - const [temperature, setTemperature] = useState(plugin.settings.temperature); - const [maxTokens, setMaxTokens] = useState(plugin.settings.maxTokens); - const [contextTurns, setContextTurns] = useState(plugin.settings.contextTurns); - - // API settings - const [openAIApiKey, setOpenAIApiKey] = useState(plugin.settings.openAIApiKey); - const [openAIOrgId, setOpenAIOrgId] = useState(plugin.settings.openAIOrgId); - const [openAICustomModel, setOpenAICustomModel] = useState(plugin.settings.openAICustomModel); - - const [googleApiKey, setGoogleApiKey] = useState(plugin.settings.googleApiKey); - const [googleCustomModel, setGoogleCustomModel] = useState(plugin.settings.googleCustomModel); - - const [anthropicApiKey, setAnthropicApiKey] = useState(plugin.settings.anthropicApiKey); - const [anthropicModel, setAnthropicModel] = useState(plugin.settings.anthropicModel); - - const [openRouterAiApiKey, setOpenRouterAiApiKey] = useState(plugin.settings.openRouterAiApiKey); - const [openRouterModel, setOpenRouterModel] = useState(plugin.settings.openRouterModel); - - const [azureOpenAIApiKey, setAzureOpenAIApiKey] = useState(plugin.settings.azureOpenAIApiKey); - const [azureOpenAIApiInstanceName, setAzureOpenAIApiInstanceName] = useState(plugin.settings.azureOpenAIApiInstanceName); - const [azureOpenAIApiDeploymentName, setAzureOpenAIApiDeploymentName] = useState(plugin.settings.azureOpenAIApiDeploymentName); - const [azureOpenAIApiVersion, setAzureOpenAIApiVersion] = useState(plugin.settings.azureOpenAIApiVersion); - const [azureOpenAIApiEmbeddingDeploymentName, setAzureOpenAIApiEmbeddingDeploymentName] = useState(plugin.settings.azureOpenAIApiEmbeddingDeploymentName); - - const [groqApiKey, setGroqApiKey] = useState(plugin.settings.groqApiKey); - const [groqModel, setGroqModel] = useState(plugin.settings.groqModel); - - // QA settings - const [embeddingModel, setEmbeddingModel] = useState(plugin.settings.embeddingModel); - const [cohereApiKey, setCohereApiKey] = useState(plugin.settings.cohereApiKey); - const [huggingfaceApiKey, setHuggingfaceApiKey] = useState(plugin.settings.huggingfaceApiKey); - const [indexVaultToVectorStore, setIndexVaultToVectorStore] = useState(plugin.settings.indexVaultToVectorStore); - const [maxSourceChunks, setMaxSourceChunks] = useState(plugin.settings.maxSourceChunks); - - // Advanced settings - const [userSystemPrompt, setUserSystemPrompt] = useState(plugin.settings.userSystemPrompt); - const [openAIProxyBaseUrl, setOpenAIProxyBaseUrl] = useState(plugin.settings.openAIProxyBaseUrl); - const [useOpenAILocalProxy, setUseOpenAILocalProxy] = useState(plugin.settings.useOpenAILocalProxy); - const [openAIProxyModelName, setOpenAIProxyModelName] = useState(plugin.settings.openAIProxyModelName); - const [openAIEmbeddingProxyBaseUrl, setOpenAIEmbeddingProxyBaseUrl] = useState(plugin.settings.openAIEmbeddingProxyBaseUrl); - const [openAIEmbeddingProxyModelName, setOpenAIEmbeddingProxyModelName] = useState(plugin.settings.openAIEmbeddingProxyModelName); - - // Local Copilot Settings - const [lmStudioBaseUrl, setlmStudioBaseUrl] = useState(plugin.settings.lmStudioBaseUrl); - const [ollamaModel, setOllamaModel] = useState(plugin.settings.ollamaModel); - const [ollamaBaseUrl, setOllamaBaseUrl] = useState(plugin.settings.ollamaBaseUrl); - - // Built-in Command Toggles - const [enabledCommands, setEnabledCommands] = useState(plugin.settings.enabledCommands); - - // NOTE: When new settings are added, make sure to add them to saveAllSettings - const saveAllSettings = async () => { - plugin.settings.defaultModelDisplayName = defaultModelDisplayName; - plugin.settings.defaultModel = DISPLAY_NAME_TO_MODEL[defaultModelDisplayName]; - plugin.settings.defaultSaveFolder = defaultSaveFolder; - plugin.settings.temperature = temperature; - plugin.settings.maxTokens = maxTokens; - plugin.settings.contextTurns = contextTurns; - - // API settings - plugin.settings.openAIApiKey = openAIApiKey; - plugin.settings.openAIOrgId = openAIOrgId; - plugin.settings.openAICustomModel = openAICustomModel; - - plugin.settings.googleApiKey = googleApiKey; - plugin.settings.googleCustomModel = googleCustomModel; - - plugin.settings.anthropicApiKey = anthropicApiKey; - plugin.settings.anthropicModel = anthropicModel; - plugin.settings.openRouterAiApiKey = openRouterAiApiKey; - plugin.settings.openRouterModel = openRouterModel; - plugin.settings.azureOpenAIApiKey = azureOpenAIApiKey; - plugin.settings.azureOpenAIApiInstanceName = azureOpenAIApiInstanceName; - plugin.settings.azureOpenAIApiDeploymentName = azureOpenAIApiDeploymentName; - plugin.settings.azureOpenAIApiVersion = azureOpenAIApiVersion; - plugin.settings.azureOpenAIApiEmbeddingDeploymentName = azureOpenAIApiEmbeddingDeploymentName; - plugin.settings.groqApiKey = groqApiKey; - plugin.settings.groqModel = groqModel; - - // QA settings - plugin.settings.embeddingModel = embeddingModel; - plugin.settings.cohereApiKey = cohereApiKey; - plugin.settings.huggingfaceApiKey = huggingfaceApiKey; - plugin.settings.indexVaultToVectorStore = indexVaultToVectorStore; - plugin.settings.maxSourceChunks = maxSourceChunks; - - // Advanced settings - plugin.settings.userSystemPrompt = userSystemPrompt; - plugin.settings.openAIProxyBaseUrl = openAIProxyBaseUrl; - plugin.settings.useOpenAILocalProxy = useOpenAILocalProxy; - plugin.settings.openAIProxyModelName = openAIProxyModelName; - plugin.settings.openAIEmbeddingProxyBaseUrl = openAIEmbeddingProxyBaseUrl; - plugin.settings.openAIEmbeddingProxyModelName = openAIEmbeddingProxyModelName; - - // Local Copilot Settings - plugin.settings.lmStudioBaseUrl = lmStudioBaseUrl; - plugin.settings.ollamaModel = ollamaModel; - plugin.settings.ollamaBaseUrl = ollamaBaseUrl; - - plugin.settings.enabledCommands = enabledCommands; - - await plugin.saveSettings(); - await reloadPlugin(); - new Notice('Settings have been saved and the plugin has been reloaded.'); - }; - - const resetToDefaultSettings = async () => { - plugin.settings = DEFAULT_SETTINGS; - await plugin.saveSettings(); - await reloadPlugin(); - new Notice('Settings have been reset to their default values.'); - }; +const SettingsMain: React.FC<{ plugin: CopilotPlugin; reloadPlugin: () => Promise }> = ({ + plugin, + reloadPlugin, +}) => { + const { settings, updateSettings, saveSettings, resetSettings } = useSettingsContext(); return ( <> -
-

Copilot Settings

-
- - -
-
- Please Save and Reload the plugin when you change any setting below! -
- - - -
- Please be mindful of the number of tokens and context conversation turns you set here, as they will affect the cost of your API requests. -
- { - setTemperature(value); - }} - /> - -

The maximum number of output tokens to generate. Default is 1000.

- This number plus the length of your prompt (input tokens) must be smaller than the context window of the model. - - } - min={0} - max={10000} - step={100} - value={maxTokens} - onChange={async (value) => { - setMaxTokens(value); - }} - /> - { - setContextTurns(value); - }} - /> - +

Copilot Settings

+
+ + +
+
+ Please Save and Reload the plugin when you change any setting below!
+ updateSettings({ openAIApiKey: value })} + setOpenAIOrgId={(value) => updateSettings({ openAIOrgId: value })} + setOpenAICustomModel={(value) => updateSettings({ openAICustomModel: value })} + setGoogleApiKey={(value) => updateSettings({ googleApiKey: value })} + setGoogleCustomModel={(value) => updateSettings({ googleCustomModel: value })} + setAnthropicApiKey={(value) => updateSettings({ anthropicApiKey: value })} + setAnthropicModel={(value) => updateSettings({ anthropicModel: value })} + setOpenRouterAiApiKey={(value) => updateSettings({ openRouterAiApiKey: value })} + setOpenRouterModel={(value) => updateSettings({ openRouterModel: value })} + setAzureOpenAIApiKey={(value) => updateSettings({ azureOpenAIApiKey: value })} + setAzureOpenAIApiInstanceName={(value) => + updateSettings({ azureOpenAIApiInstanceName: value }) + } + setAzureOpenAIApiDeploymentName={(value) => + updateSettings({ azureOpenAIApiDeploymentName: value }) + } + setAzureOpenAIApiVersion={(value) => updateSettings({ azureOpenAIApiVersion: value })} + setAzureOpenAIApiEmbeddingDeploymentName={(value) => + updateSettings({ azureOpenAIApiEmbeddingDeploymentName: value }) + } + setGroqApiKey={(value) => updateSettings({ groqApiKey: value })} + setGroqModel={(value) => updateSettings({ groqModel: value })} /> updateSettings({ embeddingModel: value })} + setCohereApiKey={(value) => updateSettings({ cohereApiKey: value })} + setHuggingfaceApiKey={(value) => updateSettings({ huggingfaceApiKey: value })} + setIndexVaultToVectorStore={(value) => updateSettings({ indexVaultToVectorStore: value })} + setMaxSourceChunks={(value) => updateSettings({ maxSourceChunks: value })} /> updateSettings({ openAIProxyBaseUrl: value })} + setUseOpenAILocalProxy={(value) => updateSettings({ useOpenAILocalProxy: value })} + setOpenAIProxyModelName={(value) => updateSettings({ openAIProxyModelName: value })} + setOpenAIEmbeddingProxyBaseUrl={(value) => + updateSettings({ openAIEmbeddingProxyBaseUrl: value }) + } + setOpenAIEmbeddingProxyModelName={(value) => + updateSettings({ openAIEmbeddingProxyModelName: value }) + } + setUserSystemPrompt={(value) => updateSettings({ userSystemPrompt: value })} /> updateSettings({ lmStudioBaseUrl: value })} + setOllamaModel={(value) => updateSettings({ ollamaModel: value })} + setOllamaBaseUrl={(value) => updateSettings({ ollamaBaseUrl: value })} /> ); -} +}; + +export default React.memo(SettingsMain); diff --git a/src/settings/contexts/SettingsContext.tsx b/src/settings/contexts/SettingsContext.tsx new file mode 100644 index 00000000..618caf86 --- /dev/null +++ b/src/settings/contexts/SettingsContext.tsx @@ -0,0 +1,60 @@ +import { DEFAULT_SETTINGS } from "@/constants"; +import CopilotPlugin from "@/main"; +import { CopilotSettings } from "@/settings/SettingsPage"; +import React, { createContext, useCallback, useContext, useState } from "react"; + +interface SettingsContextType { + settings: CopilotSettings; + updateSettings: (newSettings: Partial) => void; + saveSettings: () => Promise; + resetSettings: () => Promise; + reloadPlugin: () => Promise; +} + +const SettingsContext = createContext(undefined); + +export const SettingsProvider: React.FC<{ + plugin: CopilotPlugin; + reloadPlugin: () => Promise; + children: React.ReactNode; +}> = ({ plugin, reloadPlugin, children }) => { + const [settings, setSettings] = useState(plugin.settings); + + const updateSettings = useCallback( + async (newSettings: Partial) => { + const updatedSettings = { ...settings, ...newSettings }; + setSettings(updatedSettings); + plugin.settings = updatedSettings; + await plugin.saveSettings(); + }, + [plugin, settings] + ); + + const saveSettings = useCallback(async () => { + await plugin.saveSettings(); + await reloadPlugin(); + }, [plugin, reloadPlugin]); + + const resetSettings = useCallback(async () => { + plugin.settings = DEFAULT_SETTINGS; + setSettings(DEFAULT_SETTINGS); + await plugin.saveSettings(); + await reloadPlugin(); + }, [plugin]); + + return ( + + {children} + + ); +}; + +export const useSettingsContext = () => { + const context = useContext(SettingsContext); + if (context === undefined) { + throw new Error("useSettingsContext must be used within a SettingsProvider"); + } + return context; +};