Refactor Settings, add formatting (#518)

* Refactor settings components

* Save setting without reload, and formatting

* Add prettier config and enforce style

* Make default indexing strategy ON_MODE_SWITCH

* Add prettier pre-commit hook
This commit is contained in:
Logan Yang 2024-08-20 21:53:09 -07:00 committed by GitHub
parent 526573f571
commit 7e499073af
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1171 additions and 408 deletions

3
.husky/pre-commit Normal file
View file

@ -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

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"endOfLine": "lf",
"printWidth": 100,
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"trailingComma": "es5"
}

View file

@ -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!

713
package-lock.json generated
View file

@ -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"
}

View file

@ -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",

View file

@ -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: [],

View file

@ -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<string, { enabled: boolean; name: string; }>;
enabledCommands: Record<string, { enabled: boolean; name: string }>;
}
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(
<SettingsMain plugin={this.plugin} reloadPlugin={this.reloadPlugin.bind(this)} />
<SettingsProvider plugin={this.plugin} reloadPlugin={this.reloadPlugin.bind(this)}>
<SettingsMain plugin={this.plugin} reloadPlugin={this.reloadPlugin.bind(this)} />
</SettingsProvider>
);
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();
})

View file

@ -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<AdvancedSettingsProps> = ({
<br />
<h1>Advanced Settings</h1>
<div className="warning-message">
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.
<br />
This is in effect only when you pick any OpenAI model in the model dropdown.
<br />

View file

@ -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"}
/>
</div>
);

View file

@ -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<ApiSettingsProps> = ({
<br />
<br />
<h1>API Settings</h1>
<p>
All your API keys are stored locally.
</p>
<p>All your API keys are stored locally.</p>
<div className="warning-message">
Make sure you have access to the model and the correct API key.
<br />
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.
</div>
<div>
<div>
@ -94,8 +93,12 @@ const ApiSettings: React.FC<ApiSettingsProps> = ({
placeholder="Enter OpenAI API Key"
/>
<p>
You can find your API key at{' '}
<a href="https://platform.openai.com/api-keys" target="_blank" rel="noopener noreferrer">
You can find your API key at{" "}
<a
href="https://platform.openai.com/api-keys"
target="_blank"
rel="noopener noreferrer"
>
https://platform.openai.com/api-keys
</a>
</p>
@ -116,7 +119,11 @@ const ApiSettings: React.FC<ApiSettingsProps> = ({
</div>
<div className="warning-message">
<span>If you are a new user, try </span>
<a href="https://platform.openai.com/playground?mode=chat" target="_blank" rel="noopener noreferrer">
<a
href="https://platform.openai.com/playground?mode=chat"
target="_blank"
rel="noopener noreferrer"
>
OpenAI playground
</a>
<span> to see if you have correct API access first.</span>
@ -132,10 +139,15 @@ const ApiSettings: React.FC<ApiSettingsProps> = ({
placeholder="Enter Google API Key"
/>
<p>
If you have Google Cloud, you can get Gemini API key {' '}
<a href="https://makersuite.google.com/app/apikey" target="_blank" rel="noopener noreferrer">
If you have Google Cloud, you can get Gemini API key{" "}
<a
href="https://makersuite.google.com/app/apikey"
target="_blank"
rel="noopener noreferrer"
>
here
</a>.
</a>
.
<br />
Your API key is stored locally and is only used to make requests to Google's services.
</p>
@ -167,12 +179,18 @@ const ApiSettings: React.FC<ApiSettingsProps> = ({
type="text"
/>
<p>
If you have Anthropic API access, you can get the API key {' '}
<a href="https://console.anthropic.com/settings/keys" target="_blank" rel="noopener noreferrer">
If you have Anthropic API access, you can get the API key{" "}
<a
href="https://console.anthropic.com/settings/keys"
target="_blank"
rel="noopener noreferrer"
>
here
</a>.
</a>
.
<br />
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.
</p>
</div>
</Collapsible>
@ -194,14 +212,17 @@ const ApiSettings: React.FC<ApiSettingsProps> = ({
type="text"
/>
<p>
You can get your OpenRouterAI key {' '}
You can get your OpenRouterAI key{" "}
<a href="https://openrouter.ai/keys" target="_blank" rel="noopener noreferrer">
here
</a>.
</a>
.
<br />
Find models <a href="https://openrouter.ai/models" target="_blank" rel="noopener noreferrer">
Find models{" "}
<a href="https://openrouter.ai/models" target="_blank" rel="noopener noreferrer">
here
</a>.
</a>
.
</p>
</div>
</Collapsible>
@ -263,10 +284,11 @@ const ApiSettings: React.FC<ApiSettingsProps> = ({
type="text"
/>
<p>
If you have Groq API access, you can get the API key {' '}
If you have Groq API access, you can get the API key{" "}
<a href="https://console.groq.com/keys" target="_blank" rel="noopener noreferrer">
here
</a>.
</a>
.
<br />
Your API key is stored locally and is only used to make requests to Groq's services.
</p>

View file

@ -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<CollapsibleProps> = ({ 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 = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
);
const ChevronRight = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
);
return (
<div style={{ padding: '20px' }}>
<div style={{ padding: "20px" }}>
<div style={titleStyle} onClick={() => setIsOpen(!isOpen)}>
{title}
{isOpen ? <ChevronDown /> : <ChevronRight />}
</div>
{isOpen && (
<div style={contentStyle}>
{children}
</div>
)}
{isOpen && <div style={contentStyle}>{children}</div>}
</div>
);
};

View file

@ -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<string, { enabled: boolean; name: string }>;
@ -15,14 +15,14 @@ const CommandToggleSettings: React.FC<CommandToggleSettingsProps> = ({
const toggleCommand = (commandId: string, enabled: boolean) => {
setEnabledCommands({
...enabledCommands,
[commandId]: { ...enabledCommands[commandId], enabled }
[commandId]: { ...enabledCommands[commandId], enabled },
});
};
return (
<div>
<h2 onClick={() => setIsExpanded(!isExpanded)} style={{ cursor: 'pointer' }}>
Command Settings {isExpanded ? '▼' : '▶'}
<h2 onClick={() => setIsExpanded(!isExpanded)} style={{ cursor: "pointer" }}>
Command Settings {isExpanded ? "▼" : "▶"}
</h2>
{isExpanded && (
<div>

View file

@ -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 (
<div>
<h2>General Settings</h2>
<DropdownComponent
name="Default Model"
options={Object.values(ChatModelDisplayNames)}
value={settings.defaultModelDisplayName}
onChange={(value) => updateSettings({ defaultModelDisplayName: value })}
/>
<TextComponent
name="Default Conversation Folder Name"
description="The default folder name where chat conversations will be saved. Default is 'copilot-conversations'"
placeholder="copilot-conversations"
value={settings.defaultSaveFolder}
onChange={(value) => updateSettings({ defaultSaveFolder: value })}
/>
<h6>
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.
</h6>
<SliderComponent
name="Temperature"
description="Default is 0.1. Higher values will result in more creativeness, but also more mistakes. Set to 0 for no randomness."
min={0}
max={2}
step={0.05}
value={settings.temperature}
onChange={(value) => updateSettings({ temperature: value })}
/>
<SliderComponent
name="Token limit"
description={
<>
<p>
The maximum number of <em>output tokens</em> to generate. Default is 1000.
</p>
<em>
This number plus the length of your prompt (input tokens) must be smaller than the
context window of the model.
</em>
</>
}
min={0}
max={16000}
step={100}
value={settings.maxTokens}
onChange={(value) => updateSettings({ maxTokens: value })}
/>
<SliderComponent
name="Conversation turns in context"
description="The number of previous conversation turns to include in the context. Default is 15 turns, i.e. 30 messages."
min={1}
max={50}
step={1}
value={settings.contextTurns}
onChange={(value) => updateSettings({ contextTurns: value })}
/>
<CommandToggleSettings
enabledCommands={settings.enabledCommands}
setEnabledCommands={(value) => updateSettings({ enabledCommands: value })}
/>
</div>
);
};
export default GeneralSettings;

View file

@ -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<LocalCopilotSettingsProps> = ({
}) => {
return (
<div>
<br/>
<br />
<h1>Local Copilot (No Internet Required!)</h1>
<div className="warning-message">
Please check the doc to set up LM Studio or Ollama server on your device.
</div>
<p>
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!
</p>
<h3>LM Studio</h3>
<p>
To use Local Copilot with LM Studio:<br />
1. Start LM Studio server with CORS on. Default port is 1234 but if you change it, you can provide it below.<br />
To use Local Copilot with LM Studio:
<br />
1. Start LM Studio server with CORS on. Default port is 1234 but if you change it, you can
provide it below.
<br />
2. Pick LM Studio in the Copilot Chat model selection dropdown to chat with it!
</p>
<TextComponent
@ -42,9 +47,13 @@ const LocalCopilotSettings: React.FC<LocalCopilotSettingsProps> = ({
placeholder="http://localhost:1234/v1"
/>
<h3>Ollama</h3>
<p>To use Local Copilot with Ollama, pick Ollama in the Copilot Chat model selection dropdown.</p>
<p>
To use Local Copilot with Ollama, pick Ollama in the Copilot Chat model selection dropdown.
</p>
<p>Run the local Ollama server by running this in your terminal:</p>
<p><strong>OLLAMA_ORIGINS=app://obsidian.md* ollama serve</strong></p>
<p>
<strong>OLLAMA_ORIGINS=app://obsidian.md* ollama serve</strong>
</p>
<TextComponent
name="Ollama model"
description="Default is llama2 (7b)."

View file

@ -1,8 +1,8 @@
import { EmbeddingModels, VAULT_VECTOR_STORE_STRATEGIES } from '@/constants';
import React from 'react';
import ApiSetting from './ApiSetting';
import Collapsible from './Collapsible';
import { DropdownComponent, SliderComponent } from './SettingBlocks';
import { EmbeddingModels, VAULT_VECTOR_STORE_STRATEGIES } from "@/constants";
import React from "react";
import ApiSetting from "./ApiSetting";
import Collapsible from "./Collapsible";
import { DropdownComponent, SliderComponent } from "./SettingBlocks";
interface QASettingsProps {
embeddingModel: string;
@ -35,13 +35,26 @@ const QASettings: React.FC<QASettingsProps> = ({
<br />
<h1>QA Settings</h1>
<div className="warning-message">
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.
</div>
<p>QA mode relies a <em>local</em> vector index.</p>
<p>
QA mode relies a <em>local</em> vector index.
</p>
<h2>Long Note QA vs. Vault QA (BETA)</h2>
<p>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.</p>
<p>
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.
</p>
<h2>Local Embedding Model</h2>
<p>Check the <a href='https://github.com/logancyang/obsidian-copilot/blob/master/local_copilot.md'>local copilot</a> setup guide to setup Ollama's local embedding model (requires Ollama v0.1.26 or above).</p>
<p>
Check the{" "}
<a href="https://github.com/logancyang/obsidian-copilot/blob/master/local_copilot.md">
local copilot
</a>{" "}
setup guide to setup Ollama's local embedding model (requires Ollama v0.1.26 or above).
</p>
<DropdownComponent
name="Embedding Models"
description="The embedding API/model to use"
@ -54,9 +67,11 @@ const QASettings: React.FC<QASettingsProps> = ({
If you are using a paid embedding provider, beware of costs for large vaults!
</div>
<p>
When you switch to <strong>Long Note QA</strong> mode, your active note is indexed automatically upon mode switch.
When you switch to <strong>Long Note QA</strong> mode, your active note is indexed
automatically upon mode switch.
<br />
When you switch to <strong>Vault QA</strong> mode, your vault is indexed <em>based on the auto-index strategy you select below</em>.
When you switch to <strong>Vault QA</strong> mode, your vault is indexed{" "}
<em>based on the auto-index strategy you select below</em>.
<br />
</p>
<DropdownComponent
@ -68,14 +83,26 @@ const QASettings: React.FC<QASettingsProps> = ({
/>
<br />
<p>
<strong>NEVER</strong>: Notes are never indexed to the vector store unless users run the command <em>Index vault for QA</em> explicitly, or hit the <em>Refresh Index</em> button.
<br /><br />
<strong>NEVER</strong>: Notes are never indexed to the vector store unless users run the
command <em>Index vault for QA</em> explicitly, or hit the <em>Refresh Index</em> button.
<br />
<br />
<strong>ON STARTUP</strong>: Vault index is refreshed on plugin load/reload.
<br /><br />
<strong>ON MODE SWITCH (Recommended)</strong>: Vault index is refreshed when switching to Vault QA mode.
<br /><br />
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.<br /><br />
Beware of the cost if you are using a paid embedding model and have a large vault! You can run Copilot command <em>Count total tokens in your vault</em> and refer to your selected embedding model pricing to estimate indexing costs.
<br />
<br />
<strong>ON MODE SWITCH (Recommended)</strong>: Vault index is refreshed when switching to
Vault QA mode.
<br />
<br />
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.
<br />
<br />
Beware of the cost if you are using a paid embedding model and have a large vault! You can
run Copilot command <em>Count total tokens in your vault</em> and refer to your selected
embedding model pricing to estimate indexing costs.
</p>
<br />
<SliderComponent
@ -98,12 +125,8 @@ const QASettings: React.FC<QASettingsProps> = ({
placeholder="Enter Cohere API Key"
/>
<p>
Get your free Cohere API key{' '}
<a
href="https://dashboard.cohere.ai/api-keys"
target="_blank"
rel="noreferrer"
>
Get your free Cohere API key{" "}
<a href="https://dashboard.cohere.ai/api-keys" target="_blank" rel="noreferrer">
here
</a>
</p>

View file

@ -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<DropdownComponentProps> = ({ name, description, options, value, onChange }) => {
const DropdownComponent: React.FC<DropdownComponentProps> = ({
name,
description,
options,
value,
onChange,
}) => {
return (
<div className="copilot-setting-item">
<div className="copilot-setting-item-name">{name}</div>
@ -62,13 +68,20 @@ const DropdownComponent: React.FC<DropdownComponentProps> = ({ name, description
);
};
const TextComponent: React.FC<TextComponentProps> = ({ name, description, placeholder, value, type, onChange }) => {
const TextComponent: React.FC<TextComponentProps> = ({
name,
description,
placeholder,
value,
type,
onChange,
}) => {
return (
<div className="copilot-setting-item">
<div className="copilot-setting-item-name">{name}</div>
<div className="copilot-setting-item-description">{description}</div>
<input
type={type || 'text'}
type={type || "text"}
className="copilot-setting-item-control"
placeholder={placeholder}
value={value}
@ -78,7 +91,13 @@ const TextComponent: React.FC<TextComponentProps> = ({ name, description, placeh
);
};
const TextAreaComponent: React.FC<TextAreaComponentProps> = ({ name, description, placeholder, value, onChange }) => {
const TextAreaComponent: React.FC<TextAreaComponentProps> = ({
name,
description,
placeholder,
value,
onChange,
}) => {
return (
<div className="copilot-setting-item">
<div className="copilot-setting-item-name">{name}</div>
@ -93,13 +112,20 @@ const TextAreaComponent: React.FC<TextAreaComponentProps> = ({ name, description
);
};
const SliderComponent: React.FC<SliderComponentProps> = ({ name, description, min, max, step, value, onChange }) => {
const SliderComponent: React.FC<SliderComponentProps> = ({
name,
description,
min,
max,
step,
value,
onChange,
}) => {
return (
<div className="copilot-setting-item">
<div className="copilot-setting-item-name">{name}</div>
<div className="copilot-setting-item-description">{description}</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ display: "flex", alignItems: "center" }}>
<input
type="range"
className="copilot-setting-item-control"
@ -109,23 +135,28 @@ const SliderComponent: React.FC<SliderComponentProps> = ({ name, description, mi
value={value}
onChange={(e) => onChange(parseFloat(e.target.value))}
/>
<span style={{ marginLeft: '20px', fontWeight: 'bold', color: 'var(--inline-title-color)' }}>{value}</span>
<span
style={{ marginLeft: "20px", fontWeight: "bold", color: "var(--inline-title-color)" }}
>
{value}
</span>
</div>
</div>
);
};
const ToggleComponent: React.FC<ToggleComponentProps> = ({ name, description, value, onChange }) => {
const ToggleComponent: React.FC<ToggleComponentProps> = ({
name,
description,
value,
onChange,
}) => {
return (
<div className="copilot-setting-item">
<div className="copilot-setting-item-name">{name}</div>
<div className="copilot-setting-item-description">{description}</div>
<label className="switch">
<input
type="checkbox"
checked={value}
onChange={(e) => onChange(e.target.checked)}
/>
<input type="checkbox" checked={value} onChange={(e) => onChange(e.target.checked)} />
<span className="slider round"></span>
</label>
</div>
@ -133,4 +164,3 @@ const ToggleComponent: React.FC<ToggleComponentProps> = ({ name, description, va
};
export { DropdownComponent, SliderComponent, TextAreaComponent, TextComponent, ToggleComponent };

View file

@ -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<void>;
}
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<void> }> = ({
plugin,
reloadPlugin,
}) => {
const { settings, updateSettings, saveSettings, resetSettings } = useSettingsContext();
return (
<>
<div>
<h2>Copilot Settings</h2>
<div className="button-container">
<button className="mod-cta" onClick={saveAllSettings}>
Save and Reload
</button>
<button className="mod-cta" onClick={resetToDefaultSettings}>
Reset to Default Settings
</button>
</div>
<div className="warning-message">
Please Save and Reload the plugin when you change any setting below!
</div>
<DropdownComponent
name="Default Model"
options={Object.values(ChatModelDisplayNames)}
value={defaultModelDisplayName}
onChange={setDefaultModelDisplayName}
/>
<TextComponent
name="Default Conversation Folder Name"
description="The default folder name where chat conversations will be saved. Default is 'copilot-conversations'"
placeholder="copilot-conversations"
value={defaultSaveFolder}
onChange={setDefaultSaveFolder}
/>
<h6>
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.
</h6>
<SliderComponent
name="Temperature"
description="Default is 0.1. Higher values will result in more creativeness, but also more mistakes. Set to 0 for no randomness."
min={0}
max={2}
step={0.05}
value={temperature}
onChange={async (value) => {
setTemperature(value);
}}
/>
<SliderComponent
name="Token limit"
description={
<Fragment>
<p>The maximum number of <em>output tokens</em> to generate. Default is 1000.</p>
<em>This number plus the length of your prompt (input tokens) must be smaller than the context window of the model.</em>
</Fragment>
}
min={0}
max={10000}
step={100}
value={maxTokens}
onChange={async (value) => {
setMaxTokens(value);
}}
/>
<SliderComponent
name="Conversation turns in context"
description="The number of previous conversation turns to include in the context. Default is 15 turns, i.e. 30 messages."
min={1}
max={30}
step={1}
value={contextTurns}
onChange={async (value) => {
setContextTurns(value);
}}
/>
<CommandToggleSettings
enabledCommands={enabledCommands}
setEnabledCommands={setEnabledCommands}
/>
<h2>Copilot Settings</h2>
<div className="button-container">
<button className="mod-cta" onClick={saveSettings}>
Save and Reload
</button>
<button className="mod-cta" onClick={resetSettings}>
Reset to Default Settings
</button>
</div>
<div className="warning-message">
Please Save and Reload the plugin when you change any setting below!
</div>
<GeneralSettings />
<ApiSettings
openAIApiKey={openAIApiKey}
setOpenAIApiKey={setOpenAIApiKey}
openAIOrgId={openAIOrgId}
setOpenAIOrgId={setOpenAIOrgId}
openAICustomModel={openAICustomModel}
setOpenAICustomModel={setOpenAICustomModel}
googleApiKey={googleApiKey}
setGoogleApiKey={setGoogleApiKey}
googleCustomModel={googleCustomModel}
setGoogleCustomModel={setGoogleCustomModel}
anthropicApiKey={anthropicApiKey}
setAnthropicApiKey={setAnthropicApiKey}
anthropicModel={anthropicModel}
setAnthropicModel={setAnthropicModel}
openRouterAiApiKey={openRouterAiApiKey}
setOpenRouterAiApiKey={setOpenRouterAiApiKey}
openRouterModel={openRouterModel}
setOpenRouterModel={setOpenRouterModel}
azureOpenAIApiKey={azureOpenAIApiKey}
setAzureOpenAIApiKey={setAzureOpenAIApiKey}
azureOpenAIApiInstanceName={azureOpenAIApiInstanceName}
setAzureOpenAIApiInstanceName={setAzureOpenAIApiInstanceName}
azureOpenAIApiDeploymentName={azureOpenAIApiDeploymentName}
setAzureOpenAIApiDeploymentName={setAzureOpenAIApiDeploymentName}
azureOpenAIApiVersion={azureOpenAIApiVersion}
setAzureOpenAIApiVersion={setAzureOpenAIApiVersion}
azureOpenAIApiEmbeddingDeploymentName={azureOpenAIApiEmbeddingDeploymentName}
setAzureOpenAIApiEmbeddingDeploymentName={setAzureOpenAIApiEmbeddingDeploymentName}
groqApiKey={groqApiKey}
setGroqApiKey={setGroqApiKey}
groqModel={groqModel}
setGroqModel={setGroqModel}
{...settings}
setOpenAIApiKey={(value) => 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 })}
/>
<QASettings
embeddingModel={embeddingModel}
setEmbeddingModel={setEmbeddingModel}
cohereApiKey={cohereApiKey}
setCohereApiKey={setCohereApiKey}
huggingfaceApiKey={huggingfaceApiKey}
setHuggingfaceApiKey={setHuggingfaceApiKey}
indexVaultToVectorStore={indexVaultToVectorStore}
setIndexVaultToVectorStore={setIndexVaultToVectorStore}
maxSourceChunks={maxSourceChunks}
setMaxSourceChunks={setMaxSourceChunks}
{...settings}
setEmbeddingModel={(value) => updateSettings({ embeddingModel: value })}
setCohereApiKey={(value) => updateSettings({ cohereApiKey: value })}
setHuggingfaceApiKey={(value) => updateSettings({ huggingfaceApiKey: value })}
setIndexVaultToVectorStore={(value) => updateSettings({ indexVaultToVectorStore: value })}
setMaxSourceChunks={(value) => updateSettings({ maxSourceChunks: value })}
/>
<AdvancedSettings
openAIProxyBaseUrl={openAIProxyBaseUrl}
setOpenAIProxyBaseUrl={setOpenAIProxyBaseUrl}
useOpenAILocalProxy={useOpenAILocalProxy}
setUseOpenAILocalProxy={setUseOpenAILocalProxy}
openAIProxyModelName={openAIProxyModelName}
setOpenAIProxyModelName={setOpenAIProxyModelName}
openAIEmbeddingProxyBaseUrl={openAIEmbeddingProxyBaseUrl}
setOpenAIEmbeddingProxyBaseUrl={setOpenAIEmbeddingProxyBaseUrl}
openAIEmbeddingProxyModelName={openAIEmbeddingProxyModelName}
setOpenAIEmbeddingProxyModelName={setOpenAIEmbeddingProxyModelName}
userSystemPrompt={userSystemPrompt}
setUserSystemPrompt={setUserSystemPrompt}
{...settings}
setOpenAIProxyBaseUrl={(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 })}
/>
<LocalCopilotSettings
lmStudioBaseUrl={lmStudioBaseUrl}
setlmStudioBaseUrl={setlmStudioBaseUrl}
ollamaModel={ollamaModel}
setOllamaModel={setOllamaModel}
ollamaBaseUrl={ollamaBaseUrl}
setOllamaBaseUrl={setOllamaBaseUrl}
{...settings}
setlmStudioBaseUrl={(value) => updateSettings({ lmStudioBaseUrl: value })}
setOllamaModel={(value) => updateSettings({ ollamaModel: value })}
setOllamaBaseUrl={(value) => updateSettings({ ollamaBaseUrl: value })}
/>
</>
);
}
};
export default React.memo(SettingsMain);

View file

@ -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<CopilotSettings>) => void;
saveSettings: () => Promise<void>;
resetSettings: () => Promise<void>;
reloadPlugin: () => Promise<void>;
}
const SettingsContext = createContext<SettingsContextType | undefined>(undefined);
export const SettingsProvider: React.FC<{
plugin: CopilotPlugin;
reloadPlugin: () => Promise<void>;
children: React.ReactNode;
}> = ({ plugin, reloadPlugin, children }) => {
const [settings, setSettings] = useState<CopilotSettings>(plugin.settings);
const updateSettings = useCallback(
async (newSettings: Partial<CopilotSettings>) => {
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 (
<SettingsContext.Provider
value={{ settings, updateSettings, saveSettings, resetSettings, reloadPlugin }}
>
{children}
</SettingsContext.Provider>
);
};
export const useSettingsContext = () => {
const context = useContext(SettingsContext);
if (context === undefined) {
throw new Error("useSettingsContext must be used within a SettingsProvider");
}
return context;
};