├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .github └── workflows │ ├── codeql-analysis.yml │ └── vsix-package.yaml ├── .gitignore ├── .prettierrc.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── assets ├── log.tmLanguage.json ├── openai-dark.png ├── openai-light.png ├── openai-sidebar.svg ├── openai-webview-dark.png ├── openai-webview-light.png └── vscode-openai.woff ├── eslint.config.mjs ├── images ├── logo_128x128.png ├── vscode-openai-chat2editor.png ├── vscode-openai-configuration.png ├── vscode-openai-conversation-persona.png ├── vscode-openai-conversation.png ├── vscode-openai-embedding-welcome.png ├── vscode-openai-embedding.png ├── vscode-openai-explain.gif ├── vscode-openai-output.png ├── vscode-openai-scm.png ├── vscode-openai-serviceproviders.png ├── vscode-openai-vscode-codicons-cloud.png ├── vscode-openai-vscode-codicons-lock.png └── vscode-openai-vscode-codicons-server-environment.png ├── package-lock.json ├── package.json ├── src ├── apis │ ├── embedding │ │ ├── chunk │ │ │ ├── chunkText.ts │ │ │ ├── getEmbeddingsForText.ts │ │ │ └── searchFileChunks.ts │ │ ├── content │ │ │ └── embeddingResource.ts │ │ └── index.ts │ ├── git │ │ ├── IRepositoryChangeCallback.ts │ │ ├── IRepositoryInfo.ts │ │ ├── getComments.ts │ │ ├── getGitDifferences.ts │ │ ├── gitService.ts │ │ └── index.ts │ ├── node │ │ ├── httpClient.ts │ │ ├── index.ts │ │ ├── noop.ts │ │ ├── notificationManager.ts │ │ └── waitFor.ts │ ├── openai │ │ ├── api │ │ │ ├── apiKey.ts │ │ │ ├── chatCompletion │ │ │ │ ├── chatCompletionCallback.ts │ │ │ │ ├── createChatCompletionMessage.ts │ │ │ │ └── createChatCompletionStream.ts │ │ │ ├── chatCompletionMessages │ │ │ │ ├── chatCompletionRequestMessageEmbedding.ts │ │ │ │ ├── chatCompletionRequestMessageStandard.ts │ │ │ │ ├── index.ts │ │ │ │ ├── isSystemRoleAllowed.ts │ │ │ │ └── logChatCompletion.ts │ │ │ ├── createEmbedding.ts │ │ │ ├── createOpenAI.ts │ │ │ ├── errorHandler.ts │ │ │ └── models │ │ │ │ ├── listModelsAzureOpenAI.ts │ │ │ │ ├── listModelsOpenAI.ts │ │ │ │ └── modelCapabiliy.ts │ │ ├── index.ts │ │ └── prompt │ │ │ ├── bountyPrompt.ts │ │ │ ├── commentPrompt.ts │ │ │ ├── explainPrompt.ts │ │ │ ├── optimizePrompt.ts │ │ │ ├── patternPrompt.ts │ │ │ └── promptFactory.ts │ └── vscode │ │ ├── authentication │ │ ├── getAzureOpenAIAccessToken.ts │ │ ├── getAzureOpenAIAccessTokenSovereignUs.ts │ │ ├── getGitAccessToken.ts │ │ └── getVscodeOpenAccessToken.ts │ │ ├── editorServices │ │ ├── getActiveTextEditorValue.ts │ │ ├── getActiveTextLanguageId.ts │ │ └── insertActiveTextEditorValue.ts │ │ ├── featureFlag │ │ └── featureFlag.ts │ │ ├── index.ts │ │ ├── multiStepInput │ │ ├── InputFlowAction.ts │ │ ├── interfaces │ │ │ ├── IParametersInputBox.ts │ │ │ ├── IParametersQuickPick.ts │ │ │ └── index.ts │ │ └── multiStepInput.ts │ │ ├── outputChannel │ │ ├── outputChannel.ts │ │ └── telemetryService.ts │ │ ├── showMessage │ │ └── showMessageWithTimeout.ts │ │ ├── statusBarItem │ │ └── StatusBarServiceProvider.ts │ │ ├── storageServices │ │ ├── globalStateService.ts │ │ ├── localStorageService.ts │ │ └── secretStorageService.ts │ │ └── webviewServices │ │ ├── getNonce.ts │ │ └── getUri.ts ├── commands │ ├── ICommand.ts │ ├── commandManager.ts │ ├── configuration │ │ ├── index.ts │ │ └── showQuickpick.ts │ ├── conversation │ │ ├── copyClipboardSummary.ts │ │ ├── delete.ts │ │ ├── index.ts │ │ ├── newPersona.ts │ │ ├── newStandard.ts │ │ ├── openJson.ts │ │ ├── openMarkdown.ts │ │ └── openWebview.ts │ ├── conversations │ │ ├── deleteAll.ts │ │ ├── index.ts │ │ ├── refresh.ts │ │ └── settings.ts │ ├── editor │ │ ├── codeBounty.ts │ │ ├── codeComment.ts │ │ ├── codeExplain.ts │ │ ├── codeOptimize.ts │ │ ├── codePatterns.ts │ │ ├── index.ts │ │ └── settings.ts │ ├── embeddings │ │ ├── delete.ts │ │ ├── deleteAll.ts │ │ ├── index.ts │ │ ├── newConversationEmbedding.ts │ │ ├── newConversationEmbeddingAll.ts │ │ ├── newEmbeddingFile.ts │ │ ├── newEmbeddingFolder.ts │ │ ├── refresh.ts │ │ └── settings.ts │ ├── explorer │ │ ├── copyClipboardFolderMarkdown.ts │ │ └── index.ts │ ├── index.ts │ ├── messages │ │ ├── copyClipboardMessage.ts │ │ └── index.ts │ └── scm │ │ ├── generateComments.ts │ │ └── index.ts ├── constants │ ├── conversation.ts │ ├── embedding.ts │ ├── extension.ts │ ├── index.ts │ ├── quickPickPersona.ts │ └── quickPickSetup.ts ├── extension.ts ├── interfaces │ ├── IChatCompletion.ts │ ├── ICodeDocument.ts │ ├── IConfigurationConversation.ts │ ├── IConfigurationConversationColor.ts │ ├── IConfigurationEmbedding.ts │ ├── IConfigurationOpenAI.ts │ ├── IConfigurationSetting.ts │ ├── IConversation.ts │ ├── IDynamicLooseObject.ts │ ├── IEmbeddingFileChunk.ts │ ├── IEmbeddingFileLite.ts │ ├── IEmbeddingText.ts │ ├── IMessage.ts │ ├── IPersonaOpenAI.ts │ └── index.ts ├── models │ ├── QueryResourcePersona.ts │ ├── SystemPersonas.ts │ └── index.ts ├── panels │ ├── index.ts │ └── messageViewerPanel │ │ ├── messageViewerPanel.ts │ │ └── onDidFunctions │ │ ├── index.ts │ │ ├── onDidCopyClipboardCode.ts │ │ ├── onDidCreateDocument.ts │ │ └── onDidSaveMessages.ts ├── providers │ ├── configurationQuickPickProvider │ │ └── configurationQuickPickProvider.ts │ ├── conversationsWebviewProvider │ │ ├── conversationsWebviewProvider.ts │ │ ├── conversationsWebviewViewProvider.ts │ │ ├── index.ts │ │ └── onDidFunctions │ │ │ ├── index.ts │ │ │ ├── onDidInitialize.ts │ │ │ └── onDidOpenConversationWebview.ts │ ├── embeddingTreeDataProvider │ │ ├── embeddingTreeDataProvider.ts │ │ ├── embeddingTreeDragAndDropController.ts │ │ ├── embeddingTreeItem.ts │ │ └── index.ts │ └── index.ts ├── services │ ├── configuration │ │ ├── chatCompletionConfig.ts │ │ ├── conversationColorConfig.ts │ │ ├── conversationConfig.ts │ │ ├── embeddingConfig.ts │ │ ├── index.ts │ │ ├── settingConfig.ts │ │ └── utilities │ │ │ └── configValue.ts │ ├── configurationMonitorServices │ │ ├── index.ts │ │ └── managedApiKey.ts │ ├── featureFlagServices │ │ └── index.ts │ ├── index.ts │ └── storageServices │ │ ├── conversationStorageService.ts │ │ ├── embeddingStorageService.ts │ │ └── index.ts ├── types │ └── git.d.ts └── utilities │ ├── editor │ ├── compareResultsToClipboard.ts │ ├── getEditorPrompt.ts │ ├── index.ts │ └── newConversationEditor.ts │ └── quickPicks │ ├── commands │ ├── azure │ │ ├── showInputBoxAzureApiKey.ts │ │ ├── showInputBoxAzureBaseUrl.ts │ │ ├── showQuickPickAzureAuthentication.ts │ │ ├── showQuickPickAzureEmbeddingModel.ts │ │ ├── showQuickPickAzureInferenceModel.ts │ │ └── showQuickPickAzureScmModel.ts │ ├── custom │ │ ├── showInputBoxCustomApiKey.ts │ │ ├── showInputBoxCustomBaseUrl.ts │ │ └── showInputBoxCustomInferenceModel.ts │ ├── index.ts │ ├── openai │ │ ├── showInputBoxOpenAIApiKey.ts │ │ ├── showInputBoxOpenAIBaseUrl.ts │ │ ├── showQuickPickOpenAIEmbeddingModel.ts │ │ ├── showQuickPickOpenAIInferenceModel.ts │ │ └── showQuickPickOpenAIScmModel.ts │ ├── shouldResume.ts │ ├── validateIgnored.ts │ ├── validateUrl.ts │ └── vscode │ │ └── showQuickPickVscodeAuthentication.ts │ ├── getAvailableModelsAzure.ts │ ├── getAvailableModelsOpenai.ts │ ├── index.ts │ ├── interface │ ├── IQuickPickSetup.ts │ └── index.ts │ ├── quickPickChangeModel.ts │ ├── quickPickCreateConversation.ts │ ├── quickPickSetupAzureOpenai.ts │ ├── quickPickSetupCustomOpenai.ts │ ├── quickPickSetupOpenai.ts │ └── quickPickSetupVscodeOpenai.ts ├── tsconfig.json ├── utilities └── scripts │ ├── clean.js │ └── copy-wasm.js └── webview ├── common └── vscode.ts ├── conversations ├── .gitignore ├── index.html ├── package.json ├── src │ ├── App.tsx │ ├── assets │ │ ├── BusinessAnalystsIcon.ts │ │ ├── CloudArchitectIcon.ts │ │ ├── CyberSecurityAnalystsIcon.ts │ │ ├── DataScientistIcon.ts │ │ ├── DatabaseAdministratorIcon.ts │ │ ├── DevOpsEngineersIcon.ts │ │ ├── DeveloperProgrammerIcon.ts │ │ ├── EnterpriseArchitectIcon.ts │ │ ├── GeneralChatIcon.ts │ │ ├── ITManagerIcon.ts │ │ ├── NetworkEngineerIcon.ts │ │ ├── ProductManagerIcon.ts │ │ ├── ProjectManagerIcon.ts │ │ ├── QualityAssuranceTestersIcon.ts │ │ ├── SystemAdministratorIcon.ts │ │ ├── TechnicalWriterIcon.ts │ │ ├── UserExperienceDesignersIcon.ts │ │ └── index.ts │ ├── components │ │ ├── ConversationGrid │ │ │ ├── ConversationGrid.tsx │ │ │ └── index.ts │ │ └── ConversationGridColumnDefinition │ │ │ ├── ConversationGridColumnDefinition.tsx │ │ │ ├── index.ts │ │ │ ├── useConversationAvatar.tsx │ │ │ └── useStyles.ts │ ├── context │ │ ├── index.ts │ │ └── themeContext.ts │ ├── index.tsx │ ├── interfaces │ │ ├── IChatCompletion.ts │ │ ├── IContextTheme.ts │ │ ├── IConversation.ts │ │ ├── IDialogProps.ts │ │ ├── IMenuItemProps.ts │ │ ├── IPersonaOpenAI.ts │ │ └── index.ts │ └── utilities │ │ └── index.ts ├── tsconfig.json ├── types │ └── svg.d.ts └── vite.config.mts └── message ├── .gitignore ├── index.html ├── package.json ├── src ├── App.tsx ├── components │ ├── ButtonCopyToClipboard │ │ └── index.tsx │ ├── ButtonOpenSourceFile │ │ └── index.tsx │ ├── MessageInput │ │ ├── index.tsx │ │ └── styles │ │ │ └── useMessageInputStyles.tsx │ ├── MessageItem │ │ ├── components │ │ │ ├── CodeBlockMatched.tsx │ │ │ ├── CodeBlockUnmatched.tsx │ │ │ ├── MessageItemTokenInfo.tsx │ │ │ ├── MessageItemToolbar.tsx │ │ │ ├── ThinkSection.tsx │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── styles │ │ │ └── useMessageItemStyles.tsx │ └── MessageList │ │ ├── components │ │ └── WelcomeMessageBar.tsx │ │ ├── index.tsx │ │ └── styles │ │ └── useMessageListStyles.tsx ├── context │ ├── configurationContext.ts │ └── index.ts ├── index.tsx ├── interfaces │ ├── IChatCompletion.ts │ ├── IChatCompletionListProps.ts │ ├── IChatCompletionProps.ts │ ├── ICodeDocument.ts │ ├── IContextConfiguration.ts │ ├── IMessageInputProps.tsx │ └── index.ts ├── utilities │ ├── index.ts │ └── textToSpeech.ts └── vite-env.d.ts ├── tsconfig.json └── vite.config.mts /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-container", 3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:latest", 4 | "customizations": { 5 | "vscode": { 6 | "settings": { 7 | "files.exclude": { 8 | "out": false 9 | }, 10 | "search.exclude": { 11 | "out": true 12 | }, 13 | "typescript.tsc.autoDetect": "off", 14 | "editor.formatOnSave": true, 15 | "editor.formatOnPaste": true, 16 | "editor.tabSize": 2, 17 | "editor.insertSpaces": true, 18 | "editor.codeActionsOnSave": { 19 | "source.fixAll": "explicit", 20 | "source.organizeImports": "explicit" 21 | }, 22 | "typescript.tsdk": "node_modules/typescript/lib", 23 | "typescript.autoImportSuggestions.enabled": true, 24 | "eslint.enable": true, 25 | "eslint.format.enable": true, 26 | "eslint.lintTask.enable": true, 27 | "javascript.format.semicolons": "insert", 28 | "typescript.format.semicolons": "insert", 29 | "javascript.preferences.quoteStyle": "double", 30 | "[typescript]": { 31 | "editor.defaultFormatter": "vscode.typescript-language-features", 32 | "typescript.preferences.quoteStyle": "double" 33 | }, 34 | "[javascript]": { 35 | "editor.defaultFormatter": "vscode.typescript-language-features" 36 | }, 37 | "[json]": { 38 | "editor.defaultFormatter": "vscode.json-language-features" 39 | }, 40 | "[jsonc]": { 41 | "editor.defaultFormatter": "vscode.json-language-features" 42 | }, 43 | "[css]": { 44 | "editor.defaultFormatter": "vscode.css-language-features" 45 | } 46 | }, 47 | "extensions": [ 48 | "andrewbutson.vscode-openai", 49 | "editorconfig.editorconfig", 50 | "github.vscode-pull-request-github", 51 | "esbenp.prettier-vscode", 52 | "dbaeumer.vscode-eslint", 53 | "ms-vscode.vscode-typescript-next" 54 | ] 55 | } 56 | }, 57 | "forwardPorts": [], 58 | "postCreateCommand": "npm run devcontainer-init", 59 | "remoteUser": "node" 60 | } 61 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # EditorConfig is awesome: https://EditorConfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | indent_size = 2 10 | indent_style = space 11 | insert_final_newline = true 12 | trim_trailing_whitespace = true 13 | -------------------------------------------------------------------------------- /.github/workflows/vsix-package.yaml: -------------------------------------------------------------------------------- 1 | name: VsixPackage 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | release: 8 | types: 9 | - created 10 | 11 | jobs: 12 | build: 13 | strategy: 14 | matrix: 15 | os: [macos-latest, ubuntu-latest, windows-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - name: Checkout - vscode-openai 19 | uses: actions/checkout@v3 20 | - name: Install Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: '>=17.0.0' 24 | - name: Install vscode-openai 25 | run: npm install 26 | - name: Build vscode-openai 27 | run: npm run build 28 | - name: Publish 29 | if: success() && startsWith(github.ref, 'refs/tags/') && matrix.os == 'ubuntu-latest' 30 | run: npm run deploy 31 | env: 32 | VSCE_PAT: ${{ secrets.VSCE_PAT }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | Thumbs.db 4 | node_modules 5 | releases 6 | out 7 | */.vs/ 8 | tsconfig.lsif.json 9 | *.lsif 10 | *.db 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "singleQuote": true, 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /.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": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ], 15 | "outFiles": [ 16 | "${workspaceFolder}/out/**/*.js" 17 | ], 18 | "preLaunchTask": "npm: esbuild-watch" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "out": false 4 | }, 5 | "search.exclude": { 6 | "out": true 7 | }, 8 | "typescript.tsc.autoDetect": "off", 9 | "editor.formatOnSave": true, 10 | "editor.formatOnPaste": true, 11 | "editor.tabSize": 2, 12 | "editor.insertSpaces": true, 13 | "editor.codeActionsOnSave": { 14 | "source.fixAll": "explicit", 15 | "source.organizeImports": "explicit" 16 | }, 17 | "typescript.tsdk": "node_modules/typescript/lib", 18 | "typescript.autoImportSuggestions.enabled": true, 19 | "eslint.enable": true, 20 | "eslint.format.enable": true, 21 | "eslint.lintTask.enable": true, 22 | "javascript.format.semicolons": "insert", 23 | "typescript.format.semicolons": "insert", 24 | "javascript.preferences.quoteStyle": "double", 25 | "[typescript]": { 26 | "editor.defaultFormatter": "vscode.typescript-language-features", 27 | "typescript.preferences.quoteStyle": "double", 28 | }, 29 | "[javascript]": { 30 | "editor.defaultFormatter": "vscode.typescript-language-features" 31 | }, 32 | "[json]": { 33 | "editor.defaultFormatter": "vscode.json-language-features" 34 | }, 35 | "[jsonc]": { 36 | "editor.defaultFormatter": "vscode.json-language-features" 37 | }, 38 | "[css]": { 39 | "editor.defaultFormatter": "vscode.css-language-features" 40 | } 41 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "npm: esbuild-watch", 6 | "type": "shell", 7 | "command": "npm", 8 | "args": [ 9 | "run", 10 | "esbuild-watch" 11 | ], 12 | "isBackground": true, 13 | "presentation": { 14 | "reveal": "never", 15 | "group": "watchers" 16 | }, 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | }, 21 | "problemMatcher": { 22 | "owner": "typescript", 23 | "fileLocation": "relative", 24 | "pattern": { 25 | "regexp": "^([^\\s].*)\\((\\d+|\\,\\d+|\\d+,\\d+,\\d+,\\d+)\\):\\s+(error|warning|info)\\s+(TS\\d+)\\s*:\\s*(.*)$", 26 | "file": 1, 27 | "location": 2, 28 | "severity": 3, 29 | "code": 4, 30 | "message": 5 31 | }, 32 | "background": { 33 | "activeOnStart": true, 34 | "beginsPattern": { 35 | "regexp": "\\s*\\[watch\\] build started" 36 | }, 37 | "endsPattern": { 38 | "regexp": "^\\s*\\[watch\\] build finished" 39 | } 40 | } 41 | } 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .github/** 2 | .vscode/** 3 | .vscode-test/** 4 | node_modules 5 | releases 6 | tools 7 | utilities/** 8 | api-gateway 9 | out/test/** 10 | out/**/*.map 11 | src/** 12 | .gitignore 13 | tsconfig.json 14 | vsc-extension-quickstart.md 15 | vscode-openai-version.json 16 | tslint.json 17 | webview -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 6 | 7 | ## Issues and PRs 8 | 9 | If you have suggestions for how this project could be improved, or want to report a bug, open an issue! We'd love all and any contributions. If you have questions, too, we'd love to hear them. 10 | 11 | We'd also love PRs. If you're thinking of a large PR, we advise opening up an issue first to talk about it, though! Look at the links below if you're not sure how to open a PR. 12 | 13 | ## Submitting a pull request 14 | 15 | 1. [Fork][fork] and clone the repository. 16 | 1. Configure and install the dependencies: `npm install`. 17 | 1. Make sure the tests pass on your machine: `npm test`, note: these tests also apply the linter, so there's no need to lint separately. 18 | 1. Create a new branch: `git checkout -b my-branch-name`. 19 | 1. Make your change, add tests, and make sure the tests still pass. 20 | 1. Push to your fork and [submit a pull request][pr]. 21 | 1. Pat your self on the back and wait for your pull request to be reviewed and merged. 22 | 23 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 24 | 25 | - Follow the [style guide][style] which is using standard. Any linting errors should be shown when running `npm test`. 26 | - Write and update tests. 27 | - Keep your changes as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 28 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 29 | 30 | Work in Progress pull requests are also welcome to get feedback early on, or if there is something blocked you. 31 | 32 | ## Resources 33 | 34 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 35 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 36 | - [GitHub Help](https://help.github.com) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 arbs-io 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 | -------------------------------------------------------------------------------- /assets/openai-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/assets/openai-dark.png -------------------------------------------------------------------------------- /assets/openai-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/assets/openai-light.png -------------------------------------------------------------------------------- /assets/openai-sidebar.svg: -------------------------------------------------------------------------------- 1 | OpenAI icon -------------------------------------------------------------------------------- /assets/openai-webview-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/assets/openai-webview-dark.png -------------------------------------------------------------------------------- /assets/openai-webview-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/assets/openai-webview-light.png -------------------------------------------------------------------------------- /assets/vscode-openai.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/assets/vscode-openai.woff -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import pluginJs from "@eslint/js"; 2 | import globals from "globals"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | { ignores: ['**/out/**', '**/dist/**', '**/build/**', '**/node_modules/**'] }, 8 | { files: ["**/*.{js,mjs,cjs,ts}"] }, 9 | { languageOptions: { globals: globals.browser } }, 10 | pluginJs.configs.recommended, 11 | ...tseslint.configs.recommended, 12 | { 13 | rules: { 14 | 'no-extra-semi': 'off', 15 | '@typescript-eslint/no-extra-semi': 0, 16 | '@typescript-eslint/no-unused-vars': 0, 17 | '@typescript-eslint/no-explicit-any': 0, 18 | '@typescript-eslint/explicit-module-boundary-types': 0, 19 | '@typescript-eslint/no-non-null-assertion': 0, 20 | 'no-constant-condition': 0, 21 | quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: true }], 22 | indent: ['error', 2, { SwitchCase: 1 }], 23 | 'comma-dangle': ['error', 'only-multiline'], 24 | 'arrow-parens': ['error', 'always'], 25 | 'object-curly-spacing': ['error', 'always'], 26 | }, 27 | }, 28 | ]; 29 | -------------------------------------------------------------------------------- /images/logo_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/logo_128x128.png -------------------------------------------------------------------------------- /images/vscode-openai-chat2editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-chat2editor.png -------------------------------------------------------------------------------- /images/vscode-openai-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-configuration.png -------------------------------------------------------------------------------- /images/vscode-openai-conversation-persona.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-conversation-persona.png -------------------------------------------------------------------------------- /images/vscode-openai-conversation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-conversation.png -------------------------------------------------------------------------------- /images/vscode-openai-embedding-welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-embedding-welcome.png -------------------------------------------------------------------------------- /images/vscode-openai-embedding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-embedding.png -------------------------------------------------------------------------------- /images/vscode-openai-explain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-explain.gif -------------------------------------------------------------------------------- /images/vscode-openai-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-output.png -------------------------------------------------------------------------------- /images/vscode-openai-scm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-scm.png -------------------------------------------------------------------------------- /images/vscode-openai-serviceproviders.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-serviceproviders.png -------------------------------------------------------------------------------- /images/vscode-openai-vscode-codicons-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-vscode-codicons-cloud.png -------------------------------------------------------------------------------- /images/vscode-openai-vscode-codicons-lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-vscode-codicons-lock.png -------------------------------------------------------------------------------- /images/vscode-openai-vscode-codicons-server-environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arbs-io/vscode-openai/6e06943d65bbf0aed2dbe0bb08bfe22ac7084a05/images/vscode-openai-vscode-codicons-server-environment.png -------------------------------------------------------------------------------- /src/apis/embedding/chunk/getEmbeddingsForText.ts: -------------------------------------------------------------------------------- 1 | import { createEmbedding } from '@app/apis/openai'; 2 | import { StatusBarServiceProvider } from '@app/apis/vscode'; 3 | import { chunkText } from '@app/apis/embedding'; 4 | import { IEmbeddingText } from '@app/interfaces'; 5 | import { EmbeddingConfig as embedCfg } from '@app/services'; 6 | 7 | // This function takes a text and returns an array of embeddings for each chunk of the text 8 | // The text is split into chunks of a given maximum charcter length 9 | // The embeddings are computed in batches of a given size 10 | export async function getEmbeddingsForText( 11 | content: string 12 | ): Promise { 13 | StatusBarServiceProvider.instance.showStatusBarInformation( 14 | 'sync~spin', 15 | '- chunk data' 16 | ); 17 | 18 | const batchSize = 1; 19 | const MAX_CHAR_LENGTH = embedCfg.maxCharacterLength; 20 | const textChunks = chunkText({ content, maxCharLength: MAX_CHAR_LENGTH }); 21 | 22 | let chunkCounter = 1; 23 | const batches = []; 24 | for (let i = 0; i < textChunks.length; i += batchSize) { 25 | batches.push(textChunks.slice(i, i + batchSize)); 26 | StatusBarServiceProvider.instance.showStatusBarInformation( 27 | 'sync~spin', 28 | `- chunk data (${chunkCounter++})` 29 | ); 30 | } 31 | 32 | let itemCount = batches.length; 33 | 34 | const batchPromises = batches.map((batch) => 35 | createEmbedding({ 36 | input: batch, 37 | itemCount: itemCount--, 38 | batchLength: batches.length, 39 | }) 40 | ); 41 | 42 | const embeddings = (await Promise.all(batchPromises)).flat(); 43 | 44 | const textEmbeddings = embeddings.map((embedding, index) => { 45 | return { 46 | embedding: embedding, 47 | text: textChunks[index], 48 | }; 49 | }); 50 | StatusBarServiceProvider.instance.showStatusBarInformation( 51 | 'vscode-openai', 52 | '' 53 | ); 54 | return textEmbeddings; 55 | } 56 | -------------------------------------------------------------------------------- /src/apis/embedding/content/embeddingResource.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from 'uuid'; 2 | import { Uri, workspace } from 'vscode'; 3 | import { createDebugNotification, createInfoNotification } from '@app/apis/node'; 4 | import { getEmbeddingsForText } from '@app/apis/embedding'; 5 | import { IEmbeddingFileLite } from '@app/interfaces'; 6 | import { StatusBarServiceProvider } from '@app/apis/vscode'; 7 | import { createDocumentParser } from '@arbs.io/asset-extractor-wasm'; 8 | 9 | export async function embeddingResource(uri: Uri) { 10 | StatusBarServiceProvider.instance.showStatusBarInformation( 11 | 'sync~spin', 12 | '- memory-buffer' 13 | ); 14 | 15 | createDebugNotification(`embedding-controller memory-buffer`); 16 | const bufferArray = await workspace.fs.readFile(uri); 17 | 18 | const documentParser = createDocumentParser(bufferArray); 19 | if (!documentParser) { 20 | createInfoNotification('failed to readed buffer'); 21 | return; 22 | } 23 | const cfgMap = new Map(); 24 | cfgMap.set('path', uri.path); 25 | cfgMap.set('extension', documentParser.extension); 26 | cfgMap.set('mimetype', documentParser.mimetype); 27 | cfgMap.set('length', documentParser?.contents?.text?.length.toString() ?? '0'); 28 | createInfoNotification(Object.fromEntries(cfgMap), 'file_information'); 29 | 30 | if (!documentParser?.contents?.text) {return;} 31 | 32 | const embeddingText = await getEmbeddingsForText(documentParser.contents.text); 33 | createDebugNotification( 34 | `embedding-controller embedding ${embeddingText.length} (chunks)` 35 | ); 36 | 37 | const fileObject: IEmbeddingFileLite = { 38 | timestamp: new Date().getTime(), 39 | embeddingId: uuidv4(), 40 | name: decodeURIComponent(uri.path).substring( 41 | decodeURIComponent(uri.path).lastIndexOf('/') + 1 42 | ), 43 | url: decodeURIComponent(uri.path), 44 | type: documentParser.mimetype, 45 | size: documentParser.contents.text.length, 46 | expanded: false, 47 | chunks: embeddingText, 48 | extractedText: documentParser.contents.text, 49 | }; 50 | StatusBarServiceProvider.instance.showStatusBarInformation( 51 | 'vscode-openai', 52 | '' 53 | ); 54 | return fileObject; 55 | } 56 | -------------------------------------------------------------------------------- /src/apis/embedding/index.ts: -------------------------------------------------------------------------------- 1 | export { embeddingResource } from './content/embeddingResource'; 2 | export { chunkText } from './chunk/chunkText'; 3 | export { getEmbeddingsForText } from './chunk/getEmbeddingsForText'; 4 | export { searchFileChunks } from './chunk/searchFileChunks'; 5 | -------------------------------------------------------------------------------- /src/apis/git/IRepositoryChangeCallback.ts: -------------------------------------------------------------------------------- 1 | export type IRepositoryChangeCallback = (repositoryInfo: { 2 | numberOfRepositories: number 3 | selectedRepositoryPath: string 4 | availableRepositories: string[] 5 | }) => void; -------------------------------------------------------------------------------- /src/apis/git/IRepositoryInfo.ts: -------------------------------------------------------------------------------- 1 | export interface IRepositoryInfo { 2 | numberOfRepositories: number 3 | selectedRepositoryPath: string 4 | availableRepositories: string[] 5 | } -------------------------------------------------------------------------------- /src/apis/git/getComments.ts: -------------------------------------------------------------------------------- 1 | import { IChatCompletion, IConversation } from '@app/interfaces'; 2 | import { getSystemPersonas } from '@app/models'; 3 | import { ConversationStorageService } from '@app/services'; 4 | import { createChatCompletionMessage } from '@app/apis/openai'; 5 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 6 | import { workspace } from 'vscode'; 7 | import { 8 | ChatCompletionConfig, 9 | ChatCompletionModelType, 10 | } from '@app/services/configuration'; 11 | 12 | // This function generates comments for the given git differences using OpenAI's chatbot API. 13 | // It takes a string representing the git differences as input and returns a Promise that resolves to a string. 14 | export const getComments = async (diff: string): Promise => { 15 | try { 16 | const persona = getSystemPersonas().find( 17 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.DEVELOPER 18 | ); 19 | 20 | if (!persona) { 21 | throw new Error('Developer persona not found.'); 22 | } 23 | const conversation: IConversation = 24 | await ConversationStorageService.instance.create(persona); 25 | 26 | const personaPrompt = workspace 27 | .getConfiguration('vscode-openai') 28 | .get(`prompts.persona.scm`) as string; 29 | 30 | const prompt = `${personaPrompt}\n${diff}`; 31 | 32 | const chatCompletion: IChatCompletion = { 33 | content: prompt, 34 | author: 'vscode-openai-editor', 35 | timestamp: new Date().toLocaleString(), 36 | mine: false, 37 | completionTokens: 0, 38 | promptTokens: 0, 39 | totalTokens: 0, 40 | }; 41 | conversation.chatMessages.push(chatCompletion); 42 | 43 | const cfg = ChatCompletionConfig.create(ChatCompletionModelType.SCM); 44 | 45 | let result = ''; 46 | function messageCallback(_type: string, data: IChatCompletion): void { 47 | result = data.content; 48 | } 49 | 50 | await createChatCompletionMessage(conversation, cfg, messageCallback); 51 | return result ?? ''; 52 | } catch (error) { 53 | console.error('Failed to generate comments:', error); 54 | return ''; // Return empty string in case of error 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/apis/git/getGitDifferences.ts: -------------------------------------------------------------------------------- 1 | import { GitService } from '.'; 2 | 3 | // This function retrieves the git differences for the selected repository and returns them as a string. 4 | // It takes a GitService object as input and returns a Promise that resolves to a string or undefined. 5 | export const getGitDifferences = async ( 6 | git: GitService 7 | ): Promise => { 8 | const repo = git.getSelectedRepository(); 9 | let diff = await repo?.diff(true); 10 | if (!diff) {diff = await repo?.diff(false);} 11 | return diff; 12 | }; 13 | -------------------------------------------------------------------------------- /src/apis/git/index.ts: -------------------------------------------------------------------------------- 1 | export { default as GitService } from './gitService'; 2 | export { IRepositoryChangeCallback } from './IRepositoryChangeCallback'; 3 | export { IRepositoryInfo } from './IRepositoryInfo'; 4 | export { getComments } from './getComments'; 5 | export { getGitDifferences } from './getGitDifferences'; 6 | -------------------------------------------------------------------------------- /src/apis/node/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createErrorNotification, 3 | createWarningNotification, 4 | createInfoNotification, 5 | createDebugNotification, 6 | } from './notificationManager'; 7 | export { waitFor } from './waitFor'; 8 | export { HttpRequest } from './httpClient'; 9 | export { noop } from './noop'; 10 | -------------------------------------------------------------------------------- /src/apis/node/noop.ts: -------------------------------------------------------------------------------- 1 | export const noop = () => { 2 | /* no-operation */ 3 | }; 4 | -------------------------------------------------------------------------------- /src/apis/node/notificationManager.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TelemetryService, 3 | logDebug, 4 | logError, 5 | logInfo, 6 | logWarning, 7 | } from '@app/apis/vscode'; 8 | 9 | function ensureError(value: unknown): Error { 10 | if (value instanceof Error) {return value;} 11 | 12 | let stringified = '[Unable to stringify the thrown value]'; 13 | try { 14 | stringified = JSON.stringify(value); 15 | } catch { 16 | /* empty */ 17 | } 18 | const error = new Error( 19 | `This value was thrown as is, not through an Error: ${stringified}` 20 | ); 21 | return error; 22 | } 23 | 24 | export function createErrorNotification(value: unknown) { 25 | const error = ensureError(value); 26 | logError(error); 27 | TelemetryService.instance.sendTelemetryErrorEvent(error); 28 | } 29 | 30 | export function createWarningNotification(value: string) { 31 | logWarning(value); 32 | } 33 | 34 | export function createInfoNotification( 35 | value: string | { [key: string]: string }, 36 | eventName = 'event' 37 | ) { 38 | logInfo(value, eventName); 39 | TelemetryService.instance.sendTelemetryEvent(value, eventName); 40 | } 41 | 42 | export function createDebugNotification( 43 | value: string | { [key: string]: string }, 44 | eventName = 'event' 45 | ) { 46 | logDebug(value, eventName); 47 | } 48 | -------------------------------------------------------------------------------- /src/apis/node/waitFor.ts: -------------------------------------------------------------------------------- 1 | export const waitFor = async ( 2 | timeout: number, 3 | condition: () => boolean 4 | ): Promise => { 5 | while (!condition() && timeout > 0) { 6 | timeout -= 100; 7 | await delay(100); 8 | } 9 | 10 | return timeout > 0; 11 | }; 12 | 13 | function delay(ms: number) { 14 | return new Promise((resolve) => setTimeout(resolve, ms)); 15 | } 16 | -------------------------------------------------------------------------------- /src/apis/openai/api/apiKey.ts: -------------------------------------------------------------------------------- 1 | import { SettingConfig as settingCfg, featureVerifyApiKey } from '@app/services'; 2 | import { StatusBarServiceProvider, setFeatureFlag } from '@app/apis/vscode'; 3 | import { VSCODE_OPENAI_EXTENSION } from '@app/constants'; 4 | import { createErrorNotification, createInfoNotification } from '@app/apis/node'; 5 | import { createOpenAI, errorHandler } from '@app/apis/openai'; 6 | 7 | export async function validateApiKey() { 8 | try { 9 | await verifyApiKey(); 10 | } catch (error) { 11 | createErrorNotification(error); 12 | } 13 | } 14 | 15 | export async function verifyApiKey(): Promise { 16 | try { 17 | if (!featureVerifyApiKey()) {return true;} 18 | 19 | StatusBarServiceProvider.instance.showStatusBarInformation( 20 | 'loading~spin', 21 | '- verify authentication' 22 | ); 23 | 24 | const openai = await createOpenAI(settingCfg.baseUrl); 25 | await openai.models.list(await settingCfg.getRequestConfig()); 26 | 27 | setFeatureFlag(VSCODE_OPENAI_EXTENSION.ENABLED_COMMAND_ID, true); 28 | createInfoNotification('verifyApiKey success'); 29 | StatusBarServiceProvider.instance.showStatusBarInformation( 30 | 'vscode-openai', 31 | '' 32 | ); 33 | return true; 34 | } catch (error: any) { 35 | errorHandler(error); 36 | setFeatureFlag(VSCODE_OPENAI_EXTENSION.ENABLED_COMMAND_ID, false); 37 | } 38 | return false; 39 | } 40 | -------------------------------------------------------------------------------- /src/apis/openai/api/chatCompletion/chatCompletionCallback.ts: -------------------------------------------------------------------------------- 1 | import { IChatCompletion } from '@app/interfaces'; 2 | 3 | export type ChatCompletionStreamCallback = (type: string, data: string) => void; 4 | 5 | export type ChatCompletionMessageCallback = ( 6 | type: string, 7 | data: IChatCompletion 8 | ) => void; 9 | -------------------------------------------------------------------------------- /src/apis/openai/api/chatCompletionMessages/chatCompletionRequestMessageStandard.ts: -------------------------------------------------------------------------------- 1 | import { OpenAI } from 'openai'; 2 | import { ConversationConfig as convCfg } from '@app/services'; 3 | import { IConversation } from '@app/interfaces'; 4 | import { isSystemRoleAllowed } from './isSystemRoleAllowed'; 5 | 6 | export async function ChatCompletionRequestMessageStandard( 7 | conversation: IConversation 8 | ): Promise> { 9 | // Create a deep copy of the conversation object and assert its type 10 | const conversationCopy = JSON.parse( 11 | JSON.stringify(conversation) 12 | ) as IConversation; 13 | 14 | const chatCompletion: Array = []; 15 | 16 | if (await isSystemRoleAllowed()) { 17 | const content = [`${conversationCopy.persona.prompt.system}`].join('\n'); 18 | chatCompletion.push({ 19 | role: 'system', 20 | content: content, 21 | }); 22 | } 23 | 24 | const conversationHistory = forceToOdd(convCfg.conversationHistory); 25 | 26 | let isUserRole = true; 27 | // Use the copied conversation object 28 | conversationCopy.chatMessages 29 | .splice(conversationHistory * -1) 30 | .forEach((chatMessage) => { 31 | chatCompletion.push({ 32 | role: isUserRole ? 'user' : 'assistant', 33 | content: chatMessage.content, 34 | }); 35 | isUserRole = !isUserRole; 36 | }); 37 | 38 | return chatCompletion; 39 | } 40 | 41 | // Some model require the first role to be user and the last. Therefore 42 | // breaking some alter roles for even number of history items. Because 43 | // we always have a system role in the request... 44 | function forceToOdd(value: number): number { 45 | if (value % 2 === 0) { 46 | return value - 1; 47 | } else { 48 | return value; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/apis/openai/api/chatCompletionMessages/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ChatCompletionCreateParamsStreaming, 3 | ChatCompletionCreateParamsNonStreaming, 4 | } from 'openai/resources/chat/completions'; 5 | export { LogChatCompletion } from './logChatCompletion'; 6 | export { ChatCompletionRequestMessageStandard } from './chatCompletionRequestMessageStandard'; 7 | export { ChatCompletionRequestMessageEmbedding } from './chatCompletionRequestMessageEmbedding'; 8 | -------------------------------------------------------------------------------- /src/apis/openai/api/chatCompletionMessages/isSystemRoleAllowed.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChatCompletionConfig, 3 | ChatCompletionModelType, 4 | } from '@app/services/configuration'; 5 | 6 | export async function isSystemRoleAllowed(): Promise { 7 | const cfg = ChatCompletionConfig.create(ChatCompletionModelType.INFERENCE); 8 | 9 | return !cfg.model.startsWith('o1'); 10 | } 11 | -------------------------------------------------------------------------------- /src/apis/openai/api/chatCompletionMessages/logChatCompletion.ts: -------------------------------------------------------------------------------- 1 | import { SettingConfig as settingCfg } from '@app/services'; 2 | import { IMessage } from '@app/interfaces'; 3 | import { createErrorNotification, createInfoNotification } from '@app/apis/node'; 4 | 5 | let sessionToken = 0; 6 | export const LogChatCompletion = (message: IMessage) => { 7 | try { 8 | sessionToken = sessionToken + message.totalTokens; 9 | 10 | const infoMap = new Map(); 11 | infoMap.set('service_provider', settingCfg.serviceProvider); 12 | infoMap.set('default_model', settingCfg.defaultModel); 13 | infoMap.set('tokens_prompt', message.promptTokens.toString()); 14 | infoMap.set('tokens_completion', message.completionTokens.toString()); 15 | infoMap.set('tokens_total', message.totalTokens.toString()); 16 | infoMap.set('tokens_session', sessionToken.toString()); 17 | 18 | createInfoNotification(Object.fromEntries(infoMap), 'chat-completion'); 19 | } catch (error) { 20 | createErrorNotification(error); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/apis/openai/api/createEmbedding.ts: -------------------------------------------------------------------------------- 1 | import { StatusBarServiceProvider } from '@app/apis/vscode'; 2 | import { SettingConfig as settingCfg } from '@app/services'; 3 | import { createOpenAI, errorHandler } from '@app/apis/openai'; 4 | 5 | type EmbeddingOptions = { 6 | input: string | string[] 7 | itemCount: number 8 | batchLength: number 9 | }; 10 | 11 | export async function createEmbedding({ 12 | input, 13 | itemCount, 14 | batchLength, 15 | }: EmbeddingOptions): Promise { 16 | try { 17 | const model = settingCfg.embeddingModel; 18 | const openai = await createOpenAI(settingCfg.embeddingUrl); 19 | const requestConfig = await settingCfg.getRequestConfig(); 20 | 21 | const results = await openai.embeddings.create( 22 | { 23 | model, 24 | input, 25 | }, 26 | requestConfig 27 | ); 28 | 29 | StatusBarServiceProvider.instance.showStatusBarInformation( 30 | 'sync~spin', 31 | `- embedding chunk [${itemCount}/${batchLength}]` 32 | ); 33 | 34 | if (!results.data[0].embedding) { 35 | throw new Error('No embedding returned from the completions endpoint'); 36 | } 37 | return results.data.map((d) => d.embedding); 38 | } catch (error: any) { 39 | errorHandler(error); 40 | } 41 | return undefined; 42 | } 43 | -------------------------------------------------------------------------------- /src/apis/openai/api/createOpenAI.ts: -------------------------------------------------------------------------------- 1 | import OpenAI, { AzureOpenAI } from 'openai'; 2 | import { 3 | ConversationConfig as convCfg, 4 | SettingConfig as settingCfg, 5 | } from '@app/services'; 6 | 7 | export async function createOpenAI( 8 | baseURL: string, 9 | serviceProvider?: string, 10 | apiKey?: string 11 | ): Promise { 12 | const azureApiVersion = settingCfg.azureApiVersion; 13 | 14 | apiKey = apiKey ?? (await settingCfg.getApiKey()); 15 | serviceProvider = serviceProvider ?? settingCfg.serviceProvider; 16 | 17 | if (serviceProvider === 'Azure-OpenAI') { 18 | if (apiKey.startsWith('Bearer ')) { 19 | const token = apiKey.replace('Bearer ', ''); 20 | return new AzureOpenAI({ 21 | azureADTokenProvider: () => Promise.resolve(token), 22 | apiVersion: azureApiVersion, 23 | defaultHeaders: { Authorization: apiKey }, 24 | baseURL: baseURL, 25 | maxRetries: convCfg.numOfAttempts, 26 | }); 27 | } 28 | 29 | return new AzureOpenAI({ 30 | apiKey: apiKey, 31 | apiVersion: azureApiVersion, 32 | baseURL: baseURL, 33 | maxRetries: convCfg.numOfAttempts, 34 | }); 35 | } 36 | 37 | return new OpenAI({ 38 | apiKey: apiKey, 39 | defaultQuery: { 'api-version': azureApiVersion }, 40 | defaultHeaders: { 'api-key': apiKey }, 41 | baseURL: baseURL, 42 | maxRetries: convCfg.numOfAttempts, 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /src/apis/openai/api/models/listModelsOpenAI.ts: -------------------------------------------------------------------------------- 1 | import { SettingConfig as settingCfg } from '@app/services'; 2 | import { createOpenAI, errorHandler, ModelCapability } from '@app/apis/openai'; 3 | 4 | export async function listModelsOpenAI( 5 | apiKey: string, 6 | baseUrl: string, 7 | modelCapabiliy: ModelCapability 8 | ): Promise> { 9 | const models = new Array(); 10 | try { 11 | const openai = await createOpenAI(baseUrl, 'OpenAI', apiKey); 12 | const headers = settingCfg.apiHeaders; 13 | const response = await openai.models.list({ 14 | headers: { ...headers }, 15 | }); 16 | 17 | const isChatCompletionModel = (model: any): boolean => { 18 | return ( 19 | modelCapabiliy === ModelCapability.ChatCompletion && 20 | (model.id.includes('gpt') || model.id.startsWith('o')) 21 | ); 22 | }; 23 | 24 | const isEmbeddingModel = (model: any): boolean => { 25 | return ( 26 | modelCapabiliy === ModelCapability.Embedding && 27 | model.id.includes('embedding') 28 | ); 29 | }; 30 | 31 | response.data.forEach((model: any) => { 32 | if (isChatCompletionModel(model) || isEmbeddingModel(model)) { 33 | models.push(model.id); 34 | } 35 | }); 36 | 37 | return models.sort((a, b) => b.localeCompare(a)); 38 | } catch (error: any) { 39 | errorHandler(error); 40 | } 41 | return models; 42 | } 43 | -------------------------------------------------------------------------------- /src/apis/openai/api/models/modelCapabiliy.ts: -------------------------------------------------------------------------------- 1 | export enum ModelCapability { 2 | ChatCompletion, 3 | Embedding, 4 | } 5 | -------------------------------------------------------------------------------- /src/apis/openai/index.ts: -------------------------------------------------------------------------------- 1 | export { PromptFactory } from './prompt/promptFactory'; 2 | 3 | export { BountyPromptFactory } from './prompt/bountyPrompt'; 4 | export { CommentPromptFactory } from './prompt/commentPrompt'; 5 | export { ExplainPromptFactory } from './prompt/explainPrompt'; 6 | export { OptimizePromptFactory } from './prompt/optimizePrompt'; 7 | export { PatternPromptFactory } from './prompt/patternPrompt'; 8 | 9 | export { validateApiKey, verifyApiKey } from './api/apiKey'; 10 | 11 | export { createChatCompletionMessage } from './api/chatCompletion/createChatCompletionMessage'; 12 | export { createChatCompletionStream } from './api/chatCompletion/createChatCompletionStream'; 13 | export { 14 | ChatCompletionStreamCallback, 15 | ChatCompletionMessageCallback, 16 | } from './api/chatCompletion/chatCompletionCallback'; 17 | 18 | export { createEmbedding } from './api/createEmbedding'; 19 | 20 | export { ModelCapability } from './api/models/modelCapabiliy'; 21 | export { listModelsAzureOpenAI } from './api/models/listModelsAzureOpenAI'; 22 | export { listModelsOpenAI } from './api/models/listModelsOpenAI'; 23 | export { createOpenAI } from './api/createOpenAI'; 24 | export { errorHandler } from './api/errorHandler'; 25 | -------------------------------------------------------------------------------- /src/apis/openai/prompt/bountyPrompt.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { PromptFactory } from './promptFactory'; 3 | import { 4 | getActiveTextEditorValue, 5 | getActiveTextLanguageId, 6 | } from '@app/apis/vscode'; 7 | 8 | async function bountyPrompt(): Promise { 9 | const language = getActiveTextLanguageId(); 10 | const inputCode = getActiveTextEditorValue(); 11 | 12 | let prompt = workspace 13 | .getConfiguration('vscode-openai') 14 | .get('prompt-editor.bounty') as string; 15 | 16 | prompt = prompt.split('#{language}').join(language); 17 | prompt = prompt.split('#{source_code}').join(inputCode); 18 | return prompt; 19 | } 20 | 21 | // Define concrete prompt factories for each type of prompt 22 | export class BountyPromptFactory implements PromptFactory { 23 | createPrompt(): () => Promise { 24 | return bountyPrompt; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/apis/openai/prompt/commentPrompt.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { PromptFactory } from './promptFactory'; 3 | import { 4 | getActiveTextEditorValue, 5 | getActiveTextLanguageId, 6 | } from '@app/apis/vscode'; 7 | 8 | async function commentPrompt(): Promise { 9 | const language = getActiveTextLanguageId(); 10 | const inputCode = getActiveTextEditorValue(); 11 | 12 | let prompt = workspace 13 | .getConfiguration('vscode-openai') 14 | .get('prompt-editor.comment') as string; 15 | 16 | prompt = prompt.split('#{language}').join(language); 17 | prompt = prompt.split('#{source_code}').join(inputCode); 18 | return prompt; 19 | } 20 | 21 | // Define concrete prompt factories for each type of prompt 22 | export class CommentPromptFactory implements PromptFactory { 23 | createPrompt(): () => Promise { 24 | return commentPrompt; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/apis/openai/prompt/explainPrompt.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { PromptFactory } from './promptFactory'; 3 | import { 4 | getActiveTextEditorValue, 5 | getActiveTextLanguageId, 6 | } from '@app/apis/vscode'; 7 | 8 | async function explainPrompt(): Promise { 9 | const language = getActiveTextLanguageId(); 10 | const inputCode = getActiveTextEditorValue(); 11 | 12 | let prompt = workspace 13 | .getConfiguration('vscode-openai') 14 | .get('prompt-editor.explain') as string; 15 | 16 | prompt = prompt.split('#{language}').join(language); 17 | prompt = prompt.split('#{source_code}').join(inputCode); 18 | return prompt; 19 | } 20 | 21 | export class ExplainPromptFactory implements PromptFactory { 22 | createPrompt(): () => Promise { 23 | return explainPrompt; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/apis/openai/prompt/optimizePrompt.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { PromptFactory } from './promptFactory'; 3 | import { 4 | getActiveTextEditorValue, 5 | getActiveTextLanguageId, 6 | } from '@app/apis/vscode'; 7 | 8 | export async function optimizePrompt(): Promise { 9 | const language = getActiveTextLanguageId(); 10 | const inputCode = getActiveTextEditorValue(); 11 | 12 | let prompt = workspace 13 | .getConfiguration('vscode-openai') 14 | .get('prompt-editor.optimize') as string; 15 | 16 | prompt = prompt.split('#{language}').join(language); 17 | prompt = prompt.split('#{source_code}').join(inputCode); 18 | return prompt; 19 | } 20 | 21 | // Define concrete prompt factories for each type of prompt 22 | export class OptimizePromptFactory implements PromptFactory { 23 | createPrompt(): () => Promise { 24 | return optimizePrompt; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/apis/openai/prompt/patternPrompt.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { PromptFactory } from './promptFactory'; 3 | import { 4 | getActiveTextEditorValue, 5 | getActiveTextLanguageId, 6 | } from '@app/apis/vscode'; 7 | 8 | export async function patternPrompt(): Promise { 9 | const language = getActiveTextLanguageId(); 10 | const inputCode = getActiveTextEditorValue(); 11 | 12 | let prompt = workspace 13 | .getConfiguration('vscode-openai') 14 | .get('prompt-editor.patterns') as string; 15 | 16 | prompt = prompt.split('#{language}').join(language); 17 | prompt = prompt.split('#{source_code}').join(inputCode); 18 | return prompt; 19 | } 20 | 21 | // Define concrete prompt factories for each type of prompt 22 | export class PatternPromptFactory implements PromptFactory { 23 | createPrompt(): () => Promise { 24 | return patternPrompt; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/apis/openai/prompt/promptFactory.ts: -------------------------------------------------------------------------------- 1 | // Define a factory interface for creating prompts 2 | export interface PromptFactory { 3 | createPrompt(): () => Promise 4 | } 5 | -------------------------------------------------------------------------------- /src/apis/vscode/authentication/getAzureOpenAIAccessToken.ts: -------------------------------------------------------------------------------- 1 | import { authentication } from 'vscode'; 2 | 3 | export async function getAzureOpenAIAccessToken(): Promise { 4 | const msSession = await authentication.getSession( 5 | 'microsoft', 6 | ['https://cognitiveservices.azure.com/.default', 'offline_access'], 7 | { createIfNone: true } 8 | ); 9 | return msSession.accessToken; 10 | } 11 | -------------------------------------------------------------------------------- /src/apis/vscode/authentication/getAzureOpenAIAccessTokenSovereignUs.ts: -------------------------------------------------------------------------------- 1 | import { authentication } from 'vscode'; 2 | 3 | export async function getAzureOpenAIAccessTokenSovereignUs(): Promise { 4 | const msSession = await authentication.getSession( 5 | 'microsoft-sovereign-cloud', 6 | ['https://cognitiveservices.azure.us/.default', 'offline_access'], 7 | { createIfNone: true } 8 | ); 9 | return msSession.accessToken; 10 | } 11 | -------------------------------------------------------------------------------- /src/apis/vscode/authentication/getGitAccessToken.ts: -------------------------------------------------------------------------------- 1 | import { authentication } from 'vscode'; 2 | 3 | export async function getGitAccessToken(): Promise { 4 | const gitSession = await authentication.getSession('github', ['user:email'], { 5 | createIfNone: true, 6 | }); 7 | return gitSession.accessToken; 8 | } 9 | -------------------------------------------------------------------------------- /src/apis/vscode/authentication/getVscodeOpenAccessToken.ts: -------------------------------------------------------------------------------- 1 | import { getGitAccessToken } from '@app/apis/vscode'; 2 | import { HttpRequest, createErrorNotification } from '@app/apis/node'; 3 | 4 | export async function getVscodeOpenAccessToken(): Promise { 5 | try { 6 | const accessToken = await getGitAccessToken(); 7 | const request = new HttpRequest( 8 | 'GET', 9 | `Bearer ${accessToken}`, 10 | 'https://api.arbs.io/openai/oauth2/token' 11 | ); 12 | const resp = await request.send(); 13 | return resp.token as string; 14 | } catch (error) { 15 | createErrorNotification(error); 16 | return undefined; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/apis/vscode/editorServices/getActiveTextEditorValue.ts: -------------------------------------------------------------------------------- 1 | import { Range, window } from 'vscode'; 2 | 3 | export function getActiveTextEditorValue(): string { 4 | const editor = window.activeTextEditor; 5 | let value = ''; 6 | if (editor) { 7 | const selection = editor.selection; 8 | if (selection && !selection.isEmpty) { 9 | const selectionRange = new Range( 10 | selection.start.line, 11 | selection.start.character, 12 | selection.end.line, 13 | selection.end.character 14 | ); 15 | value = editor.document.getText(selectionRange); 16 | } else { 17 | value = editor.document.getText(); 18 | } 19 | } 20 | return value; 21 | } -------------------------------------------------------------------------------- /src/apis/vscode/editorServices/getActiveTextLanguageId.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | export function getActiveTextLanguageId(): string { 4 | const languageId = window.activeTextEditor?.document.languageId; 5 | return languageId ?? ''; 6 | } 7 | -------------------------------------------------------------------------------- /src/apis/vscode/editorServices/insertActiveTextEditorValue.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | 3 | export async function insertActiveTextEditorValue(value: string) { 4 | const editor = window.activeTextEditor; 5 | await editor?.edit((editBuilder) => { 6 | const activePos = editor.selection.active; 7 | editBuilder.insert(activePos, value); 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /src/apis/vscode/featureFlag/featureFlag.ts: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | 3 | const featureMap = new Map([]); 4 | /** 5 | * Sets a feature flag in the Visual Studio Code context. 6 | * 7 | * @param context - The name of the feature flag to set. 8 | * @param status - The boolean value representing the state of the feature 9 | * flag (true for enabled, false for disabled). 10 | */ 11 | export const setFeatureFlag = (context: string, status: boolean): void => { 12 | featureMap.set(context, status); 13 | commands.executeCommand('setContext', context, status); 14 | }; 15 | 16 | export const getFeatureFlag = (context: string): boolean => { 17 | const featureEnabled = featureMap.get(context); 18 | return featureEnabled ?? false; 19 | }; 20 | -------------------------------------------------------------------------------- /src/apis/vscode/index.ts: -------------------------------------------------------------------------------- 1 | export { getActiveTextEditorValue } from './editorServices/getActiveTextEditorValue'; 2 | export { getActiveTextLanguageId } from './editorServices/getActiveTextLanguageId'; 3 | export { insertActiveTextEditorValue } from './editorServices/insertActiveTextEditorValue'; 4 | 5 | export { getNonce } from './webviewServices/getNonce'; 6 | export { getUri } from './webviewServices/getUri'; 7 | 8 | export { default as GlobalStorageService } from './storageServices/globalStateService'; 9 | export { default as SecretStorageService } from './storageServices/secretStorageService'; 10 | 11 | export { default as StatusBarServiceProvider } from './statusBarItem/StatusBarServiceProvider'; 12 | 13 | export { showMessageWithTimeout } from './showMessage/showMessageWithTimeout'; 14 | 15 | export { MultiStepInput } from './multiStepInput/multiStepInput'; 16 | 17 | export { getAzureOpenAIAccessToken } from './authentication/getAzureOpenAIAccessToken'; 18 | export { getAzureOpenAIAccessTokenSovereignUs } from './authentication/getAzureOpenAIAccessTokenSovereignUs'; 19 | export { getGitAccessToken } from './authentication/getGitAccessToken'; 20 | export { getVscodeOpenAccessToken } from './authentication/getVscodeOpenAccessToken'; 21 | 22 | export * from './outputChannel/outputChannel'; 23 | export { default as TelemetryService } from './outputChannel/telemetryService'; 24 | 25 | export { getFeatureFlag, setFeatureFlag } from './featureFlag/featureFlag'; 26 | 27 | -------------------------------------------------------------------------------- /src/apis/vscode/multiStepInput/InputFlowAction.ts: -------------------------------------------------------------------------------- 1 | export class InputFlowAction { 2 | static readonly back = new InputFlowAction(); 3 | static readonly cancel = new InputFlowAction(); 4 | static readonly resume = new InputFlowAction(); 5 | } 6 | -------------------------------------------------------------------------------- /src/apis/vscode/multiStepInput/interfaces/IParametersInputBox.ts: -------------------------------------------------------------------------------- 1 | import { QuickInputButton } from 'vscode' 2 | 3 | export interface IParametersInputBox { 4 | title: string 5 | step: number 6 | totalSteps: number 7 | value: string 8 | valueSelection?: [number, number] 9 | prompt: string 10 | validate: (value: string) => Promise 11 | buttons?: QuickInputButton[] 12 | ignoreFocusOut?: boolean 13 | placeholder?: string 14 | shouldResume: () => Thenable 15 | } 16 | -------------------------------------------------------------------------------- /src/apis/vscode/multiStepInput/interfaces/IParametersQuickPick.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem, QuickInputButton } from 'vscode'; 2 | 3 | export interface IParametersQuickPick { 4 | title: string 5 | step: number 6 | totalSteps: number 7 | items: T[] 8 | activeItem?: T 9 | ignoreFocusOut?: boolean 10 | placeholder: string 11 | buttons?: QuickInputButton[] 12 | shouldResume: () => Thenable 13 | } 14 | -------------------------------------------------------------------------------- /src/apis/vscode/multiStepInput/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { IParametersInputBox } from './IParametersInputBox'; 2 | export { IParametersQuickPick } from './IParametersQuickPick'; 3 | -------------------------------------------------------------------------------- /src/apis/vscode/outputChannel/telemetryService.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import TelemetryReporter from '@vscode/extension-telemetry'; 3 | import { VSCODE_OPENAI_EXTENSION } from '@app/constants'; 4 | 5 | export default class TelemetryService { 6 | private static _instance: TelemetryService; 7 | 8 | constructor(private telemetryReporter: TelemetryReporter) {} 9 | 10 | static init(context: ExtensionContext): void { 11 | const telemetryReporter = new TelemetryReporter( 12 | VSCODE_OPENAI_EXTENSION.CONNECTION_STRING 13 | ); 14 | TelemetryService._instance = new TelemetryService(telemetryReporter); 15 | context.subscriptions.push(TelemetryService._instance.telemetryReporter); 16 | } 17 | 18 | static get instance(): TelemetryService { 19 | return TelemetryService._instance; 20 | } 21 | 22 | async sendTelemetryEvent( 23 | value: string | { [key: string]: string }, 24 | eventName: string 25 | ): Promise { 26 | const isString = typeof value === 'string' && value !== null; 27 | if (isString) { 28 | this.telemetryReporter.sendTelemetryEvent(eventName, { 29 | message: value, 30 | }); 31 | } else { 32 | this.telemetryReporter.sendTelemetryEvent(eventName, value); 33 | } 34 | } 35 | 36 | async sendTelemetryErrorEvent(error: Error): Promise { 37 | this.telemetryReporter.sendTelemetryErrorEvent('Error', { 38 | message: error?.message, 39 | stack: error.stack!, 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/apis/vscode/showMessage/showMessageWithTimeout.ts: -------------------------------------------------------------------------------- 1 | import { ProgressLocation, window } from 'vscode'; 2 | import { waitFor } from '@app/apis/node'; 3 | 4 | export const showMessageWithTimeout = ( 5 | message: string, 6 | timeout = 3000 7 | ): void => { 8 | window.withProgress( 9 | { 10 | location: ProgressLocation.Notification, 11 | title: message, 12 | cancellable: false, 13 | }, 14 | 15 | async (progress): Promise => { 16 | await waitFor(timeout, () => { 17 | return false; 18 | }); 19 | progress.report({ increment: 100 }); 20 | } 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/apis/vscode/storageServices/globalStateService.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, Memento } from 'vscode'; 2 | import { createErrorNotification } from '@app/apis/node'; 3 | 4 | export default class GlobalStorageService { 5 | private static _instance: GlobalStorageService; 6 | 7 | constructor(private storage: Memento) {} 8 | 9 | static init(context: ExtensionContext): void { 10 | try { 11 | GlobalStorageService._instance = new GlobalStorageService( 12 | context.globalState 13 | ); 14 | } catch (error) { 15 | createErrorNotification(error); 16 | } 17 | } 18 | 19 | static get instance(): GlobalStorageService { 20 | return GlobalStorageService._instance; 21 | } 22 | 23 | public getValue(key: string): T { 24 | return this.storage.get(key, (undefined)); 25 | } 26 | 27 | public setValue(key: string, value: T) { 28 | this.storage.update(key, value); 29 | } 30 | 31 | public deleteKey(key: string) { 32 | this.storage.update(key, undefined); 33 | } 34 | 35 | public keys(): readonly string[] { 36 | return this.storage.keys(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/apis/vscode/storageServices/localStorageService.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, Memento } from 'vscode'; 2 | 3 | export default class LocalStorageService { 4 | private static _instance: LocalStorageService; 5 | 6 | constructor(private storage: Memento) {} 7 | 8 | static init(context: ExtensionContext): void { 9 | LocalStorageService._instance = new LocalStorageService( 10 | context.workspaceState 11 | ); 12 | } 13 | 14 | static get instance(): LocalStorageService { 15 | return LocalStorageService._instance; 16 | } 17 | 18 | public getValue(key: string): T { 19 | return this.storage.get(key, (undefined)); 20 | } 21 | 22 | public setValue(key: string, value: T) { 23 | this.storage.update(key, value); 24 | 25 | } 26 | 27 | public deleteKey(key: string) { 28 | this.storage.update(key, undefined); 29 | } 30 | 31 | public keys(): readonly string[] { 32 | return this.storage.keys(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/apis/vscode/webviewServices/getNonce.ts: -------------------------------------------------------------------------------- 1 | import CryptoJS from 'crypto-js'; 2 | 3 | /** 4 | * A helper function that returns a unique alphanumeric identifier called a nonce. 5 | * 6 | * @remarks This function is primarily used to help enforce content security 7 | * policies for resources/scripts being executed in a webview context. 8 | * 9 | * @returns A nonce 10 | */ 11 | export function getNonce(): string { 12 | const nonce = CryptoJS.lib.WordArray.random(32).toString(); 13 | return nonce; 14 | } 15 | -------------------------------------------------------------------------------- /src/apis/vscode/webviewServices/getUri.ts: -------------------------------------------------------------------------------- 1 | import { Uri, Webview } from 'vscode'; 2 | 3 | /** 4 | * A helper function which will get the webview URI of a given file or resource. 5 | * 6 | * @remarks This URI can be used within a webview's HTML as a link to the 7 | * given file/resource. 8 | * 9 | * @param webview A reference to the extension webview 10 | * @param extensionUri The URI of the directory containing the extension 11 | * @param pathList An array of strings representing the path to a file/resource 12 | * @returns A URI pointing to the file/resource 13 | */ 14 | export function getUri(webview: Webview, extensionUri: Uri, pathList: string[]) { 15 | return webview.asWebviewUri(Uri.joinPath(extensionUri, ...pathList)); 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/ICommand.ts: -------------------------------------------------------------------------------- 1 | export interface ICommand { 2 | readonly id: string 3 | 4 | execute(...args: any[]): void | Promise 5 | } 6 | -------------------------------------------------------------------------------- /src/commands/commandManager.ts: -------------------------------------------------------------------------------- 1 | import { commands, Disposable } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | 4 | export class CommandManager { 5 | private readonly _commands = new Map(); 6 | 7 | public dispose() { 8 | for (const registration of this._commands.values()) { 9 | registration.dispose(); 10 | } 11 | this._commands.clear(); 12 | } 13 | 14 | public register(command: T): Disposable { 15 | this._registerCommand(command.id, command.execute, command); 16 | return new Disposable(() => { 17 | this._commands.delete(command.id); 18 | }); 19 | } 20 | 21 | private _registerCommand( 22 | id: string, 23 | impl: (...args: any[]) => void, 24 | thisArg?: any 25 | ) { 26 | if (this._commands.has(id)) { 27 | return; 28 | } 29 | 30 | this._commands.set(id, commands.registerCommand(id, impl, thisArg)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/configuration/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ConfigurationShowQuickpick } from './showQuickpick'; 2 | -------------------------------------------------------------------------------- /src/commands/configuration/showQuickpick.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { ConfigurationQuickPickProvider } from '@app/providers'; 4 | 5 | export default class SettingsCommand implements ICommand { 6 | public readonly id = 'vscode-openai.configuration.show.quickpick'; 7 | private _configurationQuickPick: ConfigurationQuickPickProvider; 8 | public constructor(context: ExtensionContext) { 9 | this._configurationQuickPick = 10 | ConfigurationQuickPickProvider.getInstance(context); 11 | } 12 | 13 | public async execute() { 14 | this._configurationQuickPick.execute(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/conversation/copyClipboardSummary.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { showMessageWithTimeout } from '@app/apis/vscode'; 4 | import { IConversation } from '@app/interfaces'; 5 | 6 | export default class ClipboardCopyConversationSummaryCommand 7 | implements ICommand 8 | { 9 | public readonly id = '_vscode-openai.conversation.clipboard-copy.summary'; 10 | 11 | // Define the 'execute' method with a single parameter 'args' which is an object containing 'data' of type 'IConversation'. 12 | public execute(args: { data: IConversation }) { 13 | // Extract the 'summary' property from the 'data' object passed in 'args'. 14 | const summary = args.data.summary; 15 | 16 | // Use the 'env.clipboard' API to write the 'summary' string to the system clipboard. 17 | env.clipboard.writeText(summary); 18 | 19 | // Call the 'showMessageWithTimeout' function to display a message with the copied 'summary' and set it to disappear after 5000 milliseconds (5 seconds). 20 | showMessageWithTimeout(`Clipboard-Copy: ${summary}`, 5000); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/conversation/delete.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { IConversation } from '@app/interfaces'; 4 | import { ConversationStorageService } from '@app/services'; 5 | 6 | export default class DeleteConversationCommand implements ICommand { 7 | public readonly id = '_vscode-openai.conversation.delete'; 8 | 9 | public execute(args: { data: IConversation }) { 10 | window 11 | .showInformationMessage( 12 | 'Are you sure you want to delete this conversation?', 13 | 'Yes', 14 | 'No' 15 | ) 16 | .then((answer) => { 17 | if (answer === 'Yes') { 18 | ConversationStorageService.instance.delete(args.data.conversationId); 19 | } 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/conversation/index.ts: -------------------------------------------------------------------------------- 1 | export { default as NewConversationStandardCommand } from './newStandard'; 2 | export { default as NewConversationPersonaCommand } from './newPersona'; 3 | 4 | export { default as OpenConversationWebviewCommand } from './openWebview'; 5 | export { default as ShowConversationJsonCommand } from './openJson'; 6 | export { default as ShowConversationMarkdownCommand } from './openMarkdown'; 7 | 8 | export { default as ClipboardCopyConversationSummaryCommand } from './copyClipboardSummary'; 9 | export { default as DeleteConversationCommand } from './delete'; 10 | -------------------------------------------------------------------------------- /src/commands/conversation/newPersona.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { quickPickCreateConversation } from '@app/utilities/quickPicks'; 4 | 5 | export default class NewConversationPersonaCommand implements ICommand { 6 | public readonly id = 'vscode-openai.conversation.new.persona'; 7 | public constructor(private _context: ExtensionContext) {} 8 | 9 | public async execute() { 10 | quickPickCreateConversation(this._context); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/commands/conversation/newStandard.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { IConversation } from '@app/interfaces'; 3 | import { getSystemPersonas } from '@app/models'; 4 | import { ConversationStorageService } from '@app/services'; 5 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 6 | 7 | export default class NewConversationStandardCommand implements ICommand { 8 | public readonly id = 'vscode-openai.conversation.new.standard'; 9 | 10 | public async execute() { 11 | const persona = getSystemPersonas().find( 12 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.GENERAL 13 | )!; 14 | const conversation: IConversation = 15 | await ConversationStorageService.instance.create(persona); 16 | ConversationStorageService.instance.update(conversation); 17 | ConversationStorageService.instance.show(conversation.conversationId); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/conversation/openJson.ts: -------------------------------------------------------------------------------- 1 | import { ViewColumn, window, workspace } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { IConversation } from '@app/interfaces'; 4 | 5 | export default class ShowConversationJsonCommand implements ICommand { 6 | public readonly id = '_vscode-openai.conversation.show.json'; 7 | 8 | public execute(args: { data: IConversation }) { 9 | workspace 10 | .openTextDocument({ 11 | content: JSON.stringify(args.data.chatMessages, undefined, 4), 12 | language: 'json', 13 | }) 14 | .then((doc) => 15 | window.showTextDocument(doc, { 16 | preserveFocus: true, 17 | preview: false, 18 | viewColumn: ViewColumn.One, 19 | }) 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/conversation/openMarkdown.ts: -------------------------------------------------------------------------------- 1 | import { ViewColumn, window, workspace } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { IConversation } from '@app/interfaces'; 4 | 5 | export default class ShowConversationMarkdownCommand implements ICommand { 6 | public readonly id = '_vscode-openai.conversation.show.markdown'; 7 | 8 | public execute(args: { data: IConversation }) { 9 | const conversation = args.data; 10 | let content = `# ${conversation.persona.roleName}\n## Summary\n${conversation.summary}\n## Content\n`; 11 | conversation.chatMessages.forEach((msg) => { 12 | content = 13 | content + `${msg.mine ? '> **Question:**' : ''} ${msg.content}\n\n`; 14 | }); 15 | 16 | workspace 17 | .openTextDocument({ 18 | content: content, 19 | language: 'markdown', 20 | }) 21 | .then((doc) => 22 | window.showTextDocument(doc, { 23 | preserveFocus: true, 24 | preview: false, 25 | viewColumn: ViewColumn.One, 26 | }) 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/commands/conversation/openWebview.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { IConversation } from '@app/interfaces'; 3 | import { ConversationStorageService } from '@app/services'; 4 | 5 | export default class OpenConversationWebviewCommand implements ICommand { 6 | public readonly id = '_vscode-openai.conversation.open.webview'; 7 | 8 | public execute(args: { data: IConversation }) { 9 | ConversationStorageService.instance.show(args.data.conversationId); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/commands/conversations/deleteAll.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { ConversationStorageService } from '@app/services'; 4 | 5 | export default class DeleteAllConversationsCommand implements ICommand { 6 | public readonly id = '_vscode-openai.conversations.delete-all'; 7 | 8 | public async execute() { 9 | window 10 | .showInformationMessage( 11 | 'Are you sure you want to delete ALL conversation?', 12 | 'Yes', 13 | 'No' 14 | ) 15 | .then((answer) => { 16 | if (answer === 'Yes') { 17 | ConversationStorageService.instance.deleteAll(); 18 | } 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/conversations/index.ts: -------------------------------------------------------------------------------- 1 | export { default as RefreshConversationsCommand } from './refresh'; 2 | export { default as DeleteAllConversationsCommand } from './deleteAll'; 3 | export { default as SettingsConversationsCommand } from './settings'; 4 | -------------------------------------------------------------------------------- /src/commands/conversations/refresh.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { ConversationStorageService } from '@app/services'; 3 | 4 | export default class RefreshConversationsCommand implements ICommand { 5 | public readonly id = '_vscode-openai.conversations.refresh'; 6 | 7 | public async execute() { 8 | ConversationStorageService.instance.refresh(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/commands/conversations/settings.ts: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | 4 | export default class SettingsConversationsCommand implements ICommand { 5 | public readonly id = '_vscode-openai.conversations.settings'; 6 | 7 | public async execute() { 8 | commands.executeCommand( 9 | 'workbench.action.openSettings', 10 | 'vscode-openai.conversation-configuration' 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/editor/codeBounty.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { getSystemPersonas } from '@app/models'; 3 | import { 4 | compareResultsToClipboard, 5 | getEditorPrompt, 6 | } from '@app/utilities/editor'; 7 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 8 | 9 | export default class CodeBountyCommand implements ICommand { 10 | public readonly id = '_vscode-openai.editor.code.bounty'; 11 | 12 | public async execute() { 13 | const prompt = await getEditorPrompt('editor.code.bounty'); 14 | const persona = getSystemPersonas().find( 15 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.DEVELOPER 16 | ); 17 | compareResultsToClipboard(persona, prompt); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/editor/codeComment.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { getSystemPersonas } from '@app/models'; 3 | import { 4 | compareResultsToClipboard, 5 | getEditorPrompt, 6 | } from '@app/utilities/editor'; 7 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 8 | 9 | export default class CodeCommentCommand implements ICommand { 10 | public readonly id = '_vscode-openai.editor.code.comment'; 11 | 12 | public async execute() { 13 | const prompt = await getEditorPrompt('editor.code.comment'); 14 | const persona = getSystemPersonas().find( 15 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.DEVELOPER 16 | ); 17 | compareResultsToClipboard(persona, prompt); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/editor/codeExplain.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { getSystemPersonas } from '@app/models'; 3 | import { 4 | compareResultsToClipboard, 5 | getEditorPrompt, 6 | } from '@app/utilities/editor'; 7 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 8 | 9 | export default class CodeExplainCommand implements ICommand { 10 | public readonly id = '_vscode-openai.editor.code.explain'; 11 | 12 | public async execute() { 13 | const prompt = await getEditorPrompt('editor.code.explain'); 14 | const persona = getSystemPersonas().find( 15 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.DEVELOPER 16 | ); 17 | compareResultsToClipboard(persona, prompt); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/editor/codeOptimize.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { getSystemPersonas } from '@app/models'; 3 | import { 4 | compareResultsToClipboard, 5 | getEditorPrompt, 6 | } from '@app/utilities/editor'; 7 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 8 | 9 | export default class CodeOptimizeCommand implements ICommand { 10 | public readonly id = '_vscode-openai.editor.code.optimize'; 11 | 12 | public async execute() { 13 | const prompt = await getEditorPrompt('editor.code.optimize'); 14 | const persona = getSystemPersonas().find( 15 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.DEVELOPER 16 | ); 17 | compareResultsToClipboard(persona, prompt); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/editor/codePatterns.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { getSystemPersonas } from '@app/models'; 3 | import { 4 | compareResultsToClipboard, 5 | getEditorPrompt, 6 | } from '@app/utilities/editor'; 7 | import { VSCODE_OPENAI_QP_PERSONA } from '@app/constants'; 8 | 9 | export default class CodePatternsCommand implements ICommand { 10 | public readonly id = '_vscode-openai.editor.code.pattern'; 11 | 12 | public async execute() { 13 | const prompt = await getEditorPrompt('editor.code.pattern'); 14 | const persona = getSystemPersonas().find( 15 | (a) => a.roleName === VSCODE_OPENAI_QP_PERSONA.DEVELOPER 16 | ); 17 | compareResultsToClipboard(persona, prompt); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { default as EditorSettingsCommand } from './settings'; 2 | 3 | export { default as EditorCodeBountyCommand } from './codeBounty'; 4 | export { default as EditorCodeCommentCommand } from './codeComment'; 5 | export { default as EditorCodeExplainCommand } from './codeExplain'; 6 | export { default as EditorCodeOptimizeCommand } from './codeOptimize'; 7 | export { default as EditorCodePatternsCommand } from './codePatterns'; 8 | -------------------------------------------------------------------------------- /src/commands/editor/settings.ts: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | 4 | export default class SettingsCommand implements ICommand { 5 | public readonly id = '_vscode-openai.editor.settings'; 6 | 7 | public async execute() { 8 | commands.executeCommand( 9 | 'workbench.action.openSettings', 10 | 'vscode-openai.editor.code' 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/embeddings/delete.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { EmbeddingTreeDataProvider, EmbeddingTreeItem } from '@app/providers'; 3 | import { ICommand } from '@app/commands'; 4 | import { EmbeddingStorageService } from '@app/services'; 5 | 6 | export default class RefreshCommand implements ICommand { 7 | public readonly id = '_vscode-openai.embeddings.delete.resource'; 8 | public constructor(private _instance: EmbeddingTreeDataProvider) {} 9 | 10 | public async execute(node: EmbeddingTreeItem) { 11 | window 12 | .showInformationMessage( 13 | 'Are you sure you want to delete this embedding?', 14 | 'Yes', 15 | 'No' 16 | ) 17 | .then((answer) => { 18 | if (answer === 'Yes') { 19 | EmbeddingStorageService.instance.delete(node.embeddingId); 20 | this._instance.refresh(); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/embeddings/deleteAll.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { EmbeddingStorageService } from '@app/services'; 4 | 5 | export default class RefreshCommand implements ICommand { 6 | public readonly id = '_vscode-openai.embeddings.delete-all'; 7 | 8 | public async execute() { 9 | window 10 | .showInformationMessage( 11 | 'Are you sure you want to delete ALL embeddings?', 12 | 'Yes', 13 | 'No' 14 | ) 15 | .then((answer) => { 16 | if (answer === 'Yes') { 17 | EmbeddingStorageService.instance.deleteAll(); 18 | } 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/embeddings/index.ts: -------------------------------------------------------------------------------- 1 | export { default as EmbeddingsRefreshCommand } from './refresh'; 2 | export { default as EmbeddingsDeleteCommand } from './delete'; 3 | export { default as DeleteAllEmbeddingsCommand } from './deleteAll'; 4 | export { default as NewConversationEmbeddingCommand } from './newConversationEmbedding'; 5 | export { default as NewConversationEmbeddingAllCommand } from './newConversationEmbeddingAll'; 6 | 7 | export { default as NewEmbeddingFileCommand } from './newEmbeddingFile'; 8 | export { default as NewEmbeddingFolderCommand } from './newEmbeddingFolder'; 9 | export { default as EmbeddingsSettingsCommand } from './settings'; 10 | -------------------------------------------------------------------------------- /src/commands/embeddings/newConversationEmbedding.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { IConversation } from '@app/interfaces'; 3 | import { ConversationStorageService } from '@app/services'; 4 | import { EmbeddingTreeItem } from '@app/providers'; 5 | import { getQueryResourcePersona } from '@app/models'; 6 | 7 | export default class NewConversationEmbeddingCommand implements ICommand { 8 | public readonly id = 'vscode-openai.embeddings.new.conversation'; 9 | 10 | public async execute(node: EmbeddingTreeItem) { 11 | const persona = getQueryResourcePersona(); 12 | const conversation: IConversation = 13 | await ConversationStorageService.instance.create( 14 | persona, 15 | node.embeddingId 16 | ); 17 | ConversationStorageService.instance.update(conversation); 18 | ConversationStorageService.instance.show(conversation.conversationId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/embeddings/newConversationEmbeddingAll.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { IConversation } from '@app/interfaces'; 3 | import { ConversationStorageService } from '@app/services'; 4 | import { getQueryResourcePersona } from '@app/models'; 5 | import { VSCODE_OPENAI_EMBEDDING } from '@app/constants'; 6 | 7 | export default class NewConversationEmbeddingAllCommand implements ICommand { 8 | public readonly id = 'vscode-openai.embeddings.new.conversation-all'; 9 | 10 | public async execute() { 11 | const persona = getQueryResourcePersona(); 12 | const conversation: IConversation = 13 | await ConversationStorageService.instance.create( 14 | persona, 15 | VSCODE_OPENAI_EMBEDDING.RESOURCE_QUERY_ALL 16 | ); 17 | ConversationStorageService.instance.update(conversation); 18 | ConversationStorageService.instance.show(conversation.conversationId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/embeddings/newEmbeddingFile.ts: -------------------------------------------------------------------------------- 1 | import { OpenDialogOptions, window } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { EmbeddingStorageService } from '@app/services'; 4 | import { 5 | createDebugNotification, 6 | createErrorNotification, 7 | } from '@app/apis/node'; 8 | import { embeddingResource } from '@app/apis/embedding'; 9 | 10 | export default class NewEmbeddingFileCommand implements ICommand { 11 | public readonly id = '_vscode-openai.embeddings.new.file'; 12 | 13 | public async execute() { 14 | // Define the options for the open dialog 15 | const options: OpenDialogOptions = { 16 | canSelectMany: true, 17 | openLabel: 'vscode-openai index file', 18 | canSelectFiles: true, 19 | canSelectFolders: false, 20 | }; 21 | 22 | // Show the open dialog and wait for the user to select a file 23 | window.showOpenDialog(options).then(async (fileUri) => { 24 | try { 25 | // Check if a file was selected 26 | if (fileUri?.[0]) { 27 | const uri = fileUri[0]; 28 | if (!uri) {return;} 29 | 30 | // Create a debug notification with the path of the selected file 31 | createDebugNotification(`file-index: ${uri.fsPath}`); 32 | 33 | // Generate an embedding for the selected file 34 | const fileObject = await embeddingResource(uri); 35 | if (!fileObject) {return;} 36 | 37 | // Update the embedding storage with the new embedding 38 | EmbeddingStorageService.instance.update(fileObject); 39 | } 40 | } catch (error) { 41 | createErrorNotification(error); 42 | } 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/embeddings/newEmbeddingFolder.ts: -------------------------------------------------------------------------------- 1 | import { FileType, OpenDialogOptions, Uri, window, workspace } from 'vscode'; 2 | 3 | import { embeddingResource } from '@app/apis/embedding'; 4 | import { createDebugNotification } from '@app/apis/node'; 5 | import { ICommand } from '@app/commands'; 6 | import { EmbeddingStorageService } from '@app/services'; 7 | 8 | export default class NewEmbeddingFolderCommand implements ICommand { 9 | public readonly id = '_vscode-openai.embeddings.new.folder'; 10 | 11 | public async execute() { 12 | const options: OpenDialogOptions = { 13 | canSelectMany: false, 14 | openLabel: 'vscode-openai index folder', 15 | canSelectFiles: false, 16 | canSelectFolders: true, 17 | }; 18 | 19 | function indexFolder(uriFolder: Uri): void { 20 | createDebugNotification(`folder-index: ${uriFolder.fsPath}`); 21 | workspace.fs.readDirectory(uriFolder).then((files) => { 22 | files.forEach(async ([file, fileType]) => { 23 | switch (fileType) { 24 | case FileType.File: { 25 | const uriFile = Uri.joinPath(uriFolder, file); 26 | createDebugNotification(`file-index: ${uriFile.fsPath}`); 27 | const fileObject = await embeddingResource(uriFile); 28 | if (fileObject) { 29 | EmbeddingStorageService.instance.update(fileObject); 30 | } 31 | break; 32 | } 33 | case FileType.Directory: { 34 | const uriNestedFolder = Uri.joinPath(uriFolder, file); 35 | indexFolder(uriNestedFolder); 36 | break; 37 | } 38 | default: 39 | break; 40 | } 41 | }); 42 | }); 43 | } 44 | 45 | window.showOpenDialog(options).then((folders) => { 46 | if (folders && folders.length > 0) { 47 | const uriFolder = folders[0]; 48 | if (!uriFolder) { return; } 49 | 50 | indexFolder(uriFolder); 51 | } 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/commands/embeddings/refresh.ts: -------------------------------------------------------------------------------- 1 | import { EmbeddingTreeDataProvider } from '@app/providers'; 2 | import { ICommand } from '@app/commands'; 3 | 4 | export default class RefreshCommand implements ICommand { 5 | public readonly id = '_vscode-openai.embeddings.refresh'; 6 | public constructor(private _instance: EmbeddingTreeDataProvider) {} 7 | 8 | public async execute() { 9 | this._instance.refresh(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/commands/embeddings/settings.ts: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | 4 | export default class SettingsCommand implements ICommand { 5 | public readonly id = '_vscode-openai.embeddings.settings'; 6 | 7 | public async execute() { 8 | commands.executeCommand( 9 | 'workbench.action.openSettings', 10 | 'vscode-openai.embedding-configuration' 11 | ); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/explorer/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ClipboardCopyFolderMarkdownCommand } from './copyClipboardFolderMarkdown'; 2 | -------------------------------------------------------------------------------- /src/commands/messages/copyClipboardMessage.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'vscode'; 2 | import { ICommand } from '@app/commands'; 3 | import { showMessageWithTimeout } from '@app/apis/vscode'; 4 | import { IChatCompletion } from '@app/interfaces'; 5 | 6 | export default class ClipboardCopyMessagesMessageCommand implements ICommand { 7 | public readonly id = '_vscode-openai.messages.clipboard-copy.message'; 8 | 9 | public execute(args: { data: IChatCompletion }) { 10 | const content = args.data.content; 11 | env.clipboard.writeText(content); 12 | showMessageWithTimeout(`Clipboard-Copy: ${content}`, 5000); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/commands/messages/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ClipboardCopyMessagesMessageCommand } from './copyClipboardMessage'; 2 | -------------------------------------------------------------------------------- /src/commands/scm/generateComments.ts: -------------------------------------------------------------------------------- 1 | import { ICommand } from '@app/commands'; 2 | import { GitService, getComments, getGitDifferences } from '@app/apis/git'; 3 | import { 4 | createErrorNotification, 5 | createDebugNotification, 6 | } from '@app/apis/node'; 7 | 8 | export default class GenerateCommentsCommand implements ICommand { 9 | public readonly id = '_vscode-openai.scm.generate.comments'; 10 | 11 | public async execute() { 12 | const gitService = new GitService(); 13 | 14 | if (!gitService.isAvailable()) { 15 | createErrorNotification(`GitService: unavailable...`); 16 | return; 17 | } 18 | 19 | const diff = await getGitDifferences(gitService); 20 | if (diff) { 21 | const s = await getComments(diff); 22 | const comments = s.replace(/<\s*think\s*>(.*?)<\/think>/gs, '').trim(); 23 | 24 | createDebugNotification( 25 | `GitService: diff(${diff.length}) ~ comments(${comments.length})` 26 | ); 27 | gitService.setSCMInputBoxMessage(comments); 28 | } else { 29 | createErrorNotification(`GitService: empty difference`); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/scm/index.ts: -------------------------------------------------------------------------------- 1 | export { default as GenerateCommentsCommand } from './generateComments'; 2 | -------------------------------------------------------------------------------- /src/constants/conversation.ts: -------------------------------------------------------------------------------- 1 | export const VSCODE_OPENAI_CONVERSATION = { 2 | STORAGE_V1_ID: 'conversation', 3 | }; 4 | -------------------------------------------------------------------------------- /src/constants/embedding.ts: -------------------------------------------------------------------------------- 1 | export const VSCODE_OPENAI_EMBEDDING = { 2 | ENABLED_COMMAND_ID: 'vscode-openai.embedding.enabled', 3 | SETUP_REQUIRED_COMMAND_ID: 'vscode-openai.embedding.setup-required', 4 | RESOURCE_QUERY_ALL: '057c0000-0000-0000-0000-000000000000', 5 | STORAGE_V1_ID: 'embedding.v1', 6 | STORAGE_V2_ID: 'embedding.v2', 7 | }; 8 | -------------------------------------------------------------------------------- /src/constants/extension.ts: -------------------------------------------------------------------------------- 1 | export const VSCODE_OPENAI_EXTENSION = { 2 | CONNECTION_STRING: 3 | 'InstrumentationKey=e01c0a97-9930-4885-b2e8-772176ced488;IngestionEndpoint=https://eastus2-3.in.applicationinsights.azure.com/;LiveEndpoint=https://eastus2.livediagnostics.monitor.azure.com/;ApplicationId=176e71d3-64bc-4bf6-b420-98c3b1120067', 4 | ENABLED_COMMAND_ID: 'vscode-openai.extension.enabled', 5 | }; 6 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export { VSCODE_OPENAI_EXTENSION } from './extension'; 2 | export { VSCODE_OPENAI_EMBEDDING } from './embedding'; 3 | export { VSCODE_OPENAI_CONVERSATION } from './conversation'; 4 | export { VSCODE_OPENAI_QP_SETUP } from './quickPickSetup'; 5 | export { VSCODE_OPENAI_QP_PERSONA } from './quickPickPersona'; 6 | -------------------------------------------------------------------------------- /src/constants/quickPickSetup.ts: -------------------------------------------------------------------------------- 1 | export const VSCODE_OPENAI_QP_SETUP = { 2 | PROVIDER_VSCODE: 'vscode-openai', 3 | PROVIDER_VSCODE_ICON: 'pulse', 4 | PROVIDER_VSCODE_DESC: '(Sponsored) Use vscode-openai service', 5 | 6 | PROVIDER_OPENAI: 'openai.com', 7 | PROVIDER_OPENAI_ICON: 'vscode-openai', 8 | PROVIDER_OPENAI_DESC: 9 | '(BYOK) Use your own OpenAI subscription (api.openai.com)', 10 | 11 | PROVIDER_AZURE: 'openai.azure.com', 12 | PROVIDER_AZURE_ICON: 'azure', 13 | PROVIDER_AZURE_DESC: 14 | '(BYOK) Use your own Azure OpenAI instance (instance.openai.azure.com)', 15 | 16 | PROVIDER_CUSTOM: 'custom', 17 | PROVIDER_CUSTOM_ICON: 'server-environment', 18 | PROVIDER_CUSTOM_DESC: 19 | '(BYOI) Use your own instance LLM or SLM (openai-api support required)', 20 | 21 | MODEL_CHANGE: 'change models', 22 | MODEL_CHANGE_ICON: 'symbol-function', 23 | MODEL_CHANGE_DESC: 'change chat and embedding model', 24 | 25 | CONFIGURATION_RESET: 'reset...', 26 | CONFIGURATION_RESET_ICON: 'wrench', 27 | CONFIGURATION_RESET_DESC: 'reset vscode-openai configuration', 28 | }; 29 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | 3 | import { CommandManager, registerVscodeOpenAICommands } from './commands'; 4 | import { StatusBarServiceProvider, TelemetryService } from '@app/apis/vscode'; 5 | 6 | import { registerVscodeOpenAIServices } from '@app/services'; 7 | import { 8 | createDebugNotification, 9 | createErrorNotification, 10 | createInfoNotification, 11 | } from '@app/apis/node'; 12 | import { 13 | EmbeddingTreeDataProvider, 14 | conversationsWebviewViewProvider, 15 | } from './providers'; 16 | import { disableServiceFeature } from './services/featureFlagServices'; 17 | 18 | export function activate(context: ExtensionContext) { 19 | try { 20 | disableServiceFeature(); 21 | 22 | // Enable logging and telemetry 23 | TelemetryService.init(context); 24 | createInfoNotification('activate vscode-openai'); 25 | 26 | createDebugNotification('initialise components'); 27 | StatusBarServiceProvider.init(context); 28 | StatusBarServiceProvider.instance.showStatusBarInformation(); 29 | 30 | registerVscodeOpenAIServices(context); 31 | 32 | // registerCommands 33 | createDebugNotification('initialise vscode commands'); 34 | const commandManager = new CommandManager(); 35 | const embeddingTree = new EmbeddingTreeDataProvider(context); 36 | context.subscriptions.push( 37 | registerVscodeOpenAICommands(context, commandManager, embeddingTree) 38 | ); 39 | conversationsWebviewViewProvider(context); 40 | 41 | createInfoNotification('vscode-openai ready'); 42 | } catch (error: unknown) { 43 | createErrorNotification(error); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/interfaces/IChatCompletion.ts: -------------------------------------------------------------------------------- 1 | export interface IChatCompletion { 2 | content: string 3 | author: string 4 | timestamp: string 5 | mine: boolean 6 | completionTokens: number 7 | promptTokens: number 8 | totalTokens: number 9 | } 10 | -------------------------------------------------------------------------------- /src/interfaces/ICodeDocument.ts: -------------------------------------------------------------------------------- 1 | export interface ICodeDocument { 2 | language: string 3 | content: string 4 | } 5 | -------------------------------------------------------------------------------- /src/interfaces/IConfigurationConversation.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigurationConversation { 2 | temperature: number 3 | presencePenalty: number 4 | topP: number 5 | frequencyPenalty: number 6 | numOfAttempts: number 7 | conversationHistory: number 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/IConfigurationConversationColor.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigurationConversationColor { 2 | userColor: string 3 | userBackground: string 4 | assistantColor: string 5 | assistantBackground: string 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/IConfigurationEmbedding.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigurationEmbedding { 2 | maxCharacterLength: number 3 | cosineSimilarityThreshold: number 4 | } 5 | -------------------------------------------------------------------------------- /src/interfaces/IConfigurationOpenAI.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigurationOpenAI { 2 | azureApiVersion: string 3 | apiKey: Promise 4 | baseURL: string 5 | model: string 6 | requestConfig: any 7 | } 8 | -------------------------------------------------------------------------------- /src/interfaces/IConfigurationSetting.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigurationSetting { 2 | serviceProvider: string; 3 | baseUrl: string; 4 | defaultModel: string; 5 | scmModel: string; 6 | embeddingModel: string; 7 | azureDeployment: string; 8 | scmDeployment: string; 9 | embeddingsDeployment: string; 10 | azureApiVersion: string; 11 | authenticationMethod: string; 12 | host: string; 13 | inferenceUrl: string; 14 | embeddingUrl: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/IConversation.ts: -------------------------------------------------------------------------------- 1 | import { IPersonaOpenAI, IChatCompletion } from '@app/interfaces'; 2 | 3 | export interface IConversation { 4 | timestamp: number 5 | conversationId: string 6 | persona: IPersonaOpenAI 7 | summary: string 8 | embeddingId?: string 9 | chatMessages: IChatCompletion[] 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/IDynamicLooseObject.ts: -------------------------------------------------------------------------------- 1 | export interface IDynamicLooseObject { 2 | [key: string]: any 3 | } 4 | -------------------------------------------------------------------------------- /src/interfaces/IEmbeddingFileChunk.ts: -------------------------------------------------------------------------------- 1 | import { IEmbeddingText } from '.'; 2 | 3 | export interface IEmbeddingFileChunk extends IEmbeddingText { 4 | filename: string 5 | score?: number 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/IEmbeddingFileLite.ts: -------------------------------------------------------------------------------- 1 | import { IEmbeddingText } from '.'; 2 | 3 | export interface IEmbeddingFileLite { 4 | timestamp: number 5 | embeddingId: string 6 | expanded?: boolean 7 | name: string 8 | url?: string 9 | type?: string 10 | score?: number 11 | size?: number 12 | embedding?: number[] // The file embedding -- or mean embedding if there are multiple embeddings for the file 13 | chunks?: IEmbeddingText[] // The chunks of text and their embeddings 14 | extractedText?: string // The extracted text from the file 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/IEmbeddingText.ts: -------------------------------------------------------------------------------- 1 | export interface IEmbeddingText { 2 | text: string 3 | embedding: number[] 4 | } 5 | -------------------------------------------------------------------------------- /src/interfaces/IMessage.ts: -------------------------------------------------------------------------------- 1 | export interface IMessage { 2 | content: string 3 | completionTokens: number 4 | promptTokens: number 5 | totalTokens: number 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/IPersonaOpenAI.ts: -------------------------------------------------------------------------------- 1 | interface IPrompt { 2 | system: string 3 | } 4 | 5 | interface IConfiguration { 6 | model: string 7 | service: string 8 | } 9 | 10 | export interface IPersonaOpenAI { 11 | roleId: string 12 | roleName: string 13 | configuration: IConfiguration 14 | prompt: IPrompt 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { IChatCompletion } from './IChatCompletion'; 2 | export { IConversation } from './IConversation'; 3 | export { IPersonaOpenAI } from './IPersonaOpenAI'; 4 | export { IMessage } from './IMessage'; 5 | export { ICodeDocument } from './ICodeDocument'; 6 | export { IEmbeddingFileLite } from './IEmbeddingFileLite'; 7 | export { IEmbeddingText } from './IEmbeddingText'; 8 | export { IEmbeddingFileChunk } from './IEmbeddingFileChunk'; 9 | export { IConfigurationSetting } from './IConfigurationSetting'; 10 | export { IConfigurationConversation } from './IConfigurationConversation'; 11 | export { IConfigurationConversationColor } from './IConfigurationConversationColor'; 12 | export { IConfigurationEmbedding } from './IConfigurationEmbedding'; 13 | export { IDynamicLooseObject } from './IDynamicLooseObject'; 14 | export { IConfigurationOpenAI } from './IConfigurationOpenAI'; 15 | -------------------------------------------------------------------------------- /src/models/QueryResourcePersona.ts: -------------------------------------------------------------------------------- 1 | import { SettingConfig as settingCfg } from '@app/services'; 2 | import { IPersonaOpenAI } from '@app/interfaces'; 3 | 4 | function getQueryResourcePersona(): IPersonaOpenAI { 5 | const getQueryResourcePersona: IPersonaOpenAI = { 6 | roleId: 'f84b3895-255a-495a-ba84-c296bf3609ab', 7 | roleName: 'Query Resource', 8 | configuration: { 9 | service: settingCfg.host, 10 | model: settingCfg.defaultModel, 11 | }, 12 | prompt: { 13 | system: 14 | `Given a question, try to answer it using the content of the file extracts below, and if you cannot answer, or find a relevant file, just output "I couldn't find the answer to that question in your files.".\n\n` + 15 | `If the answer is not contained in the files or if there are no file extracts, respond with "I couldn't find the answer to that question in your files." If the question is not actually a question, respond with "That's not a valid question."\n\n` + 16 | `In the cases where you can find the answer, first give the answer. Then explain how you found the answer from the source or sources, and use the exact filenames of the source files you mention. Do not make up the names of any other files other than those mentioned in the files context. Give the answer in markdown format.` + 17 | `Use the following format:\n\nQuestion: \n\nFiles:\n<###\n"filename 1"\nfile text>\n<###\n"filename 2"\nfile text>...\n\nAnswer: \n\n`, 18 | }, 19 | }; 20 | return getQueryResourcePersona; 21 | } 22 | 23 | export default getQueryResourcePersona; 24 | -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getSystemPersonas } from './SystemPersonas'; 2 | export { default as getQueryResourcePersona } from './QueryResourcePersona'; 3 | -------------------------------------------------------------------------------- /src/panels/index.ts: -------------------------------------------------------------------------------- 1 | export { MessageViewerPanel } from './messageViewerPanel/messageViewerPanel'; 2 | -------------------------------------------------------------------------------- /src/panels/messageViewerPanel/onDidFunctions/index.ts: -------------------------------------------------------------------------------- 1 | export { onDidCopyClipboardCode } from './onDidCopyClipboardCode'; 2 | export { onDidCreateDocument } from './onDidCreateDocument'; 3 | export { onDidSaveMessages } from './onDidSaveMessages'; 4 | -------------------------------------------------------------------------------- /src/panels/messageViewerPanel/onDidFunctions/onDidCopyClipboardCode.ts: -------------------------------------------------------------------------------- 1 | import { env } from 'vscode'; 2 | import { showMessageWithTimeout } from '@app/apis/vscode'; 3 | import { ICodeDocument } from '@app/interfaces'; 4 | 5 | export const onDidCopyClipboardCode = (codeDocument: ICodeDocument): void => { 6 | env.clipboard.writeText(codeDocument.content); 7 | showMessageWithTimeout( 8 | `Successfully copied ${codeDocument.language} code to clipboard`, 9 | 2000 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/panels/messageViewerPanel/onDidFunctions/onDidCreateDocument.ts: -------------------------------------------------------------------------------- 1 | import { ICodeDocument } from '@app/interfaces'; 2 | import { ViewColumn, window, workspace } from 'vscode'; 3 | 4 | export const onDidCreateDocument = (codeDocument: ICodeDocument): void => { 5 | workspace 6 | .openTextDocument({ 7 | content: codeDocument.content, 8 | language: codeDocument.language, 9 | }) 10 | .then((doc) => 11 | window.showTextDocument(doc, { 12 | preserveFocus: true, 13 | preview: false, 14 | viewColumn: ViewColumn.Beside, 15 | }) 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/panels/messageViewerPanel/onDidFunctions/onDidSaveMessages.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'vscode'; 2 | import { IChatCompletion, IConversation } from '@app/interfaces'; 3 | import { 4 | ConversationConfig as convCfg, 5 | ConversationStorageService, 6 | } from '@app/services'; 7 | import { createChatCompletionMessage } from '@app/apis/openai'; 8 | import { 9 | ChatCompletionConfig, 10 | ChatCompletionModelType, 11 | } from '@app/services/configuration'; 12 | 13 | export const onDidSaveMessages = async ( 14 | conversation: IConversation, 15 | chatMessages: IChatCompletion[] 16 | ): Promise => { 17 | try { 18 | if (!conversation) {return;} 19 | 20 | const cfg = ChatCompletionConfig.create(ChatCompletionModelType.INFERENCE); 21 | 22 | const SUMMARY_THRESHOLD = convCfg.summaryThreshold; 23 | const SUMMARY_MAX_LENGTH = convCfg.summaryMaxLength; 24 | 25 | conversation.chatMessages = chatMessages; 26 | ConversationStorageService.instance.update(conversation); 27 | 28 | //Add summary to conversation 29 | if (conversation.chatMessages.length % SUMMARY_THRESHOLD === 0) { 30 | //Deep clone for summary 31 | const tempConversation = JSON.parse( 32 | JSON.stringify(conversation) 33 | ) as IConversation; 34 | tempConversation.embeddingId = undefined; // ignore embedding for summary 35 | const chatCompletion: IChatCompletion = { 36 | content: `Please summarise the content above. The summary must be less than ${SUMMARY_MAX_LENGTH} words. Only provide the facts within the content.`, 37 | author: 'summary', 38 | timestamp: new Date().toLocaleString(), 39 | mine: false, 40 | completionTokens: 0, 41 | promptTokens: 0, 42 | totalTokens: 0, 43 | }; 44 | tempConversation.chatMessages.push(chatCompletion); 45 | 46 | function messageCallback(_type: string, data: IChatCompletion): void { 47 | if (!conversation) {return;} 48 | 49 | conversation.summary = data.content 50 | .replace(/<\s*think\s*>(.*?)<\/think>/gs, '') 51 | .trim(); 52 | } 53 | createChatCompletionMessage(tempConversation, cfg, messageCallback); 54 | } 55 | } catch (error) { 56 | window.showErrorMessage(error as string); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/providers/conversationsWebviewProvider/conversationsWebviewViewProvider.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, window } from 'vscode'; 2 | import { ConversationsWebviewProvider } from './conversationsWebviewProvider'; 3 | 4 | export function conversationsWebviewViewProvider(context: ExtensionContext) { 5 | const sidebarProvider = new ConversationsWebviewProvider(context.extensionUri); 6 | const view = window.registerWebviewViewProvider( 7 | 'vscode-openai.conversations.view.sidebar', 8 | sidebarProvider 9 | ); 10 | context.subscriptions.push(view); 11 | } 12 | -------------------------------------------------------------------------------- /src/providers/conversationsWebviewProvider/index.ts: -------------------------------------------------------------------------------- 1 | export { conversationsWebviewViewProvider } from './conversationsWebviewViewProvider'; 2 | -------------------------------------------------------------------------------- /src/providers/conversationsWebviewProvider/onDidFunctions/index.ts: -------------------------------------------------------------------------------- 1 | export { onDidInitialize } from './onDidInitialize'; 2 | export { onDidOpenConversationWebview } from './onDidOpenConversationWebview'; 3 | -------------------------------------------------------------------------------- /src/providers/conversationsWebviewProvider/onDidFunctions/onDidInitialize.ts: -------------------------------------------------------------------------------- 1 | import { WebviewView } from 'vscode'; 2 | import { ConversationStorageService } from '@app/services'; 3 | 4 | export const onDidInitialize = (_webView: WebviewView): void => { 5 | ConversationStorageService.instance.refresh(); 6 | }; 7 | -------------------------------------------------------------------------------- /src/providers/conversationsWebviewProvider/onDidFunctions/onDidOpenConversationWebview.ts: -------------------------------------------------------------------------------- 1 | import { commands } from 'vscode'; 2 | import { IConversation } from '@app/interfaces'; 3 | 4 | export const onDidOpenConversationWebview = ( 5 | conversation: IConversation 6 | ): void => { 7 | commands.executeCommand('_vscode-openai.conversation.open.webview', { 8 | data: conversation, 9 | }); 10 | }; 11 | -------------------------------------------------------------------------------- /src/providers/embeddingTreeDataProvider/embeddingTreeDragAndDropController.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createDebugNotification, 3 | createErrorNotification, 4 | } from '@app/apis/node'; 5 | import { 6 | EventEmitter, 7 | TreeDragAndDropController, 8 | Event, 9 | DataTransfer, 10 | CancellationToken, 11 | DataTransferItem, 12 | Uri, 13 | } from 'vscode'; 14 | import { EmbeddingTreeItem } from '.'; 15 | import { IEmbeddingFileLite } from '@app/interfaces'; 16 | import { embeddingResource } from '@app/apis/embedding'; 17 | 18 | export class EmbeddingTreeDragAndDropController 19 | implements TreeDragAndDropController 20 | { 21 | dropMimeTypes: string[] = ['text/uri-list']; 22 | dragMimeTypes: string[] = this.dropMimeTypes; 23 | 24 | private _onDidDragDropTreeData: EventEmitter = 25 | new EventEmitter(); 26 | 27 | public onDidDragDropTreeData: Event = 28 | this._onDidDragDropTreeData.event; 29 | 30 | public async handleDrop( 31 | _target: EmbeddingTreeItem | undefined, 32 | sources: DataTransfer, 33 | _token: CancellationToken 34 | ): Promise { 35 | try { 36 | const transferItem = sources.get('text/uri-list'); 37 | if (!transferItem) { 38 | createDebugNotification(`embedding drop failed`); 39 | return; 40 | } 41 | 42 | const handleDropUrls = (await transferItem.asString()).split('\r\n'); 43 | 44 | handleDropUrls.map(async (handleDropUrl) => { 45 | try { 46 | createDebugNotification(`drag-and-drop-controller: ${handleDropUrl}`); 47 | const uri = Uri.parse(handleDropUrl); 48 | const fileObject = await embeddingResource(uri); 49 | if (!fileObject) {return;} 50 | this._onDidDragDropTreeData.fire([fileObject]); 51 | } catch (error) { 52 | createErrorNotification(error); 53 | } 54 | }); 55 | } catch (error) { 56 | createErrorNotification(error); 57 | } 58 | } 59 | 60 | public async handleDrag( 61 | source: EmbeddingTreeItem[], 62 | treeDataTransfer: DataTransfer, 63 | _token: CancellationToken 64 | ): Promise { 65 | treeDataTransfer.set( 66 | 'application/vnd.vscode-openai.void', 67 | new DataTransferItem(source) 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/providers/embeddingTreeDataProvider/embeddingTreeItem.ts: -------------------------------------------------------------------------------- 1 | import { IEmbeddingFileLite } from '@app/interfaces'; 2 | import { TreeItem, TreeItemCollapsibleState, Uri, ThemeIcon } from 'vscode'; 3 | 4 | export class EmbeddingTreeItem extends TreeItem { 5 | children: TreeItem[] | undefined; 6 | private _embeddingId: string; 7 | 8 | constructor(embeddingFileLite: IEmbeddingFileLite, children?: TreeItem[]) { 9 | super( 10 | embeddingFileLite.name, 11 | children === undefined 12 | ? TreeItemCollapsibleState.None 13 | : TreeItemCollapsibleState.Expanded 14 | ); 15 | this._embeddingId = embeddingFileLite.embeddingId; 16 | this.resourceUri = Uri.parse(embeddingFileLite.url!); 17 | this.iconPath = children === undefined ? ThemeIcon.File : ThemeIcon.Folder; 18 | this.tooltip = `name: ${embeddingFileLite.name}\nchuncks: ${embeddingFileLite.chunks?.length}\nsize: ${embeddingFileLite.size}`; 19 | this.children = children; 20 | } 21 | 22 | public get embeddingId(): string { 23 | return this._embeddingId; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/providers/embeddingTreeDataProvider/index.ts: -------------------------------------------------------------------------------- 1 | export { EmbeddingTreeItem } from './embeddingTreeItem'; 2 | export { EmbeddingTreeDragAndDropController } from './embeddingTreeDragAndDropController'; 3 | -------------------------------------------------------------------------------- /src/providers/index.ts: -------------------------------------------------------------------------------- 1 | export { conversationsWebviewViewProvider } from './conversationsWebviewProvider'; 2 | export { EmbeddingTreeDataProvider } from './embeddingTreeDataProvider/embeddingTreeDataProvider'; 3 | export { EmbeddingTreeItem } from './embeddingTreeDataProvider/embeddingTreeItem'; 4 | export { ConfigurationQuickPickProvider } from './configurationQuickPickProvider/configurationQuickPickProvider'; 5 | -------------------------------------------------------------------------------- /src/services/configuration/chatCompletionConfig.ts: -------------------------------------------------------------------------------- 1 | import { IConfigurationOpenAI } from '@app/interfaces'; 2 | import { SettingConfig as settingCfg } from '.'; 3 | 4 | export enum ChatCompletionModelType { 5 | INFERENCE, 6 | SCM, 7 | } 8 | 9 | export default class ChatCompletionConfig { 10 | /** 11 | * Creates a configuration object for chat completion based on the specified type. 12 | * @param type The type of model configuration to create ('inference_model' or 'scm_model'). 13 | * @returns The configuration object for the specified type. 14 | * @throws Will throw an error if the type is not supported. 15 | */ 16 | static create(type: ChatCompletionModelType): IConfigurationOpenAI { 17 | // Using a common method to reduce redundancy and improve maintainability. 18 | const getCommonConfig = () => ({ 19 | azureApiVersion: settingCfg.azureApiVersion, 20 | apiKey: settingCfg.getApiKey(), 21 | requestConfig: settingCfg.getRequestConfig(), 22 | }); 23 | 24 | switch (type) { 25 | case ChatCompletionModelType.INFERENCE: 26 | return { 27 | ...getCommonConfig(), 28 | baseURL: settingCfg.inferenceUrl, 29 | model: settingCfg.defaultModel, 30 | }; 31 | case ChatCompletionModelType.SCM: 32 | return { 33 | ...getCommonConfig(), 34 | baseURL: settingCfg.scmUrl, 35 | model: settingCfg.scmModel, 36 | }; 37 | default: 38 | // Providing a more descriptive error message. 39 | throw new Error(`Unsupported configuration type: ${type}`); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/services/configuration/embeddingConfig.ts: -------------------------------------------------------------------------------- 1 | import { createErrorNotification, createInfoNotification } from '@app/apis/node'; 2 | import ConfigValue from './utilities/configValue'; 3 | import { IConfigurationEmbedding } from '@app/interfaces'; 4 | 5 | class EmbeddingConfig extends ConfigValue implements IConfigurationEmbedding { 6 | private static instance: EmbeddingConfig | null = null; 7 | 8 | public static getInstance(): EmbeddingConfig { 9 | if (!EmbeddingConfig.instance) { 10 | EmbeddingConfig.instance = new EmbeddingConfig(); 11 | } 12 | return EmbeddingConfig.instance; 13 | } 14 | 15 | public get maxCharacterLength(): number { 16 | return this.getConfigValue( 17 | 'embedding-configuration.max-character-length' 18 | ); 19 | } 20 | 21 | public get cosineSimilarityThreshold(): number { 22 | return this.getConfigValue( 23 | 'embedding-configuration.cosine-similarity-threshold' 24 | ); 25 | } 26 | 27 | public log(): void { 28 | try { 29 | const cfgMap = new Map(); 30 | cfgMap.set('max_character_length', this.maxCharacterLength.toString()); 31 | cfgMap.set( 32 | 'cosine_similarity_threshold', 33 | this.cosineSimilarityThreshold.toString() 34 | ); 35 | 36 | createInfoNotification( 37 | Object.fromEntries(cfgMap), 38 | 'embedding_configuration' 39 | ); 40 | } catch (error) { 41 | createErrorNotification(error); 42 | } 43 | } 44 | } 45 | export default EmbeddingConfig.getInstance(); 46 | -------------------------------------------------------------------------------- /src/services/configuration/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ConversationConfig } from './conversationConfig'; 2 | export { default as ConversationColorConfig } from './conversationColorConfig'; 3 | export { default as SettingConfig } from './settingConfig'; 4 | export { default as EmbeddingConfig } from './embeddingConfig'; 5 | export { 6 | default as ChatCompletionConfig, 7 | ChatCompletionModelType, 8 | } from './chatCompletionConfig'; 9 | -------------------------------------------------------------------------------- /src/services/configuration/utilities/configValue.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createDebugNotification, 3 | createErrorNotification, 4 | } from '@app/apis/node'; 5 | import { workspace } from 'vscode'; 6 | 7 | export default class ConfigValue { 8 | protected getConfigValue(configName: string): T { 9 | const ws = workspace.getConfiguration('vscode-openai'); 10 | return ws.get(configName) as T; 11 | } 12 | 13 | protected setConfigValue(configName: string, value: T): void { 14 | const ws = workspace.getConfiguration('vscode-openai'); 15 | const setAsGlobal = ws.inspect(configName)?.workspaceValue === undefined; 16 | ws.update(configName, value, setAsGlobal) 17 | .then(() => { 18 | createDebugNotification(`setting ${configName} ${value}`); 19 | }) 20 | .then(undefined, (err) => { 21 | createErrorNotification(`${err}`); 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/services/configurationMonitorServices/index.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext, workspace } from 'vscode'; 2 | import { createErrorNotification } from '@app/apis/node'; 3 | import { ManagedApiKey } from './managedApiKey'; 4 | 5 | export function registerConfigurationMonitorService( 6 | _context: ExtensionContext 7 | ): void { 8 | try { 9 | const managedApiKeyInstance = ManagedApiKey.getInstance(); 10 | const eventAffectsConfigurations = [ 11 | 'vscode-openai.serviceProvider', 12 | 'vscode-openai.authentication', 13 | 'vscode-openai.baseUrl', 14 | 'vscode-openai.defaultModel', 15 | 'vscode-openai.scmModel', 16 | 'vscode-openai.azureDeployment', 17 | 'vscode-openai.azureApiVersion', 18 | ]; 19 | 20 | workspace.onDidChangeConfiguration(async (event) => { 21 | try { 22 | if ( 23 | eventAffectsConfigurations.some((config) => 24 | event.affectsConfiguration(config) 25 | ) 26 | ) { 27 | await managedApiKeyInstance.verify(); 28 | } 29 | } catch (error) { 30 | createErrorNotification(error); 31 | } 32 | }); 33 | } catch (error) { 34 | createErrorNotification(error); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/services/configurationMonitorServices/managedApiKey.ts: -------------------------------------------------------------------------------- 1 | import { waitFor } from '@app/apis/node'; 2 | import { 3 | SettingConfig as settingCfg, 4 | enableServiceFeature, 5 | } from '@app/services'; 6 | import { verifyApiKey } from '@app/apis/openai'; 7 | 8 | export class ManagedApiKey { 9 | private static instance: ManagedApiKey; 10 | private _isQueued = false; 11 | 12 | public static getInstance(): ManagedApiKey { 13 | if (!ManagedApiKey.instance) { 14 | ManagedApiKey.instance = new ManagedApiKey(); 15 | } 16 | return ManagedApiKey.instance; 17 | } 18 | 19 | public async verify(): Promise { 20 | if (this._isQueued === true) {return;} 21 | 22 | this._isQueued = true; 23 | await waitFor(500, () => false); 24 | await verifyApiKey(); 25 | enableServiceFeature(); 26 | settingCfg.log(); 27 | this._isQueued = false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/services/featureFlagServices/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | VSCODE_OPENAI_EMBEDDING, 3 | VSCODE_OPENAI_EXTENSION, 4 | } from '@app/constants'; 5 | import { SettingConfig as settingCfg } from '@app/services'; 6 | import { setFeatureFlag } from '@app/apis/vscode'; 7 | 8 | export const SUPPORT_EMBEDDING = ['OpenAI', 'Azure-OpenAI']; 9 | export const SUPPORT_VERIFY_APIKEY = ['OpenAI', 'Azure-OpenAI', 'VSCode-OpenAI']; 10 | 11 | export function disableServiceFeature() { 12 | // Disable functionality until we validate auth 13 | setFeatureFlag(VSCODE_OPENAI_EXTENSION.ENABLED_COMMAND_ID, false); 14 | setFeatureFlag(VSCODE_OPENAI_EMBEDDING.ENABLED_COMMAND_ID, false); 15 | } 16 | 17 | export function enableServiceFeature() { 18 | setFeatureFlag(VSCODE_OPENAI_EXTENSION.ENABLED_COMMAND_ID, true); 19 | 20 | setFeatureFlag(VSCODE_OPENAI_EMBEDDING.ENABLED_COMMAND_ID, false); 21 | if (SUPPORT_EMBEDDING.includes(settingCfg.serviceProvider)) { 22 | setFeatureFlag(VSCODE_OPENAI_EMBEDDING.ENABLED_COMMAND_ID, true); 23 | } 24 | 25 | setFeatureFlag(VSCODE_OPENAI_EMBEDDING.SETUP_REQUIRED_COMMAND_ID, false); 26 | if (settingCfg.embeddingModel === 'setup-required') { 27 | setFeatureFlag(VSCODE_OPENAI_EMBEDDING.SETUP_REQUIRED_COMMAND_ID, true); 28 | } 29 | } 30 | 31 | export function featureVerifyApiKey(): boolean { 32 | if (SUPPORT_VERIFY_APIKEY.includes(settingCfg.serviceProvider)) { 33 | return true; 34 | } 35 | return false; 36 | } 37 | -------------------------------------------------------------------------------- /src/services/index.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | 3 | import { createDebugNotification } from '@app/apis/node'; 4 | import { validateApiKey } from '@app/apis/openai'; 5 | 6 | import { 7 | ConversationStorageService, 8 | EmbeddingStorageService, 9 | } from './storageServices'; 10 | 11 | import { registerConfigurationMonitorService } from './configurationMonitorServices'; 12 | import { 13 | ConversationConfig as convCfg, 14 | ConversationColorConfig as convColorCfg, 15 | EmbeddingConfig as embedCfg, 16 | SettingConfig as settingCfg, 17 | } from './configuration'; 18 | import { GlobalStorageService, SecretStorageService } from '@app/apis/vscode'; 19 | import { enableServiceFeature } from './featureFlagServices'; 20 | 21 | export { 22 | SettingConfig, 23 | ConversationConfig, 24 | EmbeddingConfig, 25 | } from './configuration'; 26 | 27 | export { 28 | ConversationStorageService, 29 | EmbeddingStorageService, 30 | } from './storageServices'; 31 | 32 | export { 33 | enableServiceFeature, 34 | featureVerifyApiKey, 35 | } from './featureFlagServices'; 36 | 37 | export function registerVscodeOpenAIServices(context: ExtensionContext) { 38 | //register storage (Singletons) 39 | createDebugNotification('initialise vscode services'); 40 | SecretStorageService.init(context); 41 | GlobalStorageService.init(context); 42 | 43 | createDebugNotification('starting storage services'); 44 | registerConfigurationMonitorService(context); 45 | ConversationStorageService.init(context); 46 | EmbeddingStorageService.init(context); 47 | 48 | //load configuration 49 | createDebugNotification('log configuration service'); 50 | settingCfg.log(); 51 | convCfg.log(); 52 | convColorCfg.log(); 53 | embedCfg.log(); 54 | 55 | createDebugNotification('verifying service authentication'); 56 | validateApiKey(); //On activation check if the api key is valid 57 | 58 | createDebugNotification('verifying enabled features'); 59 | enableServiceFeature(); 60 | } 61 | -------------------------------------------------------------------------------- /src/services/storageServices/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ConversationStorageService } from './conversationStorageService'; 2 | export { default as EmbeddingStorageService } from './embeddingStorageService'; 3 | -------------------------------------------------------------------------------- /src/utilities/editor/compareResultsToClipboard.ts: -------------------------------------------------------------------------------- 1 | import { commands, env, window } from 'vscode'; 2 | import { ConversationStorageService } from '@app/services'; 3 | import { IChatCompletion, IConversation, IPersonaOpenAI } from '@app/interfaces'; 4 | import { createChatCompletionMessage } from '@app/apis/openai'; 5 | import { 6 | ChatCompletionConfig, 7 | ChatCompletionModelType, 8 | } from '@app/services/configuration'; 9 | 10 | export const compareResultsToClipboard = async ( 11 | persona: IPersonaOpenAI | undefined, 12 | prompt: string | undefined 13 | ): Promise => { 14 | if (!persona || !prompt) { 15 | window.showErrorMessage('Persona or prompt is undefined.'); 16 | return; 17 | } 18 | 19 | const editor = window.activeTextEditor; 20 | if (!editor) { 21 | window.showErrorMessage('No active text editor available.'); 22 | return; 23 | } 24 | 25 | const documentUri = editor.document.uri; 26 | const conversation: IConversation = 27 | await ConversationStorageService.instance.create(persona); 28 | 29 | const chatCompletion: IChatCompletion = { 30 | content: prompt, 31 | author: 'vscode-openai-editor', 32 | timestamp: new Date().toLocaleString(), 33 | mine: false, 34 | completionTokens: 0, 35 | promptTokens: 0, 36 | totalTokens: 0, 37 | }; 38 | 39 | const cfg = ChatCompletionConfig.create(ChatCompletionModelType.INFERENCE); 40 | 41 | // Clearing the welcome message more idiomatically 42 | conversation.chatMessages.length = 0; 43 | conversation.chatMessages.push(chatCompletion); 44 | 45 | try { 46 | let result = ''; 47 | function messageCallback(_type: string, data: IChatCompletion): void { 48 | if (!conversation) {return;} 49 | result = data.content; 50 | } 51 | await createChatCompletionMessage(conversation, cfg, messageCallback); 52 | const originalValue = await env.clipboard.readText(); 53 | 54 | await env.clipboard.writeText(result ?? ''); 55 | await commands.executeCommand( 56 | 'workbench.files.action.compareWithClipboard', 57 | documentUri 58 | ); 59 | 60 | await env.clipboard.writeText(originalValue); 61 | } catch (error) { 62 | window.showErrorMessage(`An error occurred: ${error}`); 63 | console.error(error); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/utilities/editor/getEditorPrompt.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { 3 | getActiveTextEditorValue, 4 | getActiveTextLanguageId, 5 | } from '@app/apis/vscode'; 6 | 7 | export const getEditorPrompt = async ( 8 | configPrompt: string 9 | ): Promise => { 10 | const language = getActiveTextLanguageId(); 11 | const inputCode = getActiveTextEditorValue(); 12 | 13 | let prompt = workspace 14 | .getConfiguration('vscode-openai') 15 | .get(configPrompt) as string; 16 | 17 | prompt = prompt.split('#{language}').join(language); 18 | prompt = prompt.split('#{source_code}').join(inputCode); 19 | return prompt; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utilities/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { getEditorPrompt } from './getEditorPrompt'; 2 | export { compareResultsToClipboard } from './compareResultsToClipboard'; 3 | -------------------------------------------------------------------------------- /src/utilities/editor/newConversationEditor.ts: -------------------------------------------------------------------------------- 1 | import { workspace } from 'vscode'; 2 | import { 3 | getActiveTextEditorValue, 4 | getActiveTextLanguageId, 5 | } from '@app/apis/vscode'; 6 | 7 | export const newConversationEditor = async ( 8 | configPrompt: string 9 | ): Promise => { 10 | const language = getActiveTextLanguageId(); 11 | const inputCode = getActiveTextEditorValue(); 12 | 13 | let prompt = workspace 14 | .getConfiguration('vscode-openai') 15 | .get(configPrompt) as string; 16 | 17 | prompt = prompt.split('#{language}').join(language); 18 | prompt = prompt.split('#{source_code}').join(inputCode); 19 | return prompt; 20 | }; 21 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/azure/showInputBoxAzureApiKey.ts: -------------------------------------------------------------------------------- 1 | import { getAzureOpenAIAccessToken, getAzureOpenAIAccessTokenSovereignUs, MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | 5 | export async function showInputBoxAzureApiKey( 6 | input: MultiStepInput, 7 | state: Partial 8 | ): Promise { 9 | state.step = (state.step ?? 0) + 1; 10 | 11 | switch (state.authenticationMethod?.label) { 12 | case '$(azure) Microsoft': { 13 | const accessToken = await getAzureOpenAIAccessToken(); 14 | state.authApiKey = `Bearer ${accessToken}`; 15 | break; 16 | } 17 | case '$(azure) Microsoft Sovereign (US)': { 18 | const accessToken = await getAzureOpenAIAccessTokenSovereignUs(); 19 | state.authApiKey = `Bearer ${accessToken}`; 20 | break; 21 | } 22 | case '$(key) Enter your Api-Key': 23 | default: { 24 | state.authApiKey = await input.showInputBox({ 25 | title: state.title!, 26 | step: state.step, 27 | totalSteps: state.totalSteps!, 28 | ignoreFocusOut: true, 29 | value: typeof state.authApiKey === 'string' ? state.authApiKey : '', 30 | prompt: '$(key) Enter your Api-Key', 31 | placeholder: 'ed4af062d8567543ad104587ea4505ce', 32 | validate: validateApiKey, 33 | shouldResume: shouldResume, 34 | }); 35 | break; 36 | } 37 | } 38 | } 39 | 40 | async function validateApiKey(name: string): Promise { 41 | const OPENAI_APIKEY_LENGTH = 32; 42 | // Native azure service key or oauth2 token 43 | return name.length >= OPENAI_APIKEY_LENGTH 44 | ? undefined 45 | : 'Invalid Api-Key or Token'; 46 | } 47 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/azure/showInputBoxAzureBaseUrl.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | import { validateUrl } from '../validateUrl'; 5 | export async function showInputBoxAzureBaseUrl( 6 | input: MultiStepInput, 7 | state: Partial 8 | ): Promise { 9 | state.step = (state.step ?? 0) + 1; 10 | state.baseUrl = await input.showInputBox({ 11 | title: state.title!, 12 | step: state.step, 13 | totalSteps: state.totalSteps!, 14 | ignoreFocusOut: true, 15 | value: 16 | typeof state.baseUrl === 'string' 17 | ? state.baseUrl 18 | : 'https://instance.openai.azure.com/openai', 19 | valueSelection: typeof state.baseUrl === 'string' ? undefined : [8, 16], 20 | prompt: 21 | '$(globe) Enter the instance name. Provide the base url for example "https://instance.openai.azure.com/openai"', 22 | placeholder: 'https://instance.openai.azure.com/openai', 23 | validate: validateUrl, 24 | shouldResume: shouldResume, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/azure/showQuickPickAzureAuthentication.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { QuickPickItem } from 'vscode'; 3 | import { IQuickPickSetup } from '../../interface'; 4 | import { shouldResume } from '../shouldResume'; 5 | 6 | export async function showQuickPickAzureAuthentication( 7 | input: MultiStepInput, 8 | state: Partial 9 | ): Promise { 10 | state.step = (state.step ?? 0) + 1; 11 | const getAvailableRuntimes: QuickPickItem[] = [ 12 | { 13 | label: '$(key) Enter your Api-Key', 14 | description: 'Use your openai.azure.com API key', 15 | }, 16 | { 17 | label: '$(azure) Microsoft', 18 | description: 'Use microsoft profile to sign into to azure openai service', 19 | }, 20 | { 21 | label: '$(azure) Microsoft Sovereign (US)', 22 | description: 'Use microsoft profile to sign into to azure openai service', 23 | }, 24 | ]; 25 | 26 | state.authenticationMethod = await input.showQuickPick({ 27 | title: state.title!, 28 | step: state.step, 29 | totalSteps: state.totalSteps!, 30 | ignoreFocusOut: true, 31 | placeholder: 'Selected OpenAI Model', 32 | items: getAvailableRuntimes, 33 | activeItem: state.authenticationMethod, 34 | shouldResume: shouldResume, 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/azure/showQuickPickAzureEmbeddingModel.ts: -------------------------------------------------------------------------------- 1 | import { ModelCapability } from '@app/apis/openai'; 2 | import { MultiStepInput } from '@app/apis/vscode'; 3 | import { getAvailableModelsAzure } from '@app/utilities/quickPicks'; 4 | import { IQuickPickSetup } from '../../interface'; 5 | import { shouldResume } from '../shouldResume'; 6 | 7 | export async function showQuickPickAzureEmbeddingModel( 8 | input: MultiStepInput, 9 | state: Partial 10 | ): Promise { 11 | state.step = (state.step ?? 0) + 1; 12 | const models = await getAvailableModelsAzure( 13 | state.authApiKey!, 14 | state.baseUrl!, 15 | ModelCapability.Embedding 16 | ); 17 | 18 | if (models.length > 0) { 19 | state.modelEmbedding = await input.showQuickPick({ 20 | title: state.title!, 21 | step: state.step, 22 | totalSteps: state.totalSteps!, 23 | ignoreFocusOut: true, 24 | placeholder: `Selected embedding deployment/model (if empty, no valid models found)`, 25 | items: models, 26 | activeItem: state.modelEmbedding, 27 | shouldResume: shouldResume, 28 | }); 29 | } else { 30 | state.modelEmbedding = undefined; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/azure/showQuickPickAzureInferenceModel.ts: -------------------------------------------------------------------------------- 1 | import { ModelCapability } from '@app/apis/openai'; 2 | import { MultiStepInput } from '@app/apis/vscode'; 3 | import { getAvailableModelsAzure } from '@app/utilities/quickPicks'; 4 | import { IQuickPickSetup } from '../../interface'; 5 | import { shouldResume } from '../shouldResume'; 6 | 7 | export async function showQuickPickAzureInferenceModel( 8 | input: MultiStepInput, 9 | state: Partial 10 | ): Promise { 11 | state.step = (state.step ?? 0) + 1; 12 | const models = await getAvailableModelsAzure( 13 | state.authApiKey!, 14 | state.baseUrl!, 15 | ModelCapability.ChatCompletion 16 | ); 17 | state.modelInference = await input.showQuickPick({ 18 | title: state.title!, 19 | step: state.step, 20 | totalSteps: state.totalSteps!, 21 | ignoreFocusOut: true, 22 | placeholder: 23 | 'Selected chat completion deployment/model (if empty, no valid models found)', 24 | items: models, 25 | activeItem: state.modelInference, 26 | shouldResume: shouldResume, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/azure/showQuickPickAzureScmModel.ts: -------------------------------------------------------------------------------- 1 | import { ModelCapability } from '@app/apis/openai'; 2 | import { MultiStepInput } from '@app/apis/vscode'; 3 | import { getAvailableModelsAzure } from '@app/utilities/quickPicks'; 4 | import { IQuickPickSetup } from '../../interface'; 5 | import { shouldResume } from '../shouldResume'; 6 | 7 | export async function showQuickPickAzureScmModel( 8 | input: MultiStepInput, 9 | state: Partial 10 | ): Promise { 11 | state.step = (state.step ?? 0) + 1; 12 | const models = await getAvailableModelsAzure( 13 | state.authApiKey!, 14 | state.baseUrl!, 15 | ModelCapability.ChatCompletion 16 | ); 17 | state.modelScm = await input.showQuickPick({ 18 | title: state.title!, 19 | step: state.step, 20 | totalSteps: state.totalSteps!, 21 | ignoreFocusOut: true, 22 | placeholder: 23 | 'Selected SCM (git) deployment/model (if empty, no valid models found)', 24 | items: models, 25 | activeItem: state.modelScm, 26 | shouldResume: shouldResume, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/custom/showInputBoxCustomApiKey.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | import { validateIgnored } from '..'; 5 | 6 | export async function showInputBoxCustomApiKey( 7 | input: MultiStepInput, 8 | state: Partial 9 | ): Promise { 10 | state.step = (state.step ?? 0) + 1; 11 | state.authApiKey = await input.showInputBox({ 12 | title: state.title!, 13 | step: state.step, 14 | totalSteps: state.totalSteps!, 15 | ignoreFocusOut: true, 16 | value: typeof state.authApiKey === 'string' ? state.authApiKey : '', 17 | prompt: '$(key) Enter your Api-Key', 18 | placeholder: 'ed4af062d8567543ad104587ea4505ce', 19 | validate: validateIgnored, 20 | shouldResume: shouldResume, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/custom/showInputBoxCustomBaseUrl.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | import { validateUrl } from '../validateUrl'; 5 | export async function showInputBoxCustomBaseUrl( 6 | input: MultiStepInput, 7 | state: Partial 8 | ): Promise { 9 | state.step = (state.step ?? 0) + 1; 10 | state.baseUrl = await input.showInputBox({ 11 | title: state.title!, 12 | step: state.step, 13 | totalSteps: state.totalSteps!, 14 | ignoreFocusOut: true, 15 | value: typeof state.baseUrl === 'string' ? state.baseUrl : '', 16 | prompt: 17 | '$(globe) Enter the instance name. Provide the base url default https://localhost/v1"', 18 | placeholder: 'https://localhost/v1', 19 | validate: validateUrl, 20 | shouldResume: shouldResume, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/custom/showInputBoxCustomInferenceModel.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | import { validateIgnored } from '..'; 5 | 6 | export async function showInputBoxCustomInferenceModel( 7 | input: MultiStepInput, 8 | state: Partial 9 | ) { 10 | state.step = (state.step ?? 0) + 1; 11 | state.modelInferenceCustom = await input.showInputBox({ 12 | title: state.title!, 13 | step: state.step, 14 | totalSteps: state.totalSteps!, 15 | ignoreFocusOut: true, 16 | value: 17 | typeof state.modelInferenceCustom === 'string' 18 | ? state.modelInferenceCustom 19 | : '', 20 | prompt: `$(symbol-function) Enter the model name`, 21 | placeholder: ' Llama-2-70B', 22 | validate: validateIgnored, 23 | shouldResume: shouldResume, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/index.ts: -------------------------------------------------------------------------------- 1 | export { shouldResume } from './shouldResume'; 2 | export { validateUrl } from './validateUrl'; 3 | export { validateIgnored } from './validateIgnored'; 4 | 5 | //azure 6 | export { showInputBoxAzureApiKey } from './azure/showInputBoxAzureApiKey'; 7 | export { showInputBoxAzureBaseUrl } from './azure/showInputBoxAzureBaseUrl'; 8 | export { showQuickPickAzureAuthentication } from './azure/showQuickPickAzureAuthentication'; 9 | export { showQuickPickAzureEmbeddingModel } from './azure/showQuickPickAzureEmbeddingModel'; 10 | export { showQuickPickAzureInferenceModel } from './azure/showQuickPickAzureInferenceModel'; 11 | export { showQuickPickAzureScmModel } from './azure/showQuickPickAzureScmModel'; 12 | 13 | //openai 14 | export { showInputBoxOpenAIBaseUrl } from './openai/showInputBoxOpenAIBaseUrl'; 15 | export { showInputBoxOpenAIApiKey } from './openai/showInputBoxOpenAIApiKey'; 16 | export { showQuickPickOpenAIInferenceModel } from './openai/showQuickPickOpenAIInferenceModel'; 17 | export { showQuickPickOpenAIScmModel } from './openai/showQuickPickOpenAIScmModel'; 18 | export { showQuickPickOpenAIEmbeddingModel } from './openai/showQuickPickOpenAIEmbeddingModel'; 19 | 20 | //custom 21 | export { showInputBoxCustomInferenceModel } from './custom/showInputBoxCustomInferenceModel'; 22 | export { showInputBoxCustomBaseUrl } from './custom/showInputBoxCustomBaseUrl'; 23 | export { showInputBoxCustomApiKey } from './custom/showInputBoxCustomApiKey'; 24 | 25 | //vscode-openai 26 | export { showQuickPickVscodeAuthentication } from './vscode/showQuickPickVscodeAuthentication'; 27 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/openai/showInputBoxOpenAIApiKey.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | 5 | export async function showInputBoxOpenAIApiKey( 6 | input: MultiStepInput, 7 | state: Partial 8 | ): Promise { 9 | state.step = (state.step ?? 0) + 1; 10 | state.authApiKey = await input.showInputBox({ 11 | title: state.title!, 12 | step: state.step, 13 | totalSteps: state.totalSteps!, 14 | ignoreFocusOut: true, 15 | value: typeof state.authApiKey === 'string' ? state.authApiKey : '', 16 | prompt: `$(key) Enter the openai.com Api-Key`, 17 | placeholder: 'sk-8i6055nAY3eAwARfHFjiT5BlbkFJAEFUvG5GwtAV2RiwP87h', 18 | validate: validateApiKey, 19 | shouldResume: shouldResume, 20 | }); 21 | } 22 | 23 | async function validateApiKey(name: string): Promise { 24 | const OPENAI_APIKEY_MIN_LENGTH = 1; 25 | const OPENAI_APIKEY_STARTSWITH = 'sk-'; 26 | const OPENAI_OAUTH2_BEARER_STARTSWITH = 'Bearer'; 27 | const OPENAI_OAUTH2_TOKEN_STARTSWITH = 'ey'; 28 | 29 | // Native openai service key or oauth2 token 30 | return name.length >= OPENAI_APIKEY_MIN_LENGTH && 31 | (name.startsWith(OPENAI_APIKEY_STARTSWITH) || 32 | name.startsWith(OPENAI_OAUTH2_BEARER_STARTSWITH) || 33 | name.startsWith(OPENAI_OAUTH2_TOKEN_STARTSWITH)) 34 | ? undefined 35 | : 'Invalid Api-Key or Token'; 36 | } 37 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/openai/showInputBoxOpenAIBaseUrl.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { IQuickPickSetup } from '../../interface'; 3 | import { shouldResume } from '../shouldResume'; 4 | import { validateUrl } from '../validateUrl'; 5 | export async function showInputBoxOpenAIBaseUrl( 6 | input: MultiStepInput, 7 | state: Partial 8 | ): Promise { 9 | state.step = (state.step ?? 0) + 1; 10 | state.baseUrl = await input.showInputBox({ 11 | title: state.title!, 12 | step: state.step, 13 | totalSteps: state.totalSteps!, 14 | ignoreFocusOut: true, 15 | value: 16 | typeof state.baseUrl === 'string' 17 | ? state.baseUrl 18 | : 'https://api.openai.com/v1', 19 | valueSelection: typeof state.baseUrl === 'string' ? undefined : [0, 25], 20 | prompt: 21 | '$(globe) Enter the instance name. Provide the base url default https://api.openai.com/v1"', 22 | placeholder: 'https://api.openai.com/v1', 23 | validate: validateUrl, 24 | shouldResume: shouldResume, 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/openai/showQuickPickOpenAIEmbeddingModel.ts: -------------------------------------------------------------------------------- 1 | import { ModelCapability } from '@app/apis/openai'; 2 | import { MultiStepInput } from '@app/apis/vscode'; 3 | import { getAvailableModelsOpenai } from '@app/utilities/quickPicks'; 4 | import { IQuickPickSetup } from '../../interface'; 5 | import { shouldResume } from '../shouldResume'; 6 | 7 | export async function showQuickPickOpenAIEmbeddingModel( 8 | input: MultiStepInput, 9 | state: Partial 10 | ): Promise { 11 | state.step = (state.step ?? 0) + 1; 12 | const models = await getAvailableModelsOpenai( 13 | state.authApiKey!, 14 | state.baseUrl!, 15 | ModelCapability.Embedding 16 | ); 17 | state.modelEmbedding = await input.showQuickPick({ 18 | title: state.title!, 19 | step: state.step, 20 | totalSteps: state.totalSteps!, 21 | ignoreFocusOut: true, 22 | placeholder: 'Selected embedding model (if empty, no valid models found)', 23 | items: models, 24 | activeItem: state.modelEmbedding, 25 | shouldResume: shouldResume, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/openai/showQuickPickOpenAIInferenceModel.ts: -------------------------------------------------------------------------------- 1 | import { ModelCapability } from '@app/apis/openai'; 2 | import { MultiStepInput } from '@app/apis/vscode'; 3 | import { getAvailableModelsOpenai } from '@app/utilities/quickPicks'; 4 | import { IQuickPickSetup } from '../../interface'; 5 | import { shouldResume } from '../shouldResume'; 6 | 7 | export async function showQuickPickOpenAIInferenceModel( 8 | input: MultiStepInput, 9 | state: Partial 10 | ): Promise { 11 | state.step = (state.step ?? 0) + 1; 12 | const models = await getAvailableModelsOpenai( 13 | state.authApiKey!, 14 | state.baseUrl!, 15 | ModelCapability.ChatCompletion 16 | ); 17 | state.modelInference = await input.showQuickPick({ 18 | title: state.title!, 19 | step: state.step, 20 | totalSteps: state.totalSteps!, 21 | ignoreFocusOut: true, 22 | placeholder: 23 | 'Selected chat completion model (if empty, no valid models found)', 24 | items: models, 25 | activeItem: state.modelInference, 26 | shouldResume: shouldResume, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/openai/showQuickPickOpenAIScmModel.ts: -------------------------------------------------------------------------------- 1 | import { ModelCapability } from '@app/apis/openai'; 2 | import { MultiStepInput } from '@app/apis/vscode'; 3 | import { getAvailableModelsOpenai } from '@app/utilities/quickPicks'; 4 | import { IQuickPickSetup } from '../../interface'; 5 | import { shouldResume } from '../shouldResume'; 6 | 7 | export async function showQuickPickOpenAIScmModel( 8 | input: MultiStepInput, 9 | state: Partial 10 | ): Promise { 11 | state.step = (state.step ?? 0) + 1; 12 | const models = await getAvailableModelsOpenai( 13 | state.authApiKey!, 14 | state.baseUrl!, 15 | ModelCapability.ChatCompletion 16 | ); 17 | state.modelScm = await input.showQuickPick({ 18 | title: state.title!, 19 | step: state.step, 20 | totalSteps: state.totalSteps!, 21 | ignoreFocusOut: true, 22 | placeholder: 'Selected SCM (git) model (if empty, no valid models found)', 23 | items: models, 24 | activeItem: state.modelScm, 25 | shouldResume: shouldResume, 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/shouldResume.ts: -------------------------------------------------------------------------------- 1 | export function shouldResume() { 2 | // Could show a notification with the option to resume. 3 | return new Promise((_resolve, _reject) => { 4 | /* noop */ 5 | }); 6 | } 7 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/validateIgnored.ts: -------------------------------------------------------------------------------- 1 | export async function validateIgnored( 2 | _name: string 3 | ): Promise { 4 | return undefined; 5 | } 6 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/validateUrl.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from 'vscode'; 2 | 3 | export async function validateUrl( 4 | baseUrl: string 5 | ): Promise { 6 | return Uri.parse(baseUrl) ? undefined : 'Invalid Uri'; 7 | } 8 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/commands/vscode/showQuickPickVscodeAuthentication.ts: -------------------------------------------------------------------------------- 1 | import { MultiStepInput } from '@app/apis/vscode'; 2 | import { QuickPickItem } from 'vscode'; 3 | import { IQuickPickSetup } from '../../interface'; 4 | import { shouldResume } from '../shouldResume'; 5 | 6 | export async function showQuickPickVscodeAuthentication( 7 | input: MultiStepInput, 8 | state: Partial 9 | ): Promise { 10 | state.step = (state.step ?? 0) + 1; 11 | const getAvailableRuntimes: QuickPickItem[] = [ 12 | { 13 | label: '$(github) GitHub', 14 | description: 15 | 'Use your github.com profile to sign into to vscode-openai service', 16 | }, 17 | ]; 18 | 19 | state.authenticationMethod = await input.showQuickPick({ 20 | title: state.title!, 21 | step: state.step, 22 | totalSteps: state.totalSteps!, 23 | ignoreFocusOut: true, 24 | placeholder: 'Selected OpenAI Model', 25 | items: getAvailableRuntimes, 26 | activeItem: state.authenticationMethod, 27 | shouldResume: shouldResume, 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/getAvailableModelsAzure.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModelCapability, 3 | listModelsAzureOpenAI, 4 | } from '@app/apis/openai'; 5 | import { QuickPickItem } from 'vscode'; 6 | 7 | /** 8 | * This function retrieves available models from Open AI using an API key. It returns a list of QuickPickItems representing each available model. 9 | * @param openapiAPIKey - The API key used to authenticate with Open AI. 10 | * @param token - A cancellation token that can be used to cancel this operation. Not currently implemented in this codebase so defaults to undefined. 11 | * @returns A list of QuickPickItems representing each available model returned by the API call to Open AI. 12 | */ 13 | export async function getAvailableModelsAzure( 14 | openapiAPIKey: string, 15 | openapiBaseUrl: string, 16 | modelCapabiliy: ModelCapability 17 | ): Promise { 18 | const chatCompletionModels = await listModelsAzureOpenAI( 19 | openapiAPIKey, 20 | openapiBaseUrl, 21 | modelCapabiliy 22 | ); 23 | 24 | const quickPickItems: QuickPickItem[] = []; 25 | chatCompletionModels?.forEach((deployment) => { 26 | quickPickItems.push({ 27 | label: `$(symbol-function) ${deployment.deployment}`, 28 | description: deployment.model, 29 | }); 30 | }); 31 | return quickPickItems; 32 | } 33 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/getAvailableModelsOpenai.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ModelCapability, 3 | listModelsOpenAI 4 | } from '@app/apis/openai'; 5 | import { QuickPickItem } from 'vscode'; 6 | 7 | /** 8 | * This function retrieves available models from Open AI using an API key. It returns a list of QuickPickItems representing each available model. 9 | * @param openapiAPIKey - The API key used to authenticate with Open AI. 10 | * @param _token - A cancellation token that can be used to cancel this operation. Not currently implemented in this codebase so defaults to undefined. 11 | * @returns A list of QuickPickItems representing each available model returned by the API call to Open AI. 12 | */ 13 | export async function getAvailableModelsOpenai( 14 | apiKey: string, 15 | baseUrl: string, 16 | modelCapabiliy: ModelCapability 17 | ): Promise { 18 | const chatCompletionModels = await listModelsOpenAI( 19 | apiKey, 20 | baseUrl, 21 | modelCapabiliy 22 | ); 23 | 24 | // Map each returned label into a QuickPickItem object with label property set as label value returned by API call. 25 | return chatCompletionModels.map((label) => ({ 26 | label: `$(symbol-function) ${label}`, 27 | })); 28 | } 29 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/index.ts: -------------------------------------------------------------------------------- 1 | export { getAvailableModelsAzure } from './getAvailableModelsAzure'; 2 | export { getAvailableModelsOpenai } from './getAvailableModelsOpenai'; 3 | export { quickPickChangeModel } from './quickPickChangeModel'; 4 | export { quickPickCreateConversation } from './quickPickCreateConversation'; 5 | export { quickPickSetupAzureOpenai } from './quickPickSetupAzureOpenai'; 6 | export { quickPickSetupCustomOpenai } from './quickPickSetupCustomOpenai'; 7 | export { quickPickSetupOpenai } from './quickPickSetupOpenai'; 8 | export { quickPickSetupVscodeOpenai } from './quickPickSetupVscodeOpenai'; 9 | 10 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/interface/IQuickPickSetup.ts: -------------------------------------------------------------------------------- 1 | import { QuickPickItem } from 'vscode'; 2 | 3 | export interface IQuickPickSetup { 4 | serviceProvider: string; 5 | title: string; 6 | step: number; 7 | totalSteps: number; 8 | baseUrl: string; 9 | authenticationMethod: QuickPickItem; 10 | authApiKey: string; 11 | modelInferenceCustom: string; 12 | modelInference: QuickPickItem; 13 | modelScm: QuickPickItem; 14 | modelEmbedding: QuickPickItem; 15 | } 16 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/interface/index.ts: -------------------------------------------------------------------------------- 1 | export { IQuickPickSetup } from './IQuickPickSetup'; 2 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/quickPickSetupCustomOpenai.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'vscode'; 2 | import { SettingConfig as settingCfg } from '@app/services'; 3 | import { SecretStorageService, MultiStepInput } from '@app/apis/vscode'; 4 | import { IQuickPickSetup } from './interface'; 5 | import { 6 | showInputBoxCustomBaseUrl, 7 | showInputBoxCustomApiKey, 8 | showInputBoxCustomInferenceModel, 9 | } from './commands'; 10 | 11 | /** 12 | * This function sets up a quick pick menu for configuring the OpenAI service provider. 13 | * @param _context - The extension context. 14 | * @returns void 15 | */ 16 | export async function quickPickSetupCustomOpenai( 17 | _context: ExtensionContext 18 | ): Promise { 19 | async function collectInputs(): Promise { 20 | const state = {} as Partial; 21 | state.serviceProvider = 'Custom-OpenAI'; 22 | state.title = 'Configure Service Provider (openai.com)'; 23 | state.step = 0; 24 | const steps = [ 25 | (input: MultiStepInput) => showInputBoxCustomBaseUrl(input, state), 26 | (input: MultiStepInput) => showInputBoxCustomApiKey(input, state), 27 | (input: MultiStepInput) => showInputBoxCustomInferenceModel(input, state), 28 | ]; 29 | state.totalSteps = steps.length; 30 | 31 | await MultiStepInput.run(async (input) => { 32 | for (const step of steps) { 33 | await step(input); 34 | } 35 | }); 36 | 37 | return state as IQuickPickSetup; 38 | } 39 | 40 | //Start openai.com configuration processes 41 | const state = await collectInputs(); 42 | 43 | await SecretStorageService.instance.setAuthApiKey(state.authApiKey); 44 | settingCfg.serviceProvider = state.serviceProvider; 45 | settingCfg.baseUrl = state.baseUrl; 46 | settingCfg.defaultModel = state.modelInferenceCustom; 47 | settingCfg.azureDeployment = 'setup-required'; 48 | settingCfg.scmModel = state.modelInferenceCustom; 49 | settingCfg.scmDeployment = 'setup-required'; 50 | settingCfg.embeddingModel = 'setup-required'; 51 | settingCfg.embeddingsDeployment = 'setup-required'; 52 | settingCfg.azureApiVersion = '2024-06-01'; 53 | } 54 | -------------------------------------------------------------------------------- /src/utilities/quickPicks/quickPickSetupVscodeOpenai.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getVscodeOpenAccessToken, 3 | MultiStepInput, 4 | SecretStorageService, 5 | } from '@app/apis/vscode'; 6 | import { SettingConfig as settingCfg } from '@app/services'; 7 | import { ExtensionContext } from 'vscode'; 8 | import { showQuickPickVscodeAuthentication } from './commands'; 9 | import { IQuickPickSetup } from './interface'; 10 | 11 | export async function quickPickSetupVscodeOpenai( 12 | _context: ExtensionContext 13 | ): Promise { 14 | async function collectInputs(): Promise { 15 | const state = {} as Partial; 16 | state.serviceProvider = 'VSCode-OpenAI'; 17 | state.title = 'Configure Service Provider (openai.azure.com)'; 18 | state.baseUrl = `https://api.arbs.io/openai/inference/v1`; 19 | state.step = 0; 20 | const steps = [ 21 | (input: MultiStepInput) => 22 | showQuickPickVscodeAuthentication(input, state), 23 | ]; 24 | state.totalSteps = steps.length; 25 | 26 | await MultiStepInput.run(async (input) => { 27 | for (const step of steps) { 28 | await step(input); 29 | } 30 | }); 31 | 32 | return state as IQuickPickSetup; 33 | } 34 | 35 | const state = await collectInputs(); 36 | const accessToken = await getVscodeOpenAccessToken(); 37 | if (!accessToken) {return;} 38 | 39 | await SecretStorageService.instance.setAuthApiKey(accessToken); 40 | settingCfg.serviceProvider = state.serviceProvider; 41 | settingCfg.baseUrl = state.baseUrl; 42 | settingCfg.defaultModel = 'gpt-4o-mini-2024-07-18'; 43 | settingCfg.azureDeployment = 'gpt-4o'; 44 | settingCfg.scmModel = 'gpt-4o-mini-2024-07-18'; 45 | settingCfg.scmDeployment = 'gpt-4o'; 46 | settingCfg.embeddingModel = 'text-embedding-ada-002'; 47 | settingCfg.embeddingsDeployment = 'text-embedding-ada-002'; 48 | settingCfg.azureApiVersion = '2024-06-01'; 49 | } 50 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "nodenext", 4 | "moduleResolution": "nodenext", 5 | "types": ["node"], 6 | "target": "es2020", 7 | "lib": ["es2020", "dom"], 8 | "outDir": "out", 9 | "sourceMap": true, 10 | "rootDir": "src", 11 | "allowJs": false, 12 | // Strict mode (9) 13 | "strict": true, 14 | "alwaysStrict": true, 15 | "noImplicitAny": true, 16 | "noImplicitThis": true, 17 | "strictNullChecks": true, 18 | "strictBindCallApply": true, 19 | "strictFunctionTypes": true, 20 | "strictPropertyInitialization": true, 21 | "useUnknownInCatchVariables": true, 22 | // No unused code (4) 23 | "noUnusedLocals": true, 24 | "noUnusedParameters": true, 25 | "allowUnusedLabels": false, 26 | "allowUnreachableCode": false, 27 | // No implicit code (2) 28 | "noImplicitOverride": true, 29 | "noImplicitReturns": true, 30 | // 31 | // "noUncheckedIndexedAccess": true, 32 | "noPropertyAccessFromIndexSignature": true, 33 | "noFallthroughCasesInSwitch": true, 34 | // "exactOptionalPropertyTypes": true, 35 | "forceConsistentCasingInFileNames": true, 36 | "allowSyntheticDefaultImports": true, 37 | "baseUrl": ".", 38 | "paths": { 39 | "@app/*": ["./src/*"] 40 | } 41 | }, 42 | "include": ["src/**/*.ts"], 43 | "exclude": ["**/*.d.ts", "out/**/*", "node_modules", ".vscode-test"] 44 | } 45 | -------------------------------------------------------------------------------- /utilities/scripts/clean.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const folders = [ 5 | '../../dist', 6 | '../../out', 7 | '../../node_modules', 8 | '../../webview/conversations/node_modules', 9 | '../../webview/message/node_modules', 10 | ] 11 | 12 | folders.forEach((folder) => { 13 | const dir = path.join(__dirname, folder) 14 | if (fs.existsSync(dir)) { 15 | fs.rmSync(dir, { recursive: true, force: true }) 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /utilities/scripts/copy-wasm.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | 4 | const outFolder = '../../out' 5 | const targetFiles = path.join( 6 | __dirname, 7 | '../../out/asset-extractor-wasm_bg.wasm' 8 | ) 9 | const sourceFiles = path.join( 10 | __dirname, 11 | '../../node_modules/@arbs.io/asset-extractor-wasm/asset-extractor-wasm_bg.wasm' 12 | ) 13 | 14 | const dir = path.join(__dirname, outFolder) 15 | if (!fs.existsSync(dir)) { 16 | fs.mkdirSync(dir) 17 | } 18 | 19 | fs.copyFile(sourceFiles, targetFiles, (err) => { 20 | if (err) throw err 21 | console.log(`copied:\n\t${sourceFiles}\n\t${targetFiles}`) 22 | }) 23 | -------------------------------------------------------------------------------- /webview/conversations/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | build 6 | build-ssr 7 | *.local 8 | -------------------------------------------------------------------------------- /webview/conversations/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Local Debugger 7 | 8 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /webview/conversations/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vscode-openai/conversations", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "test-compile": "tsc -p ./" 10 | }, 11 | "dependencies": { 12 | "@fluentui/react-components": "^9.63.0", 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "vscode-openai": "file:../.." 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.3.18", 19 | "@types/react-dom": "^18.3.5", 20 | "@types/vscode-webview": "^1.57.5", 21 | "@vitejs/plugin-react": "^4.4.1", 22 | "typescript": "^5.8.3", 23 | "vite": "^6.3.5", 24 | "vite-tsconfig-paths": "^5.1.4" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /webview/conversations/src/App.tsx: -------------------------------------------------------------------------------- 1 | import ConversationGrid from './components/ConversationGrid/ConversationGrid' 2 | 3 | function App() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | 11 | export default App 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/BusinessAnalystsIcon.ts: -------------------------------------------------------------------------------- 1 | export const BusinessAnalystsIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/CloudArchitectIcon.ts: -------------------------------------------------------------------------------- 1 | export const CloudArchitectIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/CyberSecurityAnalystsIcon.ts: -------------------------------------------------------------------------------- 1 | export const CyberSecurityAnalystsIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 9 | 10 | ` 11 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 12 | return iconDataUrl 13 | } 14 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/DataScientistIcon.ts: -------------------------------------------------------------------------------- 1 | export const DataScientistIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/DatabaseAdministratorIcon.ts: -------------------------------------------------------------------------------- 1 | export const DatabaseAdministratorIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/DevOpsEngineersIcon.ts: -------------------------------------------------------------------------------- 1 | export const DevOpsEngineersIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/DeveloperProgrammerIcon.ts: -------------------------------------------------------------------------------- 1 | export const DeveloperProgrammerIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/EnterpriseArchitectIcon.ts: -------------------------------------------------------------------------------- 1 | export const EnterpriseArchitectIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/GeneralChatIcon.ts: -------------------------------------------------------------------------------- 1 | export const GeneralChatIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/ITManagerIcon.ts: -------------------------------------------------------------------------------- 1 | export const ITManagerIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 9 | 11 | 13 | 14 | ` 15 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 16 | return iconDataUrl 17 | } 18 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/NetworkEngineerIcon.ts: -------------------------------------------------------------------------------- 1 | export const NetworkEngineerIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/ProductManagerIcon.ts: -------------------------------------------------------------------------------- 1 | export const ProductManagerIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 9 | 10 | ` 11 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 12 | return iconDataUrl 13 | } 14 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/ProjectManagerIcon.ts: -------------------------------------------------------------------------------- 1 | export const ProjectManagerIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/QualityAssuranceTestersIcon.ts: -------------------------------------------------------------------------------- 1 | export const QualityAssuranceTestersIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/SystemAdministratorIcon.ts: -------------------------------------------------------------------------------- 1 | export const SystemAdministratorIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | 9 | ` 10 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 11 | return iconDataUrl 12 | } 13 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/UserExperienceDesignersIcon.ts: -------------------------------------------------------------------------------- 1 | export const UserExperienceDesignersIcon = (fillColor: string): string => { 2 | const iconSvg = ` 3 | 5 | 7 | 8 | ` 9 | const iconDataUrl = `data:image/svg+xml;base64,${btoa(iconSvg)}` 10 | return iconDataUrl 11 | } 12 | -------------------------------------------------------------------------------- /webview/conversations/src/assets/index.ts: -------------------------------------------------------------------------------- 1 | export { ProjectManagerIcon } from './ProjectManagerIcon' 2 | export { GeneralChatIcon } from './GeneralChatIcon' 3 | export { DeveloperProgrammerIcon } from './DeveloperProgrammerIcon' 4 | export { NetworkEngineerIcon } from './NetworkEngineerIcon' 5 | export { DatabaseAdministratorIcon } from './DatabaseAdministratorIcon' 6 | export { BusinessAnalystsIcon } from './BusinessAnalystsIcon' 7 | export { ITManagerIcon } from './ITManagerIcon' 8 | export { QualityAssuranceTestersIcon } from './QualityAssuranceTestersIcon' 9 | export { TechnicalWriterIcon } from './TechnicalWriterIcon' 10 | export { UserExperienceDesignersIcon } from './UserExperienceDesignersIcon' 11 | export { ProductManagerIcon } from './ProductManagerIcon' 12 | export { DataScientistIcon } from './DataScientistIcon' 13 | export { CyberSecurityAnalystsIcon } from './CyberSecurityAnalystsIcon' 14 | export { CloudArchitectIcon } from './CloudArchitectIcon' 15 | export { DevOpsEngineersIcon } from './DevOpsEngineersIcon' 16 | export { EnterpriseArchitectIcon } from './EnterpriseArchitectIcon' 17 | export { SystemAdministratorIcon } from './SystemAdministratorIcon' 18 | -------------------------------------------------------------------------------- /webview/conversations/src/components/ConversationGrid/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ConversationGrid } from './ConversationGrid' 2 | -------------------------------------------------------------------------------- /webview/conversations/src/components/ConversationGridColumnDefinition/ConversationGridColumnDefinition.tsx: -------------------------------------------------------------------------------- 1 | import { IConversation } from '@app/interfaces'; 2 | import { vscode } from '@app/utilities'; 3 | import { 4 | createTableColumn, 5 | TableCell, 6 | TableCellLayout, 7 | TableColumnDefinition, 8 | } from '@fluentui/react-components'; 9 | import useConversationAvatar from './useConversationAvatar'; 10 | 11 | const handleOpenConversation = (conversation: IConversation) => { 12 | vscode.postMessage({ 13 | command: 'onDidOpenConversationWebview', 14 | text: JSON.stringify(conversation), 15 | }) 16 | } 17 | 18 | const ConversationGridColumnDefinition: TableColumnDefinition[] = 19 | [ 20 | createTableColumn({ 21 | columnId: 'persona', 22 | compare: (a, b) => { 23 | return a.timestamp - b.timestamp 24 | }, 25 | renderHeaderCell: () => { 26 | return '' 27 | }, 28 | renderCell: (item) => { 29 | const avatarComponent = useConversationAvatar(item) // Call the getStatus function to get the Avatar component 30 | return ( 31 |
32 | 33 | 34 | 35 |
36 | ) 37 | }, 38 | }), 39 | createTableColumn({ 40 | columnId: 'summary', 41 | compare: (a, b) => { 42 | return a.timestamp - b.timestamp 43 | }, 44 | renderHeaderCell: () => { 45 | return 'Summary' 46 | }, 47 | renderCell: (conversation) => { 48 | return ( 49 | 50 | handleOpenConversation(conversation)} 53 | style={{ cursor: 'pointer' }} 54 | /> 55 | 56 | ) 57 | }, 58 | }), 59 | ] 60 | export default ConversationGridColumnDefinition 61 | -------------------------------------------------------------------------------- /webview/conversations/src/components/ConversationGridColumnDefinition/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ConversationGridColumnDefinition } from './ConversationGridColumnDefinition' 2 | -------------------------------------------------------------------------------- /webview/conversations/src/components/ConversationGridColumnDefinition/useStyles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@fluentui/react-components'; 2 | 3 | export const useStyles = makeStyles({ 4 | activateButtons: { 5 | color: 'var(--vscode-button-foreground)', 6 | display: 'none', 7 | ':hover': { 8 | color: 'var(--vscode-button-hoverForeground)', 9 | display: 'block', 10 | }, 11 | ':hover:active': { 12 | color: 'var(--vscode-button-activeForeground)', 13 | }, 14 | }, 15 | conversationRow: { 16 | backgroundColor: 'var(--vscode-editor-background)', 17 | color: 'var(--vscode-editor-foreground)', 18 | padding: '0.5rem', 19 | borderBottom: '1px solid var(--vscode-editorGroup-border)', 20 | ':hover': { 21 | backgroundColor: 'var(--vscode-list-hoverBackground)', 22 | }, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /webview/conversations/src/context/index.ts: -------------------------------------------------------------------------------- 1 | export { ThemeContext, createContextTheme } from './themeContext' 2 | -------------------------------------------------------------------------------- /webview/conversations/src/context/themeContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { IContextTheme } from '@app/interfaces' 3 | 4 | export function createContextTheme(isDarkMode: boolean): IContextTheme { 5 | const configuration: IContextTheme = { 6 | isDarkMode: isDarkMode, 7 | } 8 | return configuration 9 | } 10 | 11 | export const ThemeContext = React.createContext(null) 12 | -------------------------------------------------------------------------------- /webview/conversations/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | webDarkTheme, 3 | webLightTheme, 4 | FluentProvider, 5 | } from '@fluentui/react-components' 6 | import React from 'react' 7 | import ReactDOM from 'react-dom/client' 8 | import App from './App' 9 | import { ThemeContext, createContextTheme } from './context' 10 | 11 | const ele = document.getElementById('root') as HTMLElement 12 | const isDarkMode = ele.getAttribute('theme') === 'dark' 13 | const themeFluentUI = isDarkMode ? webDarkTheme : webLightTheme 14 | const root = ReactDOM.createRoot(ele) 15 | 16 | root.render( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/IChatCompletion.ts: -------------------------------------------------------------------------------- 1 | export interface IChatCompletion { 2 | content: string 3 | author: string 4 | timestamp: string 5 | mine: boolean 6 | completionTokens: number 7 | promptTokens: number 8 | totalTokens: number 9 | } 10 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/IContextTheme.ts: -------------------------------------------------------------------------------- 1 | export interface IContextTheme { 2 | isDarkMode: boolean 3 | } 4 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/IConversation.ts: -------------------------------------------------------------------------------- 1 | import { IPersonaOpenAI, IChatCompletion } from '.' 2 | 3 | export interface IConversation { 4 | timestamp: number 5 | conversationId: string 6 | persona: IPersonaOpenAI 7 | summary: string 8 | embeddingId?: string 9 | chatMessages: IChatCompletion[] 10 | } 11 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/IDialogProps.ts: -------------------------------------------------------------------------------- 1 | import { IConversation } from './IConversation' 2 | 3 | export interface IDialogProps { 4 | showDialog: boolean 5 | setShowDialog: React.Dispatch> 6 | conversation: IConversation 7 | } 8 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/IMenuItemProps.ts: -------------------------------------------------------------------------------- 1 | import { IConversation } from '.' 2 | 3 | export interface IMenuItemProps { 4 | conversation: IConversation 5 | onClick?: React.MouseEventHandler 6 | } 7 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/IPersonaOpenAI.ts: -------------------------------------------------------------------------------- 1 | interface IPrompt { 2 | system: string 3 | } 4 | 5 | interface IConfiguration { 6 | model: string 7 | service: string 8 | } 9 | 10 | export interface IPersonaOpenAI { 11 | roleId: string 12 | roleName: string 13 | configuration: IConfiguration 14 | prompt: IPrompt 15 | } 16 | -------------------------------------------------------------------------------- /webview/conversations/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export type { IChatCompletion } from './IChatCompletion' 2 | export type { IConversation } from './IConversation' 3 | export type { IPersonaOpenAI } from './IPersonaOpenAI' 4 | export type { IMenuItemProps } from './IMenuItemProps' 5 | export type { IDialogProps } from './IDialogProps' 6 | export type { IContextTheme } from './IContextTheme' 7 | -------------------------------------------------------------------------------- /webview/conversations/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export { vscode } from '@common/vscode' 2 | -------------------------------------------------------------------------------- /webview/conversations/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "useDefineForClassFields": true, 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noEmit": true, 19 | "jsx": "react-jsx", 20 | "baseUrl": ".", 21 | "paths": { 22 | "@app/*": ["./src/*"], 23 | "@common/*": ["../common/*"] 24 | }, 25 | "typeRoots": ["./node_modules/@types", "./src/types"] 26 | }, 27 | "include": ["./src", "../common/vscode.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /webview/conversations/types/svg.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.svg' { 2 | const content: string 3 | export default content 4 | } 5 | -------------------------------------------------------------------------------- /webview/conversations/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import tsconfigPaths from 'vite-tsconfig-paths' 3 | import react from '@vitejs/plugin-react' 4 | 5 | export default defineConfig(({ command, mode }) => { 6 | const minify = mode === 'production' 7 | const sourcemap = mode !== 'production' 8 | console.log(`\nmode: ${mode} :: minify: ${minify} sourcemap: ${sourcemap}`) 9 | 10 | return { 11 | plugins: [react(), tsconfigPaths()], 12 | build: { 13 | outDir: '../../out/webview/conversations', 14 | chunkSizeWarningLimit: 2048, 15 | emptyOutDir: true, 16 | minify: minify, 17 | sourcemap: sourcemap, 18 | rollupOptions: { 19 | output: { 20 | entryFileNames: `[name].js`, 21 | chunkFileNames: `[name].js`, 22 | assetFileNames: `[name].[ext]`, 23 | }, 24 | }, 25 | }, 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /webview/message/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | build 6 | build-ssr 7 | *.local 8 | -------------------------------------------------------------------------------- /webview/message/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Local Debugger 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /webview/message/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vscode-openai/message", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview", 9 | "test-compile": "tsc -p ./" 10 | }, 11 | "dependencies": { 12 | "@fluentui/react-components": "^9.63.0", 13 | "@fluentui/react-icons": "^2.0.298", 14 | "react": "^18.3.1", 15 | "react-dom": "^18.3.1", 16 | "react-markdown": "^10.1.0", 17 | "react-syntax-highlighter": "^15.6.1", 18 | "remark-gfm": "^4.0.1", 19 | "vscode-openai": "file:../.." 20 | }, 21 | "devDependencies": { 22 | "@types/react": "^18.3.18", 23 | "@types/react-dom": "^18.3.5", 24 | "@types/react-syntax-highlighter": "^15.5.13", 25 | "@types/vscode-webview": "^1.57.5", 26 | "@vitejs/plugin-react": "^4.4.1", 27 | "typescript": "^5.8.3", 28 | "vite": "^6.3.5", 29 | "vite-tsconfig-paths": "^5.1.4" 30 | } 31 | } -------------------------------------------------------------------------------- /webview/message/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { FC, useState, useEffect } from 'react' 2 | import { makeStyles, mergeClasses } from '@fluentui/react-components' 3 | import { MessageList } from './components/MessageList' 4 | import { vscode } from '@app/utilities' 5 | 6 | const useStyles = makeStyles({ 7 | container: { 8 | justifyContent: 'center', 9 | alignItems: 'flex-start', 10 | }, 11 | }) 12 | 13 | const App: FC = () => { 14 | const [didInitialize, setDidInitialize] = useState(false) 15 | 16 | const styles = useStyles() 17 | 18 | useEffect(() => { 19 | if (!didInitialize) { 20 | vscode.postMessage({ 21 | command: 'onDidInitialize', 22 | text: undefined, 23 | }) 24 | setDidInitialize(true) 25 | } 26 | }) 27 | 28 | return ( 29 |
30 | 31 |
32 | ) 33 | } 34 | 35 | export default App 36 | -------------------------------------------------------------------------------- /webview/message/src/components/ButtonCopyToClipboard/index.tsx: -------------------------------------------------------------------------------- 1 | import { ICodeDocument } from '@app/interfaces'; 2 | import { vscode } from '@app/utilities'; 3 | import { Button, Tooltip } from '@fluentui/react-components'; 4 | import { 5 | Clipboard16Regular 6 | } from '@fluentui/react-icons'; 7 | import { FC } from 'react'; 8 | 9 | const handleCopyToClipboard = (language: string, content: string) => { 10 | const codeDocument: ICodeDocument = { 11 | language, 12 | content, 13 | } 14 | vscode.postMessage({ 15 | command: 'onDidCopyClipboardCode', 16 | text: JSON.stringify(codeDocument), 17 | }) 18 | } 19 | 20 | // Defining the component with explicit props type 21 | export const ButtonCopyToClipboard: FC = ({ 22 | language, 23 | content, 24 | }) => { 25 | return ( 26 | 27 | 34 | 35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /webview/message/src/components/ButtonOpenSourceFile/index.tsx: -------------------------------------------------------------------------------- 1 | import { ICodeDocument } from '@app/interfaces'; 2 | import { vscode } from '@app/utilities'; 3 | import { Button, Tooltip } from '@fluentui/react-components'; 4 | import { Open16Regular } from '@fluentui/react-icons'; 5 | import { FC, ReactElement } from 'react'; 6 | 7 | // Function to handle the creation of a code document 8 | const handleCreateCodeDocument = (language: string, content: string) => { 9 | const codeDocument: ICodeDocument = { 10 | language, 11 | content, 12 | } 13 | // Sending a message to VSCode or similar environment to handle document creation 14 | vscode.postMessage({ 15 | command: 'onDidCreateDocument', 16 | text: JSON.stringify(codeDocument), 17 | }) 18 | } 19 | 20 | // Bundling icons for different states 21 | // const OpenIcon = bundleIcon(Open24Filled, Open24Regular) 22 | 23 | // Functional component for opening a source file 24 | export const ButtonOpenSourceFile: FC = ({ 25 | language, 26 | content, 27 | }): ReactElement => { 28 | return ( 29 | 30 | 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageInput/styles/useMessageInputStyles.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@fluentui/react-components' 2 | 3 | export const useMessageInputStyles = makeStyles({ 4 | outerWrapper: { 5 | display: 'flex', 6 | flexDirection: 'column', 7 | }, 8 | textLayer: { 9 | height: '100%', 10 | }, 11 | hidden: { 12 | visibility: 'hidden', 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageItem/components/CodeBlockMatched.tsx: -------------------------------------------------------------------------------- 1 | import { ButtonCopyToClipboard } from '@app/components/ButtonCopyToClipboard'; 2 | import { ButtonOpenSourceFile } from '@app/components/ButtonOpenSourceFile'; 3 | import { makeStyles } from '@fluentui/react-components'; 4 | import { FC } from 'react'; 5 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 6 | import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'; 7 | 8 | interface ICodeBlockMatchedProps { 9 | language: string 10 | content: string 11 | } 12 | 13 | const useCodeBlockMatchedStyles = makeStyles({ 14 | codeContainer: { 15 | backgroundColor: 'rgba(255, 255, 255,0.3)', 16 | padding: '0.3rem', 17 | paddingBottom: '0.1rem', 18 | borderRadius: '16px', 19 | overflowX: 'auto', 20 | whiteSpace: 'pre', 21 | }, 22 | 23 | toolbar: { 24 | display: 'flex', 25 | justifyContent: 'right', 26 | marginBottom: '0.5rem', 27 | }, 28 | }); 29 | 30 | const CodeBlockMatched: FC = ({ 31 | language, 32 | content, 33 | }) => { 34 | const MessageItemStyles = useCodeBlockMatchedStyles() 35 | 36 | return ( 37 |
38 |
39 | 40 | 41 |
42 | 48 | {content} 49 | 50 |
51 | ) 52 | } 53 | 54 | export default CodeBlockMatched 55 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageItem/components/CodeBlockUnmatched.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react' 2 | import { makeStyles } from '@fluentui/react-components' 3 | 4 | interface ICodeBlockUnmatchedProps { 5 | children: React.ReactNode 6 | [key: string]: any 7 | } 8 | 9 | const useCodeBlockUnmatchedStyles = makeStyles({ 10 | codeContainer: {}, 11 | }) 12 | 13 | const CodeBlockUnmatched: FC = ({ 14 | children, 15 | ...props 16 | }) => { 17 | const MessageItemStyles = useCodeBlockUnmatchedStyles() 18 | 19 | return ( 20 | 21 | {children} 22 | 23 | ) 24 | } 25 | 26 | export default CodeBlockUnmatched 27 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageItem/components/MessageItemTokenInfo.tsx: -------------------------------------------------------------------------------- 1 | import { IChatCompletionProps } from '@app/interfaces'; 2 | import { Caption1, makeStyles, shorthands } from '@fluentui/react-components'; 3 | import { 4 | CommentArrowRight16Regular, 5 | Notebook16Regular, 6 | Pen16Regular, 7 | } from '@fluentui/react-icons'; 8 | import { FC } from 'react'; 9 | 10 | const useMessageItemTokenInfoStyles = makeStyles({ 11 | container: { 12 | ...shorthands.gap('8px'), 13 | display: 'flex', 14 | flexDirection: 'column', 15 | alignItems: 'flex-start', 16 | fontSize: '0.875rem', 17 | }, 18 | }) 19 | 20 | const MessageItemTokenInfo: FC = ({ chatCompletion }) => { 21 | const styles = useMessageItemTokenInfoStyles() 22 | return ( 23 |
24 | 25 | {} Completion: {chatCompletion.completionTokens} 26 | 27 | 28 | {} Prompt: {chatCompletion.promptTokens} 29 | 30 | 31 | {} Total: {chatCompletion.totalTokens} 32 | 33 |
34 | ) 35 | } 36 | 37 | export default MessageItemTokenInfo 38 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageItem/components/MessageItemToolbar.tsx: -------------------------------------------------------------------------------- 1 | import { IChatCompletionProps } from '@app/interfaces'; 2 | import { TextToSpeech } from '@app/utilities'; 3 | import { 4 | Button, 5 | makeStyles, 6 | Popover, 7 | PopoverSurface, 8 | PopoverTrigger, 9 | } from '@fluentui/react-components'; 10 | import { Info16Regular, Speaker216Regular } from '@fluentui/react-icons'; 11 | import { FC, MouseEvent } from 'react'; 12 | import { MessageItemTokenInfo } from '.'; 13 | 14 | const handleSpeech = ( 15 | _e: MouseEvent, 16 | speechText: string 17 | ) => { 18 | const textToSpeech = TextToSpeech.getInstance() 19 | textToSpeech.activateTextToSpeech(speechText) 20 | } 21 | 22 | const useMessageItemToolbarStyles = makeStyles({ 23 | infoButton: { 24 | float: 'right', 25 | transition: 'background-color 0.2s ease', 26 | // ':hover': { 27 | // backgroundColor: '#eaeef2', // Subtle hover effect 28 | // }, 29 | }, 30 | }) 31 | 32 | const MessageItemToolbar: FC = ({ chatCompletion }) => { 33 | const styles = useMessageItemToolbarStyles() 34 | return ( 35 | 36 | 25 | {isOpen && ( 26 |
27 | {content} 28 |
29 | )} 30 | 31 | ) 32 | } 33 | 34 | export default ThinkSection 35 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageItem/components/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as CodeBlockMatched } from './CodeBlockMatched' 2 | export { default as CodeBlockUnmatched } from './CodeBlockUnmatched' 3 | export { default as MessageItemTokenInfo } from './MessageItemTokenInfo' 4 | export { default as MessageItemToolbar } from './MessageItemToolbar' 5 | export { default as ThinkSection } from './ThinkSection' 6 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageItem/styles/useMessageItemStyles.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@fluentui/react-components'; 2 | 3 | export const useMessageItemStyles = makeStyles({ 4 | messageItem: { 5 | borderRadius: '12px', 6 | margin: '1rem 0', // Add vertical spacing between messages 7 | padding: '1.5rem', // Increase padding for better readability 8 | maxWidth: '80%', 9 | boxShadow: '0 4px 6px var(--shadowColor)', // Subtle shadow 10 | border: '1px solid var(--borderColor)', // Add a light border 11 | backgroundColor: 'var(--assistantBackground)', // Use consistent background 12 | }, 13 | messageWrapper: { 14 | display: 'flex', 15 | flexDirection: 'column', 16 | gap: '1rem', // Add spacing between elements 17 | }, 18 | messageHeader: { 19 | paddingBottom: '0.5rem', 20 | borderBottom: '1px solid var(--borderColor)', // Add a separator 21 | }, 22 | author: { 23 | paddingRight: '1rem', 24 | fontWeight: 'bold', 25 | color: 'var(--userColor)', 26 | }, 27 | timestamp: { 28 | fontSize: '0.875rem', 29 | color: 'var(--userColor)', 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageList/components/WelcomeMessageBar.tsx: -------------------------------------------------------------------------------- 1 | import { IChatCompletionListProps } from '@app/interfaces'; 2 | import { 3 | Link, 4 | makeStyles, 5 | MessageBar, 6 | MessageBarBody, 7 | MessageBarGroup, 8 | MessageBarTitle, 9 | tokens, 10 | } from '@fluentui/react-components'; 11 | import * as React from 'react'; 12 | 13 | const useStyles = makeStyles({ 14 | messageBarGroup: { 15 | padding: tokens.spacingHorizontalMNudge, 16 | display: 'flex', 17 | flexDirection: 'column', 18 | marginTop: '10px', 19 | gap: '10px', 20 | flex: 1, 21 | overflow: 'auto', 22 | }, 23 | buttonGroup: { 24 | display: 'flex', 25 | justifyContent: 'end', 26 | gap: '5px', 27 | }, 28 | }) 29 | 30 | export const WelcomeMessageBar: React.FC = ({ 31 | chatCompletionList, 32 | }) => { 33 | const styles = useStyles() 34 | 35 | return ( 36 | <> 37 | {chatCompletionList.length === 0 && ( 38 | 39 | 40 | 41 | Welcome 42 | I'm your Copilot and I'm here to help you get things done faster. 43 | Please read the vscode-openai extensions page for{' '} 44 | 48 | more information 49 | {' '} 50 | 51 | 52 | 53 | )} 54 | 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /webview/message/src/components/MessageList/styles/useMessageListStyles.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles } from '@fluentui/react-components' 2 | 3 | export const useMessageListStyles = makeStyles({ 4 | container: { 5 | display: 'flex', 6 | flexDirection: 'column', 7 | flexWrap: 'nowrap', 8 | width: 'auto', 9 | height: 'auto', 10 | }, 11 | history: { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | flexGrow: 1, 15 | rowGap: '2px', 16 | paddingLeft: '1rem', 17 | paddingRight: '1rem', 18 | paddingBottom: '7rem', 19 | overflowY: 'auto', 20 | }, 21 | input: { 22 | position: 'fixed', 23 | bottom: 0, 24 | left: 0, 25 | right: 0, 26 | paddingBottom: '2rem', 27 | paddingLeft: '1rem', 28 | paddingRight: '1rem', 29 | paddingTop: '2rem', 30 | }, 31 | }) 32 | -------------------------------------------------------------------------------- /webview/message/src/context/configurationContext.ts: -------------------------------------------------------------------------------- 1 | import { IContextConfiguration } from '@app/interfaces'; 2 | import { tokens } from '@fluentui/react-components'; 3 | import React from 'react'; 4 | 5 | export function createContextConfiguration(): IContextConfiguration { 6 | const rootElement = document.getElementById('root') as HTMLElement; 7 | const configuration: IContextConfiguration = { 8 | messageShortcuts: rootElement.getAttribute('messageShortcuts') ?? 'true', 9 | assistantColor: tokens.colorPaletteBlueForeground2, 10 | assistantBackground: tokens.colorPaletteBlueBackground2, 11 | userColor: tokens.colorNeutralForeground3Hover, 12 | userBackground: tokens.colorNeutralBackground4, 13 | }; 14 | return configuration; 15 | } 16 | 17 | export const ConfigurationContext = 18 | React.createContext(null); 19 | -------------------------------------------------------------------------------- /webview/message/src/context/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | ConfigurationContext, 3 | createContextConfiguration, 4 | } from './configurationContext' 5 | -------------------------------------------------------------------------------- /webview/message/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | webDarkTheme, 3 | webLightTheme, 4 | FluentProvider, 5 | } from '@fluentui/react-components' 6 | import React from 'react' 7 | import ReactDOM from 'react-dom/client' 8 | import App from './App' 9 | import { ConfigurationContext, createContextConfiguration } from './context' 10 | 11 | const rootElement = document.getElementById('root') as HTMLElement 12 | const theme = 13 | rootElement.getAttribute('theme') === 'dark' ? webDarkTheme : webLightTheme 14 | const root = ReactDOM.createRoot(rootElement) 15 | 16 | root.render( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | -------------------------------------------------------------------------------- /webview/message/src/interfaces/IChatCompletion.ts: -------------------------------------------------------------------------------- 1 | export interface IChatCompletion { 2 | content: string 3 | author: string 4 | timestamp: string 5 | mine: boolean 6 | completionTokens: number 7 | promptTokens: number 8 | totalTokens: number 9 | } 10 | -------------------------------------------------------------------------------- /webview/message/src/interfaces/IChatCompletionListProps.ts: -------------------------------------------------------------------------------- 1 | import { IChatCompletion } from '.' 2 | 3 | export interface IChatCompletionListProps { 4 | chatCompletionList: IChatCompletion[] 5 | } 6 | -------------------------------------------------------------------------------- /webview/message/src/interfaces/IChatCompletionProps.ts: -------------------------------------------------------------------------------- 1 | import { IChatCompletion } from '.' 2 | 3 | export interface IChatCompletionProps { 4 | chatCompletion: IChatCompletion 5 | } 6 | -------------------------------------------------------------------------------- /webview/message/src/interfaces/ICodeDocument.ts: -------------------------------------------------------------------------------- 1 | export interface ICodeDocument { 2 | language: string 3 | content: string 4 | } -------------------------------------------------------------------------------- /webview/message/src/interfaces/IContextConfiguration.ts: -------------------------------------------------------------------------------- 1 | export interface IContextConfiguration { 2 | messageShortcuts: string 3 | assistantColor: string 4 | assistantBackground: string 5 | userColor: string 6 | userBackground: string 7 | } 8 | -------------------------------------------------------------------------------- /webview/message/src/interfaces/IMessageInputProps.tsx: -------------------------------------------------------------------------------- 1 | import { IChatCompletion } from '.' 2 | 3 | export interface IMessageInputProps { 4 | onSubmit: (message: IChatCompletion) => void 5 | } 6 | -------------------------------------------------------------------------------- /webview/message/src/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export type { IChatCompletion } from './IChatCompletion' 2 | export type { ICodeDocument } from './ICodeDocument' 3 | export type { IContextConfiguration } from './IContextConfiguration' 4 | export type { IMessageInputProps } from './IMessageInputProps' 5 | export type { IChatCompletionProps } from './IChatCompletionProps' 6 | export type { IChatCompletionListProps } from './IChatCompletionListProps' 7 | -------------------------------------------------------------------------------- /webview/message/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export { vscode } from '@common/vscode' 2 | export { TextToSpeech } from './textToSpeech' 3 | -------------------------------------------------------------------------------- /webview/message/src/utilities/textToSpeech.ts: -------------------------------------------------------------------------------- 1 | export class TextToSpeech { 2 | private static instance: TextToSpeech | null = null 3 | private speechSynthesis: SpeechSynthesis 4 | 5 | private constructor() { 6 | this.speechSynthesis = window.speechSynthesis 7 | } 8 | 9 | public static getInstance(): TextToSpeech { 10 | if (!TextToSpeech.instance) { 11 | TextToSpeech.instance = new TextToSpeech() 12 | } 13 | return TextToSpeech.instance 14 | } 15 | 16 | public activateTextToSpeech(text: string): void { 17 | try { 18 | const voices = this.speechSynthesis.getVoices() 19 | const enVoices = voices.find(({ lang }) => lang.indexOf('en') !== -1) 20 | 21 | const msg = this.buildSpeechSynthesisUtterance(text) 22 | msg.voice = enVoices! 23 | msg.lang = 'en-US' 24 | 25 | this.speechSynthesis.speak(msg) 26 | } catch (error) { 27 | console.error(error) 28 | } 29 | } 30 | 31 | private buildSpeechSynthesisUtterance( 32 | text: string 33 | ): SpeechSynthesisUtterance { 34 | const msg = new SpeechSynthesisUtterance(text) 35 | msg.volume = 1 36 | msg.rate = 2 37 | msg.pitch = 1 38 | return msg 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /webview/message/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /webview/message/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "useDefineForClassFields": true, 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noEmit": true, 19 | "jsx": "react-jsx", 20 | "baseUrl": ".", 21 | "paths": { 22 | "@app/*": ["./src/*"], 23 | "@common/*": ["../common/*"] 24 | }, 25 | "typeRoots": ["./node_modules/@types", "./src/types"] 26 | }, 27 | "include": ["./src", "../common/vscode.ts"] 28 | } 29 | -------------------------------------------------------------------------------- /webview/message/vite.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import tsconfigPaths from 'vite-tsconfig-paths' 3 | import react from '@vitejs/plugin-react' 4 | 5 | export default defineConfig(({ command, mode }) => { 6 | const minify = mode === 'production' 7 | const sourcemap = mode !== 'production' 8 | console.log(`\nmode: ${mode} :: minify: ${minify} sourcemap: ${sourcemap}`) 9 | 10 | return { 11 | plugins: [react(), tsconfigPaths()], 12 | build: { 13 | outDir: '../../out/webview/message', 14 | chunkSizeWarningLimit: 2048, 15 | emptyOutDir: true, 16 | minify: minify, 17 | sourcemap: sourcemap, 18 | rollupOptions: { 19 | output: { 20 | entryFileNames: `[name].js`, 21 | chunkFileNames: `[name].js`, 22 | assetFileNames: `[name].[ext]`, 23 | }, 24 | }, 25 | }, 26 | } 27 | }) 28 | --------------------------------------------------------------------------------