mirror of
https://github.com/leoyishou/obsidian-ai-autocomplete.git
synced 2026-07-22 06:51:10 +00:00
feat: AI Autocomplete - inline ghost text completion for Obsidian
Powered by Groq API. Type naturally, get ghost text suggestions. Tab to accept, Esc to dismiss. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
0fc274537a
12 changed files with 1154 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
main.js
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 liuyishou
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
41
README.md
Normal file
41
README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# AI Autocomplete
|
||||
|
||||
AI inline writing completion for Obsidian, powered by [Groq](https://groq.com).
|
||||
|
||||
Type naturally and get ghost text suggestions that appear inline. Press **Tab** to accept, **Esc** to dismiss.
|
||||
|
||||
## Features
|
||||
|
||||
- **Ghost text completion** — transparent suggestions appear at your cursor, like GitHub Copilot
|
||||
- **Context-aware** — reads text before and after cursor for coherent continuations
|
||||
- **Fast** — powered by Groq's ultra-low-latency inference (Llama 3.3 70B)
|
||||
- **Bilingual** — automatically detects and continues in Chinese or English
|
||||
- **Lightweight** — 6KB plugin, no dependencies
|
||||
|
||||
## Usage
|
||||
|
||||
1. Install the plugin
|
||||
2. Go to Settings → Groq Copilot → enter your Groq API key (get one free at [console.groq.com](https://console.groq.com))
|
||||
3. Start writing — suggestions appear after a brief pause
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| Tab | Accept suggestion |
|
||||
| Esc | Dismiss suggestion |
|
||||
| Keep typing | Suggestion auto-dismisses |
|
||||
|
||||
## Settings
|
||||
|
||||
| Setting | Default | Description |
|
||||
|---------|---------|-------------|
|
||||
| Model | Llama 3.3 70B | Also supports Llama 3.1 8B (faster), Gemma 2 9B, Mixtral 8x7B |
|
||||
| Trigger delay | 800ms | How long to wait after typing before fetching a suggestion |
|
||||
| Enabled | On | Toggle via settings or command palette |
|
||||
|
||||
## How it works
|
||||
|
||||
The plugin uses CodeMirror 6 extensions to render transparent "ghost text" at the cursor position. When you pause typing, it sends the surrounding context (up to 2000 chars before + 500 chars after cursor) to Groq's API and displays the completion as inline ghost text.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
31
esbuild.config.mjs
Normal file
31
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
|
||||
const prod = process.argv[2] === "production";
|
||||
|
||||
esbuild.build({
|
||||
entryPoints: ["src/main.ts"],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
"electron",
|
||||
"@codemirror/autocomplete",
|
||||
"@codemirror/collab",
|
||||
"@codemirror/commands",
|
||||
"@codemirror/language",
|
||||
"@codemirror/lint",
|
||||
"@codemirror/search",
|
||||
"@codemirror/state",
|
||||
"@codemirror/view",
|
||||
"@lezer/common",
|
||||
"@lezer/highlight",
|
||||
"@lezer/lr",
|
||||
],
|
||||
format: "cjs",
|
||||
target: "es2018",
|
||||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
minify: prod,
|
||||
}).catch(() => process.exit(1));
|
||||
9
manifest.json
Normal file
9
manifest.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"id": "ai-autocomplete",
|
||||
"name": "AI Autocomplete",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Inline AI writing completion with ghost text. Powered by Groq. Tab to accept.",
|
||||
"author": "liuyishou",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
584
package-lock.json
generated
Normal file
584
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
{
|
||||
"name": "obsidian-groq-copilot",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "obsidian-groq-copilot",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@types/node": "^20.0.0",
|
||||
"esbuild": "^0.21.0",
|
||||
"obsidian": "^1.5.0",
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/state": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.0.tgz",
|
||||
"integrity": "sha512-MwBHVK60IiIHDcoMet78lxt6iw5gJOGSbNbOIVBHWVXIH4/Nq1+GQgLLGgI1KlnN86WDXsPudVaqYHKBIx7Eyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.38.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.6.tgz",
|
||||
"integrity": "sha512-qiS0z1bKs5WOvHIAC0Cybmv4AJSkAXgX5aD6Mqd2epSLlVJsQl8NG23jCVouIgkh4All/mrbdsf2UOLFnJw0tw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"crelt": "^1.0.6",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
|
||||
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
|
||||
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
|
||||
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
|
||||
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
|
||||
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
|
||||
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
|
||||
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
|
||||
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
|
||||
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/codemirror": {
|
||||
"version": "5.60.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-5.60.8.tgz",
|
||||
"integrity": "sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/tern": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.19.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz",
|
||||
"integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tern": {
|
||||
"version": "0.23.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.9.tgz",
|
||||
"integrity": "sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.21.5",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.21.5",
|
||||
"@esbuild/android-arm": "0.21.5",
|
||||
"@esbuild/android-arm64": "0.21.5",
|
||||
"@esbuild/android-x64": "0.21.5",
|
||||
"@esbuild/darwin-arm64": "0.21.5",
|
||||
"@esbuild/darwin-x64": "0.21.5",
|
||||
"@esbuild/freebsd-arm64": "0.21.5",
|
||||
"@esbuild/freebsd-x64": "0.21.5",
|
||||
"@esbuild/linux-arm": "0.21.5",
|
||||
"@esbuild/linux-arm64": "0.21.5",
|
||||
"@esbuild/linux-ia32": "0.21.5",
|
||||
"@esbuild/linux-loong64": "0.21.5",
|
||||
"@esbuild/linux-mips64el": "0.21.5",
|
||||
"@esbuild/linux-ppc64": "0.21.5",
|
||||
"@esbuild/linux-riscv64": "0.21.5",
|
||||
"@esbuild/linux-s390x": "0.21.5",
|
||||
"@esbuild/linux-x64": "0.21.5",
|
||||
"@esbuild/netbsd-x64": "0.21.5",
|
||||
"@esbuild/openbsd-x64": "0.21.5",
|
||||
"@esbuild/sunos-x64": "0.21.5",
|
||||
"@esbuild/win32-arm64": "0.21.5",
|
||||
"@esbuild/win32-ia32": "0.21.5",
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/moment": {
|
||||
"version": "2.29.4",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/obsidian": {
|
||||
"version": "1.12.0",
|
||||
"resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.0.tgz",
|
||||
"integrity": "sha512-goA2DNTIPO3IRtsqzUs6UQmVJkwZayIKXnI3tLZX/NO+t1yl7YuZrfVEL1JyUkthCJyaZMn8WOlp7mX18acWxA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/codemirror": "5.60.8",
|
||||
"moment": "2.29.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@codemirror/state": "6.5.0",
|
||||
"@codemirror/view": "6.38.6"
|
||||
}
|
||||
},
|
||||
"node_modules/style-mod": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz",
|
||||
"integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
18
package.json
Normal file
18
package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "obsidian-ai-autocomplete",
|
||||
"version": "1.0.0",
|
||||
"description": "AI inline auto-completion for Obsidian powered by Groq",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "node esbuild.config.mjs production"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"esbuild": "^0.21.0",
|
||||
"obsidian": "^1.5.0",
|
||||
"typescript": "^5.4.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0"
|
||||
}
|
||||
}
|
||||
233
src/ghost-text.ts
Normal file
233
src/ghost-text.ts
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
import {
|
||||
ViewPlugin,
|
||||
ViewUpdate,
|
||||
EditorView,
|
||||
Decoration,
|
||||
DecorationSet,
|
||||
WidgetType,
|
||||
keymap,
|
||||
} from "@codemirror/view";
|
||||
import {
|
||||
StateEffect,
|
||||
StateField,
|
||||
Text,
|
||||
Prec,
|
||||
EditorState,
|
||||
EditorSelection,
|
||||
TransactionSpec,
|
||||
} from "@codemirror/state";
|
||||
|
||||
// --- State Management ---
|
||||
|
||||
export const InlineSuggestionEffect = StateEffect.define<{
|
||||
text: string | null;
|
||||
doc: Text;
|
||||
}>();
|
||||
|
||||
export const ClearSuggestionEffect = StateEffect.define<null>();
|
||||
|
||||
export const InlineSuggestionState = StateField.define<{
|
||||
suggestion: string | null;
|
||||
}>({
|
||||
create() {
|
||||
return { suggestion: null };
|
||||
},
|
||||
update(value, tr) {
|
||||
// Explicit clear
|
||||
for (const effect of tr.effects) {
|
||||
if (effect.is(ClearSuggestionEffect)) {
|
||||
return { suggestion: null };
|
||||
}
|
||||
}
|
||||
|
||||
// New suggestion arrived
|
||||
for (const effect of tr.effects) {
|
||||
if (effect.is(InlineSuggestionEffect)) {
|
||||
// Only accept if doc hasn't changed since request
|
||||
if (tr.state.doc === effect.value.doc) {
|
||||
return { suggestion: effect.value.text };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Any doc change or cursor move clears suggestion
|
||||
if (tr.docChanged || tr.selection) {
|
||||
return { suggestion: null };
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
});
|
||||
|
||||
// --- Ghost Text Widget ---
|
||||
|
||||
class GhostTextWidget extends WidgetType {
|
||||
constructor(readonly text: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: GhostTextWidget) {
|
||||
return other.text === this.text;
|
||||
}
|
||||
|
||||
toDOM() {
|
||||
const span = document.createElement("span");
|
||||
span.className = "groq-copilot-ghost-text";
|
||||
span.textContent = this.text;
|
||||
return span;
|
||||
}
|
||||
|
||||
get lineBreaks() {
|
||||
return this.text.split("\n").length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Render Plugin ---
|
||||
|
||||
const renderGhostTextPlugin = ViewPlugin.fromClass(
|
||||
class {
|
||||
decorations: DecorationSet = Decoration.none;
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
const suggestion =
|
||||
update.state.field(InlineSuggestionState)?.suggestion;
|
||||
|
||||
if (!suggestion) {
|
||||
this.decorations = Decoration.none;
|
||||
return;
|
||||
}
|
||||
|
||||
const pos = update.state.selection.main.head;
|
||||
const widget = Decoration.widget({
|
||||
widget: new GhostTextWidget(suggestion),
|
||||
side: 1,
|
||||
});
|
||||
this.decorations = Decoration.set([widget.range(pos)]);
|
||||
}
|
||||
},
|
||||
{
|
||||
decorations: (v) => v.decorations,
|
||||
}
|
||||
);
|
||||
|
||||
// --- Key Bindings ---
|
||||
|
||||
function insertCompletionText(
|
||||
state: EditorState,
|
||||
text: string,
|
||||
from: number,
|
||||
to: number
|
||||
): TransactionSpec {
|
||||
return {
|
||||
...state.changeByRange((range) => {
|
||||
if (range === state.selection.main) {
|
||||
return {
|
||||
changes: { from, to, insert: text },
|
||||
range: EditorSelection.cursor(from + text.length),
|
||||
};
|
||||
}
|
||||
return { range };
|
||||
}),
|
||||
userEvent: "input.complete",
|
||||
};
|
||||
}
|
||||
|
||||
const ghostTextKeymap = Prec.highest(
|
||||
keymap.of([
|
||||
{
|
||||
key: "Tab",
|
||||
run: (view: EditorView) => {
|
||||
const suggestion =
|
||||
view.state.field(InlineSuggestionState)?.suggestion;
|
||||
if (!suggestion) return false;
|
||||
|
||||
const head = view.state.selection.main.head;
|
||||
view.dispatch(insertCompletionText(view.state, suggestion, head, head));
|
||||
return true;
|
||||
},
|
||||
},
|
||||
{
|
||||
key: "Escape",
|
||||
run: (view: EditorView) => {
|
||||
const suggestion =
|
||||
view.state.field(InlineSuggestionState)?.suggestion;
|
||||
if (!suggestion) return false;
|
||||
|
||||
view.dispatch({ effects: ClearSuggestionEffect.of(null) });
|
||||
return true;
|
||||
},
|
||||
},
|
||||
])
|
||||
);
|
||||
|
||||
// --- Fetch Plugin (triggers AI completion) ---
|
||||
|
||||
export type FetchFn = (
|
||||
prefix: string,
|
||||
suffix: string,
|
||||
state: EditorState
|
||||
) => Promise<string | null>;
|
||||
|
||||
export function createFetchPlugin(fetchFn: FetchFn, delay: number) {
|
||||
return ViewPlugin.fromClass(
|
||||
class {
|
||||
private timer: ReturnType<typeof setTimeout> | null = null;
|
||||
private abortController: AbortController | null = null;
|
||||
|
||||
update(update: ViewUpdate) {
|
||||
if (!update.docChanged) return;
|
||||
|
||||
// Cancel pending request
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
if (this.abortController) this.abortController.abort();
|
||||
|
||||
this.timer = setTimeout(async () => {
|
||||
const doc = update.state.doc;
|
||||
const cursor = update.state.selection.main.head;
|
||||
const fullText = doc.toString();
|
||||
|
||||
const prefix = fullText.slice(Math.max(0, cursor - 2000), cursor);
|
||||
const suffix = fullText.slice(cursor, cursor + 500);
|
||||
|
||||
// Don't trigger on empty or very short prefix
|
||||
if (prefix.trim().length < 3) return;
|
||||
|
||||
this.abortController = new AbortController();
|
||||
|
||||
try {
|
||||
const result = await fetchFn(prefix, suffix, update.state);
|
||||
if (result && result.trim()) {
|
||||
update.view.dispatch({
|
||||
effects: InlineSuggestionEffect.of({
|
||||
text: result,
|
||||
doc,
|
||||
}),
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently ignore aborted requests
|
||||
if (e instanceof Error && e.name !== "AbortError") {
|
||||
console.error("Groq Copilot: fetch error", e);
|
||||
}
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.timer) clearTimeout(this.timer);
|
||||
if (this.abortController) this.abortController.abort();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
export function inlineSuggestionExtension(fetchFn: FetchFn, delay = 800) {
|
||||
return [
|
||||
InlineSuggestionState,
|
||||
createFetchPlugin(fetchFn, delay),
|
||||
renderGhostTextPlugin,
|
||||
ghostTextKeymap,
|
||||
];
|
||||
}
|
||||
56
src/groq-api.ts
Normal file
56
src/groq-api.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { requestUrl } from "obsidian";
|
||||
|
||||
const GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
|
||||
|
||||
const SYSTEM_PROMPT = `You are a writing assistant for an Obsidian note-taking app. Your job is to continue writing from where the user left off.
|
||||
|
||||
Rules:
|
||||
- Output ONLY the continuation text. Do not repeat any existing text.
|
||||
- Keep the same language (Chinese/English) as the context.
|
||||
- Keep the same writing style and tone.
|
||||
- Keep it concise: 1-2 sentences max for prose, 1-3 lines for lists/code.
|
||||
- If the context is a markdown list, continue the list pattern.
|
||||
- If the context is a code block, continue the code.
|
||||
- If the context ends mid-sentence, complete the sentence.
|
||||
- Do not add markdown formatting unless continuing an existing pattern.
|
||||
- Do not add explanations or meta-commentary.`;
|
||||
|
||||
export async function fetchGroqCompletion(
|
||||
apiKey: string,
|
||||
model: string,
|
||||
prefix: string,
|
||||
suffix: string
|
||||
): Promise<string | null> {
|
||||
const userMessage =
|
||||
suffix.trim().length > 0
|
||||
? `Context before cursor:\n${prefix}\n\nContext after cursor:\n${suffix}\n\nContinue writing from where the cursor is:`
|
||||
: `${prefix}`;
|
||||
|
||||
try {
|
||||
const response = await requestUrl({
|
||||
url: GROQ_API_URL,
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [
|
||||
{ role: "system", content: SYSTEM_PROMPT },
|
||||
{ role: "user", content: userMessage },
|
||||
],
|
||||
max_tokens: 150,
|
||||
temperature: 0.3,
|
||||
stop: ["\n\n", "---"],
|
||||
}),
|
||||
});
|
||||
|
||||
const data = response.json;
|
||||
const text = data?.choices?.[0]?.message?.content;
|
||||
return text?.trim() || null;
|
||||
} catch (e) {
|
||||
console.error("Groq Copilot: API error", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
136
src/main.ts
Normal file
136
src/main.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
import { Plugin, PluginSettingTab, App, Setting, Notice } from "obsidian";
|
||||
import { inlineSuggestionExtension } from "./ghost-text";
|
||||
import { fetchGroqCompletion } from "./groq-api";
|
||||
|
||||
interface AIAutocompleteSettings {
|
||||
apiKey: string;
|
||||
model: string;
|
||||
delay: number;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: AIAutocompleteSettings = {
|
||||
apiKey: "",
|
||||
model: "llama-3.3-70b-versatile",
|
||||
delay: 800,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
export default class AIAutocompletePlugin extends Plugin {
|
||||
settings: AIAutocompleteSettings = DEFAULT_SETTINGS;
|
||||
private editorExtensions: any[] = [];
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.editorExtensions = inlineSuggestionExtension(
|
||||
async (prefix, suffix) => {
|
||||
if (!this.settings.enabled || !this.settings.apiKey) return null;
|
||||
return fetchGroqCompletion(
|
||||
this.settings.apiKey,
|
||||
this.settings.model,
|
||||
prefix,
|
||||
suffix
|
||||
);
|
||||
},
|
||||
this.settings.delay
|
||||
);
|
||||
|
||||
this.registerEditorExtension(this.editorExtensions);
|
||||
|
||||
this.addCommand({
|
||||
id: "toggle-ai-autocomplete",
|
||||
name: "Toggle auto-completion",
|
||||
callback: () => {
|
||||
this.settings.enabled = !this.settings.enabled;
|
||||
this.saveSettings();
|
||||
new Notice(
|
||||
`AI Autocomplete: ${this.settings.enabled ? "ON" : "OFF"}`
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addSettingTab(new AIAutocompleteSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData()
|
||||
);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class AIAutocompleteSettingTab extends PluginSettingTab {
|
||||
plugin: AIAutocompletePlugin;
|
||||
|
||||
constructor(app: App, plugin: AIAutocompletePlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Groq API Key")
|
||||
.setDesc("Get your key from console.groq.com")
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("gsk_...")
|
||||
.setValue(this.plugin.settings.apiKey)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Model")
|
||||
.setDesc("Groq model to use for completions")
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption("llama-3.3-70b-versatile", "Llama 3.3 70B (recommended)")
|
||||
.addOption("llama-3.1-8b-instant", "Llama 3.1 8B (faster)")
|
||||
.addOption("gemma2-9b-it", "Gemma 2 9B")
|
||||
.addOption("mixtral-8x7b-32768", "Mixtral 8x7B")
|
||||
.setValue(this.plugin.settings.model)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.model = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Trigger delay (ms)")
|
||||
.setDesc("How long to wait after typing before triggering completion")
|
||||
.addSlider((slider) =>
|
||||
slider
|
||||
.setLimits(300, 2000, 100)
|
||||
.setValue(this.plugin.settings.delay)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.delay = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Enabled")
|
||||
.setDesc("Toggle auto-completion on/off")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.enabled)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.enabled = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
6
styles.css
Normal file
6
styles.css
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.groq-copilot-ghost-text {
|
||||
opacity: 0.4;
|
||||
font-style: italic;
|
||||
pointer-events: none;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"module": "ESNext",
|
||||
"target": "ES6",
|
||||
"allowJs": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": ["DOM", "ES5", "ES6", "ES7"]
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Loading…
Reference in a new issue