├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── check.yml │ └── release-please.yml ├── .gitignore ├── .husky ├── commit-msg ├── pre-commit └── pre-push ├── .npmrc ├── .prettierignore ├── .release-please-manifest.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── README_CN.md ├── README_JP.md ├── commitlint.config.js ├── cz-adapter.cjs ├── docs └── configuration │ ├── README.md │ └── README_CN.md ├── index.html ├── knip.config.ts ├── package.json ├── package.nls.en.json ├── package.nls.json ├── package.nls.zh-cn.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.cjs ├── release-please-config.json ├── res ├── cover.png ├── icon-mask.png ├── icon.png ├── icon.svg └── icon2.svg ├── scripts └── publish.ts ├── src ├── extension │ ├── ai │ │ ├── aide-key-request.ts │ │ ├── get-reference-file-paths.ts │ │ ├── helpers.ts │ │ ├── model-providers │ │ │ ├── azure-openai.ts │ │ │ ├── base.ts │ │ │ ├── claude.ts │ │ │ └── openai.ts │ │ └── parse-model-base-url.ts │ ├── auto-open-corresponding-files.ts │ ├── cleanup.ts │ ├── clipboard.ts │ ├── commands │ │ ├── ask-ai │ │ │ └── index.ts │ │ ├── batch-processor │ │ │ ├── get-pre-process-info.ts │ │ │ ├── index.ts │ │ │ └── write-and-save-tmp-file.ts │ │ ├── code-convert │ │ │ ├── build-convert-prompt.ts │ │ │ ├── get-target-language-info.ts │ │ │ └── index.ts │ │ ├── code-viewer-helper │ │ │ ├── build-generate-prompt.ts │ │ │ └── index.ts │ │ ├── copy-as-prompt │ │ │ └── index.ts │ │ ├── expert-code-enhancer │ │ │ ├── build-generate-prompt.ts │ │ │ ├── default-prompt-list.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── private │ │ │ ├── copy-file-text.ts │ │ │ ├── quick-close-file-without-save.ts │ │ │ ├── replace-file.ts │ │ │ ├── show-aide-key-usage-info.ts │ │ │ └── show-diff.ts │ │ ├── rename-variable │ │ │ ├── build-rename-suggestion-prompt.ts │ │ │ ├── index.ts │ │ │ └── submit-rename-variable.ts │ │ └── smart-paste │ │ │ ├── build-convert-chat-messages.ts │ │ │ └── index.ts │ ├── config.ts │ ├── constants.ts │ ├── context.ts │ ├── enable-system-proxy.ts │ ├── file-utils │ │ ├── create-tmp-file.ts │ │ ├── get-fs-prompt-info.ts │ │ ├── ignore-patterns.ts │ │ ├── insert-text-at-selection.ts │ │ ├── show-continue-message.ts │ │ ├── stream-completion-writer.ts │ │ ├── tmp-file-writer.ts │ │ ├── traverse-fs.ts │ │ └── vscode-fs.ts │ ├── i18n.ts │ ├── index.ts │ ├── loading.ts │ ├── logger.ts │ ├── polyfill.ts │ ├── providers │ │ ├── aide-key-usage-statusbar.ts │ │ ├── index.ts │ │ ├── tmp-file-action.ts │ │ └── webview.ts │ ├── storage.ts │ ├── types │ │ ├── common.ts │ │ └── global.d.ts │ └── utils.ts ├── shared │ └── types │ │ └── msg.ts └── webview │ ├── App.css │ ├── App.tsx │ ├── helpers │ └── vscode.ts │ ├── main.tsx │ └── types │ └── vite-env.d.ts ├── test └── index.test.ts ├── tsconfig.json ├── vercel.json ├── vite.config.mts └── website ├── .vitepress ├── config │ ├── en.ts │ ├── index.ts │ ├── shared.ts │ ├── svg.ts │ └── zh.ts ├── integrations.ts ├── shim.d.ts └── theme │ ├── index.ts │ └── style.css ├── components ├── AideModels │ ├── AideModelPrice │ │ ├── PriceTable.vue │ │ ├── index.vue │ │ ├── model-price.json │ │ └── types.ts │ └── AidePay.vue ├── ChatBotButton.vue ├── Image.vue └── Video.vue ├── en ├── guide │ ├── configuration │ │ ├── ai-command-auto-run.md │ │ ├── ai-command-copy-before-run.md │ │ ├── ai-command.md │ │ ├── ai-prompt.md │ │ ├── api-concurrency.md │ │ ├── auto-remember-convert-language-pairs.md │ │ ├── code-viewer-helper-prompt.md │ │ ├── convert-language-pairs.md │ │ ├── expert-code-enhancer-prompt-list.md │ │ ├── ignore-patterns.md │ │ ├── openai-base-url.md │ │ ├── openai-key.md │ │ ├── openai-model.md │ │ ├── read-clipboard-image.md │ │ ├── respect-git-ignore.md │ │ └── use-system-proxy.md │ ├── features │ │ ├── ask-ai.md │ │ ├── batch-processor.md │ │ ├── code-convert.md │ │ ├── code-viewer-helper.md │ │ ├── copy-as-prompt.md │ │ ├── expert-code-enhancer.md │ │ ├── rename-variable.md │ │ └── smart-paste.md │ ├── getting-started │ │ ├── customize-configuration.md │ │ ├── customize-shortcuts.md │ │ ├── faq.md │ │ ├── how-to-configure-openai-key.md │ │ ├── index.md │ │ └── installation.md │ └── use-another-llm │ │ ├── anthropic.md │ │ ├── azure.md │ │ ├── deepseek.md │ │ ├── google.md │ │ ├── iflytek.md │ │ ├── local-ai.md │ │ ├── ollama.md │ │ ├── openai.md │ │ ├── qwen.md │ │ └── zhipu.md └── index.md ├── package.json ├── public ├── aide-difference-with-other-ai-tools.jpg ├── aide-key-usage-info.zh.jpg ├── logo-mini.png ├── logo-mini.svg ├── logo.svg ├── og-cover.png ├── readme-banner-dark.png ├── readme-banner-light.png ├── videos │ ├── aide-ask-ai.mp4 │ ├── aide-batch-processor.mp4 │ ├── aide-code-convert.mp4 │ ├── aide-code-viewer-helper.mp4 │ ├── aide-copy-as-prompt.mp4 │ ├── aide-customize-configuration.mp4 │ ├── aide-customize-shortcuts.mp4 │ ├── aide-expert-code-enhancer.mp4 │ ├── aide-install.mp4 │ ├── aide-intro.mp4 │ ├── aide-rename-variable.mp4 │ └── aide-smart-paste.mp4 └── wechat.jpg ├── tsconfig.json ├── uno.config.ts └── zh ├── guide ├── configuration │ ├── ai-command-auto-run.md │ ├── ai-command-copy-before-run.md │ ├── ai-command.md │ ├── ai-prompt.md │ ├── api-concurrency.md │ ├── auto-remember-convert-language-pairs.md │ ├── code-viewer-helper-prompt.md │ ├── convert-language-pairs.md │ ├── expert-code-enhancer-prompt-list.md │ ├── ignore-patterns.md │ ├── openai-base-url.md │ ├── openai-key.md │ ├── openai-model.md │ ├── read-clipboard-image.md │ ├── respect-git-ignore.md │ └── use-system-proxy.md ├── features │ ├── ask-ai.md │ ├── batch-processor.md │ ├── code-convert.md │ ├── code-viewer-helper.md │ ├── copy-as-prompt.md │ ├── expert-code-enhancer.md │ ├── rename-variable.md │ └── smart-paste.md ├── getting-started │ ├── community.md │ ├── customize-configuration.md │ ├── customize-shortcuts.md │ ├── faq.md │ ├── how-to-configure-openai-key.md │ ├── index.md │ └── installation.md └── use-another-llm │ ├── aide-models.md │ ├── anthropic.md │ ├── azure.md │ ├── deepseek.md │ ├── google.md │ ├── iflytek.md │ ├── local-ai.md │ ├── ollama.md │ ├── openai.md │ ├── qwen.md │ └── zhipu.md └── index.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.{diff,md}] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /docker 2 | node_modules 3 | **/coverage/ 4 | *.js 5 | !.storybook 6 | .changeset 7 | dist 8 | CHANGELOG.md 9 | *.typegen.ts 10 | .next/ 11 | kube-manifests/ 12 | buildspec.yml 13 | deployspec.yml 14 | .storybook/*.html 15 | cz-adapter.cjs 16 | LICENSE 17 | *.vue 18 | *.html 19 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nicepkg, 2214962083] 2 | open_collective: nicepkg 3 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | 8 | jobs: 9 | release: 10 | permissions: 11 | id-token: write 12 | contents: write 13 | if: "contains(github.event.head_commit.message, 'chore: release')" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v3 22 | 23 | - name: Use Node.js v20 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: '20' 27 | registry-url: https://registry.npmjs.org/ 28 | cache: pnpm 29 | 30 | - run: pnpm install 31 | 32 | - name: Lint 33 | run: pnpm run lint 34 | 35 | - name: Test 36 | run: pnpm run test 37 | 38 | - name: Publish 39 | run: pnpm run publish 40 | env: 41 | VSCE_TOKEN: ${{secrets.VSCE_TOKEN}} 42 | OVSX_TOKEN: ${{secrets.OVSX_TOKEN}} 43 | 44 | - name: Git commit 45 | id: commit 46 | run: | 47 | git config --local user.email github-actions[bot]@users.noreply.github.com 48 | git config --local user.name github-actions[bot] 49 | git config --global core.autocrlf true 50 | git config --global core.safecrlf false 51 | git add . 52 | git commit -m "chore: ci build" -a 53 | continue-on-error: true 54 | 55 | - name: Git push 56 | uses: ad-m/github-push-action@master 57 | if: ${{ steps.commit.outcome == 'success' }} 58 | with: 59 | github_token: ${{ secrets.GITHUB_TOKEN }} 60 | branch: ${{ github.ref }} 61 | 62 | - name: Log 63 | if: ${{ steps.commit.outcome != 'success' }} 64 | run: echo Nothing to commit. 65 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | check: 13 | if: "!contains(github.event.head_commit.message, 'chore: release')" 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Install pnpm 21 | uses: pnpm/action-setup@v3 22 | 23 | - name: Use Node.js v20 24 | uses: actions/setup-node@v4 25 | with: 26 | node-version: '20' 27 | registry-url: https://registry.npmjs.org/ 28 | cache: pnpm 29 | 30 | - run: pnpm install 31 | 32 | - name: Lint 33 | run: pnpm run lint 34 | 35 | - name: Test 36 | run: pnpm run test 37 | 38 | - name: Build 39 | run: pnpm run build 40 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: release-please 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release-please: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: googleapis/release-please-action@v4 13 | id: release 14 | 15 | - name: Release Please Results 16 | env: 17 | RESULTS: ${{ toJSON(steps.release.outputs) }} 18 | run: echo "$RESULTS" 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .DS_Store 3 | .idea 4 | *.log 5 | *.tgz 6 | *.vsix 7 | coverage 8 | dist 9 | lib-cov 10 | logs 11 | node_modules 12 | temp 13 | .aider* 14 | .vscode-test-web 15 | **/.vitepress/cache 16 | **/.vitepress/.temp 17 | .gpt-runner 18 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | 2 | pnpm exec commitlint --edit ${1} 3 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run lint 2 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | 2 | pnpm run test 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-workspace-root-check=true 2 | node-linker=hoisted 3 | package-manager-strict=false 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.template 2 | .changeset 3 | .coverage_* 4 | .github 5 | CHANGELOG.md 6 | coverage 7 | dist 8 | node_modules 9 | pnpm-lock.yaml 10 | *.hbs 11 | .next/ 12 | kube-manifests/ 13 | buildspec.yml 14 | deployspec.yml 15 | /docker 16 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | {".":"1.19.1"} 2 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}" 15 | // "--disable-extensions" 16 | ], 17 | "outFiles": ["${workspaceFolder}/dist/**/*.js"], 18 | "preLaunchTask": "npm: watch", 19 | "env": { 20 | // "NODE_DEBUG": "http" 21 | } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.pathMatcher": "package.nls.{locale}.json", 3 | "i18n-ally.keystyle": "flat", 4 | "i18n-ally.localesPaths": ["."], 5 | "i18n-ally.enabledFrameworks": ["vscode", "react"], 6 | "i18n-ally.dirStructure": "file", 7 | // Enable eslint for all supported languages 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "typescript", 12 | "typescriptreact", 13 | "vue", 14 | "html", 15 | "json", 16 | "jsonc", 17 | "yaml" 18 | ], 19 | "editor.tabSize": 2, 20 | "editor.detectIndentation": false, 21 | "search.exclude": { 22 | "package-lock.json": true 23 | }, 24 | "editor.codeActionsOnSave": [ 25 | "source.addMissingImports", 26 | "source.fixAll.eslint" 27 | ], 28 | "typescript.tsdk": "node_modules/typescript/lib", // Use the workspace version of TypeScript 29 | "typescript.enablePromptUseWorkspaceTsdk": true, // For security reasons it's require that users opt into using the workspace version of typescript 30 | "typescript.preferences.preferTypeOnlyAutoImports": true, // Prefer type-only imports 31 | "testing.openTesting": "neverOpen", // Don't open the testing view automatically when running tests 32 | "prettier.enable": true, // enable prettier for those who disabled it for some reason 33 | "prettier.ignorePath": ".gitignore", // Don't run prettier for files listed in .gitignore 34 | "editor.defaultFormatter": "esbenp.prettier-vscode", 35 | "editor.formatOnPaste": false, 36 | "editor.formatOnType": false, 37 | "editor.formatOnSave": true, 38 | "editor.formatOnSaveMode": "file", 39 | "typescript.inlayHints.parameterNames.enabled": "all", 40 | // "typescript.inlayHints.variableTypes.enabled": true, 41 | // "typescript.inlayHints.propertyDeclarationTypes.enabled": true, 42 | "typescript.inlayHints.parameterTypes.enabled": true, 43 | // "typescript.inlayHints.functionLikeReturnTypes.enabled": true, 44 | "workbench.colorCustomizations": { 45 | "activityBar.background": "#0E3140", 46 | "titleBar.activeBackground": "#14455A", 47 | "titleBar.activeForeground": "#F8FCFD" 48 | }, 49 | "cSpell.words": [ 50 | "allowtransparency", 51 | "autoplay", 52 | "bilibili", 53 | "Codegee", 54 | "Codeium", 55 | "commitlint", 56 | "deepseek", 57 | "esno", 58 | "execa", 59 | "Flytek", 60 | "fullpath", 61 | "ianvs", 62 | "iconify", 63 | "iflytek", 64 | "knip", 65 | "langchain", 66 | "Nicepkg", 67 | "nodir", 68 | "Nolebase", 69 | "Ollama", 70 | "openai", 71 | "Pipfile", 72 | "pyproject", 73 | "qwen", 74 | "tailwindcss", 75 | "tomjs", 76 | "treeshake", 77 | "tsup", 78 | "undici", 79 | "vitepress", 80 | "vsix", 81 | "Xclip", 82 | "Zhipu" 83 | ] 84 | } 85 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "isBackground": true, 10 | "presentation": { 11 | "reveal": "never" 12 | }, 13 | "problemMatcher": [ 14 | { 15 | "base": "$ts-webpack-watch", 16 | "background": { 17 | "activeOnStart": true, 18 | "beginsPattern": "Build start", 19 | "endsPattern": "Build success" 20 | } 21 | } 22 | ], 23 | "group": "build" 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | src 2 | node_modules 3 | test 4 | .github 5 | .vscode 6 | dist/tsconfig.tsbuildinfo 7 | dist/*.vsix 8 | docs 9 | .github 10 | .husky 11 | scripts 12 | .vscode/** 13 | .vscode-test/** 14 | .vscode-test-web/** 15 | out/** 16 | website/** 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # 🤝 Contributing 2 | 3 | We're excited that you're interested in contributing to this project! Before submitting your contribution, please read through the following guide. 4 | 5 | ## 🔧 Setup 6 | 7 | This project uses [PNPM](https://pnpm.js.org/) for package management. Please make sure you have it installed before proceeding. 8 | 9 | 1. Fork the repo and create your branch from `master`. 10 | 2. Run `pnpm install` in the repository root. 11 | 3. Run `pnpm dev` to start the development server. 12 | 4. Click "Install" on the popup to install the development script. 13 | 5. Make your changes! 14 | 15 | ## ✅ Linting and Type Checking 16 | 17 | This project uses [ESLint](https://eslint.org/) and [TypeScript](https://www.typescriptlang.org/) for linting and type checking, respectively. Please make sure your code passes both before submitting a PR. 18 | 19 | ```bash 20 | pnpm run lint 21 | pnpm run test 22 | ``` 23 | 24 | ## 📝 Commit Message Format 25 | 26 | This project follows the [Conventional Commits](https://www.conventionalcommits.org/) specification. Please make sure your commit messages are formatted correctly. 27 | 28 | **Please mention the issue number in the commit message or the PR description.** 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nicepkg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | nicepkg 7 | 8 | 9 | 10 | [English](https://github.com/nicepkg/aide/tree/master/README.md) / 简体中文 / [日本語](https://github.com/nicepkg/aide/tree/master/README_JP.md) 11 | 12 | 在 VSCode 中征服任何代码:一键注释、转换、UI 图生成代码、AI 批量处理文件!💪 13 | 14 | [![Version](https://img.shields.io/visual-studio-marketplace/v/nicepkg.aide-pro)](https://marketplace.visualstudio.com/items?itemName=nicepkg.aide-pro) 15 | [![Downloads](https://img.shields.io/visual-studio-marketplace/d/nicepkg.aide-pro)](https://marketplace.visualstudio.com/items?itemName=nicepkg.aide-pro) 16 | [![Rating](https://img.shields.io/visual-studio-marketplace/r/nicepkg.aide-pro)](https://marketplace.visualstudio.com/items?itemName=nicepkg.aide-pro) 17 | [![License](https://img.shields.io/github/license/nicepkg/aide)](https://github.com/nicepkg/aide/blob/master/LICENSE) 18 | [![GitHub stars](https://img.shields.io/github/stars/nicepkg/aide)](https://github.com/nicepkg/aide) 19 | 20 |
21 | 22 | ## 文档 📚 23 | 24 | - 查看:[https://aide.nicepkg.cn/zh](https://aide.nicepkg.cn/zh) 25 | - 视频: 26 | 27 | https://github.com/user-attachments/assets/55f85f8e-7515-4da3-b850-9c078b3440d5 28 | 29 | ## 功能 ✨ 30 | 31 | - 🔄 **[代码转换](https://aide.nicepkg.cn/zh/guide/features/code-convert)**: 一键在任何编程语言之间转换代码。 32 | - 📖 **[代码查看器助手](https://aide.nicepkg.cn/zh/guide/features/code-viewer-helper)**: 添加详细注释以提高代码可读性。 33 | - 🔧 **[让大师帮你改代码](https://aide.nicepkg.cn/zh/guide/features/expert-code-enhancer)**: 把你的代码给 AI 优化,看看大师是怎么写代码的。 34 | - 📋 **[智能粘贴](https://aide.nicepkg.cn/zh/guide/features/smart-paste)**: 粘贴时智能转换剪贴板内容(代码或图片)。 35 | - 🗂️ **[AI 批量处理文件](https://aide.nicepkg.cn/zh/guide/features/batch-processor)**: 根据自定义要求使用 AI 处理多个文件。 36 | - 🏷 **[重命名变量](https://aide.nicepkg.cn/zh/guide/features/rename-variable)**: 获取 AI 建议的变量名及解释。 37 | - 💬 **[问 AI](https://aide.nicepkg.cn/zh/guide/features/ask-ai)**: 对选定的文件或文件夹执行自定义 AI 命令。 38 | - 📝 **[复制为 AI 提示词](https://aide.nicepkg.cn/zh/guide/features/copy-as-prompt)**: 轻松复制文件/文件夹内容作为 AI 提示。 39 | 40 | ## 安装 📦 41 | 42 | 1. 打开 Visual Studio Code 43 | 2. 进入扩展 (Ctrl+Shift+X) 44 | 3. 搜索 “[Aide](https://marketplace.visualstudio.com/items?itemName=nicepkg.aide-pro)” 45 | 4. 点击安装 46 | 47 | ## 更新日志 📅 48 | 49 | 查看最新的更新和功能:[更新日志](https://github.com/nicepkg/aide/blob/master/CHANGELOG.md) 50 | 51 | ## 贡献 🤝 52 | 53 | 欢迎贡献!请随时提交拉取请求。有关详细信息,请参阅 [贡献指南](https://github.com/nicepkg/aide/blob/master/CONTRIBUTING.md)。 54 | 55 | 这个项目的存在感谢所有贡献者: 56 | 57 | 58 | 59 | 60 | 61 | ## 许可证 📄 62 | 63 | 此项目根据 MIT 许可证授权 - 有关详细信息,请参阅 [LICENSE](https://github.com/nicepkg/aide/blob/master/LICENSE) 文件。 64 | 65 | ## 支持 💖 66 | 67 | 如果你觉得这个项目有帮助,请考虑在 [GitHub](https://github.com/nicepkg/aide) 上给它一个 ⭐️! 68 | 69 | ## Star 历史 ⭐ 70 | 71 |
72 | 73 | Star History Chart 74 | 75 |
76 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extends: ['@commitlint/config-conventional'] 3 | } 4 | -------------------------------------------------------------------------------- /cz-adapter.cjs: -------------------------------------------------------------------------------- 1 | exports.prompter = async (inquirerIns, commit) => { 2 | ;(await import('@commitlint/cz-commitlint')).prompter(inquirerIns, commit) 3 | } 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Aide 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /knip.config.ts: -------------------------------------------------------------------------------- 1 | import type { KnipConfig } from 'knip' 2 | 3 | const defineConfig = (config: KnipConfig) => config 4 | 5 | export default defineConfig({ 6 | entry: ['src/index.tsx', '*.config.cjs', '*.config.mts'], 7 | project: ['src/**/*.{ts,tsx}', 'scripts/*.ts'], 8 | ignore: ['**/define/**/*.ts', '**/*.d.ts'], 9 | ignoreDependencies: ['virtual:*', '~icons/'] 10 | }) 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - playground 3 | - examples/* 4 | - website 5 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | module.exports = { 3 | endOfLine: 'lf', 4 | semi: false, 5 | useTabs: false, 6 | singleQuote: true, 7 | arrowParens: 'avoid', 8 | tabWidth: 2, 9 | trailingComma: 'none', 10 | importOrder: [ 11 | '^(react/(.*)$)|^(react$)', 12 | '^(next/(.*)$)|^(next$)', 13 | '', 14 | '', 15 | '^types$', 16 | '^@/types/(.*)$', 17 | '^@/config/(.*)$', 18 | '^@/lib/(.*)$', 19 | '^@/hooks/(.*)$', 20 | '^@/components/ui/(.*)$', 21 | '^@/components/(.*)$', 22 | '^@/registry/(.*)$', 23 | '^@/styles/(.*)$', 24 | '^@/app/(.*)$', 25 | '', 26 | '^[./]' 27 | ], 28 | importOrderParserPlugins: ['typescript', 'jsx', 'decorators-legacy'], 29 | plugins: ['@ianvs/prettier-plugin-sort-imports'] 30 | } 31 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", 3 | "pull-request-header": ":robot: I have created a release", 4 | "pull-request-title-pattern": "chore: release${component} ${version}", 5 | "release-type": "node", 6 | "include-component-in-tag": true, 7 | "packages": { 8 | ".": { 9 | "component": "aide" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /res/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicepkg/aide/1331e73a46a7b0c058c5699ed10d7ef5c8841d31/res/cover.png -------------------------------------------------------------------------------- /res/icon-mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicepkg/aide/1331e73a46a7b0c058c5699ed10d7ef5c8841d31/res/icon-mask.png -------------------------------------------------------------------------------- /res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nicepkg/aide/1331e73a46a7b0c058c5699ed10d7ef5c8841d31/res/icon.png -------------------------------------------------------------------------------- /res/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /scripts/publish.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import process from 'node:process' 3 | import { dirname, join, resolve } from 'path' 4 | import { fileURLToPath } from 'url' 5 | import { execa } from 'execa' 6 | import fs from 'fs-extra' 7 | 8 | const dir = 9 | typeof __dirname === 'string' 10 | ? __dirname 11 | : dirname(fileURLToPath(import.meta.url)) 12 | const resolvePaths = (...paths: string[]) => resolve(dir, ...paths) 13 | const root = resolvePaths('../') 14 | 15 | const publish = async () => { 16 | const pkgPath = join(root, 'package.json') 17 | const rawJSON = await fs.readFile(pkgPath, 'utf-8') 18 | const pkg = JSON.parse(rawJSON) 19 | pkg.name = 'aide-pro' 20 | await fs.writeJSON(pkgPath, pkg, { spaces: 2 }) 21 | 22 | await execa('npm', ['run', 'build'], { cwd: root, stdio: 'inherit' }) 23 | 24 | try { 25 | console.log('\nPublish to VSCE...\n') 26 | await execa( 27 | 'npx', 28 | [ 29 | '@vscode/vsce', 30 | 'publish', 31 | '--no-dependencies', 32 | '-p', 33 | process.env.VSCE_TOKEN! 34 | ], 35 | { cwd: root, stdio: 'inherit' } 36 | ) 37 | 38 | console.log('\nPublish to OVSE...\n') 39 | await execa( 40 | 'npx', 41 | ['ovsx', 'publish', '--no-dependencies', '-p', process.env.OVSX_TOKEN!], 42 | { cwd: root, stdio: 'inherit' } 43 | ) 44 | } finally { 45 | await fs.writeFile(pkgPath, rawJSON, 'utf-8') 46 | } 47 | } 48 | 49 | publish() 50 | -------------------------------------------------------------------------------- /src/extension/ai/helpers.ts: -------------------------------------------------------------------------------- 1 | import type { BaseChatModel } from '@langchain/core/language_models/chat_models' 2 | 3 | import { AzureOpenAIModelProvider } from './model-providers/azure-openai' 4 | import type { BaseModelProvider } from './model-providers/base' 5 | import { AnthropicModelProvider } from './model-providers/claude' 6 | import { OpenAIModelProvider } from './model-providers/openai' 7 | import { parseModelBaseUrl, type ModelUrlType } from './parse-model-base-url' 8 | 9 | export const getCurrentModelProvider = async () => { 10 | const { urlType } = await parseModelBaseUrl() 11 | 12 | const urlTypeProviderMap = { 13 | openai: OpenAIModelProvider, 14 | 'azure-openai': AzureOpenAIModelProvider, 15 | anthropic: AnthropicModelProvider 16 | } satisfies Record> 17 | 18 | return urlTypeProviderMap[urlType] || OpenAIModelProvider 19 | } 20 | 21 | export const getCurrentSessionIdHistoriesMap = async () => { 22 | const ModelProvider = await getCurrentModelProvider() 23 | return ModelProvider.sessionIdHistoriesMap 24 | } 25 | 26 | export const createModelProvider = async () => { 27 | const ModelProvider = await getCurrentModelProvider() 28 | const modelProvider = new ModelProvider() 29 | return modelProvider 30 | } 31 | -------------------------------------------------------------------------------- /src/extension/ai/model-providers/azure-openai.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-escape */ 2 | import { getConfigKey } from '@extension/config' 3 | import { getContext } from '@extension/context' 4 | import { t } from '@extension/i18n' 5 | import { 6 | AzureChatOpenAI, 7 | ChatOpenAI, 8 | type ChatOpenAICallOptions 9 | } from '@langchain/openai' 10 | import * as vscode from 'vscode' 11 | 12 | import { parseModelBaseUrl } from '../parse-model-base-url' 13 | import { BaseModelProvider } from './base' 14 | 15 | export class AzureOpenAIModelProvider extends BaseModelProvider< 16 | ChatOpenAI 17 | > { 18 | async createModel() { 19 | const isDev = getContext().extensionMode !== vscode.ExtensionMode.Production 20 | const { url: openaiBaseUrl } = await parseModelBaseUrl() 21 | const openaiKey = await getConfigKey('openaiKey') 22 | 23 | const regex = 24 | /https:\/\/(.+?)\/openai\/deployments\/([^\/]+)(?:\/[^?]+)?\?api-version=(.+)/ 25 | const match = openaiBaseUrl.match(regex) 26 | 27 | if (!match) throw new Error(t('error.invalidAzureOpenaiBaseUrl')) 28 | 29 | const azureOpenAIBasePath = `https://${match[1]}/openai/deployments` 30 | const azureOpenAIApiDeploymentName = match[2] || '' 31 | const azureOpenAIApiVersion = match[3] || '' 32 | 33 | // final request url example: 34 | // https://azure-llm.openai.azure.com/openai/deployments/text-embedding-ada-002/embeddings?api-version=2022-12-01 35 | // https://westeurope.api.microsoft.com/openai/deployments/[azureOpenAIApiDeploymentName]/chat/completions?api-version=[azureOpenAIApiVersion] 36 | // basePath is: 37 | // https://westeurope.api.microsoft.com/openai/deployments 38 | // so user need to configure modelApiBaseUrl to: 39 | // https://westeurope.api.microsoft.com/openai/deployments/[azureOpenAIApiDeploymentName]?api-version=[azureOpenAIApiVersion] 40 | // https://westeurope.api.microsoft.com/openai/deployments/gpt-4o-xxx/chat/completions?api-version=2024-07-15 41 | 42 | const model = new AzureChatOpenAI({ 43 | azureOpenAIBasePath, 44 | azureOpenAIApiKey: openaiKey, 45 | azureOpenAIApiVersion, 46 | azureOpenAIApiDeploymentName, 47 | configuration: { 48 | fetch 49 | }, 50 | temperature: 0.95, // never use 1.0, some models do not support it 51 | verbose: isDev, 52 | maxRetries: 3 53 | }) 54 | ;('https://westeurope.api.microsoft.com/openai/deployments/devName/chat/completions?api-version=AVersion') 55 | return model 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/extension/ai/model-providers/claude.ts: -------------------------------------------------------------------------------- 1 | import { getConfigKey } from '@extension/config' 2 | import { getContext } from '@extension/context' 3 | import { ChatAnthropic } from '@langchain/anthropic' 4 | import * as vscode from 'vscode' 5 | 6 | import { parseModelBaseUrl } from '../parse-model-base-url' 7 | import { BaseModelProvider } from './base' 8 | 9 | export class AnthropicModelProvider extends BaseModelProvider { 10 | async createModel() { 11 | const isDev = getContext().extensionMode !== vscode.ExtensionMode.Production 12 | 13 | // anthropic@https://api.anthropic.com 14 | const { url: openaiBaseUrl } = await parseModelBaseUrl() 15 | const openaiKey = await getConfigKey('openaiKey') 16 | const openaiModel = await getConfigKey('openaiModel') 17 | 18 | const model = new ChatAnthropic({ 19 | apiKey: openaiKey, 20 | anthropicApiUrl: openaiBaseUrl, 21 | clientOptions: { 22 | fetch 23 | }, 24 | model: openaiModel, 25 | temperature: 0.95, // never use 1.0, some models do not support it 26 | maxRetries: 6, 27 | verbose: isDev 28 | }) 29 | 30 | return model 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/extension/ai/model-providers/openai.ts: -------------------------------------------------------------------------------- 1 | import { getConfigKey } from '@extension/config' 2 | import { getContext } from '@extension/context' 3 | import { ChatOpenAI, type ChatOpenAICallOptions } from '@langchain/openai' 4 | import * as vscode from 'vscode' 5 | 6 | import { parseModelBaseUrl } from '../parse-model-base-url' 7 | import { BaseModelProvider } from './base' 8 | 9 | export class OpenAIModelProvider extends BaseModelProvider< 10 | ChatOpenAI 11 | > { 12 | async createModel() { 13 | const isDev = getContext().extensionMode !== vscode.ExtensionMode.Production 14 | const { url: openaiBaseUrl } = await parseModelBaseUrl() 15 | const openaiKey = await getConfigKey('openaiKey') 16 | const openaiModel = await getConfigKey('openaiModel') 17 | 18 | const model = new ChatOpenAI({ 19 | apiKey: openaiKey, 20 | configuration: { 21 | baseURL: openaiBaseUrl, 22 | fetch 23 | }, 24 | model: openaiModel, 25 | temperature: 0.95, // never use 1.0, some models do not support it 26 | maxRetries: 3, 27 | verbose: isDev 28 | }) 29 | 30 | // some third-party language models are not compatible with the openAI specification, 31 | // they do not support some parameters, and langchain takes the initiative to add these parameters, 32 | // resulting in request failure, so here you need to clear these parameters 33 | model.frequencyPenalty = undefined as any 34 | model.n = undefined as any 35 | model.presencePenalty = undefined as any 36 | model.topP = undefined as any 37 | 38 | return model 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/extension/ai/parse-model-base-url.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-destructuring */ 2 | import { getConfigKey } from '@extension/config' 3 | import { t } from '@extension/i18n' 4 | 5 | export type ModelUrlType = 'openai' | 'azure-openai' | 'anthropic' 6 | export const parseModelBaseUrl = async (): Promise<{ 7 | urlType: ModelUrlType 8 | url: string 9 | }> => { 10 | // https://api.openai.com/v1 11 | // openai@https://api.openai.com/v1 12 | // azure-openai@https://westeurope.api.microsoft.com/openai/deployments/[azureOpenAIApiDeploymentName] 13 | // copilot 14 | // Fetch the baseUrl from the config 15 | const baseUrl = await getConfigKey('openaiBaseUrl') 16 | 17 | // Default values 18 | let urlType: ModelUrlType = 'openai' 19 | let url = '' 20 | 21 | // Use regexp to parse the urlType 22 | const regex = 23 | /^(openai|azure-openai|anthropic|copilot)?@?(https?:\/\/[^\s]+)?$/ 24 | const match = baseUrl.trim().match(regex) 25 | 26 | if (match) { 27 | if (match[1]) { 28 | urlType = match[1] as ModelUrlType 29 | } 30 | if (match[2]) { 31 | url = match[2] 32 | } 33 | } else { 34 | throw new Error(t('error.invalidBaseUrl')) 35 | } 36 | 37 | return { urlType, url } 38 | } 39 | -------------------------------------------------------------------------------- /src/extension/auto-open-corresponding-files.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { getOriginalFileUri, isTmpFileUri } from './file-utils/create-tmp-file' 4 | import { VsCodeFS } from './file-utils/vscode-fs' 5 | import { logger } from './logger' 6 | 7 | let isHandlingEditorChange = false 8 | 9 | const openCorrespondingFiles = async (tmpUri: vscode.Uri): Promise => { 10 | if (isHandlingEditorChange || !isTmpFileUri(tmpUri)) return 11 | isHandlingEditorChange = true 12 | const originalUri = getOriginalFileUri(tmpUri) 13 | 14 | try { 15 | // check if the original file exists 16 | await VsCodeFS.stat(originalUri.fsPath) 17 | 18 | // open original file 19 | const originalDocument = 20 | await vscode.workspace.openTextDocument(originalUri) 21 | await vscode.window.showTextDocument( 22 | originalDocument, 23 | vscode.ViewColumn.One 24 | ) 25 | 26 | // 重新聚焦到 .aide.vue 文件 27 | // refocus on the .aide file 28 | const tmpDocument = await vscode.workspace.openTextDocument(tmpUri) 29 | await vscode.window.showTextDocument(tmpDocument, vscode.ViewColumn.Two) 30 | } catch (e) { 31 | logger.warn('openCorrespondingFiles error', e) 32 | } finally { 33 | isHandlingEditorChange = false 34 | } 35 | } 36 | 37 | export const autoOpenCorrespondingFiles = ( 38 | context: vscode.ExtensionContext 39 | ) => { 40 | context.subscriptions.push( 41 | vscode.workspace.onDidOpenTextDocument(async document => { 42 | const maybeTmpUri = document.uri 43 | if (isTmpFileUri(maybeTmpUri)) { 44 | await openCorrespondingFiles(maybeTmpUri) 45 | } 46 | }) 47 | ) 48 | 49 | context.subscriptions.push( 50 | vscode.window.onDidChangeActiveTextEditor(editor => { 51 | const maybeTmpUri = editor?.document.uri 52 | if (maybeTmpUri && isTmpFileUri(maybeTmpUri) && !isHandlingEditorChange) { 53 | openCorrespondingFiles(maybeTmpUri) 54 | } 55 | }) 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /src/extension/cleanup.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { cleanupCodeConvertRunnables } from './commands/code-convert' 4 | import { cleanupCodeViewerHelperRunnables } from './commands/code-viewer-helper' 5 | import { cleanupExpertCodeEnhancerRunnables } from './commands/expert-code-enhancer' 6 | import { cleanupSmartPasteRunnables } from './commands/smart-paste' 7 | 8 | export const cleanup = async (context: vscode.ExtensionContext) => { 9 | context.subscriptions.push( 10 | vscode.workspace.onDidCloseTextDocument(() => { 11 | cleanupCodeConvertRunnables() 12 | cleanupCodeViewerHelperRunnables() 13 | cleanupExpertCodeEnhancerRunnables() 14 | cleanupSmartPasteRunnables() 15 | }) 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /src/extension/commands/ask-ai/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-loop-func */ 2 | import path from 'node:path' 3 | import { getConfigKey } from '@extension/config' 4 | import { 5 | traverseFileOrFolders, 6 | type FileInfo 7 | } from '@extension/file-utils/traverse-fs' 8 | import { t } from '@extension/i18n' 9 | import { executeCommand } from '@extension/utils' 10 | import { quote } from 'shell-quote' 11 | import * as vscode from 'vscode' 12 | 13 | export const handleAskAI = async ( 14 | uri: vscode.Uri, 15 | selectedUris: vscode.Uri[] = [] 16 | ) => { 17 | const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri) 18 | if (!workspaceFolder) throw new Error(t('error.noWorkspace')) 19 | 20 | const selectedItems = selectedUris?.length > 0 ? selectedUris : [uri] 21 | if (selectedItems.length === 0) throw new Error(t('error.noSelection')) 22 | 23 | const selectedFileOrFolders = selectedItems.map(item => item.fsPath) 24 | let filesPrompt = '' 25 | let filesRelativePath = '' 26 | let filesFullPath = '' 27 | 28 | const processFile = async (fileInfo: FileInfo) => { 29 | const { fullPath, relativePath, content } = fileInfo 30 | const language = path.extname(fullPath).slice(1) 31 | const promptFullContent = t( 32 | 'file.content', 33 | relativePath, 34 | language, 35 | content.toString() 36 | ) 37 | 38 | filesPrompt += promptFullContent 39 | filesRelativePath += ` "${quote([relativePath.trim()])}" ` 40 | filesFullPath += ` "${quote([fullPath.trim()])}" ` 41 | } 42 | 43 | await traverseFileOrFolders( 44 | selectedFileOrFolders, 45 | workspaceFolder.uri.fsPath, 46 | processFile 47 | ) 48 | 49 | const aiCommand = await getConfigKey('aiCommand') 50 | const aiCommandCopyBeforeRun = await getConfigKey('aiCommandCopyBeforeRun') 51 | const aiCommandAutoRun = await getConfigKey('aiCommandAutoRun') 52 | let userInput = '' 53 | 54 | if (aiCommand.includes('#{question}')) { 55 | userInput = 56 | (await vscode.window.showInputBox({ 57 | prompt: t('input.aiCommand.prompt'), 58 | placeHolder: t('input.aiCommand.placeholder') 59 | })) || '' 60 | } 61 | 62 | const finalCommand = aiCommand 63 | .replace(/#{filesRelativePath}/g, filesRelativePath) 64 | .replace(/#{filesFullPath}/g, filesFullPath) 65 | .replace(/#{question}/g, ` "${quote([userInput.trim()])}" `) 66 | .replace(/#{content}/g, ` "${quote([filesPrompt.trim()])}" `) 67 | 68 | if (aiCommandCopyBeforeRun) { 69 | await vscode.env.clipboard.writeText(finalCommand) 70 | 71 | // Show info message only if the command is not set to auto-run 72 | if (!aiCommandAutoRun) { 73 | await vscode.window.showInformationMessage( 74 | t('info.commandCopiedToClipboard') 75 | ) 76 | } 77 | } 78 | 79 | if (aiCommandAutoRun) { 80 | await executeCommand(finalCommand, workspaceFolder.uri.fsPath) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/extension/commands/code-convert/build-convert-prompt.ts: -------------------------------------------------------------------------------- 1 | import type { BaseLanguageModelInput } from '@langchain/core/language_models/base' 2 | import * as vscode from 'vscode' 3 | 4 | export const buildConvertPrompt = async ({ 5 | sourceLanguageId, 6 | targetLanguageId, 7 | targetLanguageDescription, 8 | sourceCode 9 | }: { 10 | sourceLanguageId: string 11 | targetLanguageId: string 12 | targetLanguageDescription: string 13 | sourceCode: string 14 | }): Promise => { 15 | const locale = vscode.env.language 16 | 17 | const targetLanguageDescriptionPrompt = targetLanguageDescription 18 | ? ` 19 | For the converted language, my additional notes are as follows: **${targetLanguageDescription}.** 20 | ` 21 | : '' 22 | 23 | const prompt = ` 24 | You are a programming language converter. 25 | You need to help me convert ${sourceLanguageId} code into ${targetLanguageId} code. 26 | ${targetLanguageDescriptionPrompt} 27 | All third-party API and third-party dependency names do not need to be changed, 28 | as my purpose is only to understand and read, not to run. Please use ${locale} language to add some additional comments as appropriate. 29 | Please do not reply with any text other than the code, and do not use markdown syntax. 30 | Here is the code you need to convert: 31 | 32 | ${sourceCode} 33 | ` 34 | return prompt 35 | } 36 | -------------------------------------------------------------------------------- /src/extension/commands/code-convert/get-target-language-info.ts: -------------------------------------------------------------------------------- 1 | import { getConfigKey, setConfigKey } from '@extension/config' 2 | import { languageIdExts, languageIds } from '@extension/constants' 3 | import { t } from '@extension/i18n' 4 | import { 5 | getLanguageId, 6 | getLanguageIdExt, 7 | showQuickPickWithCustomInput 8 | } from '@extension/utils' 9 | import * as vscode from 'vscode' 10 | 11 | /** 12 | * Get target language info 13 | * if user input custom language like: vue please convert to vue3 14 | * return { targetLanguageId: 'vue', targetLanguageDescription: 'please convert to vue3' } 15 | */ 16 | export const getTargetLanguageInfo = async (originalFileLanguageId: string) => { 17 | const convertLanguagePairs = await getConfigKey('convertLanguagePairs', { 18 | targetForSet: vscode.ConfigurationTarget.WorkspaceFolder, 19 | allowCustomOptionValue: true 20 | }) 21 | let targetLanguageInfo = convertLanguagePairs?.[originalFileLanguageId] || '' 22 | 23 | if (!targetLanguageInfo) { 24 | targetLanguageInfo = await showQuickPickWithCustomInput({ 25 | items: [...languageIds, ...languageIdExts], 26 | placeholder: t('input.codeConvertTargetLanguage.prompt') 27 | }) 28 | 29 | if (!targetLanguageInfo) throw new Error(t('error.noTargetLanguage')) 30 | 31 | const autoRememberConvertLanguagePairs = await getConfigKey( 32 | 'autoRememberConvertLanguagePairs' 33 | ) 34 | 35 | if (autoRememberConvertLanguagePairs) { 36 | await setConfigKey( 37 | 'convertLanguagePairs', 38 | { 39 | ...convertLanguagePairs, 40 | [originalFileLanguageId]: targetLanguageInfo 41 | }, 42 | { 43 | targetForSet: vscode.ConfigurationTarget.WorkspaceFolder, 44 | allowCustomOptionValue: true 45 | } 46 | ) 47 | } 48 | } 49 | 50 | const [targetLanguageIdOrExt = 'plaintext', ...targetLanguageRest] = 51 | targetLanguageInfo.split(/\s+/) 52 | const targetLanguageDescription = targetLanguageRest.join(' ') 53 | const targetLanguageId = getLanguageId(targetLanguageIdOrExt) 54 | 55 | return { 56 | targetLanguageId: targetLanguageId || targetLanguageInfo, 57 | targetLanguageExt: getLanguageIdExt(targetLanguageIdOrExt), 58 | targetLanguageDescription: targetLanguageDescription?.trim() || '' 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/extension/commands/code-viewer-helper/build-generate-prompt.ts: -------------------------------------------------------------------------------- 1 | import { getConfigKey } from '@extension/config' 2 | import type { BaseLanguageModelInput } from '@langchain/core/language_models/base' 3 | import * as vscode from 'vscode' 4 | 5 | export const buildGeneratePrompt = async ({ 6 | sourceLanguage, 7 | code 8 | }: { 9 | sourceLanguage: string 10 | code: string 11 | }): Promise => { 12 | const locale = vscode.env.language 13 | const codeViewerHelperPrompt = await getConfigKey('codeViewerHelperPrompt') 14 | const prompt = codeViewerHelperPrompt 15 | .replace('#{sourceLanguage}', sourceLanguage) 16 | .replace('#{locale}', locale) 17 | .replace('#{content}', code) 18 | return prompt 19 | } 20 | -------------------------------------------------------------------------------- /src/extension/commands/copy-as-prompt/index.ts: -------------------------------------------------------------------------------- 1 | import { getConfigKey } from '@extension/config' 2 | import { getFileOrFoldersPromptInfo } from '@extension/file-utils/get-fs-prompt-info' 3 | import { t } from '@extension/i18n' 4 | import * as vscode from 'vscode' 5 | 6 | export const handleCopyAsPrompt = async ( 7 | uri: vscode.Uri, 8 | selectedUris: vscode.Uri[] = [] 9 | ) => { 10 | const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri) 11 | if (!workspaceFolder) throw new Error(t('error.noWorkspace')) 12 | 13 | const selectedItems = selectedUris?.length > 0 ? selectedUris : [uri] 14 | if (selectedItems.length === 0) throw new Error(t('error.noSelection')) 15 | 16 | const selectedFileOrFolders = selectedItems.map(item => item.fsPath) 17 | const { promptFullContent } = await getFileOrFoldersPromptInfo( 18 | selectedFileOrFolders, 19 | workspaceFolder.uri.fsPath 20 | ) 21 | const aiPrompt = await getConfigKey('aiPrompt') 22 | const finalPrompt = aiPrompt.replace('#{content}', promptFullContent) 23 | 24 | await vscode.env.clipboard.writeText(finalPrompt) 25 | vscode.window.showInformationMessage(t('info.copied')) 26 | } 27 | -------------------------------------------------------------------------------- /src/extension/commands/private/copy-file-text.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@extension/i18n' 2 | import * as vscode from 'vscode' 3 | 4 | export const handleCopyFileText = async (uri?: vscode.Uri) => { 5 | const targetUri = uri || vscode.window.activeTextEditor?.document.uri 6 | 7 | if (!targetUri) throw new Error(t('error.noActiveEditor')) 8 | 9 | const document = await vscode.workspace.openTextDocument(targetUri) 10 | await vscode.env.clipboard.writeText(document.getText()) 11 | vscode.window.showInformationMessage(t('info.copied')) 12 | } 13 | -------------------------------------------------------------------------------- /src/extension/commands/private/quick-close-file-without-save.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@extension/i18n' 2 | import * as vscode from 'vscode' 3 | 4 | export const handleQuickCloseFileWithoutSave = async (uri?: vscode.Uri) => { 5 | const targetUri = uri || vscode.window.activeTextEditor?.document.uri 6 | if (!targetUri) throw new Error(t('error.noActiveEditor')) 7 | 8 | const targetEditor = vscode.window.visibleTextEditors.find( 9 | editor => editor.document.uri.toString() === targetUri.toString() 10 | ) 11 | 12 | let documentToClose: vscode.TextDocument | undefined 13 | 14 | if (targetEditor) { 15 | documentToClose = targetEditor.document 16 | } else { 17 | documentToClose = vscode.workspace.textDocuments.find( 18 | doc => doc.uri.toString() === targetUri.toString() 19 | ) 20 | } 21 | 22 | if (!documentToClose) throw new Error(t('error.noActiveEditor')) 23 | 24 | await vscode.window.showTextDocument(documentToClose) 25 | 26 | const command = documentToClose.isDirty 27 | ? 'workbench.action.revertAndCloseActiveEditor' 28 | : 'workbench.action.closeActiveEditor' 29 | 30 | await vscode.commands.executeCommand(command) 31 | } 32 | -------------------------------------------------------------------------------- /src/extension/commands/private/replace-file.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { VsCodeFS } from '@extension/file-utils/vscode-fs' 3 | import { t } from '@extension/i18n' 4 | import * as vscode from 'vscode' 5 | 6 | const replaceFileContent = async (uri: vscode.Uri, content: string) => { 7 | const editor = vscode.window.visibleTextEditors.find( 8 | e => e.document.uri.toString() === uri.toString() 9 | ) 10 | if (editor && !editor.selection.isEmpty) { 11 | await editor.edit(editBuilder => 12 | editBuilder.replace(editor.selection, content) 13 | ) 14 | } else { 15 | await VsCodeFS.writeFile(uri.fsPath, content, 'utf8') 16 | } 17 | } 18 | 19 | const changeFileExtension = async ( 20 | uri: vscode.Uri, 21 | newExt: string 22 | ): Promise => { 23 | const oldPath = uri.fsPath 24 | const dir = path.dirname(oldPath) 25 | const nameWithoutExt = path.basename(oldPath, path.extname(oldPath)) 26 | const newPath = path.join(dir, `${nameWithoutExt}${newExt}`) 27 | const newUri = vscode.Uri.file(newPath) 28 | await vscode.workspace.fs.rename(uri, newUri) 29 | return newUri 30 | } 31 | 32 | /** 33 | * Handles the replacement of a file with another file. 34 | * 35 | * @param fromFileUri - The file will not removed, but its content will be replaced. 36 | * @param toFileUri - The file will be removed. 37 | * @throws An error if either `fromFileUri` or `toFileUri` is not provided. 38 | */ 39 | export const handleReplaceFile = async ( 40 | fromFileUri: vscode.Uri, 41 | toFileUri: vscode.Uri 42 | ) => { 43 | if (!fromFileUri || !toFileUri) throw new Error(t('error.fileNotFound')) 44 | 45 | const toFileContent = ( 46 | await vscode.workspace.openTextDocument(toFileUri) 47 | ).getText() 48 | await replaceFileContent(fromFileUri, toFileContent) 49 | 50 | vscode.window.showInformationMessage(t('info.fileReplaceSuccess')) 51 | 52 | // close the toFileUri 53 | await vscode.commands.executeCommand( 54 | 'aide.quickCloseFileWithoutSave', 55 | toFileUri 56 | ) 57 | 58 | // get toFileExt and set it to fromFileUri 59 | const toFileExt = path.extname(toFileUri.fsPath) 60 | if (toFileExt) { 61 | fromFileUri = await changeFileExtension(fromFileUri, toFileExt) 62 | } 63 | 64 | try { 65 | // delete the toFileUri 66 | await VsCodeFS.unlink(toFileUri.fsPath) 67 | 68 | // if fromFileUri is not opened, open it 69 | const fromFileDocument = 70 | await vscode.workspace.openTextDocument(fromFileUri) 71 | await vscode.window.showTextDocument(fromFileDocument) 72 | } catch {} 73 | } 74 | -------------------------------------------------------------------------------- /src/extension/commands/private/show-aide-key-usage-info.ts: -------------------------------------------------------------------------------- 1 | import { aideKeyUsageInfo } from '@extension/ai/aide-key-request' 2 | import { getConfigKey } from '@extension/config' 3 | import { t } from '@extension/i18n' 4 | import { updateAideKeyUsageStatusBar } from '@extension/providers/aide-key-usage-statusbar' 5 | import { formatNumber } from '@extension/utils' 6 | import * as vscode from 'vscode' 7 | 8 | export const handleShowAideKeyUsageInfo = async () => { 9 | const openaiBaseUrl = await getConfigKey('openaiBaseUrl') 10 | const openaiKey = await getConfigKey('openaiKey') 11 | 12 | if (!openaiBaseUrl.includes('api.zyai.online')) 13 | throw new Error(t('error.aideKeyUsageInfoOnlySupportAideModels')) 14 | 15 | // show loading 16 | updateAideKeyUsageStatusBar(`$(sync~spin) ${t('info.loading')}`) 17 | 18 | try { 19 | const result = await aideKeyUsageInfo({ key: openaiKey }) 20 | 21 | if (result.success) { 22 | // create a nice message to show the result 23 | const { count, subscription } = result.data 24 | 25 | const totalUSD = subscription.hard_limit_usd 26 | const usedUSD = 27 | (subscription.used_quota / subscription.remain_quota) * totalUSD 28 | const remainUSD = totalUSD - usedUSD 29 | const formatUSD = (amount: number) => `$${formatNumber(amount, 2)}` 30 | const formatDate = (timestamp: number) => { 31 | if (timestamp === 0) return t('info.aideKey.neverExpires') 32 | return new Date(timestamp * 1000).toLocaleDateString() 33 | } 34 | 35 | const message = `${t('info.aideKey.usageInfo')}: 36 | 37 | ${t('info.aideKey.total')}: ${formatUSD(totalUSD)} 38 | 39 | ${t('info.aideKey.used')}: ${formatUSD(usedUSD)} 40 | 41 | ${t('info.aideKey.remain')}: ${formatUSD(remainUSD)} 42 | 43 | ${t('info.aideKey.callCount')}: ${count.count} 44 | 45 | ${t('info.aideKey.validUntil')}: ${formatDate(subscription.access_until)}` 46 | 47 | vscode.window.showInformationMessage(message, { 48 | modal: true 49 | }) 50 | } else { 51 | throw new Error(`Failed to fetch usage info: ${result.message}`) 52 | } 53 | } finally { 54 | // restore the original text of the status bar item 55 | updateAideKeyUsageStatusBar( 56 | `$(info) ${t('info.aideKeyUsageStatusBar.text')}` 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/extension/commands/private/show-diff.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import { t } from '@extension/i18n' 3 | import * as vscode from 'vscode' 4 | 5 | /** 6 | * Displays a diff view between two files or between a file and a selected portion of another file. 7 | * 8 | * @param fromFileUri - The URI of the source file. 9 | * @param toFileUri - The URI of the target file to compare against. 10 | * @throws An error if either `fromFileUri` or `toFileUri` is not provided. 11 | */ 12 | export const handleShowDiff = async ( 13 | fromFileUri: vscode.Uri, 14 | toFileUri: vscode.Uri 15 | ) => { 16 | if (!fromFileUri || !toFileUri) throw new Error(t('error.fileNotFound')) 17 | 18 | const fromFileEditor = vscode.window.visibleTextEditors.find( 19 | editor => editor.document.uri.toString() === fromFileUri.toString() 20 | ) 21 | 22 | let fromFileTitle: string 23 | let finalFromFileUri: vscode.Uri 24 | 25 | if (fromFileEditor && !fromFileEditor.selection.isEmpty) { 26 | // Use selected content from fromFile 27 | fromFileTitle = `${path.basename(fromFileUri.fsPath)} (Selection)` 28 | finalFromFileUri = vscode.Uri.parse(`untitled:${fromFileTitle}`) 29 | 30 | // Create an in-memory document with the selected content 31 | const selectedContent = fromFileEditor.document.getText( 32 | fromFileEditor.selection 33 | ) 34 | const inMemoryDocument = await vscode.workspace.openTextDocument({ 35 | content: selectedContent, 36 | language: fromFileEditor.document.languageId 37 | }) 38 | finalFromFileUri = inMemoryDocument.uri 39 | } else { 40 | // Use entire content of fromFile 41 | finalFromFileUri = fromFileUri 42 | fromFileTitle = path.basename(fromFileUri.fsPath) 43 | } 44 | 45 | const toFileTitle = path.basename(toFileUri.fsPath) 46 | 47 | // Show diff 48 | const title = `Diff: ${fromFileTitle} ↔ ${toFileTitle}` 49 | await vscode.commands.executeCommand( 50 | 'vscode.diff', 51 | toFileUri, 52 | finalFromFileUri, 53 | title 54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /src/extension/commands/rename-variable/build-rename-suggestion-prompt.ts: -------------------------------------------------------------------------------- 1 | import type { BaseLanguageModelInput } from '@langchain/core/language_models/base' 2 | import * as vscode from 'vscode' 3 | 4 | export const buildRenameSuggestionPrompt = async ({ 5 | contextCode, 6 | variableName, 7 | selection, 8 | fileRelativePath 9 | }: { 10 | contextCode: string 11 | variableName: string 12 | selection: vscode.Selection 13 | fileRelativePath: string 14 | }): Promise => { 15 | const lines = contextCode.split('\n') 16 | 17 | // get the line index where the selected variable is located 18 | const activeLine = selection.start.line 19 | 20 | // calculate the range of 250 lines before and after 21 | const start = Math.max(0, activeLine - 250) 22 | const end = Math.min(lines.length, activeLine + 250) 23 | 24 | // get the content of 250 lines before and after 25 | const contextLines = lines.slice(start, end) 26 | 27 | // add a comment line above the line where the variable is located 28 | contextLines.splice( 29 | activeLine - start, 30 | 0, 31 | `### Here is the variable you want to change: ${variableName} ###` 32 | ) 33 | 34 | const codeContextForPrompt = contextLines.join('\n') 35 | 36 | const prompt = ` 37 | Please refer to the following code snippet to change the variable name \`${variableName}\` to a more reasonable name. 38 | Give a few suggestions for a more reasonable name. 39 | **You should always follow the naming conventions of the current code snippet to generate new variable names.** 40 | current file relative path: ${fileRelativePath} 41 | Here is the code snippet: 42 | 43 | ${codeContextForPrompt} 44 | ` 45 | 46 | return prompt 47 | } 48 | -------------------------------------------------------------------------------- /src/extension/commands/rename-variable/submit-rename-variable.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@extension/i18n' 2 | import * as vscode from 'vscode' 3 | 4 | export const submitRenameVariable = async ({ 5 | newName, 6 | selection 7 | }: { 8 | newName: string 9 | selection: vscode.Selection 10 | }) => { 11 | const editor = vscode.window.activeTextEditor 12 | 13 | if (!editor) throw new Error(t('error.noActiveEditor')) 14 | 15 | const { document } = editor 16 | const position = selection.start 17 | 18 | // find all references 19 | const references = await vscode.commands.executeCommand( 20 | 'vscode.executeReferenceProvider', 21 | document.uri, 22 | position 23 | ) 24 | 25 | // create a workspace edit 26 | const edit = new vscode.WorkspaceEdit() 27 | 28 | if (references && references.length > 0) { 29 | // if references found, change all references 30 | references.forEach(reference => { 31 | edit.replace(reference.uri, reference.range, newName) 32 | }) 33 | } else { 34 | // if no references found, only change the selected position 35 | edit.replace( 36 | document.uri, 37 | new vscode.Range(selection.start, selection.end), 38 | newName 39 | ) 40 | } 41 | 42 | // apply the workspace edit 43 | await vscode.workspace.applyEdit(edit) 44 | await document.save() 45 | } 46 | -------------------------------------------------------------------------------- /src/extension/commands/smart-paste/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createModelProvider, 3 | getCurrentSessionIdHistoriesMap 4 | } from '@extension/ai/helpers' 5 | import { streamingCompletionWriter } from '@extension/file-utils/stream-completion-writer' 6 | import { getCurrentWorkspaceFolderEditor } from '@extension/utils' 7 | import { type BaseMessage } from '@langchain/core/messages' 8 | import * as vscode from 'vscode' 9 | 10 | import { buildConvertChatMessages } from './build-convert-chat-messages' 11 | 12 | export const cleanupSmartPasteRunnables = async () => { 13 | const openDocumentPaths = new Set( 14 | vscode.workspace.textDocuments.map(doc => doc.uri.fsPath) 15 | ) 16 | const sessionIdHistoriesMap = await getCurrentSessionIdHistoriesMap() 17 | 18 | Object.keys(sessionIdHistoriesMap).forEach(sessionId => { 19 | const path = sessionId.match(/^smartPaste:(.*)$/)?.[1] 20 | 21 | if (path && !openDocumentPaths.has(path)) { 22 | delete sessionIdHistoriesMap[sessionId] 23 | } 24 | }) 25 | } 26 | 27 | export const handleSmartPaste = async () => { 28 | const { workspaceFolder, activeEditor } = 29 | await getCurrentWorkspaceFolderEditor() 30 | const currentFilePath = activeEditor.document.uri.fsPath 31 | 32 | // ai 33 | const modelProvider = await createModelProvider() 34 | const aiModelAbortController = new AbortController() 35 | const aiModel = (await modelProvider.getModel()).bind({ 36 | signal: aiModelAbortController.signal 37 | }) 38 | 39 | const sessionId = `smartPaste:${currentFilePath}}` 40 | const sessionIdHistoriesMap = await getCurrentSessionIdHistoriesMap() 41 | 42 | // TODO: remove and support continue generate in the future 43 | delete sessionIdHistoriesMap[sessionId] 44 | 45 | await streamingCompletionWriter({ 46 | editor: activeEditor, 47 | onCancel() { 48 | aiModelAbortController.abort() 49 | }, 50 | buildAiStream: async () => { 51 | const convertMessages = await buildConvertChatMessages({ 52 | workspaceFolder, 53 | currentFilePath, 54 | selection: activeEditor.selection, 55 | abortController: aiModelAbortController 56 | }) 57 | 58 | const history = await modelProvider.getHistory(sessionId) 59 | const historyMessages = await history.getMessages() 60 | const currentMessages: BaseMessage[] = convertMessages 61 | const aiStream = aiModel.stream([...historyMessages, ...currentMessages]) 62 | history.addMessages(currentMessages) 63 | 64 | return aiStream 65 | } 66 | }) 67 | 68 | // TODO: remove and support continue generate in the future 69 | delete sessionIdHistoriesMap[sessionId] 70 | } 71 | -------------------------------------------------------------------------------- /src/extension/constants.ts: -------------------------------------------------------------------------------- 1 | export const languageIdExtMap = { 2 | abap: ['abap'], 3 | bat: ['bat', 'cmd'], 4 | bibtex: ['bib'], 5 | clojure: ['clj', 'cljs', 'cljc'], 6 | coffeescript: ['coffee'], 7 | c: ['c'], 8 | cpp: ['cpp', 'cxx', 'cc'], 9 | csharp: ['cs'], 10 | dockercompose: ['docker-compose.yml', 'docker-compose.yaml'], 11 | css: ['css'], 12 | 'cuda-cpp': ['cu', 'cuh'], 13 | d: ['d'], 14 | pascal: ['pas', 'p'], 15 | diff: ['diff', 'patch'], 16 | dockerfile: ['Dockerfile'], 17 | erlang: ['erl', 'hrl'], 18 | fsharp: ['fs', 'fsi', 'fsx'], 19 | 'git-commit': ['COMMIT_EDITMSG'], 20 | 'git-rebase': ['git-rebase-todo'], 21 | go: ['go'], 22 | groovy: ['groovy', 'gvy', 'gy', 'gsh'], 23 | handlebars: ['hbs', 'handlebars'], 24 | haml: ['haml'], 25 | haskell: ['hs', 'lhs'], 26 | html: ['html', 'htm'], 27 | ini: ['ini'], 28 | java: ['java'], 29 | javascript: ['js', 'mjs'], 30 | javascriptreact: ['jsx'], 31 | json: ['json'], 32 | jsonc: ['jsonc'], 33 | julia: ['jl'], 34 | latex: ['tex'], 35 | less: ['less'], 36 | lua: ['lua'], 37 | makefile: ['makefile', 'mk'], 38 | markdown: ['md', 'markdown'], 39 | 'objective-c': ['m'], 40 | 'objective-cpp': ['mm'], 41 | ocaml: ['ml', 'mli'], 42 | perl: ['pl', 'pm'], 43 | perl6: ['p6', 'pl6'], 44 | php: ['php'], 45 | plaintext: ['txt'], 46 | powershell: ['ps1', 'psm1', 'psd1'], 47 | jade: ['jade'], 48 | pug: ['pug'], 49 | python: ['py'], 50 | r: ['r', 'R'], 51 | razor: ['cshtml'], 52 | ruby: ['rb'], 53 | rust: ['rs'], 54 | scss: ['scss'], 55 | sass: ['sass'], 56 | shaderlab: ['shader'], 57 | shellscript: ['sh', 'bash'], 58 | slim: ['slim'], 59 | sql: ['sql'], 60 | stylus: ['styl'], 61 | svelte: ['svelte'], 62 | swift: ['swift'], 63 | typescript: ['ts'], 64 | typescriptreact: ['tsx'], 65 | tex: ['tex'], 66 | vb: ['vb'], 67 | vue: ['vue'], 68 | 'vue-html': ['vue-html'], 69 | xml: ['xml'], 70 | xsl: ['xsl', 'xslt'], 71 | yaml: ['yml', 'yaml'] 72 | } 73 | 74 | export const languageIds = Object.keys(languageIdExtMap) 75 | export const languageIdExts = Object.values(languageIdExtMap).flat() 76 | export const languageExtIdMap = Object.fromEntries( 77 | Object.entries(languageIdExtMap).flatMap(([id, exts]) => 78 | exts.map(ext => [ext, id]) 79 | ) 80 | ) 81 | 82 | export const AbortError = new Error('AbortError') 83 | -------------------------------------------------------------------------------- /src/extension/context.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { t } from './i18n' 4 | 5 | let globalContext: vscode.ExtensionContext | undefined 6 | 7 | export const setContext = (context: vscode.ExtensionContext) => { 8 | globalContext = context 9 | } 10 | 11 | export const getContext = (): vscode.ExtensionContext => { 12 | if (!globalContext) { 13 | throw new Error(t('error.noContext')) 14 | } 15 | return globalContext 16 | } 17 | -------------------------------------------------------------------------------- /src/extension/enable-system-proxy.ts: -------------------------------------------------------------------------------- 1 | import { bootstrap } from 'global-agent' 2 | import { ProxyAgent, setGlobalDispatcher } from 'undici' 3 | 4 | import { getConfigKey } from './config' 5 | import { logger } from './logger' 6 | import { tryParseJSON } from './utils' 7 | 8 | const getDefaultProxyUrl = () => { 9 | let proxyUrl = '' 10 | 11 | ;['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY'].forEach(key => { 12 | if (proxyUrl) return 13 | 14 | const upperKey = key.toUpperCase() 15 | const lowerKey = key.toLowerCase() 16 | const upperKeyValue = 17 | process.env[upperKey] && process.env[upperKey] !== 'undefined' 18 | ? process.env[upperKey] || '' 19 | : '' 20 | const lowerKeyValue = 21 | process.env[lowerKey] && process.env[lowerKey] !== 'undefined' 22 | ? process.env[lowerKey] || '' 23 | : '' 24 | 25 | proxyUrl = upperKeyValue || lowerKeyValue || '' 26 | }) 27 | 28 | return proxyUrl 29 | } 30 | 31 | export const enableSystemProxy = async () => { 32 | try { 33 | const useSystemProxy = await getConfigKey('useSystemProxy') 34 | if (!useSystemProxy) return 35 | 36 | const proxyUrl = getDefaultProxyUrl() 37 | 38 | bootstrap() 39 | 40 | if (proxyUrl) { 41 | const dispatcher = new ProxyAgent(proxyUrl) 42 | setGlobalDispatcher(dispatcher) 43 | } 44 | } catch (err) { 45 | logger.warn('Failed to enable global proxy', err) 46 | } 47 | } 48 | 49 | export const enableLogFetch = () => { 50 | const originalFetch = globalThis.fetch 51 | const logFetch: typeof globalThis.fetch = (input, init) => { 52 | const reqBody = 53 | typeof init?.body === 'string' 54 | ? tryParseJSON(init.body, true) 55 | : init?.body 56 | 57 | logger.dev.log('fetching...', { 58 | input, 59 | init, 60 | url: input.toString(), 61 | method: init?.method || 'GET', 62 | headers: init?.headers || {}, 63 | body: reqBody 64 | }) 65 | 66 | return originalFetch(input, init) 67 | } 68 | globalThis.fetch = logFetch 69 | } 70 | -------------------------------------------------------------------------------- /src/extension/file-utils/get-fs-prompt-info.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { t } from '@extension/i18n' 3 | 4 | import { traverseFileOrFolders, type FileInfo } from './traverse-fs' 5 | 6 | /** 7 | * Represents the information required for a file system prompt. 8 | */ 9 | export interface FsPromptInfo { 10 | /** 11 | * The full content of the prompt. 12 | */ 13 | promptFullContent: string 14 | 15 | /** 16 | * An array of file information. 17 | */ 18 | filesInfo: FileInfo[] 19 | } 20 | 21 | /** 22 | * Retrieves the prompt information for a given array of files or folders. 23 | * @param fileOrFolders - The array of file or folder paths. 24 | * @param workspacePath - The path of the workspace. 25 | * @returns A promise that resolves to the `FsPromptInfo` object containing the prompt information. 26 | */ 27 | export const getFileOrFoldersPromptInfo = async ( 28 | fileOrFolders: string[], 29 | workspacePath: string 30 | ): Promise => { 31 | const result: FsPromptInfo = { 32 | promptFullContent: '', 33 | filesInfo: [] 34 | } 35 | 36 | const processFile = async (fileInfo: FileInfo) => { 37 | const { fullPath, relativePath, content } = fileInfo 38 | const language = path.extname(fullPath).slice(1) 39 | 40 | const promptFullContent = t( 41 | 'file.content', 42 | relativePath, 43 | language, 44 | content.toString() 45 | ) 46 | 47 | result.filesInfo.push(fileInfo) 48 | result.promptFullContent += promptFullContent 49 | } 50 | 51 | await traverseFileOrFolders(fileOrFolders, workspacePath, processFile) 52 | 53 | return result 54 | } 55 | -------------------------------------------------------------------------------- /src/extension/file-utils/ignore-patterns.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { getConfigKey } from '@extension/config' 3 | import { t } from '@extension/i18n' 4 | import { logger } from '@extension/logger' 5 | import { glob } from 'glob' 6 | import ignore from 'ignore' 7 | import { Minimatch } from 'minimatch' 8 | import * as vscode from 'vscode' 9 | 10 | import { VsCodeFS } from './vscode-fs' 11 | 12 | /** 13 | * Creates a function that determines whether a file should be ignored based on the provided ignore patterns. 14 | * @param fullDirPath - The full directory path of the file. 15 | * @returns A function that takes a full file path as input and returns a boolean indicating whether the file should be ignored. 16 | * @throws An error if the workspace path cannot be determined. 17 | */ 18 | export const createShouldIgnore = async (fullDirPath: string) => { 19 | const dirUri = vscode.Uri.file(fullDirPath) 20 | const workspacePath = vscode.workspace.getWorkspaceFolder(dirUri)?.uri.fsPath 21 | 22 | if (!workspacePath) throw new Error(t('error.noWorkspace')) 23 | 24 | const ignorePatterns = await getConfigKey('ignorePatterns') 25 | const respectGitIgnore = await getConfigKey('respectGitIgnore') 26 | 27 | let ig: ReturnType | null = null 28 | 29 | if (respectGitIgnore) { 30 | try { 31 | const gitignorePath = path.join(workspacePath, '.gitignore') 32 | const gitIgnoreContent = await VsCodeFS.readFile(gitignorePath, 'utf-8') 33 | ig = ignore().add(gitIgnoreContent) 34 | } catch (error) { 35 | // .gitignore file doesn't exist or couldn't be read 36 | logger.warn("Couldn't read .gitignore file:", error) 37 | } 38 | } 39 | 40 | const mms = ignorePatterns.map( 41 | pattern => 42 | new Minimatch(pattern, { 43 | dot: true, 44 | matchBase: true 45 | }) 46 | ) 47 | 48 | /** 49 | * Determines whether a file should be ignored based on the ignore patterns. 50 | * @param fullFilePath - The full path of the file. 51 | * @returns A boolean indicating whether the file should be ignored. 52 | */ 53 | const shouldIgnore = (fullFilePath: string) => { 54 | const relativePath = path.relative(workspacePath, fullFilePath) 55 | const unixRelativePath = relativePath.replace(/\\/g, '/') 56 | 57 | if (ig && ig.ignores(unixRelativePath)) { 58 | return true 59 | } 60 | 61 | return mms.some(mm => mm.match(unixRelativePath)) 62 | } 63 | 64 | return shouldIgnore 65 | } 66 | 67 | /** 68 | * Retrieves all valid files in the specified directory path. 69 | * @param fullDirPath - The full path of the directory. 70 | * @returns A promise that resolves to an array of strings representing the absolute paths of the valid files. 71 | */ 72 | export const getAllValidFiles = async ( 73 | fullDirPath: string 74 | ): Promise => { 75 | const shouldIgnore = await createShouldIgnore(fullDirPath) 76 | 77 | return glob('**/*', { 78 | cwd: fullDirPath, 79 | nodir: true, 80 | absolute: true, 81 | follow: false, 82 | dot: true, 83 | ignore: { 84 | ignored(p) { 85 | return shouldIgnore(p.fullpath()) 86 | }, 87 | childrenIgnored(p) { 88 | try { 89 | return shouldIgnore(p.fullpath()) 90 | } catch { 91 | return false 92 | } 93 | } 94 | } 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /src/extension/file-utils/insert-text-at-selection.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { VsCodeFS } from './vscode-fs' 4 | 5 | /** 6 | * Inserts text at the specified selection in a file. 7 | * 8 | * @param params - The parameters object. 9 | * @param params.filePath - The path to the file. 10 | * @param params.selection - The selection where the text should be inserted. 11 | * @param params.textToInsert - The text to insert. 12 | * @returns A promise that resolves to the file content after the text has been inserted. 13 | */ 14 | export const insertTextAtSelection = async ({ 15 | filePath, 16 | selection, 17 | textToInsert 18 | }: { 19 | filePath: string 20 | selection: vscode.Selection 21 | textToInsert: string 22 | }): Promise => { 23 | // read file content 24 | const fullText = await VsCodeFS.readFileOrOpenDocumentContent( 25 | filePath, 26 | 'utf-8' 27 | ) 28 | const lines = fullText.split('\n') 29 | 30 | // get start and end position of selection 31 | const { start } = selection 32 | const { end } = selection 33 | 34 | // check if selection is empty 35 | if (selection.isEmpty) { 36 | // insert text at cursor position 37 | const line = lines[start.line] || '' 38 | lines[start.line] = 39 | line.slice(0, start.character) + 40 | textToInsert + 41 | line.slice(start.character) 42 | } else { 43 | // replace selected text 44 | const startLine = lines[start.line] || '' 45 | const endLine = lines[end.line] || '' 46 | 47 | if (start.line === end.line) { 48 | // replace single line text 49 | lines[start.line] = 50 | startLine.slice(0, start.character) + 51 | textToInsert + 52 | endLine.slice(end.character) 53 | } else { 54 | // replace multiple lines text 55 | const newLines = [ 56 | startLine.slice(0, start.character) + 57 | textToInsert + 58 | endLine.slice(end.character) 59 | ] 60 | 61 | lines.splice(start.line, end.line - start.line + 1, ...newLines) 62 | } 63 | } 64 | 65 | // recombine text and return 66 | return lines.join('\n') 67 | } 68 | -------------------------------------------------------------------------------- /src/extension/file-utils/show-continue-message.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@extension/i18n' 2 | import type { MaybePromise } from '@extension/types/common' 3 | import * as vscode from 'vscode' 4 | 5 | /** 6 | * Shows a continue message if the number of lines in the temporary file is less than the original file content line count minus a threshold. 7 | * If the user chooses to continue, the `onContinue` callback is executed. 8 | * @param tmpFileUri - The URI of the temporary file. 9 | * @param originalFileContentLineCount - The number of lines in the original file. 10 | * @param lineCountDiffThreshold - The threshold for the difference in line count between the temporary file and the original file. Default is 100. 11 | * @param continueMessage - The message to display when asking the user to continue. Default is 'info.continueMessage'. 12 | * @param onContinue - The callback function to execute when the user chooses to continue. 13 | */ 14 | export const showContinueMessage = async ({ 15 | tmpFileUri, 16 | originalFileContentLineCount, 17 | lineCountDiffThreshold = 100, 18 | continueMessage = t('info.continueMessage'), 19 | onContinue 20 | }: { 21 | tmpFileUri: vscode.Uri 22 | originalFileContentLineCount: number 23 | lineCountDiffThreshold?: number 24 | continueMessage?: string 25 | onContinue: () => MaybePromise 26 | }) => { 27 | const tmpFileDocument = vscode.workspace.textDocuments.find( 28 | document => document.uri.fsPath === tmpFileUri.fsPath 29 | ) 30 | 31 | if (!tmpFileDocument) return 32 | 33 | // if the number of lines in the output file is more than xx lines less than the number of lines in the original file, 34 | // it may need to continue 35 | if ( 36 | tmpFileDocument.lineCount < 37 | originalFileContentLineCount - lineCountDiffThreshold 38 | ) { 39 | const continueAction = t('info.continue') 40 | const cancelAction = t('info.cancel') 41 | 42 | const continueOption = await vscode.window.showInformationMessage( 43 | continueMessage, 44 | continueAction, 45 | cancelAction 46 | ) 47 | 48 | if (continueOption === continueAction) { 49 | await onContinue() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/extension/i18n.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | // locale files 4 | import en from '../../package.nls.en.json' 5 | import zhCn from '../../package.nls.zh-cn.json' 6 | import { LocalizeFunction, Messages } from './types/common' 7 | 8 | const localeFilesMap = { 9 | en, 10 | 'zh-cn': zhCn 11 | } 12 | 13 | let messages: Messages = {} 14 | 15 | export const initializeLocalization = async (): Promise => { 16 | const { language } = vscode.env 17 | 18 | messages = localeFilesMap[language as keyof typeof localeFilesMap] ?? en 19 | } 20 | 21 | const format = (message: string, args: any[]): string => 22 | message.replace(/{(\d+)}/g, (match, number) => 23 | typeof args[number] !== 'undefined' ? args[number] : match 24 | ) 25 | 26 | export const t: LocalizeFunction = (key: string, ...args: any[]) => { 27 | const message = messages[key] ?? key 28 | return args.length > 0 ? format(message, args) : message 29 | } 30 | 31 | /** 32 | * @example 33 | * translateVscodeJsonText("%config.key%") === t('config.key') 34 | */ 35 | export const translateVscodeJsonText = (text: string): string => { 36 | if (!text.match(/%[^%]+%/)) return text 37 | return text.replace(/%([^%]+)%/g, (_, key) => t(key)) 38 | } 39 | -------------------------------------------------------------------------------- /src/extension/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { BaseModelProvider } from './ai/model-providers/base' 4 | import { autoOpenCorrespondingFiles } from './auto-open-corresponding-files' 5 | import { cleanup } from './cleanup' 6 | import { registerCommands } from './commands' 7 | import { setContext } from './context' 8 | import { enableLogFetch, enableSystemProxy } from './enable-system-proxy' 9 | import { initializeLocalization } from './i18n' 10 | import { logger } from './logger' 11 | import { enablePolyfill } from './polyfill' 12 | import { registerProviders } from './providers' 13 | import { initAideKeyUsageStatusBar } from './providers/aide-key-usage-statusbar' 14 | import { redisStorage, stateStorage } from './storage' 15 | 16 | export const activate = async (context: vscode.ExtensionContext) => { 17 | try { 18 | const isDev = context.extensionMode !== vscode.ExtensionMode.Production 19 | 20 | logger.log('"Aide" is now active!') 21 | 22 | await initializeLocalization() 23 | setContext(context) 24 | await enablePolyfill() 25 | await enableSystemProxy() 26 | isDev && enableLogFetch() 27 | 28 | await registerCommands(context) 29 | await registerProviders(context) 30 | await initAideKeyUsageStatusBar(context) 31 | await autoOpenCorrespondingFiles(context) 32 | await cleanup(context) 33 | // await renderWebview(context) 34 | } catch (err) { 35 | logger.warn('Failed to activate extension', err) 36 | } 37 | } 38 | 39 | export const deactivate = () => { 40 | // clear the session history map 41 | BaseModelProvider.sessionIdHistoriesMap = {} 42 | 43 | // clear the state storage 44 | stateStorage.clear() 45 | 46 | // clear the redis storage 47 | redisStorage.clear() 48 | 49 | // destroy the logger 50 | logger.destroy() 51 | } 52 | -------------------------------------------------------------------------------- /src/extension/loading.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { t } from './i18n' 4 | 5 | export const createLoading = () => { 6 | let progressResolve: (() => void) | undefined 7 | let progressInterval: NodeJS.Timeout | undefined 8 | 9 | const updateProgress = ( 10 | progress: vscode.Progress<{ increment: number }>, 11 | token: vscode.CancellationToken 12 | ) => { 13 | let currentProgress = 0 14 | const increment = 1 // 1% each time 15 | 16 | progressInterval = setInterval(() => { 17 | if (token.isCancellationRequested) { 18 | clearInterval(progressInterval!) 19 | progressResolve?.() 20 | progressResolve = undefined 21 | return 22 | } 23 | 24 | currentProgress += increment 25 | if (currentProgress > 100) { 26 | currentProgress = 0 27 | progress.report({ increment: -100 }) 28 | } 29 | progress.report({ increment }) 30 | }, 50) 31 | } 32 | 33 | const showProcessLoading = async (options?: { 34 | title?: string 35 | onCancel?: () => void 36 | }) => { 37 | const { title = t('info.processing'), onCancel } = options ?? {} 38 | // Clear the previous progress 39 | progressResolve?.() 40 | 41 | await vscode.window.withProgress( 42 | { 43 | location: vscode.ProgressLocation.Notification, 44 | title, 45 | cancellable: true 46 | }, 47 | async (progress, token) => 48 | new Promise(resolve => { 49 | progressResolve = resolve 50 | 51 | token.onCancellationRequested(() => { 52 | clearInterval(progressInterval!) 53 | onCancel?.() 54 | hideProcessLoading() 55 | }) 56 | 57 | updateProgress(progress, token) 58 | }) 59 | ) 60 | } 61 | 62 | const hideProcessLoading = () => { 63 | clearInterval(progressInterval!) 64 | progressResolve?.() 65 | progressResolve = undefined 66 | } 67 | 68 | return { 69 | showProcessLoading, 70 | hideProcessLoading 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/extension/polyfill.ts: -------------------------------------------------------------------------------- 1 | const enableFetchPolyfill = async () => { 2 | if (!globalThis.fetch) { 3 | // if globalThis.fetch is not available, we use undici 4 | const { fetch, FormData, Headers, Request, Response, File } = await import( 5 | 'undici' 6 | ) 7 | 8 | Object.assign(globalThis, { 9 | fetch, 10 | FormData, 11 | Headers, 12 | Request, 13 | Response, 14 | File 15 | }) 16 | } 17 | 18 | // fuck, vscode fetch not working on v1.92.0 19 | // we add a polyfill here 20 | // const { 21 | // default: fetch, 22 | // Headers, 23 | // Request, 24 | // Response 25 | // } = await import('node-fetch') 26 | // const { default: FormData } = await import('form-data') 27 | 28 | // Object.assign(globalThis, { 29 | // fetch, 30 | // FormData, 31 | // Headers, 32 | // Request, 33 | // Response 34 | // }) 35 | } 36 | 37 | export const enablePolyfill = async () => { 38 | await enableFetchPolyfill() 39 | } 40 | -------------------------------------------------------------------------------- /src/extension/providers/aide-key-usage-statusbar.ts: -------------------------------------------------------------------------------- 1 | import { t } from '@extension/i18n' 2 | import * as vscode from 'vscode' 3 | 4 | let aideKeyUsageStatusBar: vscode.StatusBarItem 5 | 6 | export const initAideKeyUsageStatusBar = (context: vscode.ExtensionContext) => { 7 | aideKeyUsageStatusBar = vscode.window.createStatusBarItem( 8 | vscode.StatusBarAlignment.Right, 9 | 100 10 | ) 11 | aideKeyUsageStatusBar.text = `$(info) ${t('info.aideKeyUsageStatusBar.text')}` 12 | aideKeyUsageStatusBar.tooltip = t('info.aideKeyUsageStatusBar.tooltip') 13 | aideKeyUsageStatusBar.command = 'aide.showAideKeyUsageInfo' 14 | aideKeyUsageStatusBar.show() 15 | 16 | context.subscriptions.push(aideKeyUsageStatusBar) 17 | } 18 | 19 | export const updateAideKeyUsageStatusBar = (text: string) => { 20 | aideKeyUsageStatusBar.text = text 21 | } 22 | -------------------------------------------------------------------------------- /src/extension/providers/index.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode' 2 | 3 | import { TmpFileActionCodeLensProvider } from './tmp-file-action' 4 | 5 | export const registerProviders = async (context: vscode.ExtensionContext) => { 6 | const tmpFileActionCodeLensProvider = new TmpFileActionCodeLensProvider() 7 | 8 | // register CodeLensProvider, only for file name contains .aide 9 | context.subscriptions.push( 10 | vscode.languages.registerCodeLensProvider( 11 | { scheme: '*', pattern: '**/*.aide*' }, 12 | tmpFileActionCodeLensProvider 13 | ) 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /src/extension/providers/tmp-file-action.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getOriginalFileUri, 3 | isTmpFileUri 4 | } from '@extension/file-utils/create-tmp-file' 5 | import { t } from '@extension/i18n' 6 | import * as vscode from 'vscode' 7 | 8 | export class TmpFileActionCodeLensProvider implements vscode.CodeLensProvider { 9 | private codeLenses: vscode.CodeLens[] = [] 10 | 11 | private _onDidChangeCodeLenses: vscode.EventEmitter = 12 | new vscode.EventEmitter() 13 | 14 | public readonly onDidChangeCodeLenses: vscode.Event = 15 | this._onDidChangeCodeLenses.event 16 | 17 | constructor() { 18 | vscode.workspace.onDidChangeConfiguration(_ => { 19 | this._onDidChangeCodeLenses.fire() 20 | }) 21 | } 22 | 23 | public provideCodeLenses( 24 | document: vscode.TextDocument 25 | ): vscode.CodeLens[] | Thenable { 26 | if (!isTmpFileUri(document.uri)) return [] 27 | 28 | this.updateCodeLenses(document) 29 | return this.codeLenses 30 | } 31 | 32 | private updateCodeLenses(document: vscode.TextDocument): void { 33 | this.codeLenses = [] 34 | const firstLineRange = new vscode.Range(0, 0, 0, 0) 35 | const tmpFileUri = document.uri 36 | const originFileUri = getOriginalFileUri(tmpFileUri) 37 | 38 | const commands: vscode.Command[] = [ 39 | { 40 | title: `$(close) ${t('command.quickCloseFileWithoutSave')}`, 41 | command: 'aide.quickCloseFileWithoutSave', 42 | arguments: [tmpFileUri] 43 | }, 44 | { 45 | title: `$(explorer-view-icon) ${t('command.copyFileText')}`, 46 | command: 'aide.copyFileText', 47 | arguments: [tmpFileUri] 48 | }, 49 | { 50 | title: `$(diff) ${t('command.showDiff')}`, 51 | command: 'aide.showDiff', 52 | arguments: [originFileUri, tmpFileUri] 53 | }, 54 | { 55 | title: `$(breakpoints-activate) ${t('command.replaceFile')}`, 56 | command: 'aide.replaceFile', 57 | arguments: [originFileUri, tmpFileUri] 58 | } 59 | ] 60 | 61 | commands.forEach(command => { 62 | this.codeLenses.push(new vscode.CodeLens(firstLineRange, command)) 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/extension/providers/webview.ts: -------------------------------------------------------------------------------- 1 | import { saveImage, setupHtml } from '@extension/utils' 2 | import type { WebviewToExtensionsMsg } from '@shared/types/msg' 3 | import * as vscode from 'vscode' 4 | 5 | // { 6 | // "viewsContainers": { 7 | // "activitybar": [ 8 | // { 9 | // "id": "aide", 10 | // "title": "Aide", 11 | // "icon": "res/icon-mask.png" 12 | // } 13 | // ] 14 | // }, 15 | // "views": { 16 | // "aide": [ 17 | // { 18 | // "type": "webview", 19 | // "id": "aide.webview", 20 | // "name": "Aide" 21 | // } 22 | // ] 23 | // }, 24 | // } 25 | class AideWebViewProvider implements vscode.WebviewViewProvider { 26 | public static readonly viewType = 'aide.webview' 27 | 28 | private _view?: vscode.WebviewView 29 | 30 | private disposables: vscode.Disposable[] = [] 31 | 32 | constructor( 33 | private readonly _extensionUri: vscode.Uri, 34 | private readonly _context: vscode.ExtensionContext 35 | ) {} 36 | 37 | public resolveWebviewView( 38 | webviewView: vscode.WebviewView 39 | // context: vscode.WebviewViewResolveContext, 40 | // _token: vscode.CancellationToken 41 | ) { 42 | this._view = webviewView 43 | 44 | webviewView.webview.options = { 45 | enableScripts: true, 46 | localResourceRoots: [this._extensionUri] 47 | } 48 | 49 | webviewView.webview.html = this._getHtmlForWebview(webviewView.webview) 50 | 51 | this.disposables.push( 52 | webviewView.webview.onDidReceiveMessage( 53 | async (message: WebviewToExtensionsMsg) => { 54 | switch (message.type) { 55 | case 'save-img': 56 | await saveImage(message.data) 57 | break 58 | case 'show-settings': 59 | await vscode.commands.executeCommand( 60 | 'workbench.action.openSettings', 61 | `@ext:aide-pro` 62 | ) 63 | break 64 | default: 65 | break 66 | } 67 | }, 68 | undefined, 69 | this.disposables 70 | ) 71 | ) 72 | 73 | webviewView.onDidDispose(() => { 74 | while (this.disposables.length) { 75 | const disposable = this.disposables.pop() 76 | if (disposable) { 77 | disposable.dispose() 78 | } 79 | } 80 | }) 81 | } 82 | 83 | public reveal() { 84 | if (this._view) { 85 | this._view.show?.(true) 86 | } 87 | } 88 | 89 | private _getHtmlForWebview(webview: vscode.Webview) { 90 | return setupHtml(webview, this._context) 91 | } 92 | } 93 | 94 | export async function renderWebview( 95 | context: vscode.ExtensionContext 96 | ): Promise { 97 | const provider = new AideWebViewProvider(context.extensionUri, context) 98 | const disposable = vscode.window.registerWebviewViewProvider( 99 | AideWebViewProvider.viewType, 100 | provider 101 | ) 102 | 103 | context.subscriptions.push(disposable) 104 | 105 | provider.reveal() 106 | 107 | return () => { 108 | disposable.dispose() 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/extension/types/common.ts: -------------------------------------------------------------------------------- 1 | export type LocalizeFunction = (key: string, ...args: any[]) => string 2 | 3 | export interface Messages { 4 | [key: string]: string 5 | } 6 | 7 | export type MaybePromise = T | Promise 8 | -------------------------------------------------------------------------------- /src/extension/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import type { ExtensionContext, Webview } from 'vscode' 2 | 3 | export {} 4 | declare global { 5 | /** 6 | * fix code hint 7 | */ 8 | type UnionType = T | (string & {}) 9 | 10 | namespace NodeJS { 11 | interface ProcessEnv { 12 | /** 13 | * Node.js environment 14 | */ 15 | NODE_ENV: UnionType<'development' | 'production'> 16 | /** 17 | * `[vite serve]` The url of the vite dev server. 18 | */ 19 | VITE_DEV_SERVER_URL?: string 20 | /** 21 | * `[vite build]` All js files in the dist directory, excluding index.js. It's to be a json string. 22 | */ 23 | VITE_WEBVIEW_DIST?: string 24 | } 25 | } 26 | 27 | /** 28 | * `[vite serve]` Gets the html of webview in development mode. 29 | * @param options serverUrl: The url of the vite dev server. 30 | */ 31 | function __getWebviewHtml__(options?: string | { serverUrl: string }): string 32 | 33 | /** 34 | * `[vite build]` Gets the html of webview in production mode. 35 | * @param webview The Webview instance of the extension. 36 | * @param context The ExtensionContext instance of the extension. 37 | * @param inputName vite build.rollupOptions.input name. Default is `index`. 38 | */ 39 | function __getWebviewHtml__( 40 | webview: Webview, 41 | context: ExtensionContext, 42 | inputName?: string 43 | ): string 44 | } 45 | -------------------------------------------------------------------------------- /src/shared/types/msg.ts: -------------------------------------------------------------------------------- 1 | export type SaveImgMsgData = { 2 | fileName: string 3 | format: string 4 | base64: string 5 | } 6 | 7 | export type ExtensionsToWebviewMsg = 8 | | { 9 | type: 'update-code' 10 | data: string 11 | } 12 | | { 13 | type: 'change-theme' 14 | data: 'dark' | 'light' 15 | } 16 | 17 | export type WebviewToExtensionsMsg = 18 | | { 19 | type: 'save-img' 20 | data: SaveImgMsgData 21 | } 22 | | { 23 | type: 'show-settings' 24 | data?: undefined 25 | } 26 | -------------------------------------------------------------------------------- /src/webview/App.css: -------------------------------------------------------------------------------- 1 | main { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: flex-start; 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /src/webview/App.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { VSCodeButton, VSCodeTextField } from '@vscode/webview-ui-toolkit/react' 3 | 4 | import { vscode } from './helpers/vscode' 5 | 6 | import './App.css' 7 | 8 | function App() { 9 | function onPostMessage() { 10 | vscode.postMessage({ 11 | command: 'hello', 12 | text: 'Hey there partner! 🤠' 13 | }) 14 | } 15 | 16 | const [message, setMessage] = useState('') 17 | const [state, setState] = useState('') 18 | 19 | const onSetState = () => { 20 | vscode.setState(state) 21 | } 22 | const onGetState = async () => { 23 | console.log('state', await vscode.getState()) 24 | setState((await vscode.getState()) as string) 25 | } 26 | 27 | return ( 28 |
29 |

Hello React!

30 | Test VSCode Message 31 |
32 | setMessage(e?.target?.value)} 35 | > 36 | Please enter a message 37 | 38 |
Message is: {message}
39 |
40 |
41 | setState(e?.target?.value)} 44 | > 45 | Please enter a state 46 | 47 |
State is: {state}
48 |
49 | setState 50 | 51 | getState 52 | 53 |
54 |
55 |
56 | ) 57 | } 58 | 59 | export default App 60 | -------------------------------------------------------------------------------- /src/webview/helpers/vscode.ts: -------------------------------------------------------------------------------- 1 | import type { WebviewApi } from 'vscode-webview' 2 | 3 | /** 4 | * A utility wrapper around the acquireVsCodeApi() function, which enables 5 | * message passing and state management between the webview and extension 6 | * contexts. 7 | * 8 | * This utility also enables webview code to be run in a web browser-based 9 | * dev server by using native web browser features that mock the functionality 10 | * enabled by acquireVsCodeApi. 11 | */ 12 | class VSCodeAPIWrapper { 13 | private readonly vsCodeApi: WebviewApi | undefined 14 | 15 | constructor() { 16 | // Check if the acquireVsCodeApi function exists in the current development 17 | // context (i.e. VS Code development window or web browser) 18 | if (typeof acquireVsCodeApi === 'function') { 19 | this.vsCodeApi = acquireVsCodeApi() 20 | } 21 | } 22 | 23 | /** 24 | * Post a message (i.e. send arbitrary data) to the owner of the webview. 25 | * 26 | * @remarks When running webview code inside a web browser, postMessage will instead 27 | * log the given message to the console. 28 | * 29 | * @param message Abitrary data (must be JSON serializable) to send to the extension context. 30 | */ 31 | public postMessage(message: unknown) { 32 | if (this.vsCodeApi) { 33 | this.vsCodeApi.postMessage(message) 34 | } else { 35 | window.parent.postMessage({ type: 'page:message', data: message }, '*') 36 | console.log(message) 37 | } 38 | } 39 | 40 | /** 41 | * Get the persistent state stored for this webview. 42 | * 43 | * @remarks When running webview source code inside a web browser, getState will retrieve state 44 | * from local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). 45 | * 46 | * @return The current state or `undefined` if no state has been set. 47 | */ 48 | public async getState(): Promise { 49 | if (this.vsCodeApi) { 50 | return await this.vsCodeApi.getState() 51 | } 52 | const state = localStorage.getItem('vscodeState') 53 | return state ? JSON.parse(state) : undefined 54 | } 55 | 56 | /** 57 | * Set the persistent state stored for this webview. 58 | * 59 | * @remarks When running webview source code inside a web browser, setState will set the given 60 | * state using local storage (https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). 61 | * 62 | * @param newState New persisted state. This must be a JSON serializable object. Can be retrieved 63 | * using {@link getState}. 64 | * 65 | * @return The new state. 66 | */ 67 | public async setState( 68 | newState: T 69 | ): Promise { 70 | if (this.vsCodeApi) { 71 | return await this.vsCodeApi.setState(newState) 72 | } 73 | localStorage.setItem('vscodeState', JSON.stringify(newState)) 74 | return newState 75 | } 76 | } 77 | 78 | // Exports class singleton to prevent multiple invocations of acquireVsCodeApi. 79 | export const vscode = new VSCodeAPIWrapper() 80 | -------------------------------------------------------------------------------- /src/webview/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | 4 | import App from './App' 5 | 6 | ReactDOM.createRoot(document.getElementById('app')!).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /src/webview/types/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | describe('should', () => { 4 | it('exported', () => { 5 | expect(1).toEqual(1) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["DOM", "DOM.Iterable", "esnext"], 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "strict": true, 10 | "alwaysStrict": true, 11 | "strictNullChecks": true, 12 | "noUncheckedIndexedAccess": true, 13 | "jsx": "react-jsx", 14 | 15 | "noImplicitAny": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noUnusedLocals": false, 19 | "noUnusedParameters": false, 20 | "allowUnreachableCode": false, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | "target": "es2020", 24 | "outDir": "dist", 25 | "sourceMap": true, 26 | 27 | "esModuleInterop": true, 28 | "allowSyntheticDefaultImports": true, 29 | "allowJs": true, 30 | "checkJs": true, 31 | "skipLibCheck": true, 32 | "skipDefaultLibCheck": true, 33 | "forceConsistentCasingInFileNames": true, 34 | 35 | "noEmit": true, 36 | "isolatedModules": true, 37 | "incremental": true, 38 | "useDefineForClassFields": true, 39 | 40 | "baseUrl": ".", 41 | "types": ["vite/client", "@types/vscode-webview"], 42 | "paths": { 43 | "@extension/*": ["src/extension/*"], 44 | "@webview/*": ["./src/webview/*"], 45 | "@shared/*": ["./src/shared/*"] 46 | } 47 | }, 48 | "include": [ 49 | "**/*.ts", 50 | "**/*.tsx", 51 | "src/**/*.json", 52 | "*.json", 53 | "*.ts", 54 | "*.js", 55 | "*.cjs", 56 | "*.mjs", 57 | "vite.config.mts" 58 | ], 59 | "exclude": ["**/dist/**", "**/node_modules/**", "cz-adapter.cjs"] 60 | } 61 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "cleanUrls": true 3 | } 4 | -------------------------------------------------------------------------------- /vite.config.mts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | import { dirname } from 'path' 3 | import { fileURLToPath } from 'url' 4 | import vscode from '@tomjs/vite-plugin-vscode' 5 | import react from '@vitejs/plugin-react-swc' 6 | import { defineConfig } from 'vite' 7 | import tsconfigPaths from 'vite-tsconfig-paths' 8 | 9 | import pkg from './package.json' 10 | 11 | const dir = 12 | typeof __dirname === 'string' 13 | ? __dirname 14 | : dirname(fileURLToPath(import.meta.url)) 15 | 16 | const resolvePath = (...paths: string[]) => path.resolve(dir, ...paths) 17 | 18 | // https://vitejs.dev/config/ 19 | export default defineConfig(() => { 20 | process.env.APP_BUILD_TIME = `${Date.now()}` 21 | process.env.APP_VERSION = pkg.version 22 | 23 | return { 24 | plugins: [ 25 | tsconfigPaths(), 26 | react(), 27 | vscode({ 28 | extension: { 29 | entry: resolvePath('./src/extension/index.ts'), 30 | platform: 'node', 31 | target: 'node18', 32 | sourcemap: true, 33 | skipNodeModulesBundle: false 34 | // treeshake: { 35 | // preset: 'smallest', 36 | // moduleSideEffects: 'no-external' 37 | // } 38 | } 39 | }) 40 | ] 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /website/.vitepress/config/index.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | import { en } from './en' 4 | import { shared } from './shared' 5 | import { zh } from './zh' 6 | 7 | export default defineConfig({ 8 | ...shared, 9 | locales: { 10 | root: { label: 'English', ...en }, 11 | zh: { label: '简体中文', ...zh } 12 | } 13 | }) 14 | -------------------------------------------------------------------------------- /website/.vitepress/config/shared.ts: -------------------------------------------------------------------------------- 1 | import { InlineLinkPreviewElementTransform } from '@nolebase/vitepress-plugin-inline-link-preview/markdown-it' 2 | import Mark from 'markdown-it-mark' 3 | import Unocss from 'unocss/vite' 4 | import { defineConfig } from 'vitepress' 5 | 6 | import { bilibiliSvg } from './svg' 7 | import { search as zhSearch } from './zh' 8 | 9 | export const shared = defineConfig({ 10 | title: 'Aide', 11 | 12 | rewrites: { 13 | 'en/:rest*': ':rest*' 14 | }, 15 | 16 | lastUpdated: true, 17 | cleanUrls: true, 18 | metaChunk: true, 19 | 20 | markdown: { 21 | lineNumbers: false, 22 | config(md) { 23 | md.use(InlineLinkPreviewElementTransform) 24 | md.use(Mark) 25 | } 26 | }, 27 | 28 | sitemap: { 29 | hostname: 'https://aide.nicepkg.cn', 30 | transformItems(items) { 31 | return items.filter(item => !item.url.includes('migration')) 32 | } 33 | }, 34 | 35 | head: [ 36 | ['link', { rel: 'icon', type: 'image/svg+xml', href: '/logo-mini.svg' }], 37 | ['link', { rel: 'icon', type: 'image/png', href: '/logo-mini.png' }], 38 | ['meta', { name: 'theme-color', content: '#8c6bef' }], 39 | ['meta', { property: 'og:type', content: 'website' }], 40 | ['meta', { property: 'og:locale', content: 'en' }], 41 | [ 42 | 'meta', 43 | { 44 | property: 'og:title', 45 | content: 'Aide | Conquer Any Code in VSCode' 46 | } 47 | ], 48 | ['meta', { property: 'og:site_name', content: 'Aide' }], 49 | [ 50 | 'meta', 51 | { 52 | property: 'og:image', 53 | content: 'https://aide.nicepkg.cn/og-cover.png' 54 | } 55 | ], 56 | ['meta', { property: 'og:url', content: 'https://aide.nicepkg.cn/' }] 57 | ], 58 | 59 | themeConfig: { 60 | logo: { src: '/logo-mini.svg', width: 24, height: 24 }, 61 | 62 | socialLinks: [ 63 | { icon: 'github', link: 'https://github.com/nicepkg/aide' }, 64 | { 65 | icon: 'twitter', 66 | link: 'https://x.com/jinmingyang666' 67 | }, 68 | { 69 | icon: 'youtube', 70 | link: 'https://www.youtube.com/@jinmingyang666' 71 | }, 72 | { 73 | icon: { 74 | svg: bilibiliSvg 75 | }, 76 | link: 'https://space.bilibili.com/83540912' 77 | } 78 | ], 79 | 80 | // search: { 81 | // provider: 'algolia', 82 | // options: { 83 | // appId: 'xxx', 84 | // apiKey: 'xxx', 85 | // indexName: 'aide', 86 | // locales: { 87 | // ...zhSearch 88 | // } 89 | // } 90 | // } 91 | 92 | search: { 93 | provider: 'local' 94 | } 95 | }, 96 | 97 | vite: { 98 | plugins: [ 99 | Unocss({ 100 | configFile: '../../uno.config.ts' 101 | }) 102 | ], 103 | optimizeDeps: { 104 | exclude: ['@nolebase/vitepress-plugin-inline-link-preview/markdown-it'] 105 | }, 106 | ssr: { 107 | noExternal: ['@nolebase/*'] 108 | } 109 | } 110 | }) 111 | -------------------------------------------------------------------------------- /website/.vitepress/config/svg.ts: -------------------------------------------------------------------------------- 1 | export const bilibiliSvg = `` 2 | -------------------------------------------------------------------------------- /website/.vitepress/integrations.ts: -------------------------------------------------------------------------------- 1 | export interface Integration { 2 | icon: string 3 | name: string 4 | link: string 5 | target?: string 6 | secondary?: string 7 | } 8 | 9 | // @unocss-include 10 | 11 | export const integrations: Integration[] = [ 12 | { 13 | name: 'Vue', 14 | link: '/guide/getting-started/integrations/vue', 15 | icon: 'i-logos-vue' 16 | }, 17 | { 18 | name: 'Nuxt', 19 | link: '/guide/getting-started/integrations/nuxt', 20 | icon: 'i-logos-nuxt-icon' 21 | }, 22 | { 23 | name: 'TailWind', 24 | secondary: 'Upvote Issue', 25 | target: '_blank', 26 | link: 'https://github.com/jd-solanki/anu/issues/128', 27 | icon: 'i-logos-tailwindcss-icon' 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /website/.vitepress/shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /website/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { NolebaseInlineLinkPreviewPlugin } from '@nolebase/vitepress-plugin-inline-link-preview/client' 2 | import { ClientOnly } from 'vitepress' 3 | import DefaultTheme from 'vitepress/theme' 4 | import type { App } from 'vue' 5 | import { h } from 'vue' 6 | 7 | import '@nolebase/vitepress-plugin-inline-link-preview/client/style.css' 8 | 9 | import AideModelPrice from '../../components/AideModels/AideModelPrice/index.vue' 10 | import AidePay from '../../components/AideModels/AidePay.vue' 11 | import ChatBotButton from '../../components/ChatBotButton.vue' 12 | import Image from '../../components/Image.vue' 13 | import Video from '../../components/Video.vue' 14 | 15 | import 'virtual:uno.css' 16 | import './style.css' 17 | 18 | export default { 19 | extends: DefaultTheme, 20 | Layout() { 21 | return h(DefaultTheme.Layout, null, { 22 | 'layout-bottom': () => h(ChatBotButton) 23 | }) 24 | }, 25 | enhanceApp({ app }: { app: App }) { 26 | app.component('Image', Image) 27 | app.component('Video', Video) 28 | app.component('AidePay', AidePay) 29 | app.component('AideModelPrice', AideModelPrice) 30 | app.use(NolebaseInlineLinkPreviewPlugin) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /website/components/AideModels/AideModelPrice/PriceTable.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /website/components/AideModels/AideModelPrice/types.ts: -------------------------------------------------------------------------------- 1 | export type TokenPrice = { 2 | type: 'tokenPrice' 3 | ratio: number 4 | completionRatio: number 5 | inputMillionPrice: number 6 | outputMillionPrice: number 7 | } 8 | 9 | export type FixedPrice = { 10 | type: 'fixedPrice' 11 | fixedPrice: number 12 | } 13 | 14 | export type ModelPrice = { 15 | [modelName: string]: TokenPrice | FixedPrice 16 | } 17 | -------------------------------------------------------------------------------- /website/components/Image.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 22 | 23 | 57 | -------------------------------------------------------------------------------- /website/components/Video.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | 30 | 64 | -------------------------------------------------------------------------------- /website/en/guide/configuration/ai-command-auto-run.md: -------------------------------------------------------------------------------- 1 | # aide.aiCommandAutoRun 2 | 3 | This configuration allows you to customize ==whether the AI command should automatically== run when you click `✨ Aide: Ask AI`. 4 | 5 | If you want to copy the AI command instead of running it immediately, you can set this configuration to `false` and set [`aide.aiCommandCopyBeforeRun`](./ai-command-copy-before-run.md) to `true`. 6 | 7 | - **Default Value:** 8 | 9 | ```json 10 | { 11 | "aide.aiCommandAutoRun": true 12 | } 13 | ``` 14 | -------------------------------------------------------------------------------- /website/en/guide/configuration/ai-command-copy-before-run.md: -------------------------------------------------------------------------------- 1 | # aide.aiCommandCopyBeforeRun 2 | 3 | This configuration allows you to customize ==whether to copy the AI command== before executing the `✨ Aide: Ask AI` command. 4 | 5 | - **Default value:** 6 | 7 | ```json 8 | { 9 | "aide.aiCommandCopyBeforeRun": true 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /website/en/guide/configuration/ai-command.md: -------------------------------------------------------------------------------- 1 | # aide.aiCommand 2 | 3 | This configuration allows you to customize ==the template used== for the `✨ Aide: Ask AI` command execution. The template can include some variables: 4 | 5 | **Template Parameters:** 6 | 7 | | Parameter | Description | Output Example | 8 | | ---------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------- | 9 | | `#{filesRelativePath}` | Variable for file paths | `"./src/index.ts" "./src/utils.ts"` | 10 | | `#{filesFullPath}` | Variable for full paths | `"/project/src/index.ts" "/project/src/utils.ts"` | 11 | | `#{question}` | Variable for user query | `"What is the purpose of this code?" ` | 12 | | `#{content}` | Variable for file content |
File: example.js
\`\`\`js
const bar = "hello, aide";
console.log(bar);
\`\`\`
| 13 | 14 | **Usage Example:** 15 | 16 | - **Default Template:** 17 | 18 | By default, the template is blank, and you'll need to provide a custom template. 19 | 20 | - **Example:** 21 | 22 | It is recommended to use the [`aider (a highly regarded command-line AI tool)`](https://github.com/paul-gauthier/aider) command to ask AI questions about the selected files. 23 | 24 | - If you want to open a new terminal window each time to ask a question to [`aider`](https://github.com/paul-gauthier/aider), you can use the following template: 25 | 26 | ```plaintext 27 | aider #{filesRelativePath} 28 | ``` 29 | 30 | - If you prefer to manually start [`aider`](https://github.com/paul-gauthier/aider) and then add files manually, you can set [`aide.aiCommandCopyBeforeRun`](./ai-command-copy-before-run.md) to `true` and [`aide.aiCommandAutoRun`](./ai-command-auto-run.md) to `false`. Then, use the following template: 31 | 32 | ```plaintext 33 | /add #{filesRelativePath} 34 | ``` 35 | 36 | This way, you can copy commands like `/add ./src/aaa.ts ./src/bbb.ts` and paste them into the [`aider`](https://github.com/paul-gauthier/aider) terminal window each time. 37 | -------------------------------------------------------------------------------- /website/en/guide/configuration/ai-prompt.md: -------------------------------------------------------------------------------- 1 | # aide.aiPrompt 2 | 3 | This configuration allows you to customize ==the template for AI prompts when batch copying files==. The template can include some variables: 4 | 5 | **Template Parameters:** 6 | 7 | | Parameter | Description | Output Example | 8 | | ------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------- | 9 | | `#{content}` | Variable for full file content |
File: example.js
\`\`\`js
const bar = "hello, aide";
console.log(bar);
\`\`\`
| 10 | 11 | **Usage Example:** 12 | 13 | - **Default Template:** 14 | 15 | ```plaintext 16 | #{content} 17 | ``` 18 | 19 | - **Example:** 20 | ```plaintext 21 | Here is the code snippet: 22 | #{content} 23 | Please answer the following question: 24 | ``` 25 | -------------------------------------------------------------------------------- /website/en/guide/configuration/api-concurrency.md: -------------------------------------------------------------------------------- 1 | # aide.apiConcurrency 2 | 3 | This configuration allows you to customize the ==AI request concurrency==, which is `1` by default. It is recommended not to change it when using local models. 4 | 5 | - **Default Value:** 6 | 7 | ```json 8 | { 9 | "aide.apiConcurrency": 1 10 | } 11 | ``` 12 | 13 | ::: tip 14 | You can increase this number to speed up the [`AI Batch Processing`](../features/batch-processor.md). It is not recommended to exceed `3`. 15 | ::: 16 | -------------------------------------------------------------------------------- /website/en/guide/configuration/auto-remember-convert-language-pairs.md: -------------------------------------------------------------------------------- 1 | # aide.autoRememberConvertLanguagePairs 2 | 3 | This configuration allows you to customize ==whether to automatically remember the language mapping for code conversion==. 4 | 5 | - **Default Value:** 6 | 7 | ```json 8 | { 9 | "aide.autoRememberConvertLanguagePairs": true 10 | } 11 | ``` 12 | -------------------------------------------------------------------------------- /website/en/guide/configuration/code-viewer-helper-prompt.md: -------------------------------------------------------------------------------- 1 | # aide.codeViewerHelperPrompt 2 | 3 | This configuration allows you to customize ==the AI prompt template for the code viewer assistant==. The template can include some variables: 4 | 5 | **Template Parameters:** 6 | 7 | | Parameter | Description | Output Example | 8 | | ------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------ | 9 | | `#{sourceLanguage}` | Variable for source code language | `javascript` | 10 | | `#{locale}` | Variable for user's language/locale | `en` | 11 | | `#{content}` | Variable for full content of the file |
const bar = "hello, aide";
console.log(bar);
| 12 | 13 | **Usage Examples:** 14 | 15 | - **Default Template:** 16 | 17 | ```plaintext 18 | You are a programming language commentator. 19 | You need to help me add comments to #{sourceLanguage} code as much as possible to make it readable for beginners. 20 | Do not change the original code, just add as detailed comments as possible, because my purpose is only to understand and read. 21 | Please use my native language #{locale} as the commenting language. 22 | Please do not reply with any text other than the code, and do not use markdown syntax. 23 | Here is the code you need to comment on: 24 | #{content} 25 | ``` 26 | 27 | - **Example:** 28 | 29 | ```plaintext 30 | Provide detailed comments for the following #{sourceLanguage} code, using #{locale}: 31 | #{content} 32 | ``` 33 | -------------------------------------------------------------------------------- /website/en/guide/configuration/convert-language-pairs.md: -------------------------------------------------------------------------------- 1 | # aide.convertLanguagePairs 2 | 3 | This configuration allows you to customize ==the language mapping for code conversion==. The mapping should be in the form of `sourceLanguage: targetLanguage`. 4 | 5 | By default, the editor will remember your language mapping in the current project's `.vscode/settings.json` file, under the `aide.convertLanguagePairs` configuration. You can modify or add new language mappings here, for example: 6 | 7 | ```json 8 | { 9 | //other settings... 10 | "aide.convertLanguagePairs": { 11 | "javascript": "python", // Convert javascript to python 12 | "json": "yaml", // Convert json to yaml 13 | "vue": "vue migrate from vue2 to vue3